Skip to content

Commit b042d50

Browse files
feat(sdk-coin-ton): add jetton skeleton
Ticket: WIN-3198 TICKET: WIN-3198
1 parent c0f7e48 commit b042d50

File tree

6 files changed

+267
-1
lines changed

6 files changed

+267
-1
lines changed

modules/bitgo/src/v2/coinFactory.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AlgoToken } from '@bitgo/sdk-coin-algo';
66
import { Bcha, Tbcha } from '@bitgo/sdk-coin-bcha';
77
import { HbarToken } from '@bitgo/sdk-coin-hbar';
88
import { Near, TNear, Nep141Token } from '@bitgo/sdk-coin-near';
9+
import { TonToken } from '@bitgo/sdk-coin-ton';
910
import { SolToken } from '@bitgo/sdk-coin-sol';
1011
import { TrxToken } from '@bitgo/sdk-coin-trx';
1112
import { CoinFactory, CoinConstructor } from '@bitgo/sdk-core';
@@ -37,6 +38,7 @@ import {
3738
VetTokenConfig,
3839
TaoTokenConfig,
3940
PolyxTokenConfig,
41+
TonTokenConfig,
4042
} from '@bitgo/statics';
4143
import {
4244
Ada,
@@ -549,6 +551,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
549551
coinFactory.register(name, coinConstructor);
550552
});
551553
});
554+
555+
TonToken.createTokenConstructors([...tokens.bitcoin.ton.tokens, ...tokens.testnet.ton.tokens]).forEach(
556+
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
557+
);
552558
}
553559

554560
export function getCoinConstructor(coinName: string): CoinConstructor | undefined {
@@ -971,6 +977,9 @@ export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor |
971977
case 'flr':
972978
case 'tflr':
973979
return FlrToken.createTokenConstructor(tokenConfig as EthLikeTokenConfig);
980+
case 'ton':
981+
case 'tton':
982+
return TonToken.createTokenConstructor(tokenConfig as TonTokenConfig);
974983
default:
975984
return undefined;
976985
}

modules/sdk-coin-ton/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './lib';
22
export * from './register';
33
export * from './ton';
44
export * from './tton';
5+
export * from './tonToken';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { BitGoBase } from '@bitgo/sdk-core';
22
import { Ton } from './ton';
33
import { Tton } from './tton';
4+
import { TonToken } from './tonToken';
45

56
export const register = (sdk: BitGoBase): void => {
67
sdk.register('ton', Ton.createInstance);
78
sdk.register('tton', Tton.createInstance);
9+
10+
// Register Jetton tokens
11+
const tokens = TonToken.createTokenConstructors();
12+
tokens.forEach((token) => {
13+
sdk.register(token.name, token.coinConstructor);
14+
});
815
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { BitGoBase, CoinConstructor, NamedCoinConstructor, VerifyTransactionOptions } from '@bitgo/sdk-core';
2+
import BigNumber from 'bignumber.js';
3+
import { coins, TonTokenConfig, NetworkType, tokens } from '@bitgo/statics';
4+
5+
import { Transaction } from './lib';
6+
import { Ton } from './ton';
7+
8+
export class TonToken extends Ton {
9+
public readonly tokenConfig: TonTokenConfig;
10+
11+
constructor(bitgo: BitGoBase, tokenConfig: TonTokenConfig) {
12+
const staticsCoin = tokenConfig.network === NetworkType.MAINNET ? coins.get('ton') : coins.get('tton');
13+
super(bitgo, staticsCoin);
14+
this.tokenConfig = tokenConfig;
15+
}
16+
17+
static createTokenConstructor(config: TonTokenConfig): CoinConstructor {
18+
return (bitgo: BitGoBase) => new TonToken(bitgo, config);
19+
}
20+
21+
static createTokenConstructors(
22+
tokenConfig: TonTokenConfig[] = [...tokens.bitcoin.ton.tokens, ...tokens.testnet.ton.tokens]
23+
): NamedCoinConstructor[] {
24+
const tokensCtors: NamedCoinConstructor[] = [];
25+
for (const token of tokenConfig) {
26+
const tokenConstructor = TonToken.createTokenConstructor(token);
27+
tokensCtors.push({ name: token.type, coinConstructor: tokenConstructor });
28+
}
29+
return tokensCtors;
30+
}
31+
32+
get name(): string {
33+
return this.tokenConfig.name;
34+
}
35+
36+
get coin(): string {
37+
return this.tokenConfig.coin;
38+
}
39+
40+
get jettonMaster(): string {
41+
return this.tokenConfig.jettonMaster;
42+
}
43+
44+
get decimalPlaces(): number {
45+
return this.tokenConfig.decimalPlaces;
46+
}
47+
48+
getChain(): string {
49+
return this.tokenConfig.type;
50+
}
51+
52+
getBaseChain(): string {
53+
return this.coin;
54+
}
55+
56+
getFullName(): string {
57+
return 'TON Token';
58+
}
59+
60+
getBaseFactor(): number {
61+
return Math.pow(10, this.tokenConfig.decimalPlaces);
62+
}
63+
64+
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
65+
const { txPrebuild: txPrebuild, txParams: txParams } = params;
66+
const rawTx = txPrebuild.txHex;
67+
let totalAmount = new BigNumber(0);
68+
if (!rawTx) {
69+
throw new Error('missing required tx prebuild property txHex');
70+
}
71+
const coinConfig = coins.get(this.getChain());
72+
const transaction = new Transaction(coinConfig);
73+
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
74+
const explainedTx = transaction.explainTransaction();
75+
if (txParams.recipients !== undefined) {
76+
txParams.recipients.forEach((recipient) => {
77+
if (recipient.tokenName && recipient.tokenName !== coinConfig.name) {
78+
throw new Error('incorrect token name specified in recipients');
79+
}
80+
recipient.tokenName = coinConfig.name;
81+
});
82+
const filteredRecipients = txParams.recipients?.map((recipient) => ({
83+
address: recipient.address,
84+
amount: recipient.amount,
85+
tokenName: recipient.tokenName,
86+
}));
87+
const filteredOutputs = explainedTx.outputs.map((output) => ({
88+
address: output.address,
89+
amount: output.amount,
90+
tokenName: output.tokenName,
91+
}));
92+
const outputsMatch = JSON.stringify(filteredRecipients) === JSON.stringify(filteredOutputs);
93+
if (!outputsMatch) {
94+
throw new Error('Tx outputs does not match with expected txParams recipients');
95+
}
96+
for (const recipient of txParams.recipients) {
97+
totalAmount = totalAmount.plus(recipient.amount);
98+
}
99+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
100+
throw new Error('Tx total amount does not match with expected total amount field');
101+
}
102+
}
103+
return true;
104+
}
105+
}

