Skip to content

Commit 614fb29

Browse files
committed
TonUtils added (with AddressUtils, CoinUtils, TextUtils)
1 parent 5fad2f3 commit 614fb29

File tree

8 files changed

+219
-24
lines changed

8 files changed

+219
-24
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace TonLibDotNet.Utils
2+
{
3+
public class AddressUtilsTests
4+
{
5+
[Theory]
6+
[InlineData("EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS", true, "EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS")]
7+
[InlineData("EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS", false, "UQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GB7X")]
8+
[InlineData("UQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GB7X", false, "UQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GB7X")]
9+
[InlineData("UQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GB7X", true, "EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS")]
10+
public void SetBounceableOk(string source, bool flag, string expected)
11+
{
12+
Assert.Equal(expected, AddressUtils.Instance.SetBounceable(source, flag));
13+
}
14+
}
15+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace TonLibDotNet.Utils
2+
{
3+
public class TextUtilsTexts
4+
{
5+
[Theory]
6+
[InlineData("Hello, World!", "SGVsbG8sIFdvcmxkIQ==")]
7+
public void EncodeAsBase64(string source, string encoded)
8+
{
9+
Assert.Equal(encoded, TextUtils.Instance.EncodeAsBase64(source));
10+
}
11+
12+
[Fact]
13+
public void EncodeAsBase64ForEmpty()
14+
{
15+
Assert.Equal(string.Empty, TextUtils.Instance.EncodeAsBase64(string.Empty));
16+
}
17+
18+
[Fact]
19+
public void EncodeAsBase64ForNull()
20+
{
21+
Assert.Null(TextUtils.Instance.EncodeAsBase64(null));
22+
}
23+
24+
[Theory]
25+
[InlineData("SGVsbG8sIFdvcmxkIQ==", true, "Hello, World!")]
26+
[InlineData("SGVsbG8s\r\nIFdvcmxkIQ==", true, "Hello, World!")]
27+
[InlineData("SGVsbG8sIFdvcmxkIQ===", false, "")]
28+
[InlineData("SGVsbG8sIFdvcmxkIQ=", false, "")]
29+
[InlineData("Hello, World!", false, "")]
30+
public void TryDecodeBase64(string source, bool success, string decoded)
31+
{
32+
Assert.Equal(success, TextUtils.Instance.TryDecodeBase64(source, out var value));
33+
34+
if (success)
35+
{
36+
Assert.Equal(decoded, value);
37+
}
38+
}
39+
40+
[Fact]
41+
public void TryDecodeBase64ForEmpty()
42+
{
43+
Assert.False(TextUtils.Instance.TryDecodeBase64(string.Empty, out _));
44+
}
45+
46+
[Fact]
47+
public void TryDecodeBase64ForNull()
48+
{
49+
Assert.False(TextUtils.Instance.TryDecodeBase64(null, out _));
50+
}
51+
}
52+
}

TonLibDotNet/ITonClient.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ public interface ITonClient
1515
Task<TResponse> Execute<TResponse>(RequestBase<TResponse> request)
1616
where TResponse : TypeBase;
1717

18+
[Obsolete("Use TonUtils.Coins.FromNano()")]
1819
decimal ConvertFromNanoTon(long nano);
1920

21+
[Obsolete("Use TonUtils.Coins.ToNano()")]
2022
long ConvertToNanoTon(decimal ton);
2123

24+
[Obsolete("Use TonUtils.Text.EncodeAsBase64")]
2225
[return: NotNullIfNotNull("source")]
2326
string? EncodeStringAsBase64(string? source);
2427

28+
[Obsolete("Use TonUtils.Text.TryDecodeBase64")]
2529
bool TryDecodeBase64AsString(string? source, [NotNullWhen(true)] out string? result);
2630
}
2731
}

TonLibDotNet/TonClient.cs

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -168,43 +168,23 @@ public async Task<TResponse> Execute<TResponse>(RequestBase<TResponse> request)
168168

