Skip to content

Commit 0a13a5e

Browse files
committed
Jettons
1 parent 7739b79 commit 0a13a5e

File tree

4 files changed

+248
-0
lines changed

4 files changed

+248
-0
lines changed

TonLibDotNet.Demo/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static async Task Main(string[] args)
4949
services.AddTransient<ISample, DomainAuctionInfo>();
5050
services.AddTransient<ISample, RootDnsGetAllInfo>();
5151
services.AddTransient<ISample, TelemintGetAllInfo>();
52+
services.AddTransient<ISample, Jettons>();
5253
});
5354

5455
/// Add types from current assembly (see <see cref="LibraryExtensibility"/> class for more info).
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Net;
2+
using Microsoft.Extensions.Logging;
3+
using TonLibDotNet.Cells;
4+
using TonLibDotNet.Types;
5+
using TonLibDotNet.Types.Msg;
6+
7+
namespace TonLibDotNet.Samples.Recipes
8+
{
9+
public class Jettons : ISample
10+
{
11+
const string jettonMasterAddress = "EQBbX2khki4ynoYWgXqmc7_5Xlcley9luaHxoSE0-7R2whnK";
12+
13+
// regular wallet, not jetton one!
14+
const string ownerWalletAddress = "EQAkEWzRLi1sw9AlaGDDzPvk2_F20hjpTjlvsjQqYawVmdT0";
15+
16+
// regular wallet, not jetton one!
17+
const string receiverWalletWallet = "EQC403uCzev_-2g8fNfFPOgr5xOxCoTrCX2gp6OMK6YDtARk";
18+
19+
private readonly ITonClient tonClient;
20+
private readonly ILogger logger;
21+
22+
public Jettons(ITonClient tonClient, ILogger<Jettons> logger)
23+
{
24+
this.tonClient = tonClient ?? throw new ArgumentNullException(nameof(tonClient));
25+
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
26+
}
27+
28+
public async Task Run(bool inMainnet)
29+
{
30+
if (inMainnet)
31+
{
32+
logger.LogWarning("Jettons() sample in Mainnet is disabled for safety reasons. Switch to testnet in Program.cs and try again.");
33+
return;
34+
}
35+
36+
await tonClient.InitIfNeeded();
37+
38+
var ownerJettonAddress = await TonRecipes.Jettons.GetWalletAddress(tonClient, jettonMasterAddress, ownerWalletAddress);
39+
logger.LogInformation("Jetton address for owner wallet {Wallet} is: {Address}", ownerWalletAddress, ownerJettonAddress);
40+
41+
var (bal, own, mst) = await TonRecipes.Jettons.GetJettonAddressInfo(tonClient, ownerJettonAddress);
42+
logger.LogInformation("Info for Jetton address {Address}:", ownerJettonAddress);
43+
logger.LogInformation(" Balance: {Value}", bal);
44+
logger.LogInformation(" Owner: {Value}", own);
45+
logger.LogInformation(" Jett.Master: {Value}", mst);
46+
47+
if (string.IsNullOrWhiteSpace(Program.TestMnemonic))
48+
{
49+
logger.LogWarning("Actual mnemonic is not set, sending jettons code is skipped. Put mnemonic phrase in Prograg.cs and try again.");
50+
}
51+
else
52+
{
53+
var msg = TonRecipes.Jettons.CreateTransferMessage(ownerJettonAddress, 12345, 1_000_000_000, receiverWalletWallet, ownerWalletAddress, null, 0.01M, null);
54+
55+
var inputKey = await tonClient.ImportKey(new ExportedKey(Program.TestMnemonic.Split(' ').ToList()));
56+
57+
var action = new ActionMsg(new List<Message>() { msg });
58+
var query = await tonClient.CreateQuery(new InputKeyRegular(inputKey), ownerWalletAddress, action, TimeSpan.FromMinutes(1));
59+
_ = await tonClient.QuerySend(query.Id);
60+
61+
await tonClient.DeleteKey(inputKey);
62+
}
63+
}
64+
}
65+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using System.Globalization;
2+
using System.Numerics;
3+
using TonLibDotNet.Cells;
4+
using TonLibDotNet.Types.Msg;
5+
using TonLibDotNet.Types;
6+
using TonLibDotNet.Types.Smc;
7+
using TonLibDotNet.Utils;
8+
9+
namespace TonLibDotNet.Recipes
10+
{
11+
/// <remarks>
12+
/// Based on <see href="https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md">TEP 74: Fungible tokens (Jettons) standard</see>
13+
/// and <see href="https://github.com/ton-blockchain/token-contract/">Tokens Smart Contracts</see>.
14+
/// </remarks>
15+
public partial class Tep74Recipes
16+
{
17+
public decimal DefaultAmount { get; set; } = 0.1M;
18+
19+
public int DefaultSendMode { get; set; } = 1;
20+
21+
/// <summary>
22+
/// From <see href="https://github.com/ton-blockchain/token-contract/blob/main/ft/op-codes.fc">op-codes.fc</see>
23+
/// </summary>
24+
private const int OPTransfer = 0xf8a7ea5;
25+
26+
/// <summary>
27+
/// From <see href="https://github.com/ton-blockchain/token-contract/blob/main/ft/op-codes.fc">op-codes.fc</see>
28+
/// </summary>
29+
private const int OPBurn = 0x595f07bc;
30+
31+
public static readonly Tep74Recipes Instance = new();
32+
33+
/// <summary>
34+
/// Executes 'get_wallet_address' method on Jetton Minter contract, returns jetton address for specified owner (user) wallet address.
35+
/// </summary>
36+
/// <param name="tonClient"><see cref="ITonClient"/> instance.</param>
37+
/// <param name="jettonMinterAddress">Jetton Minter contract address.</param>
38+
/// <param name="ownerAddress">Owner (user) wallet address to get jetton address for.</param>
39+
/// <returns>Jetton address for specified Jetton and specified owner (user) wallet address.</returns>
40+
/// <exception cref="TonLibNonZeroExitCodeException" />
41+
/// <seealso href="https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-minter.fc#L114">Source of 'get_wallet_address' method.</seealso>
42+
public async Task<string> GetWalletAddress(ITonClient tonClient, string jettonMinterAddress, string ownerAddress)
43+
{
44+
await tonClient.InitIfNeeded().ConfigureAwait(false);
45+
46+
var smc = await tonClient.SmcLoad(new Types.AccountAddress(jettonMinterAddress)).ConfigureAwait(false);
47+
48+
// slice get_wallet_address(slice owner_address)
49+
var stack = new List<Types.Tvm.StackEntry>
50+
{
51+
new Types.Tvm.StackEntrySlice(new Types.Tvm.Slice(new Cells.Boc(new Cells.CellBuilder().StoreAddressIntStd(ownerAddress).Build()).SerializeToBase64())),
52+
};
53+
54+
var result = await tonClient.SmcRunGetMethod(smc.Id, new MethodIdName("get_wallet_address"), stack).ConfigureAwait(false);
55+
56+
await tonClient.SmcForget(smc.Id).ConfigureAwait(false);
57+
58+
TonLibNonZeroExitCodeException.ThrowIfNonZero(result.ExitCode);
59+
60+
return result.Stack[0].ToTvmCell().ToBoc().RootCells[0].BeginRead().LoadAddressIntStd();
61+
}
62+
63+
/// <summary>
64+
/// Executes 'get_wallet_data' method on Jetton address contract, returns information about this jetton address.
65+
/// </summary>
66+
/// <param name="tonClient"><see cref="ITonClient"/> instance.</param>
67+
/// <param name="jettonAddress">Jetton address to obtain info for.</param>
68+
/// <returns>Information about specified Jetton address: balance, owner (user) wallet address, Jetton Minter contract address.</returns>
69+
/// <remarks>Jetton contract must be deployed and active (to execute get-method).</remarks>
70+
/// <exception cref="TonLibNonZeroExitCodeException" />
71+
/// <seealso href="https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-wallet.fc#L246">Source of 'get_wallet_address' method.</seealso>
72+
public async Task<(BigInteger balance, string ownerAddress, string jettonMinterAddress)> GetJettonAddressInfo(ITonClient tonClient, string jettonAddress)
73+
{
74+
await tonClient.InitIfNeeded().ConfigureAwait(false);
75+
76+
var smc = await tonClient.SmcLoad(new Types.AccountAddress(jettonAddress)).ConfigureAwait(false);
77+
78+
// (int balance:Coins, slice owner_address:MsgAddressInt, slice jetton_master_address:MsgAddressInt, cell jetton_wallet_code:^Cell)
79+
var result = await tonClient.SmcRunGetMethod(smc.Id, new MethodIdName("get_wallet_data")).ConfigureAwait(false);
80+
81+
await tonClient.SmcForget(smc.Id).ConfigureAwait(false);
82+
83+
TonLibNonZeroExitCodeException.ThrowIfNonZero(result.ExitCode);
84+
85+
var balance = BigInteger.Parse(result.Stack[0].ToTvmNumberDecimal(), CultureInfo.InvariantCulture);
86+
var owner = result.Stack[1].ToTvmCell().ToBoc().RootCells[0].BeginRead().LoadAddressIntStd();
87+
var minter = result.Stack[2].ToTvmCell().ToBoc().RootCells[0].BeginRead().LoadAddressIntStd();
88+
89+
return (balance, owner, minter);
90+
}
91+
92+
/// <summary>
93+
/// Creates message that will transfer jettons from source to destination.
94+
/// </summary>
95+
/// <param name="sourceJettonAddress">Jetton wallet address to send coins from (use <see cref="GetWalletAddress">GetWalletAddress</see> if needed).</param>
96+
/// <param name="queryId">Arbitrary request number.</param>
97+
/// <param name="amount">Amount of transferred jettons <b>in elementary units</b>.</param>
98+
/// <param name="destination">Address of the new owner of the jettons (user main-wallet address, not his jetton address).</param>
99+
/// <param name="responseDestination">Address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins.</param>
100+
/// <param name="customPayload">Optional custom data (which is used by either sender or receiver jetton wallet for inner logic).</param>
101+
/// <param name="forwardTonAmount">The amount of nanotons to be sent to the destination address.</param>
102+
/// <param name="forwardPayload">Optional custom data that should be sent to the destination address.</param>
103+
/// <returns>Constructed and ready-to-be-sent Message (by editor/owner of <paramref name="sourceJettonAddress"/>).</returns>
104+
/// <remarks>
105+
/// <para>Your Jetton wallet address must already be deployed and active, and contain enough jettons to send.</para>
106+
/// </remarks>
107+
/// <seealso href="https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#1-transfer">Transfer message in TEP</seealso>
108+
public Message CreateTransferMessage(
109+
string sourceJettonAddress,
110+
ulong queryId,
111+
BigInteger amount,
112+
string destination,
113+
string responseDestination,
114+
Cell? customPayload,
115+
decimal forwardTonAmount,
116+
Cell? forwardPayload)
117+
{
118+
var body = new CellBuilder()
119+
.StoreUInt(OPTransfer)
120+
.StoreULong(queryId)
121+
.StoreCoins(amount)
122+
.StoreAddressIntStd(destination)
123+
.StoreAddressIntStd(responseDestination)
124+
.StoreDict(customPayload)
125+
.StoreCoins(TonUtils.Coins.ToNano(forwardTonAmount))
126+
.StoreDict(forwardPayload)
127+
;
128+
129+
return new Message(new AccountAddress(sourceJettonAddress))
130+
{
131+
Amount = TonUtils.Coins.ToNano(DefaultAmount),
132+
Data = new DataRaw(new Boc(body.Build()).SerializeToBase64(), string.Empty),
133+
SendMode = DefaultSendMode,
134+
};
135+
}
136+
137+
/// <summary>
138+
/// Creates message that will burn specified amount of jettons.
139+
/// </summary>
140+
/// <param name="sourceJettonAddress">Jetton wallet address to send coins from (use <see cref="GetWalletAddress">GetWalletAddress</see> if needed).</param>
141+
/// <param name="queryId">Arbitrary request number.</param>
142+
/// <param name="amount">Amount of jettons to burn <b>in elementary units</b>.</param>
143+
/// <param name="responseDestination">Address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins.</param>
144+
/// <param name="customPayload">Optional custom data (which is used by either sender or receiver jetton wallet for inner logic).</param>
145+
/// <returns>Constructed and ready-to-be-sent Message (by editor/owner of <paramref name="sourceJettonAddress"/>).</returns>
146+
/// <remarks>
147+
/// <para>Your Jetton wallet address must already be deployed and active, and contain enough jettons to send.</para>
148+
/// </remarks>
149+
/// <seealso href="https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#2-burn">Burn message in TEP</seealso>
150+
public Message CreateBurnMessage(
151+
string sourceJettonAddress,
152+
ulong queryId,
153+
BigInteger amount,
154+
string responseDestination,
155+
Cell? customPayload)
156+
{
157+
var body = new CellBuilder()
158+
.StoreUInt(OPBurn)
159+
.StoreULong(queryId)
160+
.StoreCoins(amount)
161+
.StoreAddressIntStd(responseDestination)
162+
.StoreDict(customPayload)
163+
;
164+
165+
return new Message(new AccountAddress(sourceJettonAddress))
166+
{
167+
Amount = TonUtils.Coins.ToNano(DefaultAmount),
168+
Data = new DataRaw(new Boc(body.Build()).SerializeToBase64(), string.Empty),
169+
SendMode = DefaultSendMode,
170+
};
171+
}
172+
}
173+
}

TonLibDotNet/TonRecipes.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,14 @@ public static class TonRecipes
3030
/// and <see href="https://github.com/TelegramMessenger/telemint">Telemint Contracts</see>.
3131
/// </remarks>
3232
public static TelegramNumbersRecipes TelegramNumbers { get; } = TelegramNumbersRecipes.Instance;
33+
34+
/// <summary>
35+
/// Functions to work with Jettons.
36+
/// </summary>
37+
/// <remarks>
38+
/// Based on <see href="https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md">TEP 74: Fungible tokens (Jettons) standard</see>
39+
/// and <see href="https://github.com/ton-blockchain/token-contract/">Tokens Smart Contracts</see>.
40+
/// </remarks>
41+
public static Tep74Recipes Jettons { get; } = Tep74Recipes.Instance;
3342
}
3443
}

0 commit comments

Comments
 (0)