From d02bae5a9ca253990008fc53d6f32bf12f321aeb Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 6 Jul 2021 22:30:53 -0400 Subject: [PATCH 01/25] add buildEvmTx function --- src/helpers/tx_helper.ts | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/helpers/tx_helper.ts b/src/helpers/tx_helper.ts index 812d1149..8497e6f0 100644 --- a/src/helpers/tx_helper.ts +++ b/src/helpers/tx_helper.ts @@ -309,39 +309,55 @@ export async function buildEvmTransferNativeTx( return tx; } -export async function buildEvmTransferErc20Tx( +export async function buildEvmTx( from: string, to: string, - amount: BN, // in wei gasPrice: BN, gasLimit: number, - contractAddress: string -) { + value: BN, + data: string +): Promise { const nonce = await web3.eth.getTransactionCount(from); const chainId = await web3.eth.getChainId(); const networkId = await web3.eth.net.getId(); + const chainParams = { common: EthereumjsCommon.forCustomChain('mainnet', { networkId, chainId }, 'istanbul'), }; - //@ts-ignore - const cont = new web3.eth.Contract(ERC20Abi.abi, contractAddress); - const tokenTx = cont.methods.transfer(to, amount.toString()); - let tx = Transaction.fromTxData( { nonce: nonce, gasPrice: '0x' + gasPrice.toString('hex'), gasLimit: gasLimit, - value: '0x0', - to: contractAddress, - data: tokenTx.encodeABI(), + value: `0x${value.toString('hex')}`, + to: to, + data: data, }, chainParams ); return tx; } +export async function buildEvmTransferErc20Tx( + from: string, + to: string, + amount: BN, // in wei + gasPrice: BN, + gasLimit: number, + contractAddress: string +) { + //@ts-ignore + const cont = new web3.eth.Contract(ERC20Abi.abi, contractAddress); + const tokenTx = cont.methods.transfer(to, amount.toString()); + + let value = new BN(0); + let data = tokenTx.encodeABI(); + let tx = await buildEvmTx(from, to, gasPrice, gasLimit, value, data); + + return tx; +} + export async function buildEvmTransferErc721Tx( from: string, to: string, From ce2d79fdcbe3fc0866589885b86194fbaa010500 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 6 Jul 2021 22:32:54 -0400 Subject: [PATCH 02/25] Add sendEvmTx method to wallet class --- src/Wallet/Wallet.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 38c76064..06cca19b 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -19,6 +19,7 @@ import { buildEvmExportTransaction, buildEvmTransferErc20Tx, buildEvmTransferNativeTx, + buildEvmTx, buildMintNftTx, } from '@/helpers/tx_helper'; import { BN, Buffer } from 'avalanche'; @@ -222,6 +223,16 @@ export abstract class WalletProvider { return await waitTxEvm(txHash); } + async sendEvmTx(to: string, gasPrice: BN, gasLimit: number, value: BN, data: string): Promise { + let from = this.getAddressC(); + let tx = await buildEvmTx(from, to, gasPrice, gasLimit, value, data); + let signedTx = await this.signEvm(tx); + let txHex = signedTx.serialize().toString('hex'); + let hash = await web3.eth.sendSignedTransaction('0x' + txHex); + const txHash = hash.transactionHash; + return await waitTxEvm(txHash); + } + /** * Returns the C chain AVAX balance of the wallet in WEI format. */ From 72420f90ad3855328a05b574d26894b1bd0206e4 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 6 Jul 2021 22:36:35 -0400 Subject: [PATCH 03/25] add description --- src/Wallet/Wallet.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 06cca19b..8aa6c133 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -223,6 +223,14 @@ export abstract class WalletProvider { return await waitTxEvm(txHash); } + /** + * Creates an EVM transaction from the given parameters, signs and issues it. + * @param to Hex address of recipient + * @param gasPrice Gas price in WEI format + * @param gasLimit Gas limit + * @param value Value field of the transaction + * @param data Hex data field of the transaction + */ async sendEvmTx(to: string, gasPrice: BN, gasLimit: number, value: BN, data: string): Promise { let from = this.getAddressC(); let tx = await buildEvmTx(from, to, gasPrice, gasLimit, value, data); From 76881ff62c3e4eea00a1c466371650908bce8fda Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 6 Jul 2021 22:41:19 -0400 Subject: [PATCH 04/25] export helpers --- src/helpers/index.ts | 3 +++ src/index.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src/helpers/index.ts diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 00000000..5d676f57 --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1,3 @@ +export * as AddressHelper from './address_helper'; +export * as TxHelper from './tx_helper'; +export * as UtxoHelper from './utxo_helper'; diff --git a/src/index.ts b/src/index.ts index f336c17f..f84ae84f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,8 @@ import Keystore from '@/Keystore/keystore'; import { BN } from 'avalanche'; import Big from 'big.js'; +export * from '@/helpers'; + export { MnemonicWallet, SingletonWallet, From 63db649bed79fad1a6926e639667afd181d8f09a Mon Sep 17 00:00:00 2001 From: paOol Date: Wed, 7 Jul 2021 11:55:07 -0700 Subject: [PATCH 05/25] add customEvmTx method --- src/Wallet/Wallet.ts | 9 +++++---- src/helpers/tx_helper.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 8aa6c133..9ad37011 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -16,6 +16,7 @@ import { import { buildAvmExportTransaction, buildCreateNftFamilyTx, + buildCustomEvmTx, buildEvmExportTransaction, buildEvmTransferErc20Tx, buildEvmTransferNativeTx, @@ -226,14 +227,14 @@ export abstract class WalletProvider { /** * Creates an EVM transaction from the given parameters, signs and issues it. * @param to Hex address of recipient - * @param gasPrice Gas price in WEI format + * @param gasPrice Hex Gas price in WEI format * @param gasLimit Gas limit - * @param value Value field of the transaction + * @param value Hex Value of the transaction * @param data Hex data field of the transaction */ - async sendEvmTx(to: string, gasPrice: BN, gasLimit: number, value: BN, data: string): Promise { + async sendEvmTx(to: string, gasPrice: string, gasLimit: number, value: string, data: string): Promise { let from = this.getAddressC(); - let tx = await buildEvmTx(from, to, gasPrice, gasLimit, value, data); + let tx = await buildCustomEvmTx(data, from, to, gasPrice, gasLimit, value); let signedTx = await this.signEvm(tx); let txHex = signedTx.serialize().toString('hex'); let hash = await web3.eth.sendSignedTransaction('0x' + txHex); diff --git a/src/helpers/tx_helper.ts b/src/helpers/tx_helper.ts index 8497e6f0..5e3bd6e4 100644 --- a/src/helpers/tx_helper.ts +++ b/src/helpers/tx_helper.ts @@ -339,6 +339,36 @@ export async function buildEvmTx( return tx; } +export async function buildCustomEvmTx( + data: string, + from: string, + to?: string, + gasPrice?: string, + gasLimit?: number, + value?: string +): Promise { + const nonce = await web3.eth.getTransactionCount(from); + const chainId = await web3.eth.getChainId(); + const networkId = await web3.eth.net.getId(); + + const chainParams = { + common: EthereumjsCommon.forCustomChain('mainnet', { networkId, chainId }, 'istanbul'), + }; + + let tx = Transaction.fromTxData( + { + nonce, + gasPrice, + gasLimit, + value, + to, + data, + }, + chainParams + ); + return tx; +} + export async function buildEvmTransferErc20Tx( from: string, to: string, From 55f4bc4955615a5c490152c49cd6f6776d26bd26 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 8 Jul 2021 19:19:57 -0400 Subject: [PATCH 06/25] add issueEvmTx method to wallets export helpers added evm helper methods --- src/Wallet/Wallet.ts | 34 +++++++------------- src/helpers/address_helper.ts | 2 +- src/helpers/tx_helper.ts | 59 +++++++++++++---------------------- src/index.ts | 16 ++-------- src/utils/utils.ts | 2 +- 5 files changed, 38 insertions(+), 75 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 9ad37011..ce821368 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -20,8 +20,8 @@ import { buildEvmExportTransaction, buildEvmTransferErc20Tx, buildEvmTransferNativeTx, - buildEvmTx, buildMintNftTx, + estimateErc20Gas, } from '@/helpers/tx_helper'; import { BN, Buffer } from 'avalanche'; import { Transaction } from '@ethereumjs/tx'; @@ -194,15 +194,8 @@ export abstract class WalletProvider { */ async sendAvaxC(to: string, amount: BN, gasPrice: BN, gasLimit: number): Promise { let fromAddr = this.getAddressC(); - let tx = await buildEvmTransferNativeTx(fromAddr, to, amount, gasPrice, gasLimit); - - let signedTx = await this.signEvm(tx); - - let txHex = signedTx.serialize().toString('hex'); - let hash = await web3.eth.sendSignedTransaction('0x' + txHex); - const txHash = hash.transactionHash; - return await waitTxEvm(txHash); + return await this.issueEvmTx(tx); } /** @@ -216,25 +209,20 @@ export abstract class WalletProvider { async sendErc20(to: string, amount: BN, gasPrice: BN, gasLimit: number, contractAddress: string): Promise { let fromAddr = this.getAddressC(); let tx = await buildEvmTransferErc20Tx(fromAddr, to, amount, gasPrice, gasLimit, contractAddress); + let txHash = await this.issueEvmTx(tx); + return txHash; + } - let signedTx = await this.signEvm(tx); - let txHex = signedTx.serialize().toString('hex'); - let hash = await web3.eth.sendSignedTransaction('0x' + txHex); - const txHash = hash.transactionHash; - return await waitTxEvm(txHash); + async estimateErc20Gas(contractAddress: string, to: string, amount: BN) { + let from = this.getAddressC(); + return await estimateErc20Gas(contractAddress, from, to, amount); } /** - * Creates an EVM transaction from the given parameters, signs and issues it. - * @param to Hex address of recipient - * @param gasPrice Hex Gas price in WEI format - * @param gasLimit Gas limit - * @param value Hex Value of the transaction - * @param data Hex data field of the transaction + * Given a `Transaction`, it will sign and issue it to the network. + * @param tx The unsigned transaction to issue. */ - async sendEvmTx(to: string, gasPrice: string, gasLimit: number, value: string, data: string): Promise { - let from = this.getAddressC(); - let tx = await buildCustomEvmTx(data, from, to, gasPrice, gasLimit, value); + async issueEvmTx(tx: Transaction): Promise { let signedTx = await this.signEvm(tx); let txHex = signedTx.serialize().toString('hex'); let hash = await web3.eth.sendSignedTransaction('0x' + txHex); diff --git a/src/helpers/address_helper.ts b/src/helpers/address_helper.ts index 37466793..92f0ce59 100644 --- a/src/helpers/address_helper.ts +++ b/src/helpers/address_helper.ts @@ -8,7 +8,7 @@ export const validateAddress = (address: string): boolean | string => { bintools.stringToAddress(address); return true; } catch (error) { - return error.message; + return false; } }; diff --git a/src/helpers/tx_helper.ts b/src/helpers/tx_helper.ts index 5e3bd6e4..156825d5 100644 --- a/src/helpers/tx_helper.ts +++ b/src/helpers/tx_helper.ts @@ -309,45 +309,19 @@ export async function buildEvmTransferNativeTx( return tx; } -export async function buildEvmTx( +export async function buildCustomEvmTx( from: string, - to: string, gasPrice: BN, gasLimit: number, - value: BN, - data: string -): Promise { - const nonce = await web3.eth.getTransactionCount(from); - const chainId = await web3.eth.getChainId(); - const networkId = await web3.eth.net.getId(); - - const chainParams = { - common: EthereumjsCommon.forCustomChain('mainnet', { networkId, chainId }, 'istanbul'), - }; - - let tx = Transaction.fromTxData( - { - nonce: nonce, - gasPrice: '0x' + gasPrice.toString('hex'), - gasLimit: gasLimit, - value: `0x${value.toString('hex')}`, - to: to, - data: data, - }, - chainParams - ); - return tx; -} - -export async function buildCustomEvmTx( - data: string, - from: string, + data?: string, to?: string, - gasPrice?: string, - gasLimit?: number, - value?: string + value?: string, + nonce?: number ): Promise { - const nonce = await web3.eth.getTransactionCount(from); + if (typeof nonce === 'undefined') { + nonce = await web3.eth.getTransactionCount(from); + } + const chainId = await web3.eth.getChainId(); const networkId = await web3.eth.net.getId(); @@ -355,10 +329,12 @@ export async function buildCustomEvmTx( common: EthereumjsCommon.forCustomChain('mainnet', { networkId, chainId }, 'istanbul'), }; + let gasPriceHex = `0x${gasPrice.toString('hex')}`; + let tx = Transaction.fromTxData( { nonce, - gasPrice, + gasPrice: gasPriceHex, gasLimit, value, to, @@ -381,9 +357,9 @@ export async function buildEvmTransferErc20Tx( const cont = new web3.eth.Contract(ERC20Abi.abi, contractAddress); const tokenTx = cont.methods.transfer(to, amount.toString()); - let value = new BN(0); let data = tokenTx.encodeABI(); - let tx = await buildEvmTx(from, to, gasPrice, gasLimit, value, data); + + let tx = await buildCustomEvmTx(from, gasPrice, gasLimit, data, contractAddress); return tx; } @@ -420,6 +396,15 @@ export async function buildEvmTransferErc721Tx( return tx; } +export async function estimateErc20Gas(tokenContract: string, from: string, to: string, value: BN) { + //@ts-ignore + const contract = new web3.eth.Contract(ERC20Abi.abi, tokenContract); + const tokenTx = contract.methods.transfer(to, value.toString()); + return await tokenTx.estimateGas({ + from: from, + }); +} + export enum AvmTxNameEnum { 'Transaction' = AVMConstants.BASETX, 'Mint' = AVMConstants.CREATEASSETTX, diff --git a/src/index.ts b/src/index.ts index f84ae84f..5decf970 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,19 +10,9 @@ import PublicMnemonicWallet from '@/Wallet/PublicMnemonicWallet'; import * as NetworkConstants from '@/Network/constants'; import * as Utils from '@/utils'; import Keystore from '@/Keystore/keystore'; -import { BN } from 'avalanche'; -import Big from 'big.js'; +export { BN } from 'avalanche'; +export { Big } from 'big.js'; export * from '@/helpers'; -export { - MnemonicWallet, - SingletonWallet, - NetworkConstants, - Utils, - BN, - Keystore, - LedgerWallet, - Big, - PublicMnemonicWallet, -}; +export { MnemonicWallet, SingletonWallet, NetworkConstants, Utils, Keystore, LedgerWallet, PublicMnemonicWallet }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 5e99471e..abb96967 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -109,7 +109,7 @@ export function bnToAvaxP(val: BN): string { export function numberToBN(val: number | string, decimals: number): BN { let valBig = Big(val); let tens = Big(10).pow(decimals); - let valBN = new BN(valBig.times(tens).toString()); + let valBN = new BN(valBig.times(tens).toFixed(0)); return valBN; } From 8b6f898961cf3d621c01b23fc3e775ce9f679e45 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 8 Jul 2021 22:58:07 -0400 Subject: [PATCH 07/25] add method sendAND --- src/Wallet/Wallet.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index ce821368..e4c69312 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -198,6 +198,26 @@ export abstract class WalletProvider { return await this.issueEvmTx(tx); } + /** + * Send Avalanche Native Tokens on X chain + * @param assetID ID of the token to send + * @param amount How many units of the token to send. Based on smallest divisible unit. + * @param to X chain address to send tokens to + */ + async sendANT(assetID: string, amount: BN, to: string): Promise { + let utxoSet = this.getUtxosX(); + let fromAddrs = this.getAllAddressesX(); + let changeAddr = this.getChangeAddressX(); + + let tx = await xChain.buildBaseTx(utxoSet, amount, assetID, [to], fromAddrs, [changeAddr]); + let signed = await this.signX(tx); + let txId = await xChain.issueTx(signed); + await waitTxX(txId); + + this.updateUtxosX(); + return txId; + } + /** * Makes a transfer call on a ERC20 contract. * @param to Hex address to transfer tokens to. From 77de377419fa7eea44c7982abfc103d4bd24544d Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 8 Jul 2021 23:17:48 -0400 Subject: [PATCH 08/25] add sendCustomEvmTx method to wallets --- src/Wallet/Wallet.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index e4c69312..91e17a1b 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -233,11 +233,32 @@ export abstract class WalletProvider { return txHash; } + /** + * Estimate the gas needed for an ERC20 Transfer transaction + * @param contractAddress The ERC20 contract address + * @param to Address receiving the tokens + * @param amount Amount to send. Given in the smallest divisible unit. + */ async estimateErc20Gas(contractAddress: string, to: string, amount: BN) { let from = this.getAddressC(); return await estimateErc20Gas(contractAddress, from, to, amount); } + /** + * A method to create custom EVM transactions. + * @param gasPrice + * @param gasLimit + * @param data `data` field of the transaction, in hex format + * @param to `to` field of the transaction, in hex format + * @param value `value` field of the transaction, in hex format + * @param nonce Nonce of the transaction, in number + */ + async sendCustomEvmTx(gasPrice: BN, gasLimit: number, data?: string, to?: string, value?: string, nonce?: number) { + let from = this.getAddressC(); + let tx = await buildCustomEvmTx(from, gasPrice, gasLimit, data, to, value, nonce); + return await this.issueEvmTx(tx); + } + /** * Given a `Transaction`, it will sign and issue it to the network. * @param tx The unsigned transaction to issue. From d90568395adca8795977f88dccc91e1de0bd03fd Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Mon, 12 Jul 2021 19:35:01 -0400 Subject: [PATCH 09/25] Bump eth libraries UniversalNode and helper methods created to manage inter chain balance transfers Tests added for UniversalNode --- package.json | 4 +- src/Wallet/Wallet.ts | 52 +++- src/helpers/UniversalNode.ts | 139 +++++++++ src/helpers/universal_tx_helper.ts | 134 ++++++--- src/utils/utils.ts | 9 + test/helpers/universal_tx_helper.test.ts | 360 ++++++++++++++++++++--- yarn.lock | 44 +-- 7 files changed, 637 insertions(+), 105 deletions(-) create mode 100644 src/helpers/UniversalNode.ts diff --git a/package.json b/package.json index aea49cef..5f6fd2c2 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "typescript": "^4.2.4" }, "dependencies": { - "@ethereumjs/common": "2.2.0", - "@ethereumjs/tx": "3.1.4", + "@ethereumjs/common": "2.4.0", + "@ethereumjs/tx": "3.3.0", "@ledgerhq/hw-app-eth": "5.42.1", "@obsidiansystems/hw-app-avalanche": "0.2.2", "@openzeppelin/contracts": "4.1.0", diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 91e17a1b..e4a6920f 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -58,7 +58,7 @@ import { PayloadBase, UnixNow } from 'avalanche/dist/utils'; import { getAssetDescription } from '@/Asset/Assets'; import { balanceOf, getErc20Token } from '@/Asset/Erc20'; import { NO_NETWORK } from '@/errors'; -import { bnToLocaleString, waitTxC, waitTxEvm, waitTxP, waitTxX } from '@/utils/utils'; +import { avaxCtoX, bnToLocaleString, waitTxC, waitTxEvm, waitTxP, waitTxX } from '@/utils/utils'; import EvmWalletReadonly from '@/Wallet/EvmWalletReadonly'; import EventEmitter from 'events'; import { @@ -70,6 +70,16 @@ import { import { HistoryItemType, ITransactionData } from '@/History/types'; import moment from 'moment'; import { bintools } from '@/common'; +import { ChainIdType } from '@/types'; +import { + canHaveBalanceOnC, + canHaveBalanceOnP, + canHaveBalanceOnX, + getStepsForBalanceC, + getStepsForBalanceP, + getStepsForBalanceX, + UniversalTx, +} from '@/helpers/universal_tx_helper'; export abstract class WalletProvider { abstract type: WalletNameType; @@ -259,6 +269,46 @@ export abstract class WalletProvider { return await this.issueEvmTx(tx); } + /** + * Can this wallet have the given amount on the given chain after a series of internal transactions (if required). + * @param chain X/P/C + * @param amount The amount to check against + */ + public canHaveBalanceOnChain(chain: ChainIdType, amount: BN): boolean { + let xBal = this.getAvaxBalanceX().unlocked; + let pBal = this.getAvaxBalanceP().unlocked; + let cBal = avaxCtoX(this.getAvaxBalanceC()); // need to use 9 decimal places + + switch (chain) { + case 'P': + return canHaveBalanceOnP(xBal, pBal, cBal, amount); + case 'C': + return canHaveBalanceOnC(xBal, pBal, cBal, amount); + case 'X': + return canHaveBalanceOnX(xBal, pBal, cBal, amount); + } + } + + /** + * Returns an array of transaction to do in order to have the target amount on the given chain + * @param chain The chain (X/P/C) to have the desired amount on + * @param amount The desired amount + */ + public getTransactionsForBalance(chain: ChainIdType, amount: BN): UniversalTx[] { + let xBal = this.getAvaxBalanceX().unlocked; + let pBal = this.getAvaxBalanceP().unlocked; + let cBal = avaxCtoX(this.getAvaxBalanceC()); // need to use 9 decimal places + + switch (chain) { + case 'P': + return getStepsForBalanceP(xBal, pBal, cBal, amount); + case 'C': + return getStepsForBalanceC(xBal, pBal, cBal, amount); + case 'X': + return getStepsForBalanceX(xBal, pBal, cBal, amount); + } + } + /** * Given a `Transaction`, it will sign and issue it to the network. * @param tx The unsigned transaction to issue. diff --git a/src/helpers/UniversalNode.ts b/src/helpers/UniversalNode.ts new file mode 100644 index 00000000..ba0673f7 --- /dev/null +++ b/src/helpers/UniversalNode.ts @@ -0,0 +1,139 @@ +import { BN } from 'avalanche'; +import { ChainIdType } from '@/types'; +import { xChain } from '@/Network/network'; +import { UniversalTx, UniversalTxActionType } from '@/helpers/universal_tx_helper'; + +export class UniversalNode { + parents: UniversalNode[]; + child: UniversalNode | null; // can only have 1 child + balance: BN; + chain: ChainIdType; + constructor(balance: BN, chain: ChainIdType, parents: UniversalNode[] = [], child: UniversalNode | null = null) { + this.parents = parents; + this.child = child; + this.balance = balance; + this.chain = chain; + } + + // Sum of the node's balance + all balance of parents minus the transfer fees + reduceTotalBalanceFromParents(): BN { + // If there are no balance return balance of self + if (this.parents.length === 0) { + return this.balance; + } + + let fee = xChain.getTxFee(); + + let parentBals = this.parents.map((node) => { + // Subtract transfer fees from parent balance + // import + export + let parentBalance = node.reduceTotalBalanceFromParents(); + parentBalance = parentBalance.sub(fee).sub(fee); + let zero = new BN(0); + return BN.max(parentBalance, zero); + }); + + let tot = parentBals.reduce((prev, current) => { + return prev.add(current); + }, new BN(0)); + + return tot.add(this.balance); + } + + /** + * Returns the export action type from this node to its child + * @param to + */ + getExportMethod(to?: ChainIdType): UniversalTxActionType { + switch (this.chain) { + case 'X': + if (to === 'P') { + return 'export_x_p'; + } else { + return 'export_x_c'; + } + case 'C': + return 'export_c_x'; + case 'P': + return 'export_p_x'; + } + } + + buildExportTx(amount: BN): UniversalTx { + return { + action: this.getExportMethod(this.child?.chain), + amount: amount, + }; + } + + /*** + * Assumes there is enough balance on node tree + * Returns empty array even if transaction not possible! + * What steps to take to have the target balance on this node. + * @param target Amount of nAVAX needed on this node. + */ + getStepsForTargetBalance(target: BN): UniversalTx[] { + // If the node has enough balance no transaction needed + // If target is negative or zero no transaction needed + if (this.balance.gte(target) || target.lte(new BN(0))) { + return []; + } + + let fee = xChain.getTxFee(); + let feeImportExport = fee.add(fee); + + // If not enough balance and no parents + // return all the balance + if (this.balance.lt(target) && this.parents.length === 0) { + let tx = this.buildExportTx(this.balance.sub(feeImportExport)); + return [tx]; + } + + // If not enough balance + + // Amount needed to collect from parents + let remaining = target.sub(this.balance); + + // Amount the parent must have + let parentBalanceNeeded = remaining.add(feeImportExport); + + if (this.parents.length === 1) { + // Export from parent to this node + let parent = this.parents[0]; + let txs = parent.getStepsForTargetBalance(parentBalanceNeeded); + let tx = parent.buildExportTx(remaining); + return [...txs, tx]; + } else { + let transactions = []; + for (let i = 0; i < this.parents.length; i++) { + let p = this.parents[i]; + let pBal = p.reduceTotalBalanceFromParents(); + let pBalAvailable = pBal.sub(feeImportExport); + + let exportableAmt = BN.min(pBalAvailable, remaining); + let target = BN.min(pBalAvailable, parentBalanceNeeded); + + if (target.lte(new BN(0))) continue; + + let pTxs = p.getStepsForTargetBalance(target); + let pTx = p.buildExportTx(exportableAmt); + + transactions.push(...pTxs); + transactions.push(pTx); + + parentBalanceNeeded = parentBalanceNeeded.sub(exportableAmt); + remaining = remaining.sub(exportableAmt); + } + + return transactions; + } + } + + addParent(node: UniversalNode) { + this.parents.push(node); + } + + setChild(node: UniversalNode) { + this.child = node; + } +} diff --git a/src/helpers/universal_tx_helper.ts b/src/helpers/universal_tx_helper.ts index ce1aca30..497e7bc6 100644 --- a/src/helpers/universal_tx_helper.ts +++ b/src/helpers/universal_tx_helper.ts @@ -1,66 +1,102 @@ -// import { WalletType } from '@/Wallet/types'; import { BN } from 'avalanche'; -// import { getAddressChain, validateAddress } from '@/helpers/address_helper'; -import { ChainIdType } from '@/types'; -import { pChain, web3, xChain } from '@/Network/network'; -// import { Utils } from '@/index'; -// import { WalletProvider } from '@/Wallet/Wallet'; -// import { PlatformVMConstants } from 'avalanche/dist/apis/platformvm'; -// import Common from '@ethereumjs/common'; - -type UniversalTxActionTypesX = 'send_x' | 'export_x_c' | 'export_x_P'; +import { UniversalNode } from '@/helpers/UniversalNode'; + +type UniversalTxActionTypesX = 'send_x' | 'export_x_c' | 'export_x_p'; type UniversalTxActionTypesC = 'send_c' | 'export_c_x'; type UniversalTxActionTypesP = 'export_p_x'; -type UniversalTxActionType = UniversalTxActionTypesX | UniversalTxActionTypesC | UniversalTxActionTypesP; +export type UniversalTxActionType = UniversalTxActionTypesX | UniversalTxActionTypesC | UniversalTxActionTypesP; -interface UniversalTx { +export interface UniversalTx { action: UniversalTxActionType; amount: BN; } +export function createGraphForP(balX: BN, balP: BN, balC: BN): UniversalNode { + let xNode = new UniversalNode(balX, 'X'); + let pNode = new UniversalNode(balP, 'P'); + let cNode = new UniversalNode(balC, 'C'); + + pNode.addParent(xNode); + xNode.addParent(cNode); + + cNode.setChild(xNode); + xNode.setChild(pNode); + return pNode; +} + +export function createGraphForC(balX: BN, balP: BN, balC: BN): UniversalNode { + let xNode = new UniversalNode(balX, 'X'); + let pNode = new UniversalNode(balP, 'P'); + let cNode = new UniversalNode(balC, 'C'); + + cNode.addParent(xNode); + xNode.addParent(pNode); + + pNode.setChild(xNode); + xNode.setChild(cNode); + + return cNode; +} + +export function createGraphForX(balX: BN, balP: BN, balC: BN): UniversalNode { + let xNode = new UniversalNode(balX, 'X'); + let pNode = new UniversalNode(balP, 'P'); + let cNode = new UniversalNode(balC, 'C'); + + xNode.addParent(pNode); + xNode.addParent(cNode); + + cNode.setChild(xNode); + pNode.setChild(xNode); + + return xNode; +} + +export function canHaveBalanceOnX(balX: BN, balP: BN, balC: BN, targetAmount: BN): boolean { + let startNode = createGraphForX(balX, balP, balC); + return startNode.reduceTotalBalanceFromParents().gte(targetAmount); +} + +export function canHaveBalanceOnP(balX: BN, balP: BN, balC: BN, targetAmount: BN): boolean { + let startNode = createGraphForP(balX, balP, balC); + return startNode.reduceTotalBalanceFromParents().gte(targetAmount); +} + /** - * Returns what transactions are needed to have the given AVAX balance on the given chain. - * @param balX current balance of the X chain in nAVAX - * @param balP current balance of the P chain in nAVAX - * @param balC current balance of the C chain in nAVAX - * @param targetChain One of the primary chain. - * @param targetAmount Desired amount on the `targetChain` + * Will return true if `targetAmount` can exist on C chain */ -export function getStepsForTargetAvaxBalance( - balX: BN, - balP: BN, - balC: BN, - targetAmount: BN, - targetChain: ChainIdType -): UniversalTx[] { - // Compute destination chain - let balances = { - X: balX, - P: balP, - C: balC, - }; - - let balDestination = balances[targetChain]; - - // Current chain has enough balance - if (balDestination.gte(targetAmount)) { - return []; +export function canHaveBalanceOnC(balX: BN, balP: BN, balC: BN, targetAmount: BN): boolean { + let startNode = createGraphForC(balX, balP, balC); + return startNode.reduceTotalBalanceFromParents().gte(targetAmount); +} + +export function getStepsForBalanceP(balX: BN, balP: BN, balC: BN, targetAmount: BN): UniversalTx[] { + let startNode = createGraphForP(balX, balP, balC); + + if (startNode.reduceTotalBalanceFromParents().lt(targetAmount)) { + throw new Error('Insufficient AVAX.'); } - let targetRemaining = targetAmount.sub(balDestination); + return startNode.getStepsForTargetBalance(targetAmount); +} - if (targetChain === 'X') { - // Use chain with bigger balance first - // if (balP.gt(balC)) { - // } +export function getStepsForBalanceC(balX: BN, balP: BN, balC: BN, targetAmount: BN): UniversalTx[] { + let startNode = createGraphForC(balX, balP, balC); - // Check if P has enough - let exportImportCost = pChain.getTxFee().mul(new BN(2)); - let tot = targetRemaining.add(exportImportCost); - if (balP.gte(tot)) { - return []; - } + if (startNode.reduceTotalBalanceFromParents().lt(targetAmount)) { + throw new Error('Insufficient AVAX.'); } - return []; + + return startNode.getStepsForTargetBalance(targetAmount); +} + +export function getStepsForBalanceX(balX: BN, balP: BN, balC: BN, targetAmount: BN): UniversalTx[] { + let startNode = createGraphForX(balX, balP, balC); + + if (startNode.reduceTotalBalanceFromParents().lt(targetAmount)) { + throw new Error('Insufficient AVAX.'); + } + + return startNode.getStepsForTargetBalance(targetAmount); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index abb96967..3c1d6c29 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -55,6 +55,15 @@ export function avaxCtoX(amount: BN) { return amount.div(tens); } +export function avaxXtoC(amount: BN) { + let tens = new BN(10).pow(new BN(9)); + return amount.mul(tens); +} + +export function avaxPtoC(amount: BN) { + return avaxXtoC(amount); +} + export function bnToBigAvaxX(val: BN): Big { return bnToBig(val, 9); } diff --git a/test/helpers/universal_tx_helper.test.ts b/test/helpers/universal_tx_helper.test.ts index eba7bd66..6f9f52e9 100644 --- a/test/helpers/universal_tx_helper.test.ts +++ b/test/helpers/universal_tx_helper.test.ts @@ -1,7 +1,7 @@ -import { getStepsForTargetAvaxBalance } from '@/helpers/universal_tx_helper'; +import { createGraphForP, createGraphForX, UniversalTx } from '@/helpers/universal_tx_helper'; import { BN, utils } from 'avalanche'; import { AVMConstants } from 'avalanche/dist/apis/avm'; -import { pChain } from '@/Network/network'; +import { pChain, xChain } from '@/Network/network'; jest.mock('@/Network/network', () => { return { @@ -13,50 +13,344 @@ jest.mock('@/Network/network', () => { pChain: { getTxFee: jest.fn(), }, + xChain: { + getTxFee: jest.fn(), + }, }; }); const addrC = '0x6a23c16777a3A194b2773df90FEB8753A8e619Ee'; const addrP = 'P-avax19v8flm9qt2gv2tctztjjerlgs4k3vgjsfw8udh'; const addrX = 'X-avax19v8flm9qt2gv2tctztjjerlgs4k3vgjsfw8udh'; -// -// describe('Universal tx helper methods', () => { -// it('No transfer necessary (C)', () => { -// let TEN = new BN(10); -// let ZERO = new BN(0); -// let sendAmount = new BN(1); -// let to = addrC; -// -// let txs = buildUniversalAvaxTransferTxs(TEN, ZERO, ZERO, to, sendAmount); -// -// console.log(txs); -// expect(1).toEqual(1); -// }); -// }); + const FEE = new BN(1000000); -describe('get steps', () => { +function compareSteps(steps: UniversalTx[], expected: UniversalTx[]) { + expect(steps.length).toEqual(expected.length); + + for (let i = 0; i < steps.length; i++) { + let step = steps[i]; + let exp = expected[i]; + expect(step).toEqual(exp); + } +} + +describe('Reduce parent balance of UniversalNode P', () => { + beforeEach(() => { + (pChain.getTxFee as jest.Mock).mockReturnValue(FEE); + (xChain.getTxFee as jest.Mock).mockReturnValue(FEE); + }); + + it('all parents have balance of 1 AVAX', () => { + let balX = new BN(1000000000); + let balP = new BN(1000000000); + let balC = new BN(1000000000); + + let expected = new BN(2996000000); + let startNode = createGraphForP(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); + + it('middle child empty', () => { + let balP = new BN(1000000000); + let balX = new BN(0); + let balC = new BN(1000000000); + + let expected = new BN(1996000000); + let startNode = createGraphForP(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); + + it('only top parent has balance', () => { + let balP = new BN(0); + let balX = new BN(0); + let balC = new BN(1000000000); + + let expected = new BN(996000000); + let startNode = createGraphForP(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); + + it('starting node has balance', () => { + let balP = new BN(1000000000); + let balX = new BN(0); + let balC = new BN(0); + + let expected = new BN(1000000000); + let startNode = createGraphForP(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); +}); + +describe('Reduce parent balance of UniversalNode X', () => { beforeEach(() => { (pChain.getTxFee as jest.Mock).mockReturnValue(FEE); + (xChain.getTxFee as jest.Mock).mockReturnValue(FEE); + }); + + it('all nodes have balance of 1 AVAX', () => { + let balX = new BN(1000000000); + let balP = new BN(1000000000); + let balC = new BN(1000000000); + + let expected = new BN(2996000000); + let startNode = createGraphForX(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); + + it('both parents have balance of 1 AVAX', () => { + let balX = new BN(0); + let balP = new BN(1000000000); + let balC = new BN(1000000000); + + let expected = new BN(1996000000); + let startNode = createGraphForX(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); }); - it('should return empty', () => { - let TEN = new BN(10); - let ZERO = new BN(0); - let res = getStepsForTargetAvaxBalance(TEN, ZERO, ZERO, TEN, 'X'); + it('one parent has balance of 1 AVAX', () => { + let balX = new BN(0); + let balP = new BN(1000000000); + let balC = new BN(0); - expect(res.length).toEqual(0); + let expected = new BN(998000000); + let startNode = createGraphForX(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); }); - // it('expect C to X 10 nAVAX', () => { - // let balX = new BN(0); - // let balP = new BN(0); - // let balC = new BN(20); - // - // let targetBal = new BN(10); - // - // let res = getStepsForTargetAvaxBalance(balX, balP, balC, targetBal, 'X'); - // - // expect(res.length).toEqual(1); - // }); + it('other parent has balance of 1 AVAX', () => { + let balX = new BN(0); + let balP = new BN(0); + let balC = new BN(1000000000); + + let expected = new BN(998000000); + let startNode = createGraphForX(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); + + it('no balance', () => { + let balX = new BN(0); + let balP = new BN(0); + let balC = new BN(0); + + let expected = new BN(0); + let startNode = createGraphForX(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); + + it('starting node has 1 AVAX', () => { + let balX = new BN(1000000000); + let balP = new BN(0); + let balC = new BN(0); + + let expected = new BN(1000000000); + let startNode = createGraphForX(balX, balP, balC); + let tot = startNode.reduceTotalBalanceFromParents(); + expect(tot).toEqual(expected); + }); +}); + +describe('Get transactions for balance on UniversalNode P', () => { + beforeEach(() => { + (pChain.getTxFee as jest.Mock).mockReturnValue(FEE); + (xChain.getTxFee as jest.Mock).mockReturnValue(FEE); + }); + + it('node has enough balance, return empty array', () => { + let balP = new BN(1000000000); + let balX = new BN(0); + let balC = new BN(0); + + let nodeP = createGraphForP(balX, balP, balC); + let target = new BN(1000000000); + + let steps = nodeP.getStepsForTargetBalance(target); + + expect(steps.length).toEqual(0); + }); + + it('node needs balance from parent', () => { + let balP = new BN(1000000000); + let balX = new BN(2000000000); + let balC = new BN(0); + + let nodeP = createGraphForP(balX, balP, balC); + let target = new BN(2000000000); + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_x_p', + amount: new BN(1000000000), + }, + ]; + + let steps = nodeP.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); + + it('node needs balance from top parent', () => { + let balP = new BN(1000000000); + let balX = new BN(0); + let balC = new BN(2000000000); + + let nodeP = createGraphForP(balX, balP, balC); + let target = new BN(2000000000); + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_c_x', + amount: new BN(1002000000), + }, + { + action: 'export_x_p', + amount: new BN(1000000000), + }, + ]; + + let steps = nodeP.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); + + it('node needs balance from both parents and self', () => { + let balP = new BN(1000000000); // 1 AVAX + let balX = new BN(500000000); // 0.5 AVAX + let balC = new BN(2000000000); // 2 AVAX + + let nodeP = createGraphForP(balX, balP, balC); + let target = new BN(2000000000); // 2 AVAX + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_c_x', + amount: new BN(502000000), + }, + { + action: 'export_x_p', + amount: new BN(1000000000), + }, + ]; + + let steps = nodeP.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); + + it('node needs balance from both parents', () => { + let balP = new BN(0); // 0 AVAX + let balX = new BN(1000000000); // 1 AVAX + let balC = new BN(2000000000); // 2 AVAX + + let nodeP = createGraphForP(balX, balP, balC); + let target = new BN(2000000000); // 2 AVAX + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_c_x', + amount: new BN(1002000000), + }, + { + action: 'export_x_p', + amount: new BN(2000000000), + }, + ]; + + let steps = nodeP.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); +}); + +describe('Get transactions for balance on UniversalNode X', () => { + beforeEach(() => { + (pChain.getTxFee as jest.Mock).mockReturnValue(FEE); + (xChain.getTxFee as jest.Mock).mockReturnValue(FEE); + }); + + it('node has enough balance return empty array', () => { + let balX = new BN(1000000000); + let balP = new BN(0); + let balC = new BN(0); + + let nodeP = createGraphForX(balX, balP, balC); + let target = new BN(1000000000); + + let steps = nodeP.getStepsForTargetBalance(target); + + compareSteps(steps, []); + }); + + it('node needs balance from P', () => { + let balX = new BN(0); + let balP = new BN(5000000000); // 5 AVAX + let balC = new BN(0); + + let nodeX = createGraphForX(balX, balP, balC); + let target = new BN(2000000000); // 2 AVAX + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_p_x', + amount: new BN(2000000000), + }, + ]; + + let steps = nodeX.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); + + it('node needs balance from C', () => { + let balX = new BN(0); + let balP = new BN(0); + let balC = new BN(5000000000); // 5 AVAX + + let nodeX = createGraphForX(balX, balP, balC); + let target = new BN(2000000000); // 2 AVAX + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_c_x', + amount: new BN(2000000000), + }, + ]; + + let steps = nodeX.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); + + it('node needs balance from both parents', () => { + let balX = new BN(0); + let balP = new BN(700000000); + let balC = new BN(700000000); + + let nodeX = createGraphForX(balX, balP, balC); + let target = new BN(1000000000); // 1 AVAX + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_p_x', + amount: new BN(698000000), + }, + { + action: 'export_c_x', + amount: new BN(302000000), + }, + ]; + + let steps = nodeX.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); }); diff --git a/yarn.lock b/yarn.lock index 6b9652c4..3a7d2236 100644 --- a/yarn.lock +++ b/yarn.lock @@ -942,29 +942,21 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@ethereumjs/common@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.2.0.tgz#850a3e3e594ee707ad8d44a11e8152fb62450535" - integrity sha512-PyQiTG00MJtBRkJmv46ChZL8u2XWxNBeAthznAUIUiefxPAXjbkuiCZOuncgJS34/XkMbNc9zMt/PlgKRBElig== - dependencies: - crc-32 "^1.2.0" - ethereumjs-util "^7.0.9" - -"@ethereumjs/common@^2.2.0": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.3.1.tgz#d692e3aff5adb35dd587dd1e6caab69e0ed2fa0b" - integrity sha512-V8hrULExoq0H4HFs3cCmdRGbgmipmlNzak6Xg34nHYfQyqkSdrCuflvYjyWmsNpI8GtrcZhzifAbgX/1C1Cjwg== +"@ethereumjs/common@2.4.0", "@ethereumjs/common@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.4.0.tgz#2d67f6e6ba22246c5c89104e6b9a119fb3039766" + integrity sha512-UdkhFWzWcJCZVsj1O/H8/oqj/0RVYjLc1OhPjBrQdALAkQHpCp8xXI4WLnuGTADqTdJZww0NtgwG+TRPkXt27w== dependencies: crc-32 "^1.2.0" - ethereumjs-util "^7.0.10" + ethereumjs-util "^7.1.0" -"@ethereumjs/tx@3.1.4": - version "3.1.4" - resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.1.4.tgz#04cf9e9406da5f04a1a26c458744641f4b4b8dd0" - integrity sha512-6cJpmmjCpG5ZVN9NJYtWvmrEQcevw9DIR8hj2ca2PszD2fxbIFXky3Z37gpf8S6u0Npv09kG8It+G4xjydZVLg== +"@ethereumjs/tx@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.0.tgz#14ed1b7fa0f28e1cd61e3ecbdab824205f6a4378" + integrity sha512-yTwEj2lVzSMgE6Hjw9Oa1DZks/nKTWM8Wn4ykDNapBPua2f4nXO3qKnni86O6lgDj5fVNRqbDsD0yy7/XNGDEA== dependencies: - "@ethereumjs/common" "^2.2.0" - ethereumjs-util "^7.0.10" + "@ethereumjs/common" "^2.4.0" + ethereumjs-util "^7.1.0" "@ethersproject/abi@5.0.7": version "5.0.7" @@ -3591,7 +3583,7 @@ ethereumjs-util@^6.0.0: ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.7, ethereumjs-util@^7.0.9: +ethereumjs-util@^7.0.7: version "7.0.10" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz#5fb7b69fa1fda0acc59634cf39d6b0291180fc1f" integrity sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw== @@ -3603,6 +3595,18 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.7, ethereumjs-util@^7.0.9: ethjs-util "0.1.6" rlp "^2.2.4" +ethereumjs-util@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz#e2b43a30bfcdbcb432a4eb42bd5f2393209b3fd5" + integrity sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" + rlp "^2.2.4" + ethers@^5.1.4, ethers@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.0.tgz#e9fe4b39350bcce5edd410c70bd57ba328ecf474" From 3764829ebcf97f7a69f1925c7d816b6e66cdeaa5 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Mon, 12 Jul 2021 20:18:21 -0400 Subject: [PATCH 10/25] use getAtomicTxStatus when waiting for C chain transactions Wait for confirmation on C chain export imports --- package.json | 4 ++-- src/Wallet/Wallet.ts | 11 ++++++---- src/utils/types.ts | 7 +++++++ src/utils/utils.ts | 50 +++++++++++++++++++++++++++++++++++--------- yarn.lock | 9 ++++---- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 5f6fd2c2..1bbd8b92 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "docs:watch": "typedoc --watch src/index.ts" }, "peerDependencies": { - "avalanche": "3.5.*" + "avalanche": "3.8.*" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" @@ -74,7 +74,7 @@ "@ledgerhq/hw-app-eth": "5.42.1", "@obsidiansystems/hw-app-avalanche": "0.2.2", "@openzeppelin/contracts": "4.1.0", - "avalanche": "3.6.2", + "avalanche": "3.8.1", "big.js": "^6.1.1", "bip32-path": "^0.4.2", "bip39": "^3.0.4", diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index e4a6920f..ecc37fdb 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -637,12 +637,11 @@ export abstract class WalletProvider { let tx = await this.signC(exportTx); - let addrC = this.getAddressC(); - let nonceBefore = await web3.eth.getTransactionCount(addrC); let txId = await cChain.issueTx(tx); - // TODO: Return the txId from the wait function, once support is there - await waitTxC(addrC, nonceBefore); + await waitTxC(txId); + + await this.updateAvaxBalanceC(); return txId; } @@ -798,6 +797,10 @@ export abstract class WalletProvider { let tx = await this.signC(unsignedTx); let id = await cChain.issueTx(tx); + await waitTxC(id); + + await this.updateAvaxBalanceC(); + return id; } diff --git a/src/utils/types.ts b/src/utils/types.ts index b5edea57..ef29d9e4 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,8 +1,10 @@ export type AvmStatusType = 'Accepted' | 'Processing' | 'Rejected' | 'Unknown'; export type PlatformStatusType = 'Committed' | 'Processing' | 'Dropped' | 'Unknown'; +export type ChainStatusTypeC = 'Accepted' | 'Processing' | 'Dropped' | 'Unknown'; export type AvmStatusResponseType = AvmStatusType | iAvmStatusResponse; export type PlatformStatusResponseType = PlatformStatusType | iPlatformStatusResponse; +export type ChainStatusResponseTypeC = ChainStatusTypeC | iChainStatusResponseC; export interface iAvmStatusResponse { status: AvmStatusType; @@ -13,3 +15,8 @@ export interface iPlatformStatusResponse { status: PlatformStatusType; reason: string; } + +export interface iChainStatusResponseC { + status: PlatformStatusType; + reason: string; +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3c1d6c29..8a6ad1d2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,8 +4,15 @@ import { BN, Buffer as BufferAvalanche } from 'avalanche'; import { validateAddress } from '@/helpers/address_helper'; import createHash from 'create-hash'; import axios from 'axios'; -import { pChain, web3, xChain } from '@/Network/network'; -import { AvmStatusResponseType, AvmStatusType, PlatformStatusResponseType, PlatformStatusType } from '@/utils/types'; +import { cChain, pChain, web3, xChain } from '@/Network/network'; +import { + AvmStatusResponseType, + AvmStatusType, + ChainStatusResponseTypeC, + ChainStatusTypeC, + PlatformStatusResponseType, + PlatformStatusType, +} from '@/utils/types'; import { PayloadBase, PayloadTypes } from 'avalanche/dist/utils'; declare module 'big.js' { @@ -293,25 +300,34 @@ export async function waitTxEvm(txHash: string, tryCount = 10): Promise } //TODO: There is no getTxStatus on C chain. Switch the current setup once that is ready -export async function waitTxC(cAddress: string, nonce?: number, tryCount = 10): Promise { +export async function waitTxC(txId: string, tryCount = 10): Promise { if (tryCount <= 0) { throw new Error('Timeout'); } + let resp: ChainStatusResponseTypeC = (await cChain.getAtomicTxStatus(txId)) as ChainStatusResponseTypeC; - let nonceNow = await web3.eth.getTransactionCount(cAddress); - - if (typeof nonce === 'undefined') { - nonce = nonceNow; + let status: ChainStatusTypeC; + let reason; + if (typeof resp === 'string') { + status = resp as ChainStatusTypeC; + } else { + status = resp.status as ChainStatusTypeC; + reason = resp.reason; } - if (nonce === nonceNow) { + if (status === 'Unknown' || status === 'Processing') { return await new Promise((resolve) => { setTimeout(async () => { - resolve(await waitTxC(cAddress, nonce, tryCount - 1)); + resolve(await waitTxC(txId, tryCount - 1)); }, 1000); }); + // return await waitTxX(txId, tryCount - 1); + } else if (status === 'Dropped') { + throw new Error(reason); + } else if (status === 'Accepted') { + return txId; } else { - return 'success'; + throw new Error('Unknown status type.'); } } @@ -327,3 +343,17 @@ export function parseNftPayload(rawPayload: string): PayloadBase { return payloadbase; } + +/** + * Returns the transaction fee for X chain. + */ +export function getTxFeeX() { + return xChain.getTxFee(); +} + +/** + * Returns the transaction fee for P chain. + */ +export function getTxFeeP() { + return pChain.getTxFee(); +} diff --git a/yarn.lock b/yarn.lock index 3a7d2236..f381e843 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2139,10 +2139,10 @@ available-typed-arrays@^1.0.2: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9" integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA== -avalanche@3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/avalanche/-/avalanche-3.6.2.tgz#90764abc8e41d6c49b9720aa3a159e9ba16c710c" - integrity sha512-MahWLfs2NOZphEBTwrXq7QFfBQA4QMQqvxCAHyCVfUvqBoOSFstRLH7cBV+sxym/1c3wF9bAfB4TwneKQrGt7g== +avalanche@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/avalanche/-/avalanche-3.8.1.tgz#56eac5487a736415536f2dcd1bfc5aaa5195bd64" + integrity sha512-p+bqjsbKynN+oUpi7Eoddavxfbx5wISzv3zXG/8/YUB0kbMnzScO8A5Dfv2Kz1BIQxu6sCb8u6UhJdGId9ElGw== dependencies: axios "0.21.1" bech32 "1.1.4" @@ -2152,6 +2152,7 @@ avalanche@3.6.2: create-hash "1.2.0" elliptic "6.5.4" ethers "^5.3.0" + hdkey "2.0.1" isomorphic-ws "^4.0.1" store2 "2.11.0" ws "^7.4.6" From cbe5eec888f19d662bdad491c979e9fee1c3fa3e Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Mon, 12 Jul 2021 23:30:39 -0400 Subject: [PATCH 11/25] UniversalNode handle edge case --- src/Network/providers/AVMWebSocketProvider.ts | 10 +++++----- src/Wallet/Wallet.ts | 5 ++++- src/helpers/UniversalNode.ts | 16 +++++++-------- test/helpers/universal_tx_helper.test.ts | 20 +++++++++++++++++++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/Network/providers/AVMWebSocketProvider.ts b/src/Network/providers/AVMWebSocketProvider.ts index d21e7cc8..cf84c11f 100644 --- a/src/Network/providers/AVMWebSocketProvider.ts +++ b/src/Network/providers/AVMWebSocketProvider.ts @@ -33,7 +33,7 @@ export default class AVMWebSocketProvider { * Starts watching for transactions on this wallet. * @param wallet The wallet instance to track */ - trackWallet(wallet: WalletType) { + trackWallet(wallet: WalletType): void { if (this.wallets.includes(wallet)) { return; } @@ -43,11 +43,11 @@ export default class AVMWebSocketProvider { this.updateFilterAddresses(); } - onWalletAddressChange() { + onWalletAddressChange(): void { this.updateFilterAddresses(); } - removeWallet(w: WalletType) { + removeWallet(w: WalletType): void { if (!this.wallets.includes(w)) { return; } @@ -57,7 +57,7 @@ export default class AVMWebSocketProvider { w.off('addressChanged', this.boundHandler); } - setEndpoint(wsUrl: string) { + setEndpoint(wsUrl: string): void { this.socket.close(); this.socket = new Sockette(wsUrl, { @@ -77,7 +77,7 @@ export default class AVMWebSocketProvider { } // Clears the filter listening to X chain transactions - clearFilter() { + clearFilter(): void { let pubsub = new PubSub(); let bloom = pubsub.newBloom(FILTER_ADDRESS_SIZE); this.socket.send(bloom); diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index ecc37fdb..6d19a367 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -205,7 +205,9 @@ export abstract class WalletProvider { async sendAvaxC(to: string, amount: BN, gasPrice: BN, gasLimit: number): Promise { let fromAddr = this.getAddressC(); let tx = await buildEvmTransferNativeTx(fromAddr, to, amount, gasPrice, gasLimit); - return await this.issueEvmTx(tx); + let txId = await this.issueEvmTx(tx); + await this.updateAvaxBalanceC(); + return txId; } /** @@ -495,6 +497,7 @@ export abstract class WalletProvider { this.balanceX = res; + // TODO: Check previous value this.emitBalanceChangeX(); return res; } diff --git a/src/helpers/UniversalNode.ts b/src/helpers/UniversalNode.ts index ba0673f7..d9459852 100644 --- a/src/helpers/UniversalNode.ts +++ b/src/helpers/UniversalNode.ts @@ -95,11 +95,11 @@ export class UniversalNode { let remaining = target.sub(this.balance); // Amount the parent must have - let parentBalanceNeeded = remaining.add(feeImportExport); if (this.parents.length === 1) { // Export from parent to this node let parent = this.parents[0]; + let parentBalanceNeeded = remaining.add(feeImportExport); let txs = parent.getStepsForTargetBalance(parentBalanceNeeded); let tx = parent.buildExportTx(remaining); return [...txs, tx]; @@ -108,21 +108,21 @@ export class UniversalNode { for (let i = 0; i < this.parents.length; i++) { let p = this.parents[i]; let pBal = p.reduceTotalBalanceFromParents(); - let pBalAvailable = pBal.sub(feeImportExport); + let pBalMax = pBal.sub(feeImportExport); + let parentBalanceNeeded = remaining.add(feeImportExport); - let exportableAmt = BN.min(pBalAvailable, remaining); - let target = BN.min(pBalAvailable, parentBalanceNeeded); + let exportAmt = BN.min(pBalMax, remaining); // The amount that will cross to the target chain + let target = BN.min(pBalMax, parentBalanceNeeded); - if (target.lte(new BN(0))) continue; + if (exportAmt.lte(new BN(0))) continue; let pTxs = p.getStepsForTargetBalance(target); - let pTx = p.buildExportTx(exportableAmt); + let pTx = p.buildExportTx(exportAmt); transactions.push(...pTxs); transactions.push(pTx); - parentBalanceNeeded = parentBalanceNeeded.sub(exportableAmt); - remaining = remaining.sub(exportableAmt); + remaining = remaining.sub(exportAmt); } return transactions; diff --git a/test/helpers/universal_tx_helper.test.ts b/test/helpers/universal_tx_helper.test.ts index 6f9f52e9..75a2d334 100644 --- a/test/helpers/universal_tx_helper.test.ts +++ b/test/helpers/universal_tx_helper.test.ts @@ -310,6 +310,26 @@ describe('Get transactions for balance on UniversalNode X', () => { compareSteps(steps, stepsExpected); }); + it('node needs balance from P, both parent have balance', () => { + let balX = new BN(0); + let balP = new BN(5000000000); // 5 AVAX + let balC = new BN(5000000000); // 5 AVAX + + let nodeX = createGraphForX(balX, balP, balC); + let target = new BN(2000000000); // 2 AVAX + + let stepsExpected: UniversalTx[] = [ + { + action: 'export_p_x', + amount: new BN(2000000000), + }, + ]; + + let steps = nodeX.getStepsForTargetBalance(target); + + compareSteps(steps, stepsExpected); + }); + it('node needs balance from C', () => { let balX = new BN(0); let balP = new BN(0); From 1961b5b7b8a82572eae173857d3be376ccd7eaa0 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 13 Jul 2021 00:19:43 -0400 Subject: [PATCH 12/25] wait for utxos to refresh before returning update ERC20 balances after erc20send --- src/Wallet/Wallet.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 6d19a367..5e2883f7 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -242,6 +242,7 @@ export abstract class WalletProvider { let fromAddr = this.getAddressC(); let tx = await buildEvmTransferErc20Tx(fromAddr, to, amount, gasPrice, gasLimit, contractAddress); let txHash = await this.issueEvmTx(tx); + this.updateBalanceERC20(); return txHash; } @@ -687,7 +688,7 @@ export abstract class WalletProvider { await waitTxX(txId); // Update UTXOs - this.updateUtxosX(); + await this.updateUtxosX(); return txId; } @@ -740,12 +741,12 @@ export abstract class WalletProvider { await waitTxX(txId); // Update UTXOs - this.updateUtxosX(); + await this.updateUtxosX(); return txId; } - async importP(): Promise { + async importP(toAddress?: string): Promise { const utxoSet = await this.getAtomicUTXOsP(); if (utxoSet.getAllUTXOs().length === 0) { @@ -753,7 +754,7 @@ export abstract class WalletProvider { } // Owner addresses, the addresses we exported to - let pToAddr = this.getAddressP(); + let walletAddrP = this.getAddressP(); let hrp = avalanche.getHRP(); let utxoAddrs = utxoSet.getAddresses().map((addr) => bintools.addressToString(hrp, 'P', addr)); @@ -761,13 +762,17 @@ export abstract class WalletProvider { // let fromAddrs = utxoAddrs; let ownerAddrs = utxoAddrs; + if (!toAddress) { + toAddress = walletAddrP; + } + const unsignedTx = await pChain.buildImportTx( utxoSet, ownerAddrs, xChain.getBlockchainID(), - [pToAddr], - [pToAddr], - [pToAddr], + [toAddress], + ownerAddrs, + [walletAddrP], undefined, undefined ); @@ -776,7 +781,7 @@ export abstract class WalletProvider { await waitTxP(txId); - this.updateUtxosP(); + await this.updateUtxosP(); return txId; } From 5032e2dae0792eb581a5d117fdee27aa15d45e3c Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 13 Jul 2021 17:10:00 -0400 Subject: [PATCH 13/25] init changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f7ae0f65 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# CHANGELOG + +## v0.8.4 + +#### Notes + +- Init CHANGELOG + +#### Added + +- Created universal helper functions and `UniversalNode` class +- `sendCustomEvmTx`, `canHaveBalanceOnChain`, `getTransactionsForBalance` in wallet classes + +#### Changed + +- `waitTxC` utils function uses getTx status to wait +- Wallet AVM, EVM/C chain function now wait for confirmation and balance refresh before returning From 340cff8fb3b2a29e9afb184c37f20b3e8419b3a4 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 13 Jul 2021 17:31:14 -0400 Subject: [PATCH 14/25] push debug logs --- src/Wallet/Wallet.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 5e2883f7..da0e1120 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -404,8 +404,10 @@ export abstract class WalletProvider { let strBalNow = JSON.stringify(balNow); // Compare stringified balances if (strNewBal !== strBalNow) { + console.log('ERC20 check'); this.emitBalanceChangeC(); } + console.log('ERC20 update dict'); this.balanceERC20 = newBal; return this.balanceERC20; } From ce94888eb0e0b99af6324407bdfc8b0256b35c24 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 13 Jul 2021 18:39:43 -0400 Subject: [PATCH 15/25] undo comments --- src/Wallet/Wallet.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index da0e1120..5e2883f7 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -404,10 +404,8 @@ export abstract class WalletProvider { let strBalNow = JSON.stringify(balNow); // Compare stringified balances if (strNewBal !== strBalNow) { - console.log('ERC20 check'); this.emitBalanceChangeC(); } - console.log('ERC20 update dict'); this.balanceERC20 = newBal; return this.balanceERC20; } From fe4de9ccd5cfa6758bb58e2ee7eae2b84eb536fd Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Tue, 13 Jul 2021 18:42:30 -0400 Subject: [PATCH 16/25] fix change log version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ae0f65..09a8f321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## v0.8.4 +## v0.8.0 #### Notes From 2facdd816954b0c59e176de2700607124a1b49ab Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 15 Jul 2021 02:03:56 -0400 Subject: [PATCH 17/25] Refactoring erc20 module. Removing internal erc20 state from the SDK and keeping the cache Start using Promise.all --- CHANGELOG.md | 5 + src/Asset/Erc20.ts | 212 ++++-------------- src/Asset/index.ts | 14 +- src/Network/index.ts | 6 +- src/Network/network.ts | 6 + src/Network/providers/EVMWebSocketProvider.ts | 1 - src/Wallet/Wallet.ts | 92 +++++--- src/utils/utils.ts | 3 +- 8 files changed, 132 insertions(+), 207 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a8f321..a8d89342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,3 +15,8 @@ - `waitTxC` utils function uses getTx status to wait - Wallet AVM, EVM/C chain function now wait for confirmation and balance refresh before returning +- Assets module `getContractData` renamed to `getContractDataErc20` + +#### Removed + +- `updateBalanceERC20` from wallet instances diff --git a/src/Asset/Erc20.ts b/src/Asset/Erc20.ts index 0a56476f..797a3f87 100644 --- a/src/Asset/Erc20.ts +++ b/src/Asset/Erc20.ts @@ -4,143 +4,27 @@ import Erc20Token from '@/Asset/Erc20Token'; import { WalletBalanceERC20 } from '@/Wallet/types'; import { bnToLocaleString } from '@/utils/utils'; -const DEFAULT_TOKENS: Erc20TokenData[] = [ - { - chainId: 43114, - address: '0x60781C2586D68229fde47564546784ab3fACA982', - decimals: 18, - name: 'Pangolin', - symbol: 'PNG', - }, - { - chainId: 43114, - address: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', - decimals: 18, - name: 'Wrapped AVAX', - symbol: 'WAVAX', - }, - { - chainId: 43114, - address: '0xf20d962a6c8f70c731bd838a3a388D7d48fA6e15', - decimals: 18, - name: 'Ether', - symbol: 'ETH', - }, - { - chainId: 43114, - address: '0xde3A24028580884448a5397872046a019649b084', - decimals: 6, - name: 'Tether USD', - symbol: 'USDT', - }, - { - chainId: 43114, - address: '0xB3fe5374F67D7a22886A0eE082b2E2f9d2651651', - decimals: 18, - name: 'ChainLink Token', - symbol: 'LINK', - }, - { - chainId: 43114, - address: '0x8cE2Dee54bB9921a2AE0A63dBb2DF8eD88B91dD9', - decimals: 18, - name: 'Aave Token', - symbol: 'AAVE', - }, - { - chainId: 43114, - address: '0xf39f9671906d8630812f9d9863bBEf5D523c84Ab', - decimals: 18, - name: 'Uniswap', - symbol: 'UNI', - }, - { - chainId: 43114, - address: '0x408D4cD0ADb7ceBd1F1A1C33A0Ba2098E1295bAB', - decimals: 8, - name: 'Wrapped BTC', - symbol: 'WBTC', - }, - { - chainId: 43114, - address: '0x8DF92E9C0508aB0030d432DA9F2C65EB1Ee97620', - decimals: 18, - name: 'Maker', - symbol: 'MKR', - }, - { - chainId: 43114, - address: '0x68e44C4619db40ae1a0725e77C02587bC8fBD1c9', - decimals: 18, - name: 'Synthetix Network Token', - symbol: 'SNX', - }, - { - chainId: 43114, - address: '0x53CEedB4f6f277edfDDEdB91373B044FE6AB5958', - decimals: 18, - name: 'Compound', - symbol: 'COMP', - }, - { - chainId: 43114, - address: '0x421b2a69b886BA17a61C7dAd140B9070d5Ef300B', - decimals: 18, - name: 'HuobiToken', - symbol: 'HT', - }, - { - chainId: 43114, - address: '0x39cf1BD5f15fb22eC3D9Ff86b0727aFc203427cc', - decimals: 18, - name: 'SushiToken', - symbol: 'SUSHI', - }, - { - chainId: 43114, - address: '0xC84d7bfF2555955b44BDF6A307180810412D751B', - decimals: 18, - name: 'UMA Voting Token v1', - symbol: 'UMA', - }, - { - chainId: 43114, - address: '0xaEb044650278731Ef3DC244692AB9F64C78FfaEA', - decimals: 18, - name: 'Binance USD', - symbol: 'BUSD', - }, - { - chainId: 43114, - address: '0xbA7dEebBFC5fA1100Fb055a87773e1E99Cd3507a', - decimals: 18, - name: 'Dai Stablecoin', - symbol: 'DAI', - }, -]; +export let erc20Cache: Erc20Store = {}; -export let erc20Store: Erc20Store = {}; -export let erc20StoreCustom: Erc20Store = {}; - -export function getErc20Store(): Erc20Store { +export function getErc20Cache(): Erc20Store { return { - ...erc20Store, + ...erc20Cache, }; } -export function getErc20StoreCustom(): Erc20Store { - return { - ...erc20StoreCustom, - }; +/** + * Clears the internal erc20 cache. + */ +export function bustErc20Cache() { + erc20Cache = {}; } /** * Fetches ERC20 data from the given contract address and adds the token to the given store. * @param address ERC20 Contract address - * @param store Which ERC20 store to add to */ -export async function addErc20Token(address: string, store: Erc20Store = erc20StoreCustom): Promise { - let existing = erc20Store[address] || erc20StoreCustom[address]; +async function addErc20Token(address: string): Promise { + let existing = erc20Cache[address]; if (existing) { return existing; } @@ -148,29 +32,29 @@ export async function addErc20Token(address: string, store: Erc20Store = erc20St let data: Erc20TokenData = await Erc20Token.getData(address); let token = new Erc20Token(data); - store[address] = token; + erc20Cache[address] = token; return token; } -export function addErc20TokenFromData(data: Erc20TokenData, store: Erc20Store = erc20StoreCustom): Erc20Token { +function addErc20TokenFromData(data: Erc20TokenData): Erc20Token { let address = data.address; - let existing = erc20Store[address] || erc20StoreCustom[address]; + let existing = erc20Cache[address]; if (existing) { return existing; } let token = new Erc20Token(data); - store[address] = token; + erc20Cache[address] = token; return token; } -export async function getContractData(address: string): Promise { +export async function getContractDataErc20(address: string): Promise { let data: Erc20TokenData = await Erc20Token.getData(address); return data; } export async function getErc20Token(address: string): Promise { - let storeItem = erc20Store[address] || erc20StoreCustom[address]; + let storeItem = erc20Cache[address]; if (storeItem) { return storeItem; } else { @@ -182,35 +66,35 @@ export async function getErc20Token(address: string): Promise { * Returns the balance of the given address for each ERC20 Token in the SDK. * @param address EVM address `0x...` */ -export async function balanceOf(address: string): Promise { - let balance: WalletBalanceERC20 = {}; - - let store = { - ...erc20Store, - ...erc20StoreCustom, - }; - - for (let tokenAddress in store) { - let token = store[tokenAddress]; - if (token.chainId === activeNetwork?.evmChainID) { - let bal = await token.balanceOf(address); - balance[tokenAddress] = { - name: token.name, - symbol: token.symbol, - denomination: token.decimals, - balance: bal, - balanceParsed: bnToLocaleString(bal, token.decimals), - address: tokenAddress, - }; - } - } - - return balance; -} - -function initStore() { - DEFAULT_TOKENS.forEach((token) => { - addErc20TokenFromData(token, erc20Store); - }); -} -initStore(); +// export async function balanceOf(address: string): Promise { +// let balance: WalletBalanceERC20 = {}; +// +// let store = { +// ...erc20Store, +// ...erc20StoreCustom, +// }; +// +// for (let tokenAddress in store) { +// let token = store[tokenAddress]; +// if (token.chainId === activeNetwork?.evmChainID) { +// let bal = await token.balanceOf(address); +// balance[tokenAddress] = { +// name: token.name, +// symbol: token.symbol, +// denomination: token.decimals, +// balance: bal, +// balanceParsed: bnToLocaleString(bal, token.decimals), +// address: tokenAddress, +// }; +// } +// } +// +// return balance; +// } + +// function initStore() { +// DEFAULT_TOKENS.forEach((token) => { +// addErc20TokenFromData(token, erc20Store); +// }); +// } +// initStore(); diff --git a/src/Asset/index.ts b/src/Asset/index.ts index b2f73540..12c137d0 100644 --- a/src/Asset/index.ts +++ b/src/Asset/index.ts @@ -1,10 +1,6 @@ -export { - addErc20Token, - addErc20TokenFromData, - getErc20Token, - balanceOf, - getContractData, - getErc20Store, - getErc20StoreCustom, -} from './Erc20'; +export { getErc20Token, getContractDataErc20, getErc20Cache } from './Erc20'; export * from './Assets'; + +import Erc20Token from './Erc20Token'; + +export { Erc20Token }; diff --git a/src/Network/index.ts b/src/Network/index.ts index 9c15573d..321087da 100644 --- a/src/Network/index.ts +++ b/src/Network/index.ts @@ -1,10 +1,12 @@ import { NetworkConfig } from './types'; import { MainnetConfig } from '@/Network/constants'; -import { activeNetwork, setRpcNetwork } from '@/Network/network'; +import { activeNetwork, setRpcNetwork, getEvmChainID } from '@/Network/network'; import WebsocketProvider from '@/Network/providers/WebsocketProvider'; +import { bustErc20Cache } from '@/Asset/Erc20'; export function setNetwork(conf: NetworkConfig) { setRpcNetwork(conf); + bustErc20Cache(); } // Default connection is Mainnet @@ -14,4 +16,4 @@ export function getAvaxAssetID() { return activeNetwork.avaxID; } -export { WebsocketProvider }; +export { WebsocketProvider, getEvmChainID }; diff --git a/src/Network/network.ts b/src/Network/network.ts index 6a4caedd..d7ea54d9 100644 --- a/src/Network/network.ts +++ b/src/Network/network.ts @@ -41,6 +41,12 @@ function createExplorerApi(networkConfig: NetworkConfig) { }); } +/** + * Returns the evm chain ID of the active network + */ +export function getEvmChainID(): number { + return activeNetwork.evmChainID; +} export function setRpcNetwork(conf: NetworkConfig): void { avalanche.setAddress(conf.apiIp, conf.apiPort, conf.apiProtocol); avalanche.setNetworkID(conf.networkID); diff --git a/src/Network/providers/EVMWebSocketProvider.ts b/src/Network/providers/EVMWebSocketProvider.ts index 2a1ab471..1cacd7cd 100644 --- a/src/Network/providers/EVMWebSocketProvider.ts +++ b/src/Network/providers/EVMWebSocketProvider.ts @@ -86,7 +86,6 @@ export default class EVMWebSocketProvider { // Update wallet balances this.wallets.forEach((w) => { w.updateAvaxBalanceC(); - w.updateBalanceERC20(); }); } } diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 5e2883f7..6f7578f2 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -56,7 +56,7 @@ import { UnsignedTx as EVMUnsignedTx, Tx as EVMTx, UTXOSet as EVMUTXOSet } from import { PayloadBase, UnixNow } from 'avalanche/dist/utils'; import { getAssetDescription } from '@/Asset/Assets'; -import { balanceOf, getErc20Token } from '@/Asset/Erc20'; +import { getErc20Token } from '@/Asset/Erc20'; import { NO_NETWORK } from '@/errors'; import { avaxCtoX, bnToLocaleString, waitTxC, waitTxEvm, waitTxP, waitTxX } from '@/utils/utils'; import EvmWalletReadonly from '@/Wallet/EvmWalletReadonly'; @@ -240,9 +240,19 @@ export abstract class WalletProvider { */ async sendErc20(to: string, amount: BN, gasPrice: BN, gasLimit: number, contractAddress: string): Promise { let fromAddr = this.getAddressC(); + let token = await getErc20Token(contractAddress); + let balOld = await token.balanceOf(fromAddr); let tx = await buildEvmTransferErc20Tx(fromAddr, to, amount, gasPrice, gasLimit, contractAddress); let txHash = await this.issueEvmTx(tx); - this.updateBalanceERC20(); + + let balNew = await token.balanceOf(fromAddr); + + console.log(balOld.toString(), balNew.toString()); + // If new balance doesnt match old, emit balance change + if (!balOld.eq(balNew)) { + this.emitBalanceChangeC(); + } + return txHash; } @@ -396,36 +406,60 @@ export abstract class WalletProvider { * - Makes network requests. * - Updates the value of `this.balanceERC20` */ - public async updateBalanceERC20(): Promise { - let newBal = await balanceOf(this.getAddressC()); - let balNow = this.balanceERC20; - - let strNewBal = JSON.stringify(newBal); - let strBalNow = JSON.stringify(balNow); - // Compare stringified balances - if (strNewBal !== strBalNow) { - this.emitBalanceChangeC(); - } - this.balanceERC20 = newBal; - return this.balanceERC20; - } + // public async updateBalanceERC20(): Promise { + // let newBal = await balanceOf(this.getAddressC()); + // let balNow = this.balanceERC20; + // + // let strNewBal = JSON.stringify(newBal); + // let strBalNow = JSON.stringify(balNow); + // // Compare stringified balances + // if (strNewBal !== strBalNow) { + // this.emitBalanceChangeC(); + // } + // this.balanceERC20 = newBal; + // return this.balanceERC20; + // } /** - * Returns the wallet's balance of the given ERC20 contract - * @param address ERC20 Contract address + * Returns the wallet's balance of the given ERC20 contracts + * @param addresses ERC20 Contract addresses */ - public async getBalanceERC20(address: string): Promise { - let token = await getErc20Token(address); - let bal = await token.balanceOf(this.getAddressC()); - let res: ERC20Balance = { - address: address, - denomination: token.decimals, - balanceParsed: bnToLocaleString(bal, token.decimals), - balance: bal, - name: token.name, - symbol: token.symbol, - }; - return res; + public async getBalanceERC20(addresses: string[]): Promise { + let walletAddr = this.getAddressC(); + let tokenCalls = addresses.map((addr) => getErc20Token(addr)); + let tokens = await Promise.all(tokenCalls); + + let balanceCalls = tokens.map((token) => token.balanceOf(walletAddr)); + let balances = await Promise.all(balanceCalls); + + return balances.map((bal, i) => { + let token = tokens[i]; + let balance: ERC20Balance = { + address: token.address, + denomination: token.decimals, + balanceParsed: bnToLocaleString(bal, token.decimals), + balance: bal, + name: token.name, + symbol: token.symbol, + }; + return balance; + }); + // for (let i = 0; i < addresses.length; i++) { + // let address = addresses[i]; + // let token = await getErc20Token(address); + // let bal = await token.balanceOf(this.getAddressC()); + // let balance: ERC20Balance = { + // address: address, + // denomination: token.decimals, + // balanceParsed: bnToLocaleString(bal, token.decimals), + // balance: bal, + // name: token.name, + // symbol: token.symbol, + // }; + // res.push(balance); + // } + // + // return res; } private async updateUnknownAssetsX() { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8a6ad1d2..ef03dafa 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -283,7 +283,7 @@ export async function waitTxEvm(txHash: string, tryCount = 10): Promise } let receipt = await web3.eth.getTransactionReceipt(txHash); - + console.log(receipt); if (!receipt) { return await new Promise((resolve) => { setTimeout(async () => { @@ -299,7 +299,6 @@ export async function waitTxEvm(txHash: string, tryCount = 10): Promise } } -//TODO: There is no getTxStatus on C chain. Switch the current setup once that is ready export async function waitTxC(txId: string, tryCount = 10): Promise { if (tryCount <= 0) { throw new Error('Timeout'); From f0e4f71073c6582b62c364528843955de837a9d7 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 15 Jul 2021 10:17:02 -0400 Subject: [PATCH 18/25] Refresh balance after erc20 send --- src/Wallet/Wallet.ts | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 6f7578f2..234cc66d 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -245,10 +245,8 @@ export abstract class WalletProvider { let tx = await buildEvmTransferErc20Tx(fromAddr, to, amount, gasPrice, gasLimit, contractAddress); let txHash = await this.issueEvmTx(tx); - let balNew = await token.balanceOf(fromAddr); - - console.log(balOld.toString(), balNew.toString()); // If new balance doesnt match old, emit balance change + let balNew = await token.balanceOf(fromAddr); if (!balOld.eq(balNew)) { this.emitBalanceChangeC(); } @@ -401,25 +399,6 @@ export abstract class WalletProvider { return await getStakeForAddresses(addrs); } - /** - * Requests the balance for each ERC20 contract in the SDK. - * - Makes network requests. - * - Updates the value of `this.balanceERC20` - */ - // public async updateBalanceERC20(): Promise { - // let newBal = await balanceOf(this.getAddressC()); - // let balNow = this.balanceERC20; - // - // let strNewBal = JSON.stringify(newBal); - // let strBalNow = JSON.stringify(balNow); - // // Compare stringified balances - // if (strNewBal !== strBalNow) { - // this.emitBalanceChangeC(); - // } - // this.balanceERC20 = newBal; - // return this.balanceERC20; - // } - /** * Returns the wallet's balance of the given ERC20 contracts * @param addresses ERC20 Contract addresses @@ -444,22 +423,6 @@ export abstract class WalletProvider { }; return balance; }); - // for (let i = 0; i < addresses.length; i++) { - // let address = addresses[i]; - // let token = await getErc20Token(address); - // let bal = await token.balanceOf(this.getAddressC()); - // let balance: ERC20Balance = { - // address: address, - // denomination: token.decimals, - // balanceParsed: bnToLocaleString(bal, token.decimals), - // balance: bal, - // name: token.name, - // symbol: token.symbol, - // }; - // res.push(balance); - // } - // - // return res; } private async updateUnknownAssetsX() { From a2d93a2d9d5412738f41af885759842ece5c8c0b Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 15 Jul 2021 10:57:55 -0400 Subject: [PATCH 19/25] add set timeout to refresh balance on sendErc20 --- src/Wallet/Wallet.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 234cc66d..4a483a37 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -245,11 +245,16 @@ export abstract class WalletProvider { let tx = await buildEvmTransferErc20Tx(fromAddr, to, amount, gasPrice, gasLimit, contractAddress); let txHash = await this.issueEvmTx(tx); + // TODO: We should not be using setTimeout, wait until tx is confirmed on chain + // TODO: Can it be an issue with sticky sessions? Nodes behind a LB? // If new balance doesnt match old, emit balance change - let balNew = await token.balanceOf(fromAddr); - if (!balOld.eq(balNew)) { - this.emitBalanceChangeC(); - } + setTimeout(async () => { + let balNew = await token.balanceOf(fromAddr); + console.log(balOld.toString(), balNew.toString()); + if (!balOld.eq(balNew)) { + this.emitBalanceChangeC(); + } + }, 2000); return txHash; } From 4d08d5648da74a6e12fc8dfe69a902c3b7271a34 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 15 Jul 2021 17:22:50 -0400 Subject: [PATCH 20/25] Add wallet universal tx helper methods --- src/Wallet/Wallet.ts | 53 ++++++++++++++++++++++++++++++------ src/helpers/UniversalNode.ts | 5 ++++ src/helpers/tx_helper.ts | 15 ++++++++++ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 4a483a37..5aa90cbe 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -21,6 +21,7 @@ import { buildEvmTransferErc20Tx, buildEvmTransferNativeTx, buildMintNftTx, + estimateAvaxGas, estimateErc20Gas, } from '@/helpers/tx_helper'; import { BN, Buffer } from 'avalanche'; @@ -75,11 +76,15 @@ import { canHaveBalanceOnC, canHaveBalanceOnP, canHaveBalanceOnX, + createGraphForC, + createGraphForP, + createGraphForX, getStepsForBalanceC, getStepsForBalanceP, getStepsForBalanceX, UniversalTx, } from '@/helpers/universal_tx_helper'; +import { UniversalNode } from '@/helpers/UniversalNode'; export abstract class WalletProvider { abstract type: WalletNameType; @@ -265,11 +270,21 @@ export abstract class WalletProvider { * @param to Address receiving the tokens * @param amount Amount to send. Given in the smallest divisible unit. */ - async estimateErc20Gas(contractAddress: string, to: string, amount: BN) { + async estimateErc20Gas(contractAddress: string, to: string, amount: BN): Promise { let from = this.getAddressC(); return await estimateErc20Gas(contractAddress, from, to, amount); } + /** + * Estimate the gas needed for a AVAX send transaction on the C chain. + * @param to Destination address. + * @param amount Amount of AVAX to send, in WEI. + */ + async estimateAvaxGasLimit(to: string, amount: BN, gasPrice: BN): Promise { + let from = this.getAddressC(); + return await estimateAvaxGas(from, to, amount, gasPrice); + } + /** * A method to create custom EVM transactions. * @param gasPrice @@ -286,25 +301,45 @@ export abstract class WalletProvider { } /** - * Can this wallet have the given amount on the given chain after a series of internal transactions (if required). - * @param chain X/P/C - * @param amount The amount to check against + * Returns the maximum spendable AVAX balance for the given chain. + * Scans all chains and take cross over fees into account + * @param chainType X, P or C */ - public canHaveBalanceOnChain(chain: ChainIdType, amount: BN): boolean { + public getUsableAvaxBalanceForChain(chainType: ChainIdType): BN { + return this.createUniversalNode(chainType).reduceTotalBalanceFromParents(); + } + + /** + * Create a new instance of a UniversalNode for the given chain using current balance state + * @param chain Chain of the universal node. + * @private + */ + private createUniversalNode(chain: ChainIdType): UniversalNode { let xBal = this.getAvaxBalanceX().unlocked; let pBal = this.getAvaxBalanceP().unlocked; let cBal = avaxCtoX(this.getAvaxBalanceC()); // need to use 9 decimal places switch (chain) { + case 'X': + return createGraphForX(xBal, pBal, cBal); case 'P': - return canHaveBalanceOnP(xBal, pBal, cBal, amount); + return createGraphForP(xBal, pBal, cBal); case 'C': - return canHaveBalanceOnC(xBal, pBal, cBal, amount); - case 'X': - return canHaveBalanceOnX(xBal, pBal, cBal, amount); + return createGraphForC(xBal, pBal, cBal); } } + /** + * Can this wallet have the given amount on the given chain after a series of internal transactions (if required). + * @param chain X/P/C + * @param amount The amount to check against + */ + public canHaveBalanceOnChain(chain: ChainIdType, amount: BN): boolean { + // The maximum amount of AVAX we can have on this chain + let maxAmt = this.createUniversalNode(chain).reduceTotalBalanceFromParents(); + return amount.gte(maxAmt); + } + /** * Returns an array of transaction to do in order to have the target amount on the given chain * @param chain The chain (X/P/C) to have the desired amount on diff --git a/src/helpers/UniversalNode.ts b/src/helpers/UniversalNode.ts index d9459852..d47192ab 100644 --- a/src/helpers/UniversalNode.ts +++ b/src/helpers/UniversalNode.ts @@ -125,6 +125,11 @@ export class UniversalNode { remaining = remaining.sub(exportAmt); } + // If we still have remaining balance, we can not complete this transfer + if (remaining.gt(new BN(0))) { + throw new Error('Insufficient AVAX balances.'); + } + return transactions; } } diff --git a/src/helpers/tx_helper.ts b/src/helpers/tx_helper.ts index 156825d5..210e26b9 100644 --- a/src/helpers/tx_helper.ts +++ b/src/helpers/tx_helper.ts @@ -405,6 +405,21 @@ export async function estimateErc20Gas(tokenContract: string, from: string, to: }); } +/** + * Estimates the gas needed to send AVAX + * @param to Destination address + * @param amount Amount of AVAX to send, given in WEI + * @param gasPrice Given in WEI + */ +export async function estimateAvaxGas(from: string, to: string, amount: BN, gasPrice: BN): Promise { + return await web3.eth.estimateGas({ + from, + to, + gasPrice: `0x${gasPrice.toString('hex')}`, + value: `0x${amount.toString('hex')}`, + }); +} + export enum AvmTxNameEnum { 'Transaction' = AVMConstants.BASETX, 'Mint' = AVMConstants.CREATEASSETTX, From cfef7ab55d63bc94109299044ecd908e0788f81a Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Thu, 15 Jul 2021 17:46:03 -0400 Subject: [PATCH 21/25] fix bug at canHaveBalanceOnChain --- src/Wallet/Wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 5aa90cbe..103d1bdb 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -337,7 +337,7 @@ export abstract class WalletProvider { public canHaveBalanceOnChain(chain: ChainIdType, amount: BN): boolean { // The maximum amount of AVAX we can have on this chain let maxAmt = this.createUniversalNode(chain).reduceTotalBalanceFromParents(); - return amount.gte(maxAmt); + return maxAmt.gte(amount); } /** From f42e55e8e97c75e4dcec55d7cc82633d1aa7e885 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Fri, 16 Jul 2021 00:48:56 -0400 Subject: [PATCH 22/25] add credentials to RPC connections by default --- src/Network/network.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Network/network.ts b/src/Network/network.ts index d7ea54d9..f17cae1c 100644 --- a/src/Network/network.ts +++ b/src/Network/network.ts @@ -21,8 +21,17 @@ export const cChain: EVMAPI = avalanche.CChain(); export const pChain = avalanche.PChain(); export const infoApi: InfoAPI = avalanche.Info(); +const web3RpcConfig = { + timeout: 20000, // ms + withCredentials: true, +}; + +function getProviderFromUrl(url: string) { + return new Web3.providers.HttpProvider(url, web3RpcConfig); +} + const rpcUrl = rpcUrlFromConfig(DefaultConfig); -export const web3 = new Web3(rpcUrl); +export const web3 = new Web3(getProviderFromUrl(rpcUrl)); export let explorer_api: AxiosInstance | null = null; export let activeNetwork: NetworkConfig = DefaultConfig; @@ -50,6 +59,7 @@ export function getEvmChainID(): number { export function setRpcNetwork(conf: NetworkConfig): void { avalanche.setAddress(conf.apiIp, conf.apiPort, conf.apiProtocol); avalanche.setNetworkID(conf.networkID); + avalanche.setRequestConfig('withCredentials', true); xChain.refreshBlockchainID(conf.xChainID); xChain.setBlockchainAlias('X'); @@ -71,7 +81,7 @@ export function setRpcNetwork(conf: NetworkConfig): void { } let rpcUrl = rpcUrlFromConfig(conf); - web3.setProvider(rpcUrl); + web3.setProvider(getProviderFromUrl(rpcUrl)); activeNetwork = conf; } From c539e705e05162f4787f7043674ef903bb21ad36 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Fri, 16 Jul 2021 01:19:00 -0400 Subject: [PATCH 23/25] cleanup --- src/Wallet/Wallet.ts | 3 +-- src/utils/utils.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 103d1bdb..6f9c11de 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -255,7 +255,6 @@ export abstract class WalletProvider { // If new balance doesnt match old, emit balance change setTimeout(async () => { let balNew = await token.balanceOf(fromAddr); - console.log(balOld.toString(), balNew.toString()); if (!balOld.eq(balNew)) { this.emitBalanceChangeC(); } @@ -651,7 +650,7 @@ export abstract class WalletProvider { let txId = await pChain.issueTx(tx); await waitTxP(txId); - this.updateUtxosP(); + await this.updateUtxosP(); return txId; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ef03dafa..499c07e7 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -283,7 +283,6 @@ export async function waitTxEvm(txHash: string, tryCount = 10): Promise } let receipt = await web3.eth.getTransactionReceipt(txHash); - console.log(receipt); if (!receipt) { return await new Promise((resolve) => { setTimeout(async () => { From 67e69f5ec8f28f0c78423477dfb174c6ad543324 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Fri, 16 Jul 2021 18:15:13 -0400 Subject: [PATCH 24/25] move code in file --- src/Wallet/Wallet.ts | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 6f9c11de..0f3edf51 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -90,6 +90,23 @@ export abstract class WalletProvider { abstract type: WalletNameType; abstract evmWallet: EvmWallet | EvmWalletReadonly; + /** + * The X chain UTXOs of the wallet's current state + */ + public utxosX: AVMUTXOSet = new AVMUTXOSet(); + + /** + * The P chain UTXOs of the wallet's current state + */ + public utxosP: PlatformUTXOSet = new PlatformUTXOSet(); + + public balanceX: WalletBalanceX = {}; + + abstract signEvm(tx: Transaction): Promise; + abstract signX(tx: AVMUnsignedTx): Promise; + abstract signP(tx: PlatformUnsignedTx): Promise; + abstract signC(tx: EVMUnsignedTx): Promise; + abstract getAddressX(): string; abstract getChangeAddressX(): string; abstract getAddressP(): string; @@ -145,25 +162,6 @@ export abstract class WalletProvider { this.emit('balanceChangedC', this.getAvaxBalanceC()); } - /** - * The X chain UTXOs of the wallet's current state - */ - public utxosX: AVMUTXOSet = new AVMUTXOSet(); - - /** - * The P chain UTXOs of the wallet's current state - */ - public utxosP: PlatformUTXOSet = new PlatformUTXOSet(); - - public balanceX: WalletBalanceX = {}; - - public balanceERC20: WalletBalanceERC20 = {}; - - abstract signEvm(tx: Transaction): Promise; - abstract signX(tx: AVMUnsignedTx): Promise; - abstract signP(tx: PlatformUnsignedTx): Promise; - abstract signC(tx: EVMUnsignedTx): Promise; - /** * * @param to - the address funds are being send to. From f85d25755c6d75a811dbbcdd43fe1d2ae2b88c01 Mon Sep 17 00:00:00 2001 From: Emre Kanatli Date: Wed, 21 Jul 2021 12:43:25 -0400 Subject: [PATCH 25/25] cleanup --- src/Asset/Erc20.ts | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/Asset/Erc20.ts b/src/Asset/Erc20.ts index 797a3f87..a1e4f312 100644 --- a/src/Asset/Erc20.ts +++ b/src/Asset/Erc20.ts @@ -1,8 +1,5 @@ import { Erc20Store, Erc20TokenData } from '@/Asset/types'; -import { activeNetwork } from '@/Network/network'; import Erc20Token from '@/Asset/Erc20Token'; -import { WalletBalanceERC20 } from '@/Wallet/types'; -import { bnToLocaleString } from '@/utils/utils'; export let erc20Cache: Erc20Store = {}; @@ -61,40 +58,3 @@ export async function getErc20Token(address: string): Promise { return await addErc20Token(address); } } - -/** - * Returns the balance of the given address for each ERC20 Token in the SDK. - * @param address EVM address `0x...` - */ -// export async function balanceOf(address: string): Promise { -// let balance: WalletBalanceERC20 = {}; -// -// let store = { -// ...erc20Store, -// ...erc20StoreCustom, -// }; -// -// for (let tokenAddress in store) { -// let token = store[tokenAddress]; -// if (token.chainId === activeNetwork?.evmChainID) { -// let bal = await token.balanceOf(address); -// balance[tokenAddress] = { -// name: token.name, -// symbol: token.symbol, -// denomination: token.decimals, -// balance: bal, -// balanceParsed: bnToLocaleString(bal, token.decimals), -// address: tokenAddress, -// }; -// } -// } -// -// return balance; -// } - -// function initStore() { -// DEFAULT_TOKENS.forEach((token) => { -// addErc20TokenFromData(token, erc20Store); -// }); -// } -// initStore();