Skip to content

feat(sdk-coin-near): added fungible token transfer builder #6203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion modules/bitgo/src/v2/coinFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AdaToken } from '@bitgo/sdk-coin-ada';
import { AlgoToken } from '@bitgo/sdk-coin-algo';
import { Bcha, Tbcha } from '@bitgo/sdk-coin-bcha';
import { HbarToken } from '@bitgo/sdk-coin-hbar';
import { Near, TNear } from '@bitgo/sdk-coin-near';
import { Near, TNear, Nep141Token } from '@bitgo/sdk-coin-near';
import { SolToken } from '@bitgo/sdk-coin-sol';
import { TrxToken } from '@bitgo/sdk-coin-trx';
import { CoinFactory } from '@bitgo/sdk-core';
Expand Down Expand Up @@ -455,6 +455,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
Sip10Token.createTokenConstructors([...tokens.bitcoin.stx.tokens, ...tokens.testnet.stx.tokens]).forEach(
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
);

Nep141Token.createTokenConstructors([...tokens.bitcoin.near.tokens, ...tokens.testnet.near.tokens]).forEach(
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
);
}

export const GlobalCoinFactory: CoinFactory = new CoinFactory();
Expand Down
1 change: 1 addition & 0 deletions modules/bitgo/test/browser/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('Coins', () => {
Polyx: 1,
Tpolyx: 1,
CoredaoToken: 1,
Nep141Token: 1,
};
Object.keys(BitGoJS.Coin)
.filter((coinName) => !excludedKeys[coinName])
Expand Down
5 changes: 3 additions & 2 deletions modules/sdk-coin-near/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@
"@bitgo/sdk-core": "^35.2.0",
"@bitgo/sdk-lib-mpc": "^10.5.0",
"@bitgo/statics": "^54.2.0",
"@near-js/crypto": "^2.0.1",
"@near-js/transactions": "^2.0.1",
"@stablelib/hex": "^1.0.0",
"bignumber.js": "^9.0.0",
"bn.js": "^5.2.1",
"bs58": "^4.0.1",
"js-sha256": "^0.9.0",
"lodash": "^4.17.14",
"near-api-js": "^0.44.2",
"near-api-js": "^5.1.1",
"superagent": "^9.0.1",
"tweetnacl": "^1.0.3"
},
Expand Down
4 changes: 4 additions & 0 deletions modules/sdk-coin-near/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ export const StakingContractMethodNames = {
Withdraw: 'withdraw',
} as const;

export const FT_TRANSFER = 'ft_transfer';
export const STORAGE_DEPOSIT = 'storage_deposit';
export const FUNGIBLE_TOKEN_RELATED_METHODS = [FT_TRANSFER, STORAGE_DEPOSIT];

