Skip to content

Commit 7b39cdc

Browse files
committed
feat(sdk-coin-near): added fungible token transfer builder
Ticket: COIN-4148
1 parent 4801d51 commit 7b39cdc

30 files changed

+1539
-241
lines changed

modules/bitgo/src/v2/coinFactory.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AdaToken } from '@bitgo/sdk-coin-ada';
55
import { AlgoToken } from '@bitgo/sdk-coin-algo';
66
import { Bcha, Tbcha } from '@bitgo/sdk-coin-bcha';
77
import { HbarToken } from '@bitgo/sdk-coin-hbar';
8-
import { Near, TNear } from '@bitgo/sdk-coin-near';
8+
import { Near, TNear, Nep141Token } from '@bitgo/sdk-coin-near';
99
import { SolToken } from '@bitgo/sdk-coin-sol';
1010
import { TrxToken } from '@bitgo/sdk-coin-trx';
1111
import { CoinFactory } from '@bitgo/sdk-core';
@@ -455,6 +455,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
455455
Sip10Token.createTokenConstructors([...tokens.bitcoin.stx.tokens, ...tokens.testnet.stx.tokens]).forEach(
456456
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
457457
);
458+
459+
Nep141Token.createTokenConstructors([...tokens.bitcoin.near.tokens, ...tokens.testnet.near.tokens]).forEach(
460+
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
461+
);
458462
}
459463

460464
export const GlobalCoinFactory: CoinFactory = new CoinFactory();

modules/bitgo/test/browser/browser.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('Coins', () => {
4343
Polyx: 1,
4444
Tpolyx: 1,
4545
CoredaoToken: 1,
46+
Nep141Token: 1,
4647
};
4748
Object.keys(BitGoJS.Coin)
4849
.filter((coinName) => !excludedKeys[coinName])

modules/sdk-coin-near/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@
4343
"@bitgo/sdk-core": "^35.1.0",
4444
"@bitgo/sdk-lib-mpc": "^10.4.0",
4545
"@bitgo/statics": "^54.1.0",
46+
"@near-js/crypto": "^2.0.1",
47+
"@near-js/transactions": "^2.0.1",
4648
"@stablelib/hex": "^1.0.0",
4749
"bignumber.js": "^9.0.0",
48-
"bn.js": "^5.2.1",
4950
"bs58": "^4.0.1",
5051
"js-sha256": "^0.9.0",
5152
"lodash": "^4.17.14",
52-
"near-api-js": "^0.44.2",
53+
"near-api-js": "^5.1.1",
5354
"superagent": "^9.0.1",
5455
"tweetnacl": "^1.0.3"
5556
},

modules/sdk-coin-near/src/lib/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ export const StakingContractMethodNames = {
99
Withdraw: 'withdraw',
1010
} as const;
1111

12+
export const FT_TRANSFER = 'ft_transfer';
13+
export const STORAGE_DEPOSIT = 'storage_deposit';
14+
export const FUNGIBLE_TOKEN_RELATED_METHODS = [FT_TRANSFER, STORAGE_DEPOSIT];
15+
1216
export const HEX_REGEX = /^[0-9a-fA-F]+$/;