169169
public decimal ConvertFromNanoTon(long nano)
170170
{
171-
// Last division - to get rid of trailing zeroes, see https://stackoverflow.com/questions/4525854/remove-trailing-zeros
172-
return nano * 0.000_000_001M / 1.000000000000000000000000000000000m;
171+
return TonUtils.Coins.FromNano(nano);
173172
}
174173

175174
public long ConvertToNanoTon(decimal ton)
176175
{
177-
return Convert.ToInt64(ton * 1_000_000_000M);
176+
return TonUtils.Coins.ToNano(ton);
178177
}
179178

180179
[return: NotNullIfNotNull("source")]
181180
public string? EncodeStringAsBase64(string? source)
182181
{
183-
if (string.IsNullOrEmpty(source))
184-
{
185-
return source;
186-
}
187-
188-
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(source));
182+
return TonUtils.Text.EncodeAsBase64(source);
189183
}
190184

191185
public bool TryDecodeBase64AsString(string? source, [NotNullWhen(true)] out string? result)
192186
{
193-
if (string.IsNullOrEmpty(source))
194-
{
195-
result = null;
196-
return false;
197-
}
198-
199-
var bytes = new byte[source.Length];
200-
if (!Convert.TryFromBase64String(source, bytes, out var count))
201-
{
202-
result = null;
203-
return false;
204-
}
205-
206-
result = System.Text.Encoding.UTF8.GetString(bytes.AsSpan()[..count]);
207-
return true;
187+
return TonUtils.Text.TryDecodeBase64(source, out result);
208188
}
209189

210190
public void Dispose()

TonLibDotNet/TonUtils.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using TonLibDotNet.Utils;
3+
4+
namespace TonLibDotNet
5+
{
6+
public static class TonUtils
7+
{
8+
/// <summary>
9+
/// Useful function to work with coin amounts.
10+
/// </summary>
11+
public static CoinUtils Coins { get; } = CoinUtils.Instance;
12+
13+
/// <summary>
14+
/// Useful function to work with addresses.
15+
/// </summary>
16+
public static AddressUtils Address { get; } = AddressUtils.Instance;
17+
18+
/// <summary>
19+
/// Useful function to work with text strings.
20+
/// </summary>
21+
public static TextUtils Text { get; } = TextUtils.Instance;
22+
}
23+
}

TonLibDotNet/Utils/AddressUtils.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace TonLibDotNet.Utils
4+
{
5+
public class AddressUtils
6+
{
7+
public static readonly AddressUtils Instance = new ();
8+
9+
public const byte BasechainId = AddressValidator.BasechainId;
10+
public const byte MasterchainId = AddressValidator.MasterchainId;
11+
12+
/// <summary>
13+
/// Checks if provided 'address' is valid (including checksum check), return
14+
/// </summary>
15+
/// <param name="address">Address to check.</param>
16+
/// <param name="workchainId">After successful validation contains 'Workchain ID' of address (compare with <see cref="BasechainId"/> or <see cref="MasterchainId"/>).</param>
17+
/// <param name="bounceable">After successful validation contains 'Bounceable' flag of address.</param>
18+
/// <param name="testnetOnly">After successful validation contains 'TestnetOnly' flag of address.</param>
19+
/// <returns>Returns <b>true</b> if address is valid, and <b>false</b> otherwise.</returns>
20+
/// <exception cref="ArgumentNullException">When 'address' is null or empty string.</exception>
21+
public bool IsValid([NotNullWhen(true)] string address, out byte workchainId, out bool bounceable, out bool testnetOnly)
22+
{
23+
return AddressValidator.TryParseAddress(address, out workchainId, out _, out bounceable, out testnetOnly, out _);
24+
}
25+
26+
/// <summary>
27+
/// Updates address (if needed): sets 'bounceable' flag to required value.
28+
/// </summary>
29+
/// <param name="address">Address to convert.</param>
30+
/// <param name="bounceable">Required value of 'bounceable' flag.</param>
31+
/// <returns>Returns updated address (with flag changed). Returns original one when flag has already been equal to required value.</returns>
32+
/// <exception cref="ArgumentException">When 'address' is not valid (<see cref="IsValid"/>).</exception>
33+
/// <exception cref="ArgumentNullException">When 'address' is null or empty string.</exception>
34+
public string SetBounceable(string address, bool bounceable)
35+
{
36+
if (!AddressValidator.TryParseAddress(address, out var workchainId, out var accountId, out var bounceableOld, out var testnetOnly, out var urlSafe))
37+
{
38+
throw new ArgumentException("Invalid address", nameof(address));
39+
}
40+
41+
return bounceable == bounceableOld ? address : AddressValidator.MakeAddress(workchainId, accountId, bounceable, testnetOnly, urlSafe);
42+
}
43+
}
44+
}

