Comments (4)
I was able to figure this out for the following cases:
Where the Content Representation
is On-chain content layout
or Off-chain content layout
Where the data stored, is serialized in snake case
You can call HydrateFromUri to pull additional attributes that will provide missing data to the metadata.
public record JettonMetadata
{
private const int DefaultDecimals = 9;
private const string KeyHashUri = "70e5d7b6a29b392f85076fe15ca2f2053c56c2338728c4e33c9e8ddb1ee827cc";
private const string KeyHashName = "82a3537ff0dbce7eec35d69edc3a189ee6f17d82f353a553f9aa96cb0be3ce89";
private const string KeyHashDescription = "c9046f7a37ad0ea7cee73355984fa5428982f8b37c8f7bcec91f7ac71a7cd104";
private const string KeyHashImage = "6105d6cc76af400325e94d588ce511be5bfdbb73b437dc51eca43917d7a43e3d";
private const string KeyHashImageData = "d9a88ccec79eef59c84b671136a20ece4cd00caaad5bc47e2c208829154ee9e4";
private const string KeyHashSymbol = "b76a7ca153c24671658335bbd08946350ffc621fa1c516e7123095d4ffd5c581";
private const string KeyHashDecimals = "ee80fd2f1e03480e2282363596ee752d7bb27f50776b95086a0279189675923e";
private const string KeyHashAmountStyle = "8b10e058ce46c44bc1ba139bc9761721e49170e2c0a176129250a70af053b700";
private const string KeyHashRenderType = "d33ae06043036d0d1c3be27201ac15ee4c73da8cdb7c8f3462ce308026095ac0";
private bool isHydrated = false;
public string? Uri { get; init; }
public string? Name { get; init; }
public string? Description { get; init; }
public string? Image { get; init; }
public byte[]? ImageData { get; init; }
public string? Symbol { get; init; }
public int Decimals { get; init; } = DefaultDecimals;
public string? RenderType { get; init; }
public string? AmountStyle { get; init; }
public bool CanHydrate => Uri != null && isHydrated == false;
public static JettonMetadata ParseFromCell(Cell cell)
{
const int OnchainContent = 0;
const int OffchainContent = 1;
var slice = cell.BeginRead();
var contentType = slice.LoadByte();
if (contentType == OnchainContent)
{
return FromOnchainContent(slice);
}
else if (contentType == OffchainContent)
{
var uri = slice.LoadString();
return new JettonMetadata { Uri = uri };
}
throw new Exception("Unsupported representation type");
}
private static JettonMetadata FromOnchainContent(Slice slice)
{
var onchainData = slice.LoadAndParseDict(
256,
x => Convert.ToHexString(x.LoadBytes(32)).ToLower(),
x => x,
StringComparer.Instance);
var metadata = new JettonMetadata();
string? GetString(string key)
{
if (onchainData.TryGetValue(key, out var value))
{
var format = value.LoadByte();
return format switch
{
0 => value.LoadString(),
1 => throw new NotImplementedException("Not yet support Chunked Format https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#specification"),
_ => throw new Exception("Unknown data serialization format https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#specification")
};
}
return null;
}
int decimals = DefaultDecimals;
var decimalsStr = GetString(KeyHashDecimals);
if (decimalsStr != null)
decimals = int.Parse(decimalsStr);
metadata = new JettonMetadata
{
Decimals = decimals,
Uri = GetString(KeyHashUri),
Name = GetString(KeyHashName),
Description = GetString(KeyHashDescription),
Image = GetString(KeyHashImage),
Symbol = GetString(KeyHashSymbol),
AmountStyle = GetString(KeyHashAmountStyle),
RenderType = GetString(KeyHashRenderType)
};
return metadata;
}
public async Task<JettonMetadata> HydrateFromUri(HttpClient? httpClient = null)
{
if(string.IsNullOrEmpty(Uri))
throw new InvalidOperationException("Uri is not set");
httpClient ??= new HttpClient();
var stream = await httpClient.GetStreamAsync(Uri);
var jsonDoc = JsonDocument.Parse(stream);
var result = this with { isHydrated = true };
if (string.IsNullOrEmpty(Name) && jsonDoc.RootElement.TryGetProperty("name", out var name))
result = result with { Name = name.GetString() };
if (string.IsNullOrEmpty(Description) && jsonDoc.RootElement.TryGetProperty("description", out var description))
result = result with { Description = description.GetString() };
if (string.IsNullOrEmpty(Image) && jsonDoc.RootElement.TryGetProperty("image", out var image))
result = result with { Image = image.GetString() };
if (string.IsNullOrEmpty(Symbol) && jsonDoc.RootElement.TryGetProperty("symbol", out var symbol))
result = result with { Symbol = symbol.GetString() };
if (string.IsNullOrEmpty(RenderType) && jsonDoc.RootElement.TryGetProperty("render_type", out var renderType))
result = result with { RenderType = renderType.GetString() };
if (string.IsNullOrEmpty(AmountStyle) && jsonDoc.RootElement.TryGetProperty("amount_style", out var amountStyle))
result = result with { AmountStyle = amountStyle.GetString() };
return result;
}
}
}
public class StringComparer : IEqualityComparer<string>
{
public static readonly StringComparer Instance = new();
public bool Equals(string? x, string? y) => x == y;
public int GetHashCode(string obj) => obj.GetHashCode();
}
from tonlib.net.
Hi,
Yes, using ...ParseDict
with slices is the correct way, your solution seems fine in general.
Also, please note that v0.21.3 introduced ...ParseDictRef
function that gives you value as Cell (instead of Slice), which is useful when dict is stored inside dict (or other cases when dict_set_ref
in FunC had been used).
Some minor fixes in your code:
else if (contentType == OffchainContent) { var uri = slice.LoadString(); return new JettonMetadata { Uri = uri }; }
I think it should be slice.LoadStringSnake()
here, and in 0 => value.LoadString(),
too.
BTW, LoadStringSnake takes a parameter to omit/skip check of 0x00 prefix (which I saw missing in many places).
.
httpClient ??= new HttpClient();
Don't forget to dispose self-created HttpClient
from tonlib.net.
Oh nice thanks for the tips!
Yes temp HttpClient should be disposed. Appreciate the library.
Off topic: Is there a way to subscribe to Blocks/Transactions using tonlib, such that I can grab Transactions for Accounts as they are emitted?
from tonlib.net.
Is there a way to subscribe to Blocks/Transactions using tonlib, such that I can grab Transactions for Accounts as they are emitted?
No, there is no such functionality in tonlib.
You need either to use some HTTP API (https://docs.ton.org/develop/dapps/apis/) that supports blocks/transactions streaming, or request account tx/status every several seconds.
from tonlib.net.
Related Issues (6)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tonlib.net.