export const HEX_REGEX = /^[0-9a-fA-F]+$/;
12 changes: 6 additions & 6 deletions modules/sdk-coin-near/src/lib/contractCallWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FunctionCall } from './iface';
*/
export class ContractCallWrapper {
private _methodName: string;
private _args: Record<string, unknown>;
private _args: Record<string, unknown> = {};
private _gas: string;
private _deposit: string;

Expand All @@ -21,35 +21,35 @@ export class ContractCallWrapper {
return this._methodName;
}

/** Set gas, expresed on yocto */
/** Set gas, expressed on yocto */
public set gas(gas: string) {
if (!this.isValidAmount(new BigNumber(gas))) {
throw new InvalidParameterValueError('Invalid gas value');
}
this._gas = gas;
}

/** Get gas, expresed on yocto*/
/** Get gas, expressed on yocto*/
public get gas(): string {
return this._gas;
}

/** Set deposit, expresed on yocto */
/** Set deposit, expressed on yocto */
public set deposit(deposit: string) {
if (!this.isValidAmount(new BigNumber(deposit))) {
throw new InvalidParameterValueError('Invalid deposit value');
}
this._deposit = deposit;
}

/** Get deposit, expresed on yocto */
/** Get deposit, expressed on yocto */
public get deposit(): string {
return this._deposit;
}

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

/** Set args, which are the parameters of a method */
Expand Down
134 changes: 134 additions & 0 deletions modules/sdk-coin-near/src/lib/fungibleTokenTransferBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import assert from 'assert';

import BigNumber from 'bignumber.js';
import * as NearAPI from 'near-api-js';

import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';

import { FT_TRANSFER, STORAGE_DEPOSIT } from './constants';
import { ContractCallWrapper } from './contractCallWrapper';
import { StorageDepositInput } from './iface';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import utils from './utils';

export class FungibleTokenTransferBuilder extends TransactionBuilder {
private contractCallWrapper: ContractCallWrapper;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this.contractCallWrapper = new ContractCallWrapper();
this.contractCallWrapper.methodName = FT_TRANSFER;
}

/**
* Check if a transaction is a fungible token transfer
*
* @param {NearAPI.transactions.Action[]} actions near transaction actions
* @returns {Boolean} true if more than 1 action present or first action method name is ft transfer
*/
public static isFungibleTokenTransferTransaction(actions: NearAPI.transactions.Action[]): boolean {
return actions.length > 1 || actions[0].functionCall?.methodName === FT_TRANSFER;
}

/**
* Initialize the transaction builder fields using the decoded transaction data
*
* @param {Transaction} tx the transaction data
*/
initBuilder(tx: Transaction): void {
super.initBuilder(tx);
for (const action of tx.nearTransaction.actions) {
if (action.functionCall && action.functionCall.methodName === FT_TRANSFER) {
this.contractCallWrapper.deposit = action.functionCall.deposit.toString();
this.contractCallWrapper.gas = action.functionCall.gas.toString();
}
}
}

/**
* Sets the gas of this transaction.
*
* @param {String} gas the gas of this transaction
* @returns {TransactionBuilder} This transaction builder
*/
public gas(gas: string): this {
this.validateValue(new BigNumber(gas));
this.contractCallWrapper.gas = gas;
return this;
}

/**
* Sets the deposit of at-least 1 yoctoNear
*
* @param {string} deposit the deposit in the minimum unit (1 Near = 1e24 yoctoNear) of this transaction
* @returns {TransactionBuilder} This transaction builder
*/
public deposit(deposit: string): this {
this.validateValue(new BigNumber(deposit));
this.contractCallWrapper.deposit = deposit;
return this;
}

/**
* Sets the actual receiver account id inside args
*
* @param accountId the receiver account id
*/
public ftReceiverId(accountId: string): this {
utils.isValidAddress(accountId);
this.contractCallWrapper.args = { receiver_id: accountId };
return this;
}

/**
* Sets the ft amount to be transferred
*
* @param amount the amount of fungible token to be transferred
*/
public amount(amount: string): this {
this.validateValue(new BigNumber(amount));
this.contractCallWrapper.args = { amount };
return this;
}

/**
* Sets the optional memo for the transfer
*
* @param memo
*/
public memo(memo: string): this {
this.contractCallWrapper.args = { memo };
return this;
}

/**
* Sets the storage deposit action
*
* @param {StorageDepositInput} input contains the deposit value, gas and optional account id
* if account id is not provided then it is self transfer
*/
public addStorageDeposit(input: StorageDepositInput): void {
const methodName = STORAGE_DEPOSIT;
assert(input.deposit, new BuildTransactionError('deposit is required before building storage deposit transfer'));
assert(input.gas, new BuildTransactionError('gas is required before building fungible token transfer'));
const args = input.accountId ? { account_id: input.accountId } : {};
const action = NearAPI.transactions.functionCall(methodName, args, input.gas, input.deposit);
super.action(action);
}

/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
const { methodName, args, gas, deposit } = this.contractCallWrapper.getParams();
assert(gas, new BuildTransactionError('gas is required before building fungible token transfer'));
assert(deposit, new BuildTransactionError('deposit is required before building fungible token transfer'));

if (!this._actions || this._actions.length === 0 || this._actions[0].functionCall?.methodName !== methodName) {
super.action(NearAPI.transactions.functionCall(methodName, args, BigInt(gas), BigInt(deposit)));
}
const tx = await super.buildImplementation();
tx.setTransactionType(TransactionType.Send);
return tx;
}
}
18 changes: 10 additions & 8 deletions modules/sdk-coin-near/src/lib/iface.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import BN from 'bn.js';
import { KeyType } from '@near-js/crypto';
import { TransactionType, TransactionExplanation as BaseTransactionExplanation } from '@bitgo/sdk-core';

export interface TransactionExplanation extends BaseTransactionExplanation {
type: TransactionType;
}

export enum KeyType {
ED25519 = 0,
}

export interface Signature {
keyType: KeyType;
data: Uint8Array;
}

export interface Transfer {
deposit: BN;
deposit: bigint;
}