TonLibDotNet/Utils/CoinUtils.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace TonLibDotNet.Utils
2+
{
3+
public class CoinUtils
4+
{
5+
public static readonly CoinUtils Instance = new ();
6+
7+
/// <summary>
8+
/// Converts nano-TON amount to decimal TON (moves decimal point to 9 positions to the left).
9+
/// </summary>
10+
/// <remarks>It's safe to store nano-TON as 'long' while total TON supply is unchanged (5*10^18), details are in <see href="https://t.me/tondev/122940"/>.</remarks>
11+
public decimal FromNano(long nano)
12+
{
13+
// Last division is to get rid of trailing zeroes, see https://stackoverflow.com/questions/4525854/remove-trailing-zeros
14+
return nano * 0.000_000_001M / 1.000000000000000000000000000000000m;
15+
}
16+
17+
/// <summary>
18+
/// Converts decimal TON amount to nano-TONs (moves decimal point to 9 positions to the right).
19+
/// </summary>
20+
/// <remarks>It's safe to store nano-TON as 'long' while total TON supply is unchanged (5*10^18), details are in <see href="https://t.me/tondev/122940"/>.</remarks>
21+
public long ToNano(decimal ton)
22+
{
23+
return Convert.ToInt64(ton * 1_000_000_000M);
24+
}
25+
}
26+
}

TonLibDotNet/Utils/TextUtils.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace TonLibDotNet.Utils
4+
{
5+
public class TextUtils
6+
{
7+
public static readonly TextUtils Instance = new ();
8+
9+
/// <summary>
10+
/// Encodes string for transaction comment (which usually stored in Base64).
11+
/// </summary>
12+
/// <param name="source">Source string (to encode).</param>
13+
/// <returns>Encoded string.</returns>
14+
/// <remarks>Returns null or empty string when 'source' is null or empty string, respectively.</remarks>
15+
[return: NotNullIfNotNull("source")]
16+
public string? EncodeAsBase64(string? source)
17+
{
18+
if (string.IsNullOrEmpty(source))
19+
{
20+
return source;
21+
}
22+
23+
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(source));
24+
}
25+
26+
/// <summary>
27+
/// Tries to decode string from Base64 (transaction comments usually stored in Base64).
28+
/// </summary>
29+
/// <param name="source">Source string (to decode).</param>
30+
/// <param name="result">Decoded string.</param>
31+
/// <remarks>Returns <b>false</b> when 'source' is null, empty string or invalid/incomplete Base64 string.</remarks>
32+
public bool TryDecodeBase64(string? source, [NotNullWhen(true)] out string? result)
33+
{
34+
if (string.IsNullOrEmpty(source))
35+
{
36+
result = null;
37+
return false;
38+
}
39+
40+
Span<byte> bytes = stackalloc byte[source.Length];
41+
if (!Convert.TryFromBase64String(source, bytes, out var count))
42+
{
43+
result = null;
44+
return false;
45+
}
46+
47+
result = System.Text.Encoding.UTF8.GetString(bytes[..count]);
48+
return true;
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)