UNPKG

butterjs-sdk

Version:
486 lines (445 loc) 14.8 kB
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); } } }