/** Interface with parameters needed to perform FunctionCall to a contract */
Expand All @@ -38,9 +34,15 @@ export interface Action {
export interface TxData {
id?: string;
signerId: string;
publicKey: string;
nonce: number;
publicKey?: string;
nonce: bigint;
receiverId: string;
actions: Action[];
signature?: Signature;
}

export interface StorageDepositInput {
deposit: bigint;
gas: bigint;
accountId?: string;
}
13 changes: 7 additions & 6 deletions modules/sdk-coin-near/src/lib/stakingActivateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Transaction } from './transaction';
import BigNumber from 'bignumber.js';
import * as NearAPI from 'near-api-js';
import assert from 'assert';
import BN from 'bn.js';

import { ContractCallWrapper } from './contractCallWrapper';
import { TransactionBuilder } from './transactionBuilder';
Expand Down Expand Up @@ -32,14 +31,16 @@ export class StakingActivateBuilder extends TransactionBuilder {
initBuilder(tx: Transaction): void {
super.initBuilder(tx);
const functionCall = tx.nearTransaction.actions[0].functionCall;
this.contractCallWrapper.deposit = functionCall.deposit.toString();
this.contractCallWrapper.gas = functionCall.gas.toString();
if (functionCall) {
this.contractCallWrapper.deposit = functionCall.deposit.toString();
this.contractCallWrapper.gas = functionCall.gas.toString();
}
}

/**
* Sets the gas of this transaction.
*
* @param {string} value the gas of this transaction
* @param {string} gas the gas of this transaction
* @returns {TransactionBuilder} This transaction builder
*/
public gas(gas: string): this {
Expand All @@ -51,7 +52,7 @@ export class StakingActivateBuilder extends TransactionBuilder {
/**
* Sets the amount of this transaction.
*
* @param {string} value the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
* @param {string} amount the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
* @returns {TransactionBuilder} This transaction builder
*/
public amount(amount: string): this {
Expand All @@ -67,7 +68,7 @@ export class StakingActivateBuilder extends TransactionBuilder {
assert(gas, new BuildTransactionError('gas is required before building staking activate'));
assert(deposit, new BuildTransactionError('amount is required before building staking activate'));

super.actions([NearAPI.transactions.functionCall(methodName, args, new BN(gas), new BN(deposit, 10))]);
super.actions([NearAPI.transactions.functionCall(methodName, args, BigInt(gas), BigInt(deposit))]);
const tx = await super.buildImplementation();
tx.setTransactionType(TransactionType.StakingActivate);
return tx;
Expand Down
13 changes: 7 additions & 6 deletions modules/sdk-coin-near/src/lib/stakingDeactivateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Transaction } from './transaction';
import BigNumber from 'bignumber.js';
import * as NearAPI from 'near-api-js';
import assert from 'assert';
import BN from 'bn.js';

import { ContractCallWrapper } from './contractCallWrapper';
import { TransactionBuilder } from './transactionBuilder';
Expand Down Expand Up @@ -32,14 +31,16 @@ export class StakingDeactivateBuilder extends TransactionBuilder {
initBuilder(tx: Transaction): void {
super.initBuilder(tx);
const functionCall = tx.nearTransaction.actions[0].functionCall;
this.contractCallWrapper.args = JSON.parse(Buffer.from(functionCall.args).toString());
this.contractCallWrapper.gas = functionCall.gas.toString();
if (functionCall) {
this.contractCallWrapper.args = JSON.parse(Buffer.from(functionCall.args).toString());
this.contractCallWrapper.gas = functionCall.gas.toString();
}
}

/**
* Sets the gas of this transaction.
*
* @param {string} value the gas of this transaction
* @param {string} gas the gas of this transaction
* @returns {TransactionBuilder} This transaction builder
*/
public gas(gas: string): this {
Expand All @@ -51,7 +52,7 @@ export class StakingDeactivateBuilder extends TransactionBuilder {
/**
* Sets the amount of this transaction.
*
* @param {string} value the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
* @param {string} amount the amount in the minimum unit (1 Near = 1e24 yoctos) of this transaction
* @returns {TransactionBuilder} This transaction builder
*/
public amount(amount: string): this {
Expand All @@ -66,7 +67,7 @@ export class StakingDeactivateBuilder extends TransactionBuilder {
assert(gas, new BuildTransactionError('gas is required before building staking deactivate'));
assert(args?.amount, new BuildTransactionError('amount is required before building staking deactivate'));

super.actions([NearAPI.transactions.functionCall(methodName, args, new BN(gas), new BN(deposit))]);
super.actions([NearAPI.transactions.functionCall(methodName, args, BigInt(gas), BigInt(deposit))]);
const tx = await super.buildImplementation();
tx.setTransactionType(TransactionType.StakingDeactivate);
return tx;
Expand Down
Loading