Skip to content

Commit 64f6b20

Browse files
committed
fix ton address issue
1 parent 56e2801 commit 64f6b20

File tree

2 files changed

+236
-3
lines changed

2 files changed

+236
-3
lines changed

projects/helper/chain/ton.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const plimit = require('p-limit')
44
const _rateLimited = plimit(1)
55
const rateLimited = fn => (...args) => _rateLimited(() => fn(...args))
66
const { sumTokens2 } = require('../unwrapLPs')
7+
const tonUtils = require('../utils/ton')
78

89
const { getUniqueAddresses, sleep, sliceIntoChunks } = require('../utils')
910

@@ -28,15 +29,16 @@ async function _sumTokensAccount({ api, addr, tokens = [], onlyWhitelistedTokens
2829
const { balances } = await get(`https://tonapi.io/v2/accounts/${addr}/jettons?currencies=usd`)
2930
await sleep(1000 * (3 * Math.random() + 3))
3031
balances.forEach(({ balance, price, jetton }) => {
31-
if (onlyWhitelistedTokens && !tokens.includes(jetton.address)) return;
32+
const address = tonUtils.address(jetton.address).toString()
33+
if (onlyWhitelistedTokens && !tokens.includes(address)) return;
3234
if (!useTonApiForPrices) {
33-
api.add(jetton.address, balance)
35+
api.add(address, balance)
3436
return;
3537
}
3638
const decimals = jetton.decimals
3739
price = price?.prices?.USD
3840
if (!decimals || !price) {
39-
api.add(jetton.address, balance)
41+
api.add(address, balance)
4042
return;
4143
}
4244
const bal = balance * price / 10 ** decimals

projects/helper/utils/ton.js

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// https://github.com/ton-org/ton-core/blob/main/src/utils/crc16.ts
2+
3+
function crc16(data) {
4+
const poly = 0x1021;
5+
let reg = 0;
6+
const message = Buffer.alloc(data.length + 2);
7+
message.set(data);
8+
for (let byte of message) {
9+
let mask = 0x80;
10+
while (mask > 0) {
11+
reg <<= 1;
12+
if (byte & mask) {
13+
reg += 1;
14+
}
15+
mask >>= 1
16+
if (reg > 0xffff) {
17+
reg &= 0xffff;
18+
reg ^= poly;
19+
}
20+
}
21+
}
22+
return Buffer.from([Math.floor(reg / 256), reg % 256]);
23+
}
24+
const bounceable_tag = 0x11;
25+
const non_bounceable_tag = 0x51;
26+
const test_flag = 0x80;
27+
28+
function parseFriendlyAddress(src) {
29+
if (typeof src === 'string' && !Address.isFriendly(src)) {
30+
throw new Error('Unknown address type');
31+
}
32+
33+
const data = Buffer.isBuffer(src) ? src : Buffer.from(src, 'base64');
34+
35+
// 1byte tag + 1byte workchain + 32 bytes hash + 2 byte crc
36+
if (data.length !== 36) {
37+
throw new Error('Unknown address type: byte length is not equal to 36');
38+
}
39+
40+
// Prepare data
41+
const addr = data.subarray(0, 34);
42+
const crc = data.subarray(34, 36);
43+
const calcedCrc = crc16(addr);
44+
if (!(calcedCrc[0] === crc[0] && calcedCrc[1] === crc[1])) {
45+
throw new Error('Invalid checksum: ' + src);
46+
}
47+
48+
// Parse tag
49+
let tag = addr[0];
50+
let isTestOnly = false;
51+
let isBounceable = false;
52+
if (tag & test_flag) {
53+
isTestOnly = true;
54+
tag = tag ^ test_flag;
55+
}
56+
if ((tag !== bounceable_tag) && (tag !== non_bounceable_tag))
57+
throw "Unknown address tag";
58+
59+
isBounceable = tag === bounceable_tag;
60+
61+
let workchain = null;
62+
if (addr[1] === 0xff) { // TODO we should read signed integer here
63+
workchain = -1;
64+
} else {
65+
workchain = addr[1];
66+
}
67+
68+
const hashPart = addr.subarray(2, 34);
69+
70+
return { isTestOnly, isBounceable, workchain, hashPart };
71+
}
72+
73+
class Address {
74+
75+
static isAddress(src) {
76+
return src instanceof Address;
77+
}
78+
79+
static isFriendly(source) {
80+
// Check length
81+
if (source.length !== 48) {
82+
return false;
83+
}
84+
// Check if address is valid base64
85+
if (!/[A-Za-z0-9+/_-]+/.test(source)) {
86+
return false;
87+
}
88+
89+
return true;
90+
}
91+
92+
static isRaw(source) {
93+
// Check if has delimiter
94+
if (source.indexOf(':') === -1) {
95+
return false;
96+
}
97+
98+
let [wc, hash] = source.split(':');
99+
100+
// wc is not valid
101+
if (!Number.isInteger(parseFloat(wc))) {
102+
return false;
103+
}
104+
105+
// hash is not valid
106+
if (!/[a-f0-9]+/.test(hash.toLowerCase())) {
107+
return false;
108+
}
109+
110+
// has is not correct
111+
if (hash.length !== 64) {
112+
return false;
113+
}
114+
115+
return true;
116+
}
117+
118+
static normalize(source) {
119+
if (typeof source === 'string') {
120+
return Address.parse(source).toString();
121+
} else {
122+
return source.toString();
123+
}
124+
}
125+
126+
static parse(source) {
127+
if (Address.isFriendly(source)) {
128+
return this.parseFriendly(source).address;
129+
} else if (Address.isRaw(source)) {
130+
return this.parseRaw(source);
131+
} else {
132+
throw new Error('Unknown address type: ' + source);
133+
}
134+
}
135+
136+
static parseRaw(source) {
137+
let workChain = parseInt(source.split(":")[0]);
138+
let hash = Buffer.from(source.split(":")[1], 'hex');
139+
140+
return new Address(workChain, hash);
141+
}
142+
143+
static parseFriendly(source) {
144+
if (Buffer.isBuffer(source)) {
145+
let r = parseFriendlyAddress(source);
146+
return {
147+
isBounceable: r.isBounceable,
148+
isTestOnly: r.isTestOnly,
149+
address: new Address(r.workchain, r.hashPart)
150+
};
151+
} else {
152+
let addr = source.replace(/\-/g, '+').replace(/_/g, '\/'); // Convert from url-friendly to true base64
153+
let r = parseFriendlyAddress(addr);
154+
return {
155+
isBounceable: r.isBounceable,
156+
isTestOnly: r.isTestOnly,
157+
address: new Address(r.workchain, r.hashPart)
158+
};
159+
}
160+
}
161+
162+
workChain;
163+
hash;
164+
165+
constructor(workChain, hash) {
166+
if (hash.length !== 32) {
167+
throw new Error('Invalid address hash length: ' + hash.length);
168+
}
169+
170+
this.workChain = workChain;
171+
this.hash = hash;
172+
Object.freeze(this);
173+
}
174+
175+
toRawString = () => {
176+
return this.workChain + ':' + this.hash.toString('hex');
177+
}
178+
179+
equals(src) {
180+
if (src.workChain !== this.workChain) {
181+
return false;
182+
}
183+
return src.hash.equals(this.hash);
184+
}
185+
186+
toRaw = () => {
187+
const addressWithChecksum = Buffer.alloc(36);
188+
addressWithChecksum.set(this.hash);
189+
addressWithChecksum.set([this.workChain, this.workChain, this.workChain, this.workChain], 32);
190+
return addressWithChecksum;
191+
}
192+
193+
toStringBuffer = (args) => {
194+
let testOnly = (args && args.testOnly !== undefined) ? args.testOnly : false;
195+
let bounceable = (args && args.bounceable !== undefined) ? args.bounceable : true;
196+
197+
let tag = bounceable ? bounceable_tag : non_bounceable_tag;
198+
if (testOnly) {
199+
tag |= test_flag;
200+
}
201+
202+
const addr = Buffer.alloc(34);
203+
addr[0] = tag;
204+
addr[1] = this.workChain;
205+
addr.set(this.hash, 2);
206+
const addressWithChecksum = Buffer.alloc(36);
207+
addressWithChecksum.set(addr);
208+
addressWithChecksum.set(crc16(addr), 34);
209+
return addressWithChecksum;
210+
}
211+
212+
toString = (args) => {
213+
let urlSafe = (args && args.urlSafe !== undefined) ? args.urlSafe : true;
214+
let buffer = this.toStringBuffer(args);
215+
if (urlSafe) {
216+
return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
217+
} else {
218+
return buffer.toString('base64');
219+
}
220+
}
221+
222+
}
223+
224+
function address(src) {
225+
return Address.parse(src);
226+
}
227+
228+
module.exports = {
229+
address,
230+
Address,
231+
}

0 commit comments

Comments
 (0)