modules/sdk-coin-near/src/lib/contractCallWrapper.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { FunctionCall } from './iface';
77
*/
88
export class ContractCallWrapper {
99
private _methodName: string;
10-
private _args: Record<string, unknown>;
10+
private _args: Record<string, unknown> = {};
1111
private _gas: string;
1212
private _deposit: string;
1313

@@ -21,35 +21,35 @@ export class ContractCallWrapper {
2121
return this._methodName;
2222
}
2323

24-
/** Set gas, expresed on yocto */
24+
/** Set gas, expressed on yocto */
2525
public set gas(gas: string) {
2626
if (!this.isValidAmount(new BigNumber(gas))) {
2727
throw new InvalidParameterValueError('Invalid gas value');
2828
}
2929
this._gas = gas;
3030
}
3131

32-
/** Get gas, expresed on yocto*/
32+
/** Get gas, expressed on yocto*/
3333
public get gas(): string {
3434
return this._gas;
3535
}
3636

37-
/** Set deposit, expresed on yocto */
37+
/** Set deposit, expressed on yocto */
3838
public set deposit(deposit: string) {
3939
if (!this.isValidAmount(new BigNumber(deposit))) {
4040
throw new InvalidParameterValueError('Invalid deposit value');
4141
}
4242
this._deposit = deposit;
4343
}
4444

45-
/** Get deposit, expresed on yocto */
45+
/** Get deposit, expressed on yocto */
4646
public get deposit(): string {
4747
return this._deposit;
4848
}
4949

5050
/** Get args, which are the parameters of a method */
5151
public set args(args: Record<string, unknown>) {
52-
this._args = args;
52+
this._args = { ...this._args, ...args };
5353
}
5454

5555
/** Set args, which are the parameters of a method */
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import assert from 'assert';
2+
3+
import BigNumber from 'bignumber.js';
4+
import * as NearAPI from 'near-api-js';
5+
6+
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
7+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
8+
9+
import { FT_TRANSFER, STORAGE_DEPOSIT } from './constants';
10+
import { ContractCallWrapper } from './contractCallWrapper';
11+
import { StorageDepositInput } from './iface';
12+
import { Transaction } from './transaction';
13+
import { TransactionBuilder } from './transactionBuilder';
14+
import utils from './utils';
15+
16+
export class FungibleTokenTransferBuilder extends TransactionBuilder {
17+
private contractCallWrapper: ContractCallWrapper;
18+
19+
constructor(_coinConfig: Readonly<CoinConfig>) {
20+
super(_coinConfig);
21+
this.contractCallWrapper = new ContractCallWrapper();
22+
this.contractCallWrapper.methodName = FT_TRANSFER;
23+
}
24+
25+
/**
26+
* Check if a transaction is a fungible token transfer
27+
*
28+
* @param {NearAPI.transactions.Action[]} actions near transaction actions
29+
* @returns {Boolean} true if more than 1 action present or first action method name is ft transfer
30+
*/
31+
public static isFungibleTokenTransferTransaction(actions: NearAPI.transactions.Action[]): boolean {
32+
return actions.length > 1 || actions[0].functionCall?.methodName === FT_TRANSFER;
33+
}
34+
35+
/**
36+
* Initialize the transaction builder fields using the decoded transaction data
37+
*
38+
* @param {Transaction} tx the transaction data
39+
*/
40+
initBuilder(tx: Transaction): void {
41+
super.initBuilder(tx);
42+
for (const action of tx.nearTransaction.actions) {
43+
if (action.functionCall && action.functionCall.methodName === FT_TRANSFER) {
44+
this.contractCallWrapper.deposit = action.functionCall.deposit.toString();
45+
this.contractCallWrapper.gas = action.functionCall.gas.toString();
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Sets the gas of this transaction.
52+
*
53+
* @param {String} gas the gas of this transaction
54+
* @returns {TransactionBuilder} This transaction builder
55+
*/
56+
public gas(gas: string): this {
57+
this.validateValue(new BigNumber(gas));
58+
this.contractCallWrapper.gas = gas;
59+
return this;
60+
}
61+
62+
/**
63+
* Sets the deposit of at-least 1 yoctoNear
64+
*
65+
* @param {string} deposit the deposit in the minimum unit (1 Near = 1e24 yoctoNear) of this transaction
66+
* @returns {TransactionBuilder} This transaction builder
67+
*/
68+
public deposit(deposit: string): this {
69+
this.validateValue(new BigNumber(deposit));
70+
this.contractCallWrapper.deposit = deposit;
71+
return this;
72+
}
73+
74+
/**
75+
* Sets the actual receiver account id inside args
76+
*
77+
* @param accountId the receiver account id
78+
*/
79+
public ftReceiverId(accountId: string): this {
80+
utils.isValidAddress(accountId);
81+
this.contractCallWrapper.args = { receiver_id: accountId };
82+
return this;
83+
}
84+
85+
/**
86+
* Sets the ft amount to be transferred
87+
*
88+
* @param amount the amount of fungible token to be transferred
89+
*/
90+
public amount(amount: string): this {
91+
this.validateValue(new BigNumber(amount));
92+
this.contractCallWrapper.args = { amount };
93+
return this;
94+
}
95+
96+
/**
97+
* Sets the optional memo for the transfer
98+
*
99+
* @param memo
100+
*/
101+
public memo(memo: string): this {
102+
this.contractCallWrapper.args = { memo };
103+
return this;
104+
}
105+
106+
/**
107+
* Sets the storage deposit action
108+
*
109+
* @param {StorageDepositInput} input contains the deposit value, gas and optional account id
110+
* if account id is not provided then it is self transfer
111+
*/
112+
public addStorageDeposit(input: StorageDepositInput): void {
113+
const methodName = STORAGE_DEPOSIT;
114+
assert(input.deposit, new BuildTransactionError('deposit is required before building storage deposit transfer'));
115+
assert(input.gas, new BuildTransactionError('gas is required before building fungible token transfer'));
116+
const args = input.accountId ? { account_id: input.accountId } : {};
117+
const action = NearAPI.transactions.functionCall(methodName, args, input.gas, input.deposit);
118+
super.action(action);
119+
}
120+
121+
/** @inheritdoc */
122+
protected async buildImplementation(): Promise<Transaction> {
123+
const { methodName, args, gas, deposit } = this.contractCallWrapper.getParams();
124+
assert(gas, new BuildTransactionError('gas is required before building fungible token transfer'));
125+
assert(deposit, new BuildTransactionError('deposit is required before building fungible token transfer'));
126+
127+
if (!this._actions || this._actions.length === 0 || this._actions[0].functionCall?.methodName !== methodName) {
128+
super.action(NearAPI.transactions.functionCall(methodName, args, BigInt(gas), BigInt(deposit)));
129+
}
130+
const tx = await super.buildImplementation();
131+
tx.setTransactionType(TransactionType.Send);
132+
return tx;
133+
}
134+
}

modules/sdk-coin-near/src/lib/iface.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
import BN from 'bn.js';
1+
import { KeyType } from '@near-js/crypto';
22
import { TransactionType, TransactionExplanation as BaseTransactionExplanation } from '@bitgo/sdk-core';
33

44
export interface TransactionExplanation extends BaseTransactionExplanation {
55
type: TransactionType;
66
}
77

8-
export enum KeyType {
9-
ED25519 = 0,
10-
}
11-
128
export interface Signature {
139
keyType: KeyType;
1410
data: Uint8Array;
1511
}
1612

1713
export interface Transfer {
18-
deposit: BN;
14+
deposit: bigint;
1915
}
2016

2117
/** Interface with parameters needed to perform FunctionCall to a contract */
@@ -38,9 +34,15 @@ export interface Action {
3834
export interface TxData {
3935
id?: string;
4036
signerId: string;
41-
publicKey: string;
42-
nonce: number;
37+
publicKey?: string;
38+
nonce: bigint;
4339
receiverId: string;
4440
actions: Action[];
4541
signature?: Signature;
4642
}
43+
44+
export interface StorageDepositInput {
45+
deposit: bigint;
46+
gas: bigint;
47+
accountId?: string;
48+
}

modules/sdk-coin-near/src/lib/stakingActivateBuilder.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Transaction } from './transaction';
44
import BigNumber from 'bignumber.js';
55
import * as NearAPI from 'near-api-js';
66
import assert from 'assert';
7-
import BN from 'bn.js';
87

98
import { ContractCallWrapper } from './contractCallWrapper';
109
import { TransactionBuilder } from './transactionBuilder';
@@ -32,14 +31,16 @@ export class StakingActivateBuilder extends TransactionBuilder {
3231
initBuilder(tx: Transaction): void {
3332
super.initBuilder(tx);
3433
const functionCall = tx.nearTransaction.actions[0].functionCall;
35-
this.contractCallWrapper.deposit = functionCall.deposit.toString();
36-
this.contractCallWrapper.gas = functionCall.gas.toString();
34+
if (functionCall) {
35+
this.contractCallWrapper.deposit = functionCall.deposit.toString();
36+
this.contractCallWrapper.gas = functionCall.gas.toString();
37+
}
3738
}
3839

3940
/**
4041
* Sets the gas of this transaction.
4142
*
42-
* @param {string} value the gas of this transaction
43+
* @param {string} gas the gas of this transaction
4344
* @returns {TransactionBuilder} This transaction builder
4445
*/
4546
public gas(gas: string): this {
@@ -51,7 +52,7 @@ export class StakingActivateBuilder extends TransactionBuilder {
5152
/**
5253
* Sets the amount of this transaction.
5354
*
54-
* @param {string} value the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
55+
* @param {string} amount the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
5556
* @returns {TransactionBuilder} This transaction builder
5657
*/
5758
public amount(amount: string): this {
@@ -67,7 +68,7 @@ export class StakingActivateBuilder extends TransactionBuilder {
6768
assert(gas, new BuildTransactionError('gas is required before building staking activate'));
6869
assert(deposit, new BuildTransactionError('amount is required before building staking activate'));
6970

70-
super.actions([NearAPI.transactions.functionCall(methodName, args, new BN(gas), new BN(deposit, 10))]);
71+
super.actions([NearAPI.transactions.functionCall(methodName, args, BigInt(gas), BigInt(deposit))]);
7172
const tx = await super.buildImplementation();
7273
tx.setTransactionType(TransactionType.StakingActivate);
7374
return tx;

modules/sdk-coin-near/src/lib/stakingDeactivateBuilder.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Transaction } from './transaction';
44
import BigNumber from 'bignumber.js';
55
import * as NearAPI from 'near-api-js';
66
import assert from 'assert';
7-
import BN from 'bn.js';
87

98
import { ContractCallWrapper } from './contractCallWrapper';
109
import { TransactionBuilder } from './transactionBuilder';
@@ -32,14 +31,16 @@ export class StakingDeactivateBuilder extends TransactionBuilder {
3231
initBuilder(tx: Transaction): void {
3332
super.initBuilder(tx);
3433
const functionCall = tx.nearTransaction.actions[0].functionCall;
35-
this.contractCallWrapper.args = JSON.parse(Buffer.from(functionCall.args).toString());
36-
this.contractCallWrapper.gas = functionCall.gas.toString();
34+
if (functionCall) {
35+
this.contractCallWrapper.args = JSON.parse(Buffer.from(functionCall.args).toString());
36+
this.contractCallWrapper.gas = functionCall.gas.toString();
37+
}
3738
}
3839

3940
/**
4041
* Sets the gas of this transaction.
4142
*
42-
* @param {string} value the gas of this transaction
43+
* @param {string} gas the gas of this transaction
4344
* @returns {TransactionBuilder} This transaction builder
4445
*/
4546
public gas(gas: string): this {
@@ -51,7 +52,7 @@ export class StakingDeactivateBuilder extends TransactionBuilder {
5152
/**
5253
* Sets the amount of this transaction.
5354
*
54-
* @param {string} value the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
55+
* @param {string} amount the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
5556
* @returns {TransactionBuilder} This transaction builder
5657
*/
5758
public amount(amount: string): this {
@@ -66,7 +67,7 @@ export class StakingDeactivateBuilder extends TransactionBuilder {
6667
assert(gas, new BuildTransactionError('gas is required before building staking deactivate'));
6768
assert(args?.amount, new BuildTransactionError('amount is required before building staking deactivate'));
6869

69-
super.actions([NearAPI.transactions.functionCall(methodName, args, new BN(gas), new BN(deposit))]);
70+
super.actions([NearAPI.transactions.functionCall(methodName, args, BigInt(gas), BigInt(deposit))]);
7071
const tx = await super.buildImplementation();
7172
tx.setTransactionType(TransactionType.StakingDeactivate);
7273
return tx;

0 commit comments

Comments
 (0)