modules/statics/src/account.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ export interface Nep141TokenConstructorOptions extends AccountConstructorOptions
160160
storageDepositAmount: string;
161161
}
162162

163+
export interface TonTokenConstructorOptions extends AccountConstructorOptions {
164+
jettonMaster: string;
165+
}
166+
163167
export interface VetTokenConstructorOptions extends AccountConstructorOptions {
164168
contractAddress: string;
165169
gasTankToken?: string;
@@ -653,6 +657,18 @@ export class Nep141Token extends AccountCoinToken {
653657
}
654658
}
655659

660+
export class TonToken extends AccountCoinToken {
661+
public jettonMaster: string;
662+
663+
constructor(options: TonTokenConstructorOptions) {
664+
super({
665+
...options,
666+
});
667+
668+
this.jettonMaster = options.jettonMaster;
669+
}
670+
}
671+
656672
export class VetToken extends AccountCoinToken {
657673
public contractAddress: string;
658674
public gasTankToken?: string;
@@ -3282,6 +3298,95 @@ export function sip10Token(
32823298
* @param network? Optional token network. Defaults to the testnet Stacks network.
32833299
* @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
32843300
*/
3301+
/**
3302+
* Factory function for TON token instances.
3303+
*
3304+
* @param id uuid v4
3305+
* @param name unique identifier of the token
3306+
* @param fullName Complete human-readable name of the token
3307+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
3308+
* @param jettonMaster Jetton master address of this token
3309+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
3310+
* @param features Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
3311+
* @param prefix Optional token prefix. Defaults to empty string
3312+
* @param suffix Optional token suffix. Defaults to token name.
3313+
* @param network Optional token network. Defaults to TON main network.
3314+
* @param primaryKeyCurve The elliptic curve for this chain/token
3315+
*/
3316+
export function tonToken(
3317+
id: string,
3318+
name: string,
3319+
fullName: string,
3320+
decimalPlaces: number,
3321+
jettonMaster: string,
3322+
asset: UnderlyingAsset,
3323+
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
3324+
prefix = '',
3325+
suffix: string = name.toUpperCase(),
3326+
network: AccountNetwork = Networks.main.ton,
3327+
primaryKeyCurve: KeyCurve = KeyCurve.Ed25519
3328+
) {
3329+
return Object.freeze(
3330+
new TonToken({
3331+
id,
3332+
name,
3333+
fullName,
3334+
network,
3335+
jettonMaster,
3336+
prefix,
3337+
suffix,
3338+
features,
3339+
decimalPlaces,
3340+
asset,
3341+
isToken: true,
3342+
primaryKeyCurve,
3343+
baseUnit: BaseUnit.TON,
3344+
})
3345+
);
3346+
}
3347+
3348+
/**
3349+
* Factory function for testnet TON token instances.
3350+
*
3351+
* @param id uuid v4
3352+
* @param name unique identifier of the token
3353+
* @param fullName Complete human-readable name of the token
3354+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
3355+
* @param jettonMaster Jetton master address of this token
3356+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
3357+
* @param features Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
3358+
* @param prefix Optional token prefix. Defaults to empty string
3359+
* @param suffix Optional token suffix. Defaults to token name.
3360+
* @param network Optional token network. Defaults to the testnet TON network.
3361+
*/
3362+
export function ttonToken(
3363+
id: string,
3364+
name: string,
3365+
fullName: string,
3366+
decimalPlaces: number,
3367+
jettonMaster: string,
3368+
asset: UnderlyingAsset,
3369+
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
3370+
prefix = '',
3371+
suffix: string = name.toUpperCase(),
3372+
network: AccountNetwork = Networks.test.ton,
3373+
primaryKeyCurve: KeyCurve = KeyCurve.Ed25519
3374+
) {
3375+
return tonToken(
3376+
id,
3377+
name,
3378+
fullName,
3379+
decimalPlaces,
3380+
jettonMaster,
3381+
asset,
3382+
features,
3383+
prefix,
3384+
suffix,
3385+
network,
3386+
primaryKeyCurve
3387+
);
3388+
}
3389+
32853390
export function tsip10Token(
32863391
id: string,
32873392
name: string,

modules/statics/src/tokenConfig.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Erc721Coin,
1616
FlrERC20Token,
1717
HederaToken,
18+
TonToken,
1819
Nep141Token,
1920
OpethERC20Token,
2021
PolygonERC20Token,
@@ -129,6 +130,10 @@ export type Nep141TokenConfig = BaseNetworkConfig & {
129130
storageDepositAmount: string;
130131
};
131132

133+
export type TonTokenConfig = BaseNetworkConfig & {
134+
jettonMaster: string;
135+
};
136+
132137
export type VetTokenConfig = BaseNetworkConfig & {
133138
contractAddress: string;
134139
};
@@ -164,7 +169,8 @@ export type TokenConfig =
164169
| VetTokenConfig
165170
| VetNFTCollectionConfig
166171
| TaoTokenConfig
167-
| PolyxTokenConfig;
172+
| PolyxTokenConfig
173+
| TonTokenConfig;
168174

169175
export interface Tokens {
170176
bitcoin: {
@@ -261,6 +267,9 @@ export interface Tokens {
261267
cosmos: {
262268
tokens: CosmosTokenConfig[];
263269
};
270+
ton: {
271+
tokens: TonTokenConfig[];
272+
};
264273
};
265274
testnet: {
266275
eth: {
@@ -356,6 +365,9 @@ export interface Tokens {
356365
cosmos: {
357366
tokens: CosmosTokenConfig[];
358367
};
368+
ton: {
369+
tokens: TonTokenConfig[];
370+
};
359371
};
360372
}
361373

@@ -1053,6 +1065,25 @@ function getCosmosTokenConfig(coin: CosmosChainToken): CosmosTokenConfig {
10531065
};
10541066
}
10551067

1068+
function getTonTokenConfig(coin: TonToken): TonTokenConfig {
1069+
return {
1070+
type: coin.name,
1071+
coin: coin.network.type === NetworkType.MAINNET ? 'ton' : 'tton',
1072+
network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
1073+
name: coin.fullName,
1074+
jettonMaster: coin.jettonMaster,
1075+
decimalPlaces: coin.decimalPlaces,
1076+
};
1077+
}
1078+
1079+
const getFormattedTonTokens = (customCoinMap = coins) =>
1080+
customCoinMap.reduce((acc: TonTokenConfig[], coin) => {
1081+
if (coin instanceof TonToken) {
1082+
acc.push(getTonTokenConfig(coin));
1083+
}
1084+
return acc;
1085+
}, []);
1086+
10561087
export const getFormattedTokens = (coinMap = coins): Tokens => {
10571088
const formattedAptNFTCollections = getFormattedAptNFTCollections(coinMap);
10581089
const formattedVetNFTCollections = getFormattedVetNFTCollections(coinMap);
@@ -1157,6 +1188,9 @@ export const getFormattedTokens = (coinMap = coins): Tokens => {
11571188
cosmos: {
11581189
tokens: getFormattedCosmosChainTokens(coinMap).filter((token) => token.network === 'Mainnet'),
11591190
},
1191+
ton: {
1192+
tokens: getFormattedTonTokens(coinMap).filter((token) => token.network === 'Mainnet'),
1193+
},
11601194
},
11611195
testnet: {
11621196
eth: {
@@ -1258,6 +1292,9 @@ export const getFormattedTokens = (coinMap = coins): Tokens => {
12581292
cosmos: {
12591293
tokens: getFormattedCosmosChainTokens(coinMap).filter((token) => token.network === 'Testnet'),
12601294
},
1295+
ton: {
1296+
tokens: getFormattedTonTokens(coinMap).filter((token) => token.network === 'Testnet'),
1297+
},
12611298
},
12621299
};
12631300
};
@@ -1369,6 +1406,8 @@ export function getFormattedTokenConfigForCoin(coin: Readonly<BaseCoin>): TokenC
13691406
return getVetNFTCollectionConfig(coin);
13701407
} else if (coin instanceof CoredaoERC20Token) {
13711408
return getCoredaoTokenConfig(coin);
1409+
} else if (coin instanceof TonToken) {
1410+
return getTonTokenConfig(coin);
13721411
}
13731412
return undefined;
13741413
}

0 commit comments

Comments
 (0)