butterjs-sdk
Version:
Butter Network SDK
486 lines (445 loc) • 14.8 kB
text/typescript
import { Account, connect, ConnectedWalletAccount, Near } from 'near-api-js';
import {
NearNetworkConfig,
TransactionOptions,
} from '../../types/requestTypes';
import { MOS_CONTRACT_ADDRESS_SET } from '../../constants/addresses';
import { ChainId } from '../../constants/chains';
import {
ADD_MCS_TOKEN_TO_CHAIN,
ADD_NATIVE_TO_CHAIN,
FT_TRANSFER_CALL,
SWAP_OUT_NATIVE,
TRANSFER_OUT_NATIVE,
TRANSFER_OUT_TOKEN,
VALID_MCS_TOKEN_OUT,
} from '../../constants/near_method_names';
import BN from 'bn.js';
import {
ChangeFunctionCallOptions,
FunctionCallOptions,
ViewFunctionCallOptions,
} from 'near-api-js/lib/account';
import { IMapOmnichainService } from '../interfaces/IMapOmnichainService';
import { hexToDecimalArray } from '../../utils';
import { ButterTransactionResponse } from '../../types/responseTypes';
import { assembleNearTransactionResponse } from '../../utils/responseUtil';
import { FinalExecutionOutcome } from 'near-api-js/lib/providers';
import { NearProviderType } from '../../types/paramTypes';
export class NearOmnichainService implements IMapOmnichainService {
provider: NearProviderType;
/**
* we treat account as class member cuz to initialize a near account, async is required
* @param provider
*/
constructor(provider: NearProviderType) {
this.provider = provider;
}
/**
* transfer out token(not native coin) from source chain to designated token on target chain
* @param fromAddress
* @param tokenAddress input token address
* @param amount amount in minimal unit
* @param toAddress target chain receiving address
* @param toChainId target chain id
* @param options see {@link TransactionOptions} for more detail
*/
async doTransferOutToken(
fromAddress: string,
tokenAddress: string,
amount: string,
toAddress: string,
toChainId: string,
options: TransactionOptions
): Promise<ButterTransactionResponse> {
let mosAccountId: string;
let account: Account | ConnectedWalletAccount;
if (this.provider instanceof NearNetworkConfig) {
// get mos contract address
mosAccountId =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
// prep near connection
const near: Near = await connect(this.provider);
account = await near.account(this.provider.fromAccount);
} else {
// this.provider._networkId;
mosAccountId =
this.provider._networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
account = this.provider.account();
}
try {
// the receiving address on Near need be in the format of number array as input
const decimalArrayAddress: number[] = hexToDecimalArray(
toAddress,
toChainId
);
// check if token is mintable
const isMintable: boolean = await this._checkMintable(
mosAccountId,
tokenAddress,
toChainId,
account
);
let nearTransferOutOptions: ChangeFunctionCallOptions;
// contract call option
if (isMintable) {
nearTransferOutOptions = {
contractId: mosAccountId,
methodName: TRANSFER_OUT_TOKEN,
args: {
token: tokenAddress,
to: decimalArrayAddress,
amount: amount,
to_chain: toChainId,
},
attachedDeposit: new BN(1, 10),
};
} else {
const msg = {
type: 'Transfer',
to: decimalArrayAddress,
to_chain: toChainId,
};
nearTransferOutOptions = {
contractId: tokenAddress,
methodName: FT_TRANSFER_CALL,
args: {
receiver_id: mosAccountId,
amount: amount,
msg: JSON.stringify(msg),
},
attachedDeposit: new BN(1, 10),
};
}
// manual input gas if necessary
if (options.gas != undefined) {
nearTransferOutOptions.gas = new BN(options.gas, 10);
} else {
nearTransferOutOptions.gas = new BN('300000000000000', 10);
}
const executionOutcome: FinalExecutionOutcome =
await this._doNearFunctionCall(account, nearTransferOutOptions);
return assembleNearTransactionResponse(executionOutcome);
} catch (error) {
throw error;
}
}
/**
* transfer out native coin from source chain to designated token on target chain
* @param fromAddress
* @param toAddress target chain receiving address
* @param toChainId target chain id
* @param amount amount to bridge in minimal unit
* @param options see {@link TransactionOptions} for more detail
*/
async doTransferOutNative(
fromAddress: string,
toAddress: string,
toChainId: string,
amount: string,
options: TransactionOptions
): Promise<ButterTransactionResponse> {
let mosAccountId: string;
let account: Account | ConnectedWalletAccount;
if (this.provider instanceof NearNetworkConfig) {
mosAccountId =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
const near: Near = await connect(this.provider);
account = await near.account(this.provider.fromAccount);
} else {
mosAccountId = this.provider.getAccountId().endsWith('testnet')
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
account = this.provider.account();
}
try {
const decimalArrayAddress: number[] = hexToDecimalArray(
toAddress,
toChainId
);
const nearCallOptions: ChangeFunctionCallOptions = {
contractId: mosAccountId,
methodName: TRANSFER_OUT_NATIVE,
args: {
to: decimalArrayAddress,
to_chain: Number.parseInt(toChainId),
},
attachedDeposit: new BN(amount, 10),
};
if (options.gas != undefined) {
nearCallOptions.gas = new BN(options.gas, 10);
}
const executionOutcome: FinalExecutionOutcome =
await this._doNearFunctionCall(account, nearCallOptions);
return assembleNearTransactionResponse(executionOutcome);
} catch (error) {
throw error;
}
}
async doSwapOutToken(
fromAddress: string,
tokenAddress: string,
amount: string,
toAddress: string,
toChainId: string,
msg: string,
options: TransactionOptions
): Promise<ButterTransactionResponse> {
let mosAccountId: string;
let account: Account | ConnectedWalletAccount;
if (this.provider instanceof NearNetworkConfig) {
// get mos contract address
mosAccountId =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
// prep near connection
const near: Near = await connect(this.provider);
account = await near.account(this.provider.fromAccount);
} else {
// this.provider._networkId;
mosAccountId =
this.provider._networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
account = this.provider.account();
}
try {
// the receiving address on Near need be in the format of number array as input
const nearTransferOutOptions: FunctionCallOptions = {
contractId: tokenAddress,
methodName: FT_TRANSFER_CALL,
args: {
receiver_id: mosAccountId,
amount: amount,
msg: msg,
},
attachedDeposit: new BN(1, 10),
};
// manual input gas if necessary
nearTransferOutOptions.gas = new BN('300000000000000', 10);
const executionOutcome: FinalExecutionOutcome =
await this._doNearFunctionCall(account, nearTransferOutOptions);
return assembleNearTransactionResponse(executionOutcome);
} catch (error) {
throw error;
}
}
async doSwapOutNative(
fromAddress: string,
toAddress: string,
toChainId: string,
amount: string,
swapInfo: string,
options: TransactionOptions
): Promise<ButterTransactionResponse> {
let mosAccountId: string;
let account: Account | ConnectedWalletAccount;
if (this.provider instanceof NearNetworkConfig) {
mosAccountId =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
const near: Near = await connect(this.provider);
account = await near.account(this.provider.fromAccount);
} else {
mosAccountId = this.provider.getAccountId().endsWith('testnet')
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
account = this.provider.account();
}
try {
const decimalArrayAddress: number[] = hexToDecimalArray(
toAddress,
toChainId
);
const nearCallOptions: ChangeFunctionCallOptions = {
contractId: mosAccountId,
methodName: SWAP_OUT_NATIVE,
args: {
to: decimalArrayAddress,
to_chain: toChainId,
swap_info: JSON.parse(swapInfo),
},
attachedDeposit: new BN(amount, 10),
};
if (options.gas != undefined) {
nearCallOptions.gas = new BN('300000000000000', 10);
}
const executionOutcome: FinalExecutionOutcome =
await this._doNearFunctionCall(account, nearCallOptions);
return assembleNearTransactionResponse(executionOutcome);
} catch (error) {
throw error;
}
}
/**
* add tochain to allowed transfer out chains.
* @param toChainId
*/
public async addNativeToChain(toChainId: string) {
if (this.provider instanceof NearNetworkConfig) {
const mosAccountId: string =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
const near: Near = await connect(this.provider);
const account = await near.account(this.provider.fromAccount);
const nearCallOptions: ChangeFunctionCallOptions = {
contractId: mosAccountId,
methodName: ADD_NATIVE_TO_CHAIN,
args: {
to_chain: toChainId,
},
};
return await this._doNearFunctionCall(account, nearCallOptions);
}
}
public async addTokenToChain(tokenAddress: string, toChainId: number) {
if (this.provider instanceof NearNetworkConfig) {
const mosAccountId: string =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
const near: Near = await connect(this.provider);
const account = await near.account(this.provider.fromAccount);
const nearCallOptions: ChangeFunctionCallOptions = {
contractId: mosAccountId,
methodName: ADD_MCS_TOKEN_TO_CHAIN,
args: {
token: tokenAddress,
to_chain: toChainId,
},
};
return await this._doNearFunctionCall(account, nearCallOptions);
}
}
/**
* call near smart contract
* @param account
* @param options
* @private
*/
private async _doNearFunctionCall(
account: Account | ConnectedWalletAccount,
options: ChangeFunctionCallOptions
): Promise<FinalExecutionOutcome> {
let outcome: FinalExecutionOutcome;
try {
outcome = await account.functionCall(options);
} catch (e) {
console.log(e);
}
return outcome!;
}
private async _doNearViewFunctionCall(
account: Account | ConnectedWalletAccount,
options: ViewFunctionCallOptions
) {
let result;
try {
result = await account.viewFunctionV2(options);
} catch (e) {
console.log(e);
}
return result;
}
doDepositOutToken(
tokenAddress: string,
from: string,
to: string,
amount: string,
options?: TransactionOptions
): Promise<string> {
return Promise.resolve('not implemented');
}
gasEstimateTransferOutNative(
fromAddress: string,
toAddress: string,
toChainId: string,
amount: string,
options?: TransactionOptions
): Promise<string> {
return Promise.resolve('not supported');
}
gasEstimateTransferOutToken(
fromAddress: string,
tokenAddress: string,
amount: string,
toAddress: string,
toChainId: string,
options?: TransactionOptions
): Promise<string> {
return Promise.resolve('not supported');
}
gasEstimateSwapOutNative(
fromAddress: string,
toAddress: string,
toChainId: string,
amount: string,
swapData: string,
options?: TransactionOptions
): Promise<string> {
return Promise.resolve('not supported');
}
gasEstimateSwapOutToken(
fromAddress: string,
tokenAddress: string,
toAddress: string,
toChainId: string,
amount: string,
swapData: string,
options?: TransactionOptions
): Promise<string> {
return Promise.resolve('not supported');
}
private async _checkMintable(
mosAccountId: string,
tokenAddress: string,
toChainId: string,
account: Account | ConnectedWalletAccount
): Promise<boolean> {
const nearCheckMintableOptions: ViewFunctionCallOptions = {
contractId: mosAccountId,
methodName: VALID_MCS_TOKEN_OUT,
args: {
token: tokenAddress,
to_chain: toChainId,
},
};
return await this._doNearViewFunctionCall(
account,
nearCheckMintableOptions
);
}
async addFungibleTokenToChain(
tokenAddress: string,
toChainId: string
): Promise<void> {
if (this.provider instanceof NearNetworkConfig) {
const mosAccountId: string =
this.provider.networkId === 'testnet'
? MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_TESTNET]
: MOS_CONTRACT_ADDRESS_SET[ChainId.NEAR_MAINNET];
const near: Near = await connect(this.provider);
const account = await near.account(this.provider.fromAccount);
const nearCallOptions: ChangeFunctionCallOptions = {
contractId: mosAccountId,
methodName: 'add_fungible_token_to_chain',
args: {
token: tokenAddress,
to_chain: toChainId,
},
gas: new BN('300000000000000', 10),
};
const executionOutcome: FinalExecutionOutcome =
await this._doNearFunctionCall(account, nearCallOptions);
}
}
}