Skip to content

Commit a17ad7e

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

File tree

6 files changed

+266
-1
lines changed

6 files changed

+266
-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 { JettonToken } 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';
@@ -35,6 +36,7 @@ import {
3536
CosmosTokenConfig,
3637
VetTokenConfig,
3738
TaoTokenConfig,
39+
JettonTokenConfig,
3840
} from '@bitgo/statics';
3941
import {
4042
Ada,
@@ -519,6 +521,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
519521
VetToken.createTokenConstructors().forEach(({ name, coinConstructor }) =>
520522
coinFactory.register(name, coinConstructor)
521523
);
524+
525+
JettonToken.createTokenConstructors([...tokens.bitcoin.ton.tokens, ...tokens.testnet.ton.tokens]).forEach(
526+
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
527+
);
522528
}
523529

524530
export function getCoinConstructor(coinName: string): CoinConstructor | undefined {
@@ -963,6 +969,9 @@ export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor |
963969
case 'zeta':
964970
case 'tzeta':
965971
return CosmosToken.createTokenConstructor(tokenConfig as CosmosTokenConfig);
972+
case 'ton':
973+
case 'tton':
974+
return JettonToken.createTokenConstructor(tokenConfig as JettonTokenConfig);
966975
default:
967976
return undefined;
968977
}

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 './jettonToken';
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import BigNumber from 'bignumber.js';
2+
import { BitGoBase, CoinConstructor, NamedCoinConstructor, VerifyTransactionOptions } from '@bitgo/sdk-core';
3+
import { coins, JettonTokenConfig, NetworkType, tokens } from '@bitgo/statics';
4+
5+
import { Transaction } from './lib';
6+
import { Ton } from './ton';
7+
8+
export class JettonToken extends Ton {
9+
public readonly tokenConfig: JettonTokenConfig;
10+
11+
constructor(bitgo: BitGoBase, tokenConfig: JettonTokenConfig) {
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: JettonTokenConfig): CoinConstructor {
18+
return (bitgo: BitGoBase) => new JettonToken(bitgo, config);
19+
}
20+
21+
static createTokenConstructors(
22+
tokenConfig: JettonTokenConfig[] = [...tokens.bitcoin.ton.tokens, ...tokens.testnet.ton.tokens]
23+
): NamedCoinConstructor[] {
24+
const tokensCtors: NamedCoinConstructor[] = [];
25+
for (const token of tokenConfig) {
26+
const tokenConstructor = JettonToken.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 contractAddress(): string {
41+
return this.tokenConfig.contractAddress;
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 'Jetton 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/sdk-coin-ton/src/register.ts

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 { JettonToken } from './jettonToken';
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 = JettonToken.createTokenConstructors();
12+
tokens.forEach((token) => {
13+
sdk.register(token.name, token.coinConstructor);
14+
});
815
};

modules/statics/src/account.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ export interface Nep141TokenConstructorOptions extends AccountConstructorOptions
153153
storageDepositAmount: string;
154154
}
155155

156+
export interface JettonTokenConstructorOptions extends AccountConstructorOptions {
157+
contractAddress: string;
158+
}
159+
156160
export interface VetTokenConstructorOptions extends AccountConstructorOptions {
157161
contractAddress: string;
158162
gasTankToken?: string;
@@ -630,6 +634,17 @@ export class Nep141Token extends AccountCoinToken {
630634
}
631635
}
632636

637+
export class JettonToken extends AccountCoinToken {
638+
public contractAddress: string;
639+
constructor(options: JettonTokenConstructorOptions) {
640+
super({
641+
...options,
642+
});
643+
644+
this.contractAddress = options.contractAddress;
645+
}
646+
}
647+
633648
export class VetToken extends AccountCoinToken {
634649
public contractAddress: string;
635650
public gasTankToken?: string;
@@ -3069,6 +3084,95 @@ export function sip10Token(
30693084
* @param network? Optional token network. Defaults to the testnet Stacks network.
30703085
* @param features? Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
30713086
*/
3087+
/**
3088+
* Factory function for jetton token instances.
3089+
*
3090+
* @param id uuid v4
3091+
* @param name unique identifier of the token
3092+
* @param fullName Complete human-readable name of the token
3093+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
3094+
* @param contractAddress Contract address of this token
3095+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
3096+
* @param features Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
3097+
* @param prefix Optional token prefix. Defaults to empty string
3098+
* @param suffix Optional token suffix. Defaults to token name.
3099+
* @param network Optional token network. Defaults to TON main network.
3100+
* @param primaryKeyCurve The elliptic curve for this chain/token
3101+
*/
3102+
export function jettonToken(
3103+
id: string,
3104+
name: string,
3105+
fullName: string,
3106+
decimalPlaces: number,
3107+
contractAddress: string,
3108+
asset: UnderlyingAsset,
3109+
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
3110+
prefix = '',
3111+
suffix: string = name.toUpperCase(),
3112+
network: AccountNetwork = Networks.main.ton,
3113+
primaryKeyCurve: KeyCurve = KeyCurve.Ed25519
3114+
) {
3115+
return Object.freeze(
3116+
new JettonToken({
3117+
id,
3118+
name,
3119+
fullName,
3120+
network,
3121+
contractAddress,
3122+
prefix,
3123+
suffix,
3124+
features,
3125+
decimalPlaces,
3126+
asset,
3127+
isToken: true,
3128+
primaryKeyCurve,
3129+
baseUnit: BaseUnit.TON,
3130+
})
3131+
);
3132+
}
3133+
3134+
/**
3135+
* Factory function for testnet jetton token instances.
3136+
*
3137+
* @param id uuid v4
3138+
* @param name unique identifier of the token
3139+
* @param fullName Complete human-readable name of the token
3140+
* @param decimalPlaces Number of decimal places this token supports (divisibility exponent)
3141+
* @param contractAddress Contract address of this token
3142+
* @param asset Asset which this coin represents. This is the same for both mainnet and testnet variants of a coin.
3143+
* @param features Features of this coin. Defaults to the DEFAULT_FEATURES defined in `AccountCoin`
3144+
* @param prefix Optional token prefix. Defaults to empty string
3145+
* @param suffix Optional token suffix. Defaults to token name.
3146+
* @param network Optional token network. Defaults to the testnet TON network.
3147+
*/
3148+
export function tjettonToken(
3149+
id: string,
3150+
name: string,
3151+
fullName: string,
3152+
decimalPlaces: number,
3153+
contractAddress: string,
3154+
asset: UnderlyingAsset,
3155+
features: CoinFeature[] = AccountCoin.DEFAULT_FEATURES,
3156+
prefix = '',
3157+
suffix: string = name.toUpperCase(),
3158+
network: AccountNetwork = Networks.test.ton,
3159+
primaryKeyCurve: KeyCurve = KeyCurve.Ed25519
3160+
) {
3161+
return jettonToken(
3162+
id,
3163+
name,
3164+
fullName,
3165+
decimalPlaces,
3166+
contractAddress,
3167+
asset,
3168+
features,
3169+
prefix,
3170+
suffix,
3171+
network,
3172+
primaryKeyCurve
3173+
);
3174+
}
3175+
30723176
export function tsip10Token(
30733177
id: string,
30743178
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
Erc20Coin,
1616
Erc721Coin,
1717
HederaToken,
18+
JettonToken,
1819
Nep141Token,
1920
OpethERC20Token,
2021
PolygonERC20Token,
@@ -120,6 +121,10 @@ export type Nep141TokenConfig = BaseNetworkConfig & {
120121
storageDepositAmount: string;
121122
};
122123

124+
export type JettonTokenConfig = BaseNetworkConfig & {
125+
contractAddress: string;
126+
};
127+
123128
export type VetTokenConfig = BaseNetworkConfig & {
124129
contractAddress: string;
125130
};
@@ -149,7 +154,8 @@ export type TokenConfig =
149154
| Nep141TokenConfig
150155
| CosmosTokenConfig
151156
| VetTokenConfig
152-
| TaoTokenConfig;
157+
| TaoTokenConfig
158+
| JettonTokenConfig;
153159

154160
export interface Tokens {
155161
bitcoin: {
@@ -238,6 +244,9 @@ export interface Tokens {
238244
cosmos: {
239245
tokens: CosmosTokenConfig[];
240246
};
247+
ton: {
248+
tokens: JettonTokenConfig[];
249+
};
241250
};
242251
testnet: {
243252
eth: {
@@ -325,6 +334,9 @@ export interface Tokens {
325334
cosmos: {
326335
tokens: CosmosTokenConfig[];
327336
};
337+
ton: {
338+
tokens: JettonTokenConfig[];
339+
};
328340
};
329341
}
330342

@@ -942,6 +954,25 @@ function getCosmosTokenConfig(coin: CosmosChainToken): CosmosTokenConfig {
942954
};
943955
}
944956

957+
function getJettonTokenConfig(coin: JettonToken): JettonTokenConfig {
958+
return {
959+
type: coin.name,
960+
coin: coin.network.type === NetworkType.MAINNET ? 'ton' : 'tton',
961+
network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
962+
name: coin.fullName,
963+
contractAddress: coin.contractAddress,
964+
decimalPlaces: coin.decimalPlaces,
965+
};
966+
}
967+
968+
const getFormattedJettonTokens = (customCoinMap = coins) =>
969+
customCoinMap.reduce((acc: JettonTokenConfig[], coin) => {
970+
if (coin instanceof JettonToken) {
971+
acc.push(getJettonTokenConfig(coin));
972+
}
973+
return acc;
974+
}, []);
975+
945976
export const getFormattedTokens = (coinMap = coins): Tokens => {
946977
const formattedAptNFTCollections = getFormattedAptNFTCollections(coinMap);
947978
return {
@@ -1035,6 +1066,9 @@ export const getFormattedTokens = (coinMap = coins): Tokens => {
10351066
cosmos: {
10361067
tokens: getFormattedCosmosChainTokens(coinMap).filter((token) => token.network === 'Mainnet'),
10371068
},
1069+
ton: {
1070+
tokens: getFormattedJettonTokens(coinMap).filter((token) => token.network === 'Mainnet'),
1071+
},
10381072
},
10391073
testnet: {
10401074
eth: {
@@ -1126,6 +1160,9 @@ export const getFormattedTokens = (coinMap = coins): Tokens => {
11261160
cosmos: {
11271161
tokens: getFormattedCosmosChainTokens(coinMap).filter((token) => token.network === 'Testnet'),
11281162
},
1163+
ton: {
1164+
tokens: getFormattedJettonTokens(coinMap).filter((token) => token.network === 'Testnet'),
1165+
},
11291166
},
11301167
};
11311168
};
@@ -1227,6 +1264,8 @@ export function getFormattedTokenConfigForCoin(coin: Readonly<BaseCoin>): TokenC
12271264
return getCosmosTokenConfig(coin);
12281265
} else if (coin instanceof VetToken) {
12291266
return getVetTokenConfig(coin);
1267+
} else if (coin instanceof JettonToken) {
1268+
return getJettonTokenConfig(coin);
12301269
}
12311270
return undefined;
12321271
}

0 commit comments

Comments
 (0)