From 4fdab05642f944665be1ad57dbbb69cc415746d1 Mon Sep 17 00:00:00 2001 From: Sergei Novikov Date: Mon, 20 Nov 2023 16:48:57 +0300 Subject: [PATCH] solution: new ui for bitcoin cancel tx --- packages/core/src/index.ts | 2 +- .../workflow/TxBuilder.spec.ts} | 71 ++--- .../src/transaction/workflow/TxBuilder.ts | 279 +++++++++++++++++ .../{ => transaction}/workflow/TxSigner.ts | 14 +- .../create-tx/CreateBitcoinCancelTx.ts | 40 +++ .../create-tx/CreateBitcoinSpeedUpTx.ts | 24 ++ .../create-tx}/CreateBitcoinTx.spec.ts | 284 +++++++++-------- .../workflow/create-tx}/CreateBitcoinTx.ts | 23 +- .../create-tx}/CreateErc20ApproveTx.spec.ts | 15 +- .../create-tx}/CreateErc20ApproveTx.ts | 13 +- .../workflow/create-tx}/CreateErc20Tx.spec.ts | 5 +- .../workflow/create-tx}/CreateErc20Tx.ts | 16 +- .../create-tx}/CreateErc20WrappedTx.spec.ts | 10 +- .../create-tx}/CreateErc20WrappedTx.ts | 18 +- .../create-tx}/CreateEthereumTx.spec.ts | 6 +- .../workflow/create-tx}/CreateEthereumTx.ts | 14 +- .../transaction/workflow/create-tx/index.ts | 9 + .../transaction/workflow/create-tx/types.ts | 110 +++++++ .../core/src/transaction/workflow/index.ts | 56 ++++ .../core/src/transaction/workflow/types.ts | 102 ++++++ .../src/workflow/CreateBitcoinCancelTx.ts | 46 --- .../core/src/workflow/CreateTxConverter.ts | 268 ---------------- packages/core/src/workflow/index.ts | 26 -- packages/core/src/workflow/types.ts | 91 ------ .../src/app/screen/Screen/Screen.tsx | 3 - .../SetupApproveTransaction.tsx | 16 +- .../CreateConvertTransaction.tsx | 4 +- .../CreateRecoverTransaction.tsx | 1 + .../BroadcastTransaction.tsx | 9 +- .../BroadcastTransaction/display/common.tsx | 3 +- .../display/components/Actions.tsx | 53 +++- .../BroadcastTransaction/display/display.ts | 2 +- .../BroadcastTransaction/display/types.ts | 2 +- .../CreateTransaction/CreateTransaction.tsx | 10 +- .../SetupTransaction/SetupTransaction.tsx | 105 ++++--- .../flow/blockchain/bitcoin/cancel.tsx | 51 +++ .../flow/blockchain/bitcoin/index.ts | 1 + .../flow/blockchain/bitcoin/transfer.tsx | 11 +- .../flow/blockchain/ethereum/transfer.tsx | 16 +- .../SetupTransaction/flow/blockchain/index.ts | 2 +- .../SetupTransaction/flow/common/cancel.tsx | 36 +++ .../SetupTransaction/flow/common/index.ts | 3 + .../flow/components/BitcoinFee.tsx | 4 +- .../flow/components/EthereumFee.tsx | 4 +- .../SetupTransaction/flow/flow.tsx | 41 ++- .../SetupTransaction/flow/types.ts | 12 +- .../SignTransaction/SignTransaction.tsx | 7 +- .../SignTransaction/display/display.ts | 2 +- .../ModifyBitcoinTransaction.tsx | 15 +- .../src/transactions/TxDetails/TxDetails.tsx | 6 +- .../TxHistory/List/Transaction.tsx | 7 +- .../wallets/WalletDetails/WalletAllowance.tsx | 11 +- packages/store/src/index.ts | 2 +- packages/store/src/screen/actions.ts | 2 +- packages/store/src/screen/types.ts | 1 - packages/store/src/transaction/actions.ts | 26 +- packages/store/src/txstash/actions.ts | 215 ++----------- .../txstash/handler/blockchain/bitcoin/fee.ts | 66 ++++ .../handler/blockchain/bitcoin/index.ts | 4 + .../handler/blockchain/bitcoin/prepare.ts | 23 ++ .../handler/blockchain/bitcoin/restore.ts | 294 ++++++++++++++++++ .../handler/blockchain/ethereum/fee.ts | 130 ++++++++ .../handler/blockchain/ethereum/index.ts | 4 + .../handler/blockchain/ethereum/prepare.ts | 10 + .../handler/blockchain/ethereum/restore.ts | 78 +++++ .../src/txstash/handler/blockchain/index.ts | 4 + packages/store/src/txstash/handler/handler.ts | 34 ++ packages/store/src/txstash/handler/index.ts | 1 + packages/store/src/txstash/handler/types.ts | 20 ++ packages/store/src/txstash/reducer.ts | 37 ++- packages/store/src/txstash/selectors.spec.ts | 10 +- packages/store/src/txstash/selectors.ts | 43 ++- packages/store/src/txstash/types.ts | 40 ++- 73 files changed, 2039 insertions(+), 984 deletions(-) rename packages/core/src/{workflow/CreateTxConverter.spec.ts => transaction/workflow/TxBuilder.spec.ts} (93%) create mode 100644 packages/core/src/transaction/workflow/TxBuilder.ts rename packages/core/src/{ => transaction}/workflow/TxSigner.ts (92%) create mode 100644 packages/core/src/transaction/workflow/create-tx/CreateBitcoinCancelTx.ts create mode 100644 packages/core/src/transaction/workflow/create-tx/CreateBitcoinSpeedUpTx.ts rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateBitcoinTx.spec.ts (78%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateBitcoinTx.ts (93%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateErc20ApproveTx.spec.ts (90%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateErc20ApproveTx.ts (93%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateErc20Tx.spec.ts (98%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateErc20Tx.ts (94%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateErc20WrappedTx.spec.ts (92%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateErc20WrappedTx.ts (89%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateEthereumTx.spec.ts (98%) rename packages/core/src/{workflow => transaction/workflow/create-tx}/CreateEthereumTx.ts (94%) create mode 100644 packages/core/src/transaction/workflow/create-tx/index.ts create mode 100644 packages/core/src/transaction/workflow/create-tx/types.ts create mode 100644 packages/core/src/transaction/workflow/index.ts create mode 100644 packages/core/src/transaction/workflow/types.ts delete mode 100644 packages/core/src/workflow/CreateBitcoinCancelTx.ts delete mode 100644 packages/core/src/workflow/CreateTxConverter.ts delete mode 100644 packages/core/src/workflow/index.ts delete mode 100644 packages/core/src/workflow/types.ts create mode 100644 packages/react-app/src/transaction/CreateTransaction/SetupTransaction/flow/blockchain/bitcoin/cancel.tsx create mode 100644 packages/react-app/src/transaction/CreateTransaction/SetupTransaction/flow/common/cancel.tsx create mode 100644 packages/store/src/txstash/handler/blockchain/bitcoin/fee.ts create mode 100644 packages/store/src/txstash/handler/blockchain/bitcoin/index.ts create mode 100644 packages/store/src/txstash/handler/blockchain/bitcoin/prepare.ts create mode 100644 packages/store/src/txstash/handler/blockchain/bitcoin/restore.ts create mode 100644 packages/store/src/txstash/handler/blockchain/ethereum/fee.ts create mode 100644 packages/store/src/txstash/handler/blockchain/ethereum/index.ts create mode 100644 packages/store/src/txstash/handler/blockchain/ethereum/prepare.ts create mode 100644 packages/store/src/txstash/handler/blockchain/ethereum/restore.ts create mode 100644 packages/store/src/txstash/handler/blockchain/index.ts create mode 100644 packages/store/src/txstash/handler/handler.ts create mode 100644 packages/store/src/txstash/handler/index.ts create mode 100644 packages/store/src/txstash/handler/types.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 385320006..e3a7cf6e9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,8 +2,8 @@ import * as blockchains from './blockchains'; import * as config from './config'; +import * as workflow from './transaction/workflow'; import * as utils from './utils'; -import * as workflow from './workflow'; export * as PersistentState from './persistentState'; export { BackendApi } from './BackendApi'; diff --git a/packages/core/src/workflow/CreateTxConverter.spec.ts b/packages/core/src/transaction/workflow/TxBuilder.spec.ts similarity index 93% rename from packages/core/src/workflow/CreateTxConverter.spec.ts rename to packages/core/src/transaction/workflow/TxBuilder.spec.ts index 68d16a0af..8c1b18c27 100644 --- a/packages/core/src/workflow/CreateTxConverter.spec.ts +++ b/packages/core/src/transaction/workflow/TxBuilder.spec.ts @@ -1,13 +1,14 @@ import { BigAmount } from '@emeraldpay/bigamount'; import { Satoshi, Wei } from '@emeraldpay/bigamount-crypto'; import { BitcoinEntry, EthereumEntry } from '@emeraldpay/emerald-vault-core'; -import { InputUtxo, TokenData, TokenRegistry, amountFactory, blockchainIdToCode } from '../blockchains'; -import { DEFAULT_GAS_LIMIT, EthereumTransactionType } from '../transaction/ethereum'; -import { DEFAULT_VKB_FEE } from './CreateBitcoinTx'; -import { CreateTxConverter, FeeRange } from './CreateTxConverter'; -import { TxTarget, isBitcoinCreateTx, isErc20CreateTx, isEthereumCreateTx } from './types'; - -describe('CreateTxConverter', () => { +import { InputUtxo, TokenData, TokenRegistry, amountFactory, blockchainIdToCode } from '../../blockchains'; +import { DEFAULT_GAS_LIMIT, EthereumTransactionType } from '../ethereum'; +import { DEFAULT_VKB_FEE } from './create-tx/CreateBitcoinTx'; +import { isAnyBitcoinCreateTx, isErc20CreateTx, isEthereumCreateTx } from './create-tx/types'; +import { TxBuilder } from './TxBuilder'; +import { FeeRange, TxTarget } from './types'; + +describe('TxBuilder', () => { const tokenData: TokenData = { name: 'Wrapped Ether', blockchain: 100, @@ -173,7 +174,7 @@ describe('CreateTxConverter', () => { } it('create initial BTC tx', () => { - const { createTx } = new CreateTxConverter( + const { createTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'BTC', @@ -186,7 +187,7 @@ describe('CreateTxConverter', () => { tokenRegistry, ); - const isCorrectCreateTx = isBitcoinCreateTx(createTx); + const isCorrectCreateTx = isAnyBitcoinCreateTx(createTx); expect(isCorrectCreateTx).toBeTruthy(); @@ -202,7 +203,7 @@ describe('CreateTxConverter', () => { }); it('create initial ETH tx', () => { - const { createTx } = new CreateTxConverter( + const { createTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -235,7 +236,7 @@ describe('CreateTxConverter', () => { }); it('create initial ERC20 tx', () => { - const { createTx } = new CreateTxConverter( + const { createTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -273,7 +274,7 @@ describe('CreateTxConverter', () => { }); it('create initial ETC tx', () => { - const { createTx } = new CreateTxConverter( + const { createTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETC', @@ -306,7 +307,7 @@ describe('CreateTxConverter', () => { }); it('create initial ERC20 tx with allowance', () => { - const { createTx } = new CreateTxConverter( + const { createTx } = new TxBuilder( { feeRange: ethFeeRange, ownerAddress: ethOwnerAddress, @@ -346,7 +347,7 @@ describe('CreateTxConverter', () => { }); it('change asset from ETH to ERC20 token', () => { - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -367,7 +368,7 @@ describe('CreateTxConverter', () => { ethCreateTx.amount = new Wei(1); ethCreateTx.to = ethToAddress; - const { createTx: erc20CreateTx } = new CreateTxConverter( + const { createTx: erc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -408,7 +409,7 @@ describe('CreateTxConverter', () => { }); it('change asset from ETH to ERC20 token with max amount', () => { - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -431,7 +432,7 @@ describe('CreateTxConverter', () => { ethCreateTx.target = TxTarget.SEND_ALL; ethCreateTx.rebalance(); - const { createTx: erc20CreateTx } = new CreateTxConverter( + const { createTx: erc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -472,7 +473,7 @@ describe('CreateTxConverter', () => { }); it('change asset from ERC20 token to ETH with max amount', () => { - const { createTx: erc20CreateTx } = new CreateTxConverter( + const { createTx: erc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -495,7 +496,7 @@ describe('CreateTxConverter', () => { erc20CreateTx.target = TxTarget.SEND_ALL; erc20CreateTx.rebalance(); - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -535,7 +536,7 @@ describe('CreateTxConverter', () => { }); it('change entry from BTC to ETH', () => { - const { createTx: btcCreateTx } = new CreateTxConverter( + const { createTx: btcCreateTx } = new TxBuilder( { asset: 'BTC', entry: btcEntry, @@ -548,7 +549,7 @@ describe('CreateTxConverter', () => { tokenRegistry, ); - const isCorrectCreateTx = isBitcoinCreateTx(btcCreateTx); + const isCorrectCreateTx = isAnyBitcoinCreateTx(btcCreateTx); expect(isCorrectCreateTx).toBeTruthy(); @@ -556,7 +557,7 @@ describe('CreateTxConverter', () => { btcCreateTx.amount = new Satoshi(1); btcCreateTx.to = btcToAddress; - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -592,7 +593,7 @@ describe('CreateTxConverter', () => { }); it('change entry from ETH to BTC', () => { - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -613,7 +614,7 @@ describe('CreateTxConverter', () => { ethCreateTx.amount = new Wei(1); ethCreateTx.to = ethToAddress; - const { createTx: btcCreateTx } = new CreateTxConverter( + const { createTx: btcCreateTx } = new TxBuilder( { asset: 'BTC', entry: btcEntry, @@ -627,7 +628,7 @@ describe('CreateTxConverter', () => { tokenRegistry, ); - const isConvertedCreateTx = isBitcoinCreateTx(btcCreateTx); + const isConvertedCreateTx = isAnyBitcoinCreateTx(btcCreateTx); expect(isConvertedCreateTx).toBeTruthy(); @@ -645,7 +646,7 @@ describe('CreateTxConverter', () => { }); it('change entry from ETH to ETC', () => { - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { asset: 'ETH', entry: ethEntry1, @@ -666,7 +667,7 @@ describe('CreateTxConverter', () => { ethCreateTx.amount = new Wei(1); ethCreateTx.to = ethToAddress; - const { createTx: etcCreateTx } = new CreateTxConverter( + const { createTx: etcCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETC', @@ -702,7 +703,7 @@ describe('CreateTxConverter', () => { }); it('change entry from ETH to other ETH', () => { - const { createTx: eth1CreateTx } = new CreateTxConverter( + const { createTx: eth1CreateTx } = new TxBuilder( { asset: 'ETH', entry: ethEntry1, @@ -723,7 +724,7 @@ describe('CreateTxConverter', () => { eth1CreateTx.amount = new Wei(1); eth1CreateTx.to = ethToAddress; - const { createTx: eth2CreateTx } = new CreateTxConverter( + const { createTx: eth2CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -759,7 +760,7 @@ describe('CreateTxConverter', () => { }); it('restore ETH tx', () => { - const { createTx: ethCreateTx } = new CreateTxConverter( + const { createTx: ethCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -782,7 +783,7 @@ describe('CreateTxConverter', () => { ethCreateTx.target = TxTarget.SEND_ALL; ethCreateTx.rebalance(); - const { createTx: restoredEthCreateTx } = new CreateTxConverter( + const { createTx: restoredEthCreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: 'ETH', @@ -820,7 +821,7 @@ describe('CreateTxConverter', () => { }); it('restore ERC20 tx', () => { - const { createTx: erc20CreateTx } = new CreateTxConverter( + const { createTx: erc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -843,7 +844,7 @@ describe('CreateTxConverter', () => { erc20CreateTx.target = TxTarget.SEND_ALL; erc20CreateTx.rebalance(); - const { createTx: restoredErc20CreateTx } = new CreateTxConverter( + const { createTx: restoredErc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -884,7 +885,7 @@ describe('CreateTxConverter', () => { }); it('restore ERC20 tx with allowance', () => { - const { createTx: erc20CreateTx } = new CreateTxConverter( + const { createTx: erc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, asset: tokenData.address, @@ -907,7 +908,7 @@ describe('CreateTxConverter', () => { erc20CreateTx.target = TxTarget.SEND_ALL; erc20CreateTx.rebalance(); - const { createTx: restoredErc20CreateTx } = new CreateTxConverter( + const { createTx: restoredErc20CreateTx } = new TxBuilder( { feeRange: ethFeeRange, ownerAddress: ethOwnerAddress, diff --git a/packages/core/src/transaction/workflow/TxBuilder.ts b/packages/core/src/transaction/workflow/TxBuilder.ts new file mode 100644 index 000000000..1d9bb101c --- /dev/null +++ b/packages/core/src/transaction/workflow/TxBuilder.ts @@ -0,0 +1,279 @@ +import { BigAmount } from '@emeraldpay/bigamount'; +import { WeiAny } from '@emeraldpay/bigamount-crypto'; +import { + BitcoinEntry, + EthereumEntry, + WalletEntry, + isBitcoinEntry, + isEthereumEntry, +} from '@emeraldpay/emerald-vault-core'; +import { + Blockchains, + InputUtxo, + TokenRegistry, + amountFactory, + blockchainIdToCode, + isBitcoin, + isEthereum, +} from '../../blockchains'; +import { EthereumTransactionType } from '../ethereum'; +import { CreateBitcoinTx, CreateERC20Tx, CreateEthereumTx } from './create-tx'; +import { + AnyCreateTx, + AnyEthereumCreateTx, + fromBitcoinPlainTx, + fromEthereumPlainTx, + isErc20CreateTx, +} from './create-tx/types'; +import { + BitcoinPlainTx, + EthereumPlainTx, + FeeRange, + TxTarget, + isBitcoinFeeRange, + isBitcoinPlainTx, + isEthereumFeeRange, +} from './types'; + +interface BuilderOrigin { + asset: string; + changeAddress?: string; + entry: WalletEntry; + feeRange: FeeRange; + ownerAddress?: string; + transaction?: BitcoinPlainTx | EthereumPlainTx; +} + +interface DataProvider { + getBalance(entry: WalletEntry, asset: string, ownerAddress?: string): BigAmount; + getUtxo(entry: BitcoinEntry): InputUtxo[]; +} + +export class TxBuilder implements BuilderOrigin { + readonly asset: string; + readonly changeAddress?: string; + readonly entry: WalletEntry; + readonly feeRange: FeeRange; + readonly ownerAddress?: string; + readonly transaction?: BitcoinPlainTx | EthereumPlainTx; + + private readonly dataProvider: DataProvider; + private readonly tokenRegistry: TokenRegistry; + + constructor(origin: BuilderOrigin, dataProvider: DataProvider, tokenRegistry: TokenRegistry) { + const { asset, changeAddress, entry, feeRange, ownerAddress, transaction } = origin; + + this.asset = asset; + this.changeAddress = changeAddress; + this.entry = entry; + this.feeRange = feeRange; + this.ownerAddress = ownerAddress; + this.transaction = transaction; + + this.dataProvider = dataProvider; + this.tokenRegistry = tokenRegistry; + } + + get createTx(): AnyCreateTx { + const { asset, changeAddress, entry, feeRange, tokenRegistry, transaction } = this; + + const blockchain = blockchainIdToCode(entry.blockchain); + + let createTx: AnyCreateTx; + + if ( + transaction == null || + (isBitcoin(blockchain) && isEthereum(transaction.blockchain)) || + (isEthereum(blockchain) && isBitcoin(transaction.blockchain)) + ) { + if (isBitcoinEntry(entry)) { + createTx = this.initBitcoinTx(entry); + } else { + if (tokenRegistry.hasAddress(blockchain, asset)) { + createTx = this.initErc20Tx(entry); + } else { + createTx = this.initEthereumTx(entry); + } + + createTx.from = entry.address?.value; + + if (isEthereumFeeRange(feeRange)) { + createTx.gasPrice = feeRange.stdMaxGasPrice; + createTx.maxGasPrice = feeRange.stdMaxGasPrice; + createTx.priorityGasPrice = feeRange.stdPriorityGasPrice; + } + + createTx.type = + Blockchains[blockchain].params.eip1559 ?? false + ? EthereumTransactionType.EIP1559 + : EthereumTransactionType.LEGACY; + } + } else { + if (isBitcoinPlainTx(transaction)) { + if (isEthereumEntry(entry)) { + throw new Error('Ethereum entry provided for Bitcoin transaction'); + } + + createTx = fromBitcoinPlainTx(transaction, { blockchain, changeAddress, entryId: entry.id }); + } else { + if (isBitcoinEntry(entry)) { + throw new Error('Bitcoin entry provided for Ethereum transaction'); + } + + createTx = fromEthereumPlainTx(transaction, tokenRegistry); + + if (asset !== createTx.getAsset() || blockchain !== createTx.blockchain) { + return this.convertEthereumTx(createTx); + } + + this.populateEthereumTx(createTx, transaction); + } + } + + return createTx; + } + + private initBitcoinTx(entry: BitcoinEntry): CreateBitcoinTx { + const { + changeAddress, + feeRange, + dataProvider: { getUtxo }, + } = this; + + const createTx = new CreateBitcoinTx( + { + changeAddress, + blockchain: blockchainIdToCode(entry.blockchain), + entryId: entry.id, + }, + getUtxo(entry), + ); + + if (isBitcoinFeeRange(feeRange)) { + createTx.feePrice = feeRange.std; + } + + return createTx; + } + + private initEthereumTx(entry: EthereumEntry): CreateEthereumTx { + const { asset } = this; + const { getBalance } = this.dataProvider; + + const createTx = new CreateEthereumTx(null, blockchainIdToCode(entry.blockchain)); + + createTx.totalBalance = getBalance(entry, asset) as WeiAny; + + return createTx; + } + + private initErc20Tx(entry: EthereumEntry): CreateERC20Tx { + const { asset, ownerAddress, tokenRegistry } = this; + const { getBalance } = this.dataProvider; + + const blockchain = blockchainIdToCode(entry.blockchain); + + const { coinTicker } = Blockchains[blockchain].params; + + const createTx = new CreateERC20Tx(tokenRegistry, asset, blockchain); + + createTx.totalBalance = getBalance(entry, coinTicker) as WeiAny; + createTx.totalTokenBalance = getBalance(entry, asset, ownerAddress); + createTx.transferFrom = ownerAddress; + + return createTx; + } + + private convertEthereumTx(oldCreateTx: AnyEthereumCreateTx): AnyEthereumCreateTx { + const { asset, entry, feeRange, ownerAddress, tokenRegistry } = this; + const { getBalance } = this.dataProvider; + + const blockchain = blockchainIdToCode(entry.blockchain); + const { coinTicker, eip1559: supportEip1559 = false } = Blockchains[blockchain].params; + + const type = supportEip1559 ? oldCreateTx.type : EthereumTransactionType.LEGACY; + + let newCreateTx: AnyEthereumCreateTx; + + if (tokenRegistry.hasAddress(blockchain, asset)) { + newCreateTx = new CreateERC20Tx(tokenRegistry, asset, blockchain, type); + newCreateTx.totalBalance = getBalance(entry, coinTicker) as WeiAny; + newCreateTx.totalTokenBalance = getBalance(entry, asset, newCreateTx.transferFrom); + + newCreateTx.transferFrom = isErc20CreateTx(oldCreateTx, tokenRegistry) + ? oldCreateTx.transferFrom ?? ownerAddress + : ownerAddress; + } else { + newCreateTx = new CreateEthereumTx(null, blockchain, type); + newCreateTx.totalBalance = getBalance(entry, asset) as WeiAny; + } + + newCreateTx.from = entry.address?.value; + newCreateTx.to = oldCreateTx.to; + + if (blockchain === oldCreateTx.blockchain && (oldCreateTx.gasPrice?.isPositive() ?? false)) { + newCreateTx.gasPrice = oldCreateTx.gasPrice; + newCreateTx.maxGasPrice = oldCreateTx.maxGasPrice; + newCreateTx.priorityGasPrice = oldCreateTx.priorityGasPrice; + } else if (isEthereumFeeRange(feeRange)) { + newCreateTx.gasPrice = feeRange.stdMaxGasPrice; + newCreateTx.maxGasPrice = feeRange.stdMaxGasPrice; + newCreateTx.priorityGasPrice = feeRange.stdPriorityGasPrice; + } + + if (oldCreateTx.target === TxTarget.SEND_ALL) { + newCreateTx.target = TxTarget.SEND_ALL; + + if (!newCreateTx.rebalance()) { + newCreateTx.target = TxTarget.MANUAL; + + newCreateTx.amount = isErc20CreateTx(newCreateTx, tokenRegistry) + ? tokenRegistry.byAddress(blockchain, newCreateTx.getAsset()).getAmount(0) + : amountFactory(blockchain)(0); + } + } + + return newCreateTx; + } + + private populateEthereumTx(createTx: AnyEthereumCreateTx, transaction: EthereumPlainTx): void { + const { asset, entry, feeRange, ownerAddress, tokenRegistry } = this; + const { getBalance } = this.dataProvider; + + const gasPrice = createTx.gasPrice ?? createTx.maxGasPrice; + + if ((gasPrice?.isZero() ?? true) && isEthereumFeeRange(feeRange)) { + createTx.gasPrice = feeRange.stdMaxGasPrice; + createTx.maxGasPrice = feeRange.stdMaxGasPrice; + createTx.priorityGasPrice = feeRange.stdPriorityGasPrice; + } + + if ( + transaction.from !== entry.address?.value || + transaction.transferFrom !== ownerAddress || + (transaction.transferFrom == null && ownerAddress != null) + ) { + createTx.from = entry.address?.value; + + const blockchain = blockchainIdToCode(entry.blockchain); + const { coinTicker } = Blockchains[blockchain].params; + + if (isErc20CreateTx(createTx, tokenRegistry)) { + createTx.transferFrom = ownerAddress; + + createTx.totalBalance = getBalance(entry, coinTicker) as WeiAny; + createTx.totalTokenBalance = getBalance(entry, asset, createTx.transferFrom); + } else { + createTx.totalBalance = getBalance(entry, asset) as WeiAny; + } + + if (createTx.target === TxTarget.SEND_ALL && !createTx.rebalance()) { + createTx.target = TxTarget.MANUAL; + + createTx.amount = isErc20CreateTx(createTx, tokenRegistry) + ? tokenRegistry.byAddress(blockchain, createTx.getAsset()).getAmount(0) + : amountFactory(blockchain)(0); + } + } + } +} diff --git a/packages/core/src/workflow/TxSigner.ts b/packages/core/src/transaction/workflow/TxSigner.ts similarity index 92% rename from packages/core/src/workflow/TxSigner.ts rename to packages/core/src/transaction/workflow/TxSigner.ts index 201d5de47..1a35cafff 100644 --- a/packages/core/src/workflow/TxSigner.ts +++ b/packages/core/src/transaction/workflow/TxSigner.ts @@ -14,11 +14,11 @@ import { isEthereumTx, } from '@emeraldpay/emerald-vault-core'; import { Transaction as BitcoinTx } from 'bitcoinjs-lib'; -import { BlockchainCode, Blockchains } from '../blockchains'; -import { EthereumTx } from '../blockchains/ethereum'; -import { EthereumAddress } from '../blockchains/ethereum/EthereumAddress'; -import { EthereumTransaction, EthereumTransactionType } from '../transaction/ethereum'; -import { AnyCreateTx, isBitcoinCreateTx } from './types'; +import { BlockchainCode, Blockchains } from '../../blockchains'; +import { EthereumTx } from '../../blockchains/ethereum'; +import { EthereumAddress } from '../../blockchains/ethereum/EthereumAddress'; +import { EthereumTransaction, EthereumTransactionType } from '../ethereum'; +import { AnyCreateTx, isAnyBitcoinCreateTx } from './create-tx/types'; interface SignerOrigin { createTx: AnyCreateTx; @@ -90,7 +90,7 @@ export class TxSigner implements SignerOrigin { let unsigned: UnsignedTx; - if (isBitcoinCreateTx(createTx)) { + if (isAnyBitcoinCreateTx(createTx)) { unsigned = createTx.build(); } else { unsigned = TxSigner.convertEthereumTx(createTx.build()); @@ -114,7 +114,7 @@ export class TxSigner implements SignerOrigin { private verifySigned(raw: string): void { const { createTx } = this; - if (isBitcoinCreateTx(createTx)) { + if (isAnyBitcoinCreateTx(createTx)) { const transaction = BitcoinTx.fromHex(raw); const correctInputs = transaction.ins.every(({ hash }) => { diff --git a/packages/core/src/transaction/workflow/create-tx/CreateBitcoinCancelTx.ts b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinCancelTx.ts new file mode 100644 index 000000000..9baa593db --- /dev/null +++ b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinCancelTx.ts @@ -0,0 +1,40 @@ +import { SatoshiAny } from '@emeraldpay/bigamount-crypto'; +import { amountDecoder } from '../../../blockchains'; +import { BitcoinPlainTx, TxMetaType } from '../types'; +import { BitcoinTxOrigin, BitcoinTxOutput, CreateBitcoinTx } from './CreateBitcoinTx'; + +export class CreateBitcoinCancelTx extends CreateBitcoinTx { + meta = { type: TxMetaType.BITCOIN_CANCEL }; + + static fromPlain(origin: BitcoinTxOrigin, plain: BitcoinPlainTx): CreateBitcoinCancelTx { + const changeAddress = origin.changeAddress ?? plain.changeAddress; + + const tx = new CreateBitcoinCancelTx({ ...origin, changeAddress }, plain.utxo); + + const decoder = amountDecoder(origin.blockchain); + + tx.amount = decoder(plain.amount); + tx.meta.type = TxMetaType.BITCOIN_CANCEL; + tx.target = plain.target; + tx.to = plain.to; + tx.vkbPrice = plain.vkbPrice; + + return tx; + } + + get outputs(): BitcoinTxOutput[] { + let totalChange = this.change; + + if (this.transaction.amount != null) { + totalChange = totalChange.plus(this.transaction.amount); + } + + return [ + { + address: this.changeAddress ?? '', + amount: totalChange.number.toNumber(), + entryId: this.entryId, + }, + ]; + } +} diff --git a/packages/core/src/transaction/workflow/create-tx/CreateBitcoinSpeedUpTx.ts b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinSpeedUpTx.ts new file mode 100644 index 000000000..20bbc462a --- /dev/null +++ b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinSpeedUpTx.ts @@ -0,0 +1,24 @@ +import { SatoshiAny } from '@emeraldpay/bigamount-crypto'; +import { amountDecoder } from '../../../blockchains'; +import { BitcoinPlainTx, TxMetaType } from '../types'; +import { BitcoinTxOrigin, CreateBitcoinTx } from './CreateBitcoinTx'; + +export class CreateBitcoinSpeedUpTx extends CreateBitcoinTx { + meta = { type: TxMetaType.BITCOIN_SPEED_UP }; + + static fromPlain(origin: BitcoinTxOrigin, plain: BitcoinPlainTx): CreateBitcoinSpeedUpTx { + const changeAddress = origin.changeAddress ?? plain.changeAddress; + + const tx = new CreateBitcoinSpeedUpTx({ ...origin, changeAddress }, plain.utxo); + + const decoder = amountDecoder(origin.blockchain); + + tx.amount = decoder(plain.amount); + tx.meta.type = TxMetaType.BITCOIN_SPEED_UP; + tx.target = plain.target; + tx.to = plain.to; + tx.vkbPrice = plain.vkbPrice; + + return tx; + } +} diff --git a/packages/core/src/workflow/CreateBitcoinTx.spec.ts b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinTx.spec.ts similarity index 78% rename from packages/core/src/workflow/CreateBitcoinTx.spec.ts rename to packages/core/src/transaction/workflow/create-tx/CreateBitcoinTx.spec.ts index afd0305d7..9956a8a9b 100644 --- a/packages/core/src/workflow/CreateBitcoinTx.spec.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinTx.spec.ts @@ -1,7 +1,7 @@ import { SATOSHIS, Satoshi } from '@emeraldpay/bigamount-crypto'; -import { BlockchainCode, InputUtxo, amountFactory } from '../blockchains'; +import { BlockchainCode, InputUtxo, amountFactory } from '../../../blockchains'; +import { TxTarget, ValidationResult } from '../types'; import { BitcoinTxMetric, BitcoinTxOutput, CreateBitcoinTx, convertWUToVB } from './CreateBitcoinTx'; -import { TxTarget, ValidationResult } from './types'; const basicEntryId = 'f76416d7-3510-4d80-85df-52e7222e56df-1'; const restoreEntryId = '2a19e023-f119-4dab-b2cb-4b3e73fa32c9-1'; @@ -35,23 +35,27 @@ class TestMetric implements BitcoinTxMetric { const defaultMetric = new TestMetric(120, 80); describe('CreateBitcoinTx', () => { - const defaultBitcoin = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [], - }); + const defaultBitcoin = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [], + ); defaultBitcoin.metric = defaultMetric; defaultBitcoin.feePrice = 100; it('create', () => { - const act = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [], - }); + const act = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [], + ); expect(act).toBeDefined(); expect(act.totalToSpend).toBeDefined(); @@ -87,16 +91,18 @@ describe('CreateBitcoinTx', () => { ).toBe(Satoshi.fromBitcoin(0.5 + 0.61 + 0.756).toString())); it('rebalance when have enough', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.5).encode(), address: 'ADDR' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.61).encode(), address: 'ADDR' }, { txid: '3', vout: 0, value: Satoshi.fromBitcoin(0.756).encode(), address: 'ADDR' }, ], - }); + ); create.metric = defaultMetric; @@ -130,16 +136,18 @@ describe('CreateBitcoinTx', () => { }); it('rebalance when less that enough', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.5).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.61).encode(), address: 'addr2' }, { txid: '3', vout: 0, value: Satoshi.fromBitcoin(0.756).encode(), address: 'addr3' }, ], - }); + ); create.amount = Satoshi.fromBitcoin(2); create.to = 'addrTo'; @@ -160,17 +168,19 @@ describe('CreateBitcoinTx', () => { }); it('rebalance when no change', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.005).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.005).encode(), address: 'addr2' }, { txid: '3', vout: 0, value: Satoshi.fromBitcoin(0.005).encode(), address: 'addr3' }, { txid: '4', vout: 0, value: Satoshi.fromBitcoin(0.005).encode(), address: 'addr4' }, ], - }); + ); create.metric = defaultMetric; @@ -197,15 +207,17 @@ describe('CreateBitcoinTx', () => { }); it('rebalance with send all target', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); create.metric = defaultMetric; @@ -221,15 +233,17 @@ describe('CreateBitcoinTx', () => { }); it('simple fee', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); create.metric = defaultMetric; @@ -242,15 +256,17 @@ describe('CreateBitcoinTx', () => { }); it('fee when not enough', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); create.metric = defaultMetric; @@ -261,15 +277,17 @@ describe('CreateBitcoinTx', () => { }); it('update fee', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); create.metric = defaultMetric; @@ -287,15 +305,17 @@ describe('CreateBitcoinTx', () => { }); it('estimate fees', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); create.metric = defaultMetric; @@ -317,15 +337,17 @@ describe('CreateBitcoinTx', () => { }); it('estimate price', () => { - const tx = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const tx = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 1, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); tx.amount = Satoshi.fromBitcoin(0.08); tx.metric = defaultMetric; @@ -338,48 +360,56 @@ describe('CreateBitcoinTx', () => { }); it('total available', () => { - let create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [{ txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }], - }); + let create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [{ txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }], + ); expect(create.totalAvailable.toString()).toBe(Satoshi.fromBitcoin(0.05).toString()); - create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr2' }, ], - }); + ); expect(create.totalAvailable.toString()).toBe(Satoshi.fromBitcoin(0.1).toString()); - create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: Satoshi.fromBitcoin(0.05).encode(), address: 'addr1' }, { txid: '2', vout: 0, value: Satoshi.fromBitcoin(0.06).encode(), address: 'addr2' }, { txid: '3', vout: 0, value: Satoshi.fromBitcoin(0.07).encode(), address: 'addr3' }, ], - }); + ); expect(create.totalAvailable.toString()).toBe(Satoshi.fromBitcoin(0.18).toString()); }); it('creates unsigned', () => { - const create = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrChange', - utxo: [{ txid: '1', vout: 0, value: new Satoshi(112233).encode(), address: 'addr1' }], - }); + const create = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrChange', + }, + [{ txid: '1', vout: 0, value: new Satoshi(112233).encode(), address: 'addr1' }], + ); create.metric = defaultMetric; @@ -415,11 +445,13 @@ describe('CreateBitcoinTx', () => { }); it('creates restored', () => { - const tx = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: restoreEntryId, - changeAddress: 'tb1q8grga8c48wa4dsevt0v0gcl6378rfljj6vrz0u', - utxo: [ + const tx = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: restoreEntryId, + changeAddress: 'tb1q8grga8c48wa4dsevt0v0gcl6378rfljj6vrz0u', + }, + [ { address: 'tb1qjg445dvh6krr6gtmuh4eqgua372vxaf4q07nv9', txid: 'fd53023c4a9627c26c5d930f3149890b2eecf4261f409bd1a340454b7dede244', @@ -427,7 +459,7 @@ describe('CreateBitcoinTx', () => { vout: 0, }, ], - }); + ); tx.amount = new Satoshi(1000); tx.feePrice = 1067; @@ -441,12 +473,14 @@ describe('CreateBitcoinTx', () => { }); it('creates with zero fee', () => { - const tx = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [{ txid: '1', vout: 0, value: new Satoshi(1000).encode(), address: 'addr1' }], - }); + const tx = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [{ txid: '1', vout: 0, value: new Satoshi(1000).encode(), address: 'addr1' }], + ); tx.amount = new Satoshi(1000); tx.feePrice = 0; @@ -460,15 +494,17 @@ describe('CreateBitcoinTx', () => { }); it('creates with enough inputs for fee', () => { - const tx = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const tx = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: new Satoshi(1000).encode(), address: 'addr1' }, { txid: '2', vout: 1, value: new Satoshi(1000).encode(), address: 'addr2' }, ], - }); + ); tx.amount = new Satoshi(1000); tx.feePrice = 1024; @@ -482,15 +518,17 @@ describe('CreateBitcoinTx', () => { }); it('creates with inputs amount equals required amount and zero fee', () => { - const tx = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const tx = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: new Satoshi(1000).encode(), address: 'addr1' }, { txid: '2', vout: 1, value: new Satoshi(1000).encode(), address: 'addr2' }, ], - }); + ); tx.to = 'addrTo'; tx.amount = new Satoshi(2000); @@ -504,15 +542,17 @@ describe('CreateBitcoinTx', () => { }); it('creates cancel transaction', () => { - const tx = new CreateBitcoinTx({ - blockchain: BlockchainCode.BTC, - entryId: basicEntryId, - changeAddress: 'addrchange', - utxo: [ + const tx = new CreateBitcoinTx( + { + blockchain: BlockchainCode.BTC, + entryId: basicEntryId, + changeAddress: 'addrchange', + }, + [ { txid: '1', vout: 0, value: new Satoshi(1000).encode(), address: 'addr1' }, { txid: '2', vout: 1, value: new Satoshi(1000).encode(), address: 'addr2' }, ], - }); + ); tx.amount = new Satoshi(1000); tx.feePrice = 1024; diff --git a/packages/core/src/workflow/CreateBitcoinTx.ts b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinTx.ts similarity index 93% rename from packages/core/src/workflow/CreateBitcoinTx.ts rename to packages/core/src/transaction/workflow/create-tx/CreateBitcoinTx.ts index cc9b581f1..23d074c28 100644 --- a/packages/core/src/workflow/CreateBitcoinTx.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateBitcoinTx.ts @@ -1,9 +1,9 @@ import { CreateAmount } from '@emeraldpay/bigamount'; -import { SatoshiAny } from '@emeraldpay/bigamount-crypto'; // TODO SatoshiAnyAny +import { SatoshiAny } from '@emeraldpay/bigamount-crypto'; import { EntryId, UnsignedBitcoinTx } from '@emeraldpay/emerald-vault-core'; import BigNumber from 'bignumber.js'; -import { BlockchainCode, InputUtxo, amountDecoder, amountFactory } from '../blockchains'; -import { BitcoinPlainTx, TxTarget, ValidationResult } from './types'; +import { BlockchainCode, InputUtxo, amountDecoder, amountFactory } from '../../../blockchains'; +import { BitcoinPlainTx, CommonTx, TxMetaType, TxTarget, ValidationResult } from '../types'; const DEFAULT_SEQUENCE = 0xfffffff0 as const; const MAX_SEQUENCE = 0xffffffff as const; @@ -35,15 +35,16 @@ export interface BitcoinTxMetric { weightOf(inputs: InputUtxo[], outputs: BitcoinTxOutput[]): number; } -interface CommonBitcoinTx { +interface CommonBitcoinTx extends CommonTx { readonly changeAddress?: string; readonly entryId: EntryId; readonly tx: BitcoinTxDetails; metric: BitcoinTxMetric; vkbPrice: number; build(): UnsignedBitcoinTx; - estimateVkbPrice(price: SatoshiAny): number; - getFees(price: number): SatoshiAny; + dump(): BitcoinPlainTx; + estimateVkbPrice(fee: SatoshiAny): number; + getFees(): SatoshiAny; rebalance(): boolean; totalUtxo(utxo: InputUtxo[]): SatoshiAny; validate(): ValidationResult; @@ -88,10 +89,11 @@ export interface BitcoinTxOrigin { blockchain: BlockchainCode; changeAddress?: string; entryId: EntryId; - utxo: InputUtxo[]; } export class CreateBitcoinTx implements BitcoinTx { + meta = { type: TxMetaType.BITCOIN_TRANSFER }; + readonly tx: BitcoinTxDetails; readonly blockchain: BlockchainCode; @@ -107,7 +109,7 @@ export class CreateBitcoinTx implements BitcoinTx { private readonly zero: SatoshiAny; - constructor({ blockchain, changeAddress, entryId, utxo }: BitcoinTxOrigin) { + constructor({ blockchain, changeAddress, entryId }: BitcoinTxOrigin, utxo: InputUtxo[]) { this.tx = { from: [], target: TxTarget.MANUAL }; this.blockchain = blockchain; @@ -123,7 +125,7 @@ export class CreateBitcoinTx implements BitcoinTx { } static fromPlain(origin: BitcoinTxOrigin, plain: BitcoinPlainTx): CreateBitcoinTx { - const tx = new CreateBitcoinTx(origin); + const tx = new CreateBitcoinTx(origin, plain.utxo); const decoder = amountDecoder(origin.blockchain); @@ -243,9 +245,12 @@ export class CreateBitcoinTx implements BitcoinTx { return { amount: this.amount.encode(), blockchain: this.blockchain, + changeAddress: this.changeAddress, + meta: { type: this.meta.type }, target: this.tx.target, vkbPrice: this.vkbPrice, to: this.tx.to, + utxo: this.utxo, }; } diff --git a/packages/core/src/workflow/CreateErc20ApproveTx.spec.ts b/packages/core/src/transaction/workflow/create-tx/CreateErc20ApproveTx.spec.ts similarity index 90% rename from packages/core/src/workflow/CreateErc20ApproveTx.spec.ts rename to packages/core/src/transaction/workflow/create-tx/CreateErc20ApproveTx.spec.ts index 48c38676a..69225de90 100644 --- a/packages/core/src/workflow/CreateErc20ApproveTx.spec.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateErc20ApproveTx.spec.ts @@ -1,8 +1,8 @@ import { Wei } from '@emeraldpay/bigamount-crypto'; -import { BlockchainCode, INFINITE_ALLOWANCE, TokenData, TokenRegistry } from '../blockchains'; -import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransactionType } from '../transaction/ethereum'; +import { BlockchainCode, INFINITE_ALLOWANCE, TokenData, TokenRegistry } from '../../../blockchains'; +import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransactionType } from '../../ethereum'; +import { TxMetaType, ValidationResult } from '../types'; import { ApproveTarget, CreateErc20ApproveTx } from './CreateErc20ApproveTx'; -import { ValidationResult } from './types'; describe('CreateErc20ApproveTx', () => { const wethTokenData: TokenData = { @@ -31,6 +31,7 @@ describe('CreateErc20ApproveTx', () => { test('should create legacy approve tx', () => { const tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, token: wethTokenData, totalBalance: new Wei(0), totalTokenBalance: wethToken.getAmount(0), @@ -46,6 +47,7 @@ describe('CreateErc20ApproveTx', () => { test('should create eip1559 approve tx', () => { const tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, token: wethTokenData, totalBalance: new Wei(0), totalTokenBalance: wethToken.getAmount(0), @@ -62,6 +64,7 @@ describe('CreateErc20ApproveTx', () => { test('should set target', () => { let tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, target: ApproveTarget.MANUAL, token: wethTokenData, totalBalance: new Wei(0), @@ -75,6 +78,7 @@ describe('CreateErc20ApproveTx', () => { tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, target: ApproveTarget.MAX_AVAILABLE, token: wethTokenData, totalBalance: new Wei(0), @@ -86,6 +90,7 @@ describe('CreateErc20ApproveTx', () => { tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, target: ApproveTarget.INFINITE, token: wethTokenData, totalBalance: new Wei(0), @@ -101,6 +106,7 @@ describe('CreateErc20ApproveTx', () => { const tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, token: wethTokenData, totalBalance: new Wei(0), totalTokenBalance: amount, @@ -128,6 +134,7 @@ describe('CreateErc20ApproveTx', () => { const tx = new CreateErc20ApproveTx({ amount, blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, token: wethTokenData, totalBalance: new Wei(0), totalTokenBalance: amount, @@ -146,6 +153,7 @@ describe('CreateErc20ApproveTx', () => { test('should validate tx', () => { const tx = new CreateErc20ApproveTx({ blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, token: wethTokenData, totalBalance: new Wei(0), totalTokenBalance: wethToken.getAmount(0), @@ -168,6 +176,7 @@ describe('CreateErc20ApproveTx', () => { approveBy: '0xe62c6f33a58d7f49e6b782aab931450e53d01f12', allowFor: '0x3f54eb67fea225d0a21263f1a7cb456e342cb1e8', blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_APPROVE }, token: wethTokenData, totalBalance: new Wei(0), totalTokenBalance: wethToken.getAmount(0), diff --git a/packages/core/src/workflow/CreateErc20ApproveTx.ts b/packages/core/src/transaction/workflow/create-tx/CreateErc20ApproveTx.ts similarity index 93% rename from packages/core/src/workflow/CreateErc20ApproveTx.ts rename to packages/core/src/transaction/workflow/create-tx/CreateErc20ApproveTx.ts index 4b535faf4..c98205414 100644 --- a/packages/core/src/workflow/CreateErc20ApproveTx.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateErc20ApproveTx.ts @@ -7,10 +7,10 @@ import { TokenData, amountFactory, tokenAbi, -} from '../blockchains'; -import { Contract } from '../Contract'; -import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransaction, EthereumTransactionType } from '../transaction/ethereum'; -import { ValidationResult } from './types'; +} from '../../../blockchains'; +import { Contract } from '../../../Contract'; +import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransaction, EthereumTransactionType } from '../../ethereum'; +import { CommonTx, TxMetaType, ValidationResult } from '../types'; export enum ApproveTarget { MANUAL, @@ -18,7 +18,7 @@ export enum ApproveTarget { INFINITE, } -export interface Erc20ApproveTxDetails { +export interface Erc20ApproveTxDetails extends CommonTx { allowFor?: string; amount?: TokenAmount; approveBy?: string; @@ -35,6 +35,8 @@ export interface Erc20ApproveTxDetails { } export class CreateErc20ApproveTx implements Erc20ApproveTxDetails { + meta = { type: TxMetaType.ETHEREUM_APPROVE }; + private _amount: TokenAmount; private _target: ApproveTarget; private _token: Token; @@ -154,6 +156,7 @@ export class CreateErc20ApproveTx implements Erc20ApproveTxDetails { gas: this.gas, gasPrice: this.gasPrice, maxGasPrice: this.maxGasPrice, + meta: this.meta, priorityGasPrice: this.priorityGasPrice, target: this._target, totalBalance: this.totalBalance, diff --git a/packages/core/src/workflow/CreateErc20Tx.spec.ts b/packages/core/src/transaction/workflow/create-tx/CreateErc20Tx.spec.ts similarity index 98% rename from packages/core/src/workflow/CreateErc20Tx.spec.ts rename to packages/core/src/transaction/workflow/create-tx/CreateErc20Tx.spec.ts index 58ee33b09..be394ee17 100644 --- a/packages/core/src/workflow/CreateErc20Tx.spec.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateErc20Tx.spec.ts @@ -1,7 +1,7 @@ import { Wei } from '@emeraldpay/bigamount-crypto'; -import { BlockchainCode, TokenRegistry } from '../blockchains'; +import { BlockchainCode, TokenRegistry } from '../../../blockchains'; +import { EthereumPlainTx, TxMetaType, TxTarget, ValidationResult } from '../types'; import { CreateERC20Tx } from './CreateErc20Tx'; -import { EthereumPlainTx, TxTarget, ValidationResult } from './types'; describe('CreateErc20Tx', () => { const tokenRegistry = new TokenRegistry([ @@ -249,6 +249,7 @@ describe('CreateErc20Tx', () => { from: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', gas: 42011, maxGasPrice: '10007000000/WEI', + meta: { type: TxMetaType.ETHEREUM_TRANSFER }, priorityGasPrice: '5007000000/WEI', target: 1, to: '0x2af2d8be60ca2c0f21497bb57b0037d44b8df3bd', diff --git a/packages/core/src/workflow/CreateErc20Tx.ts b/packages/core/src/transaction/workflow/create-tx/CreateErc20Tx.ts similarity index 94% rename from packages/core/src/workflow/CreateErc20Tx.ts rename to packages/core/src/transaction/workflow/create-tx/CreateErc20Tx.ts index 3b839aaf6..4418c3a97 100644 --- a/packages/core/src/workflow/CreateErc20Tx.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateErc20Tx.ts @@ -1,12 +1,13 @@ import { BigAmount } from '@emeraldpay/bigamount'; import { WeiAny } from '@emeraldpay/bigamount-crypto'; import BigNumber from 'bignumber.js'; -import { BlockchainCode, Token, TokenRegistry, amountDecoder, amountFactory, tokenAbi } from '../blockchains'; -import { Contract } from '../Contract'; -import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransaction, EthereumTransactionType } from '../transaction/ethereum'; -import { EthereumPlainTx, EthereumTx, TxTarget, ValidationResult } from './types'; +import { BlockchainCode, Token, TokenRegistry, amountDecoder, amountFactory, tokenAbi } from '../../../blockchains'; +import { Contract } from '../../../Contract'; +import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransaction, EthereumTransactionType } from '../../ethereum'; +import { CommonTx, EthereumPlainTx, TxMetaType, TxTarget, ValidationResult } from '../types'; +import { EthereumTx } from './types'; -export interface ERC20TxDetails { +export interface ERC20TxDetails extends CommonTx { amount: BigAmount; asset: string; blockchain: BlockchainCode; @@ -26,6 +27,7 @@ export interface ERC20TxDetails { const TxDefaults: Omit = { from: undefined, gas: DEFAULT_GAS_LIMIT_ERC20, + meta: { type: TxMetaType.ETHEREUM_TRANSFER }, target: TxTarget.MANUAL, to: undefined, }; @@ -43,6 +45,7 @@ function fromPlainDetails(tokenRegistry: TokenRegistry, plain: EthereumPlainTx): gas: plain.gas, gasPrice: plain.gasPrice == null ? undefined : decoder(plain.gasPrice), maxGasPrice: plain.maxGasPrice == null ? undefined : decoder(plain.maxGasPrice), + meta: plain.meta, priorityGasPrice: plain.priorityGasPrice == null ? undefined : decoder(plain.priorityGasPrice), target: plain.target, to: plain.to, @@ -62,6 +65,7 @@ function toPlainDetails(tx: ERC20TxDetails): EthereumPlainTx { gas: tx.gas, gasPrice: tx.gasPrice?.encode(), maxGasPrice: tx.maxGasPrice?.encode(), + meta: tx.meta, priorityGasPrice: tx.priorityGasPrice?.encode(), target: tx.target.valueOf(), to: tx.to, @@ -73,6 +77,8 @@ function toPlainDetails(tx: ERC20TxDetails): EthereumPlainTx { } export class CreateERC20Tx implements ERC20TxDetails, EthereumTx { + meta = { type: TxMetaType.ETHEREUM_TRANSFER }; + public amount: BigAmount; public blockchain: BlockchainCode; public asset: string; diff --git a/packages/core/src/workflow/CreateErc20WrappedTx.spec.ts b/packages/core/src/transaction/workflow/create-tx/CreateErc20WrappedTx.spec.ts similarity index 92% rename from packages/core/src/workflow/CreateErc20WrappedTx.spec.ts rename to packages/core/src/transaction/workflow/create-tx/CreateErc20WrappedTx.spec.ts index 4296a01ff..45371cacf 100644 --- a/packages/core/src/workflow/CreateErc20WrappedTx.spec.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateErc20WrappedTx.spec.ts @@ -1,8 +1,8 @@ import { Wei } from '@emeraldpay/bigamount-crypto'; -import { BlockchainCode, TokenData, TokenRegistry, amountFactory } from '../blockchains'; -import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransactionType } from '../transaction/ethereum'; +import { BlockchainCode, TokenData, TokenRegistry, amountFactory } from '../../../blockchains'; +import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransactionType } from '../../ethereum'; +import { TxMetaType, TxTarget } from '../types'; import { CreateErc20WrappedTx } from './CreateErc20WrappedTx'; -import { TxTarget } from './types'; describe('CreateErc20WrappedTx', () => { const tokenData: TokenData = { @@ -23,6 +23,7 @@ describe('CreateErc20WrappedTx', () => { token: tokenData, address: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_WRAP }, totalBalance: new Wei(0), totalTokenBalance: token.getAmount(0), type: EthereumTransactionType.LEGACY, @@ -39,6 +40,7 @@ describe('CreateErc20WrappedTx', () => { token: tokenData, address: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_WRAP }, totalBalance: new Wei(0), totalTokenBalance: token.getAmount(0), type: EthereumTransactionType.EIP1559, @@ -58,6 +60,7 @@ describe('CreateErc20WrappedTx', () => { totalBalance, address: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_WRAP }, token: tokenData, totalTokenBalance: token.getAmount(0), type: EthereumTransactionType.EIP1559, @@ -81,6 +84,7 @@ describe('CreateErc20WrappedTx', () => { address: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', amount: token.getAmount(0), blockchain: BlockchainCode.Goerli, + meta: { type: TxMetaType.ETHEREUM_WRAP }, totalBalance: new Wei(0), totalTokenBalance: token.getAmount(1), type: EthereumTransactionType.EIP1559, diff --git a/packages/core/src/workflow/CreateErc20WrappedTx.ts b/packages/core/src/transaction/workflow/create-tx/CreateErc20WrappedTx.ts similarity index 89% rename from packages/core/src/workflow/CreateErc20WrappedTx.ts rename to packages/core/src/transaction/workflow/create-tx/CreateErc20WrappedTx.ts index 83007d9ab..c2f24ee04 100644 --- a/packages/core/src/workflow/CreateErc20WrappedTx.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateErc20WrappedTx.ts @@ -1,10 +1,10 @@ import { BigAmount } from '@emeraldpay/bigamount'; -import { BlockchainCode, Token, TokenAmount, TokenData, amountFactory, wrapTokenAbi } from '../blockchains'; -import { Contract } from '../Contract'; -import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransaction, EthereumTransactionType } from '../transaction/ethereum'; -import { TxTarget, ValidationResult } from './types'; +import { BlockchainCode, Token, TokenAmount, TokenData, amountFactory, wrapTokenAbi } from '../../../blockchains'; +import { Contract } from '../../../Contract'; +import { DEFAULT_GAS_LIMIT_ERC20, EthereumTransaction, EthereumTransactionType } from '../../ethereum'; +import { CommonTx, TxMetaType, TxTarget, ValidationResult } from '../types'; -export interface Erc20WrappedTxDetails { +export interface Erc20WrappedTxDetails extends CommonTx { address?: string; amount?: BigAmount; blockchain: BlockchainCode; @@ -19,7 +19,9 @@ export interface Erc20WrappedTxDetails { type: EthereumTransactionType; } -export class CreateErc20WrappedTx { +export class CreateErc20WrappedTx implements Erc20WrappedTxDetails { + meta = { type: TxMetaType.ETHEREUM_TRANSFER }; + address?: string; amount: BigAmount; blockchain: BlockchainCode; @@ -32,7 +34,8 @@ export class CreateErc20WrappedTx { totalTokenBalance: TokenAmount; type: EthereumTransactionType; - private readonly token: Token; + readonly token: Token; + private readonly zeroAmount: BigAmount; private tokenContract = new Contract(wrapTokenAbi); @@ -92,6 +95,7 @@ export class CreateErc20WrappedTx { gas: this.gas, gasPrice: this.gasPrice, maxGasPrice: this.maxGasPrice, + meta: this.meta, priorityGasPrice: this.priorityGasPrice, target: this.target, token: this.token.toPlain(), diff --git a/packages/core/src/workflow/CreateEthereumTx.spec.ts b/packages/core/src/transaction/workflow/create-tx/CreateEthereumTx.spec.ts similarity index 98% rename from packages/core/src/workflow/CreateEthereumTx.spec.ts rename to packages/core/src/transaction/workflow/create-tx/CreateEthereumTx.spec.ts index cb49f359c..41fb78fca 100644 --- a/packages/core/src/workflow/CreateEthereumTx.spec.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateEthereumTx.spec.ts @@ -1,8 +1,8 @@ import { Wei } from '@emeraldpay/bigamount-crypto'; import BigNumber from 'bignumber.js'; -import { BlockchainCode } from '../blockchains'; +import { BlockchainCode } from '../../../blockchains'; +import { EthereumPlainTx, TxMetaType, TxTarget, ValidationResult } from '../types'; import { CreateEthereumTx } from './CreateEthereumTx'; -import { EthereumPlainTx, TxTarget, ValidationResult } from './types'; describe('CreateEthereumTx', () => { it('creates tx', () => { @@ -305,6 +305,7 @@ describe('CreateEthereumTx', () => { from: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', gas: 42011, maxGasPrice: '10007000000/WEI', + meta: { type: TxMetaType.ETHEREUM_TRANSFER }, priorityGasPrice: '5007000000/WEI', target: 1, to: '0x2af2d8be60ca2c0f21497bb57b0037d44b8df3bd', @@ -332,6 +333,7 @@ describe('CreateEthereumTx', () => { from: '0x2C80BfA8E69fdd12853Fd010A520B29cfa01E2cD', gas: 42011, maxGasPrice: '10007000000/WEI', + meta: { type: TxMetaType.ETHEREUM_TRANSFER }, priorityGasPrice: '5007000000/WEI', target: 0, to: '0x2af2d8be60ca2c0f21497bb57b0037d44b8df3bd', diff --git a/packages/core/src/workflow/CreateEthereumTx.ts b/packages/core/src/transaction/workflow/create-tx/CreateEthereumTx.ts similarity index 94% rename from packages/core/src/workflow/CreateEthereumTx.ts rename to packages/core/src/transaction/workflow/create-tx/CreateEthereumTx.ts index b3dec3b6c..f0826fdd0 100644 --- a/packages/core/src/workflow/CreateEthereumTx.ts +++ b/packages/core/src/transaction/workflow/create-tx/CreateEthereumTx.ts @@ -1,10 +1,11 @@ import { WeiAny } from '@emeraldpay/bigamount-crypto'; import BigNumber from 'bignumber.js'; -import { BlockchainCode, amountDecoder, amountFactory } from '../blockchains'; -import { DEFAULT_GAS_LIMIT, EthereumTransaction, EthereumTransactionType } from '../transaction/ethereum'; -import { EthereumPlainTx, EthereumTx, TxTarget, ValidationResult } from './types'; +import { BlockchainCode, amountDecoder, amountFactory } from '../../../blockchains'; +import { DEFAULT_GAS_LIMIT, EthereumTransaction, EthereumTransactionType } from '../../ethereum'; +import { CommonTx, EthereumPlainTx, TxMetaType, TxTarget, ValidationResult } from '../types'; +import { EthereumTx } from './types'; -export interface TxDetails { +export interface TxDetails extends CommonTx { amount: WeiAny; blockchain: BlockchainCode; from?: string; @@ -20,6 +21,7 @@ export interface TxDetails { const TxDefaults: Omit = { gas: DEFAULT_GAS_LIMIT, + meta: { type: TxMetaType.ETHEREUM_TRANSFER }, target: TxTarget.MANUAL, }; @@ -33,6 +35,7 @@ function fromPlainDetails(plain: EthereumPlainTx): TxDetails { gas: plain.gas, gasPrice: plain.gasPrice == null ? undefined : (decoder(plain.gasPrice) as WeiAny), maxGasPrice: plain.maxGasPrice == null ? undefined : decoder(plain.maxGasPrice), + meta: plain.meta, priorityGasPrice: plain.priorityGasPrice == null ? undefined : decoder(plain.priorityGasPrice), target: plain.target, to: plain.to, @@ -50,6 +53,7 @@ function toPlainDetails(tx: TxDetails): EthereumPlainTx { gas: tx.gas, gasPrice: tx.gasPrice?.encode(), maxGasPrice: tx.maxGasPrice?.encode(), + meta: tx.meta, priorityGasPrice: tx.priorityGasPrice?.encode(), target: tx.target.valueOf(), to: tx.to, @@ -59,6 +63,8 @@ function toPlainDetails(tx: TxDetails): EthereumPlainTx { } export class CreateEthereumTx implements TxDetails, EthereumTx { + meta = { type: TxMetaType.ETHEREUM_TRANSFER }; + public amount: WeiAny; public blockchain: BlockchainCode; public from?: string; diff --git a/packages/core/src/transaction/workflow/create-tx/index.ts b/packages/core/src/transaction/workflow/create-tx/index.ts new file mode 100644 index 000000000..7dd2a07b5 --- /dev/null +++ b/packages/core/src/transaction/workflow/create-tx/index.ts @@ -0,0 +1,9 @@ +/* eslint sort-exports/sort-exports: error */ + +export { ApproveTarget, CreateErc20ApproveTx, Erc20ApproveTxDetails } from './CreateErc20ApproveTx'; +export { BitcoinTx, BitcoinTxDetails, BitcoinTxOrigin, CreateBitcoinTx } from './CreateBitcoinTx'; +export { CreateBitcoinCancelTx } from './CreateBitcoinCancelTx'; +export { CreateBitcoinSpeedUpTx } from './CreateBitcoinSpeedUpTx'; +export { CreateERC20Tx, ERC20TxDetails } from './CreateErc20Tx'; +export { CreateErc20WrappedTx, Erc20WrappedTxDetails } from './CreateErc20WrappedTx'; +export { CreateEthereumTx, TxDetails } from './CreateEthereumTx'; diff --git a/packages/core/src/transaction/workflow/create-tx/types.ts b/packages/core/src/transaction/workflow/create-tx/types.ts new file mode 100644 index 000000000..878905534 --- /dev/null +++ b/packages/core/src/transaction/workflow/create-tx/types.ts @@ -0,0 +1,110 @@ +import { BigAmount } from '@emeraldpay/bigamount'; +import BigNumber from 'bignumber.js'; +import { Blockchains, TokenRegistry } from '../../../blockchains'; +import { AnyPlainTx, BitcoinPlainTx, CommonTx, EthereumPlainTx, TxMetaType, isBitcoinPlainTx } from '../types'; +import { CreateBitcoinCancelTx } from './CreateBitcoinCancelTx'; +import { CreateBitcoinSpeedUpTx } from './CreateBitcoinSpeedUpTx'; +import { BitcoinTx, BitcoinTxOrigin, CreateBitcoinTx } from './CreateBitcoinTx'; +import { CreateERC20Tx } from './CreateErc20Tx'; +import { CreateEthereumTx } from './CreateEthereumTx'; + +// TODO Make unified interface for all create tx classes +export interface EthereumTx extends CommonTx { + getAmount(): T; + getAsset(): string; + getTotalBalance(): T; + setAmount(amount: T | BigNumber, tokenSymbol?: string): void; + setTotalBalance(total: T): void; +} + +export type AnyBitcoinCreateTx = CreateBitcoinTx | CreateBitcoinCancelTx; +export type AnyEthereumCreateTx = CreateEthereumTx | CreateERC20Tx; +export type AnyCreateTx = AnyBitcoinCreateTx | AnyEthereumCreateTx; + +type BitcoinTxFactory = (...params: ConstructorParameters) => BitcoinTx; + +export function bitcoinTxFactory(metaType: TxMetaType): BitcoinTxFactory { + return (...params) => { + switch (metaType) { + case TxMetaType.BITCOIN_CANCEL: + return new CreateBitcoinCancelTx(...params); + case TxMetaType.BITCOIN_SPEED_UP: + return new CreateBitcoinSpeedUpTx(...params); + case TxMetaType.BITCOIN_TRANSFER: + return new CreateBitcoinTx(...params); + default: + throw new Error(); + } + }; +} + +const bitcoinTxMetaTypes: Readonly = [ + TxMetaType.BITCOIN_CANCEL, + TxMetaType.BITCOIN_SPEED_UP, + TxMetaType.BITCOIN_TRANSFER, +]; + +const ethereumTxMetaTypes: Readonly = [ + TxMetaType.ETHEREUM_APPROVE, + TxMetaType.ETHEREUM_CANCEL, + TxMetaType.ETHEREUM_RECOVERY, + TxMetaType.ETHEREUM_SPEED_UP, + TxMetaType.ETHEREUM_TRANSFER, + TxMetaType.ETHEREUM_WRAP, +]; + +export function isAnyBitcoinCreateTx(createTx: AnyCreateTx): createTx is CreateBitcoinTx { + return bitcoinTxMetaTypes.includes(createTx.meta.type); +} + +export function isBitcoinCancelCreateTx(createTx: AnyCreateTx): createTx is CreateBitcoinCancelTx { + return createTx.meta.type === TxMetaType.BITCOIN_CANCEL; +} + +export function isBitcoinSpeedUpCreateTx(createTx: AnyCreateTx): createTx is CreateBitcoinCancelTx { + return createTx.meta.type === TxMetaType.BITCOIN_SPEED_UP; +} + +export function isAnyEthereumCreateTx(createTx: AnyCreateTx): createTx is AnyEthereumCreateTx { + return ethereumTxMetaTypes.includes(createTx.meta.type); +} + +export function isEthereumCreateTx(createTx: AnyCreateTx): createTx is CreateEthereumTx { + return isAnyEthereumCreateTx(createTx) && createTx.getAsset() === Blockchains[createTx.blockchain].params.coinTicker; +} + +export function isErc20CreateTx(createTx: AnyCreateTx, tokenRegistry: TokenRegistry): createTx is CreateERC20Tx { + return isAnyEthereumCreateTx(createTx) && tokenRegistry.hasAddress(createTx.blockchain, createTx.getAsset()); +} + +export function fromBitcoinPlainTx(transaction: BitcoinPlainTx, origin: BitcoinTxOrigin): AnyBitcoinCreateTx { + switch (transaction.meta.type) { + case TxMetaType.BITCOIN_CANCEL: + return CreateBitcoinCancelTx.fromPlain(origin, transaction); + case TxMetaType.BITCOIN_SPEED_UP: + return CreateBitcoinSpeedUpTx.fromPlain(origin, transaction); + default: + return CreateBitcoinTx.fromPlain(origin, transaction); + } +} + +export function fromEthereumPlainTx(transaction: EthereumPlainTx, tokenRegistry: TokenRegistry): AnyEthereumCreateTx { + // TODO Use meta to detect transaction type + if (tokenRegistry.hasAddress(transaction.blockchain, transaction.asset)) { + return CreateERC20Tx.fromPlain(tokenRegistry, transaction); + } + + return CreateEthereumTx.fromPlain(transaction); +} + +export function fromPlainTx( + transaction: AnyPlainTx, + origin: BitcoinTxOrigin, + tokenRegistry: TokenRegistry, +): AnyCreateTx { + if (isBitcoinPlainTx(transaction)) { + return fromBitcoinPlainTx(transaction, origin); + } + + return fromEthereumPlainTx(transaction, tokenRegistry); +} diff --git a/packages/core/src/transaction/workflow/index.ts b/packages/core/src/transaction/workflow/index.ts new file mode 100644 index 000000000..b1ccf400a --- /dev/null +++ b/packages/core/src/transaction/workflow/index.ts @@ -0,0 +1,56 @@ +/* eslint sort-exports/sort-exports: error */ + +export { + AnyBitcoinCreateTx, + AnyCreateTx, + AnyEthereumCreateTx, + EthereumTx, + bitcoinTxFactory, + fromBitcoinPlainTx, + fromEthereumPlainTx, + fromPlainTx, + isAnyBitcoinCreateTx, + isAnyEthereumCreateTx, + isBitcoinCancelCreateTx, + isBitcoinSpeedUpCreateTx, + isErc20CreateTx, + isEthereumCreateTx, +} from './create-tx/types'; + +export { + AnyPlainTx, + BitcoinFeeRange, + BitcoinPlainTx, + EthereumFeeRange, + EthereumPlainTx, + FeeRange, + TxMeta, + TxMetaType, + TxTarget, + ValidationResult, + isBitcoinFeeRange, + isBitcoinPlainTx, + isEthereumFeeRange, + isEthereumPlainTx, +} from './types'; + +export { + ApproveTarget, + BitcoinTx, + BitcoinTxDetails, + BitcoinTxOrigin, + CreateBitcoinCancelTx, + CreateBitcoinSpeedUpTx, + CreateBitcoinTx, + CreateERC20Tx, + CreateErc20ApproveTx, + CreateErc20WrappedTx, + CreateEthereumTx, + ERC20TxDetails, + Erc20ApproveTxDetails, + Erc20WrappedTxDetails, + TxDetails, +} from './create-tx'; + +export { TxBuilder } from './TxBuilder'; +export { TxSigner } from './TxSigner'; diff --git a/packages/core/src/transaction/workflow/types.ts b/packages/core/src/transaction/workflow/types.ts new file mode 100644 index 000000000..f847fa36c --- /dev/null +++ b/packages/core/src/transaction/workflow/types.ts @@ -0,0 +1,102 @@ +import { WeiAny } from '@emeraldpay/bigamount-crypto'; +import { BlockchainCode, InputUtxo, isBitcoin, isEthereum } from '../../blockchains'; + +export enum TxTarget { + MANUAL, + SEND_ALL, +} + +export enum TxMetaType { + BITCOIN_CANCEL, + BITCOIN_SPEED_UP, + BITCOIN_TRANSFER, + ETHEREUM_APPROVE, + ETHEREUM_CANCEL, + ETHEREUM_RECOVERY, + ETHEREUM_SPEED_UP, + ETHEREUM_TRANSFER, + ETHEREUM_WRAP, +} + +export enum ValidationResult { + INSUFFICIENT_FEE_PRICE, + INSUFFICIENT_FUNDS, + INSUFFICIENT_TOKEN_FUNDS, + NO_CHANGE_ADDRESS, + NO_AMOUNT, + NO_FROM, + NO_TO, + OK, +} + +export interface TxMeta { + type: TxMetaType; +} + +export interface CommonTx { + meta: TxMeta; +} + +export interface BitcoinPlainTx extends CommonTx { + amount: string; + blockchain: BlockchainCode; + changeAddress?: string; + target: number; + to?: string; + vkbPrice: number; + utxo: InputUtxo[]; +} + +export interface EthereumPlainTx extends CommonTx { + amount: string; + asset: string; + blockchain: BlockchainCode; + from?: string; + gas: number; + gasPrice?: string; + maxGasPrice?: string; + priorityGasPrice?: string; + target: number; + to?: string; + totalBalance?: string; + totalTokenBalance?: string; + transferFrom?: string; + type: string; +} + +export type AnyPlainTx = BitcoinPlainTx | EthereumPlainTx; + +export function isBitcoinPlainTx(transaction: BitcoinPlainTx | EthereumPlainTx): transaction is BitcoinPlainTx { + return isBitcoin(transaction.blockchain); +} + +export function isEthereumPlainTx(transaction: BitcoinPlainTx | EthereumPlainTx): transaction is EthereumPlainTx { + return isEthereum(transaction.blockchain); +} + +export interface BitcoinFeeRange { + std: number; + min: number; + max: number; +} + +export interface EthereumFeeRange { + stdMaxGasPrice: T; + lowMaxGasPrice: T; + highMaxGasPrice: T; + stdPriorityGasPrice: T; + lowPriorityGasPrice: T; + highPriorityGasPrice: T; +} + +export type FeeRange = BitcoinFeeRange | EthereumFeeRange; + +export function isBitcoinFeeRange(feeRange: unknown): feeRange is BitcoinFeeRange { + return feeRange != null && typeof feeRange === 'object' && 'std' in feeRange && typeof feeRange.std === 'number'; +} + +export function isEthereumFeeRange(feeRange: unknown): feeRange is EthereumFeeRange { + return ( + feeRange != null && typeof feeRange === 'object' && 'stdMaxGasPrice' in feeRange && feeRange.stdMaxGasPrice != null + ); +} diff --git a/packages/core/src/workflow/CreateBitcoinCancelTx.ts b/packages/core/src/workflow/CreateBitcoinCancelTx.ts deleted file mode 100644 index 6ebcd23dd..000000000 --- a/packages/core/src/workflow/CreateBitcoinCancelTx.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { BitcoinTxOutput, CreateBitcoinTx } from './CreateBitcoinTx'; -import { ValidationResult } from './types'; - -export class CreateBitcoinCancelTx extends CreateBitcoinTx { - private originalFeePrice: number | undefined; - - get outputs(): BitcoinTxOutput[] { - let totalChange = this.change; - - if (this.transaction.amount != null) { - totalChange = totalChange.plus(this.transaction.amount); - } - - return [ - { - address: this.changeAddress ?? '', - amount: totalChange.number.toNumber(), - entryId: this.entryId, - }, - ]; - } - - set feePrice(price: number) { - if (this.originalFeePrice == null) { - this.originalFeePrice = price; - } - - super.feePrice = price; - } - - validate(): ValidationResult { - if (this.originalFeePrice != null && this.originalFeePrice >= this.feePrice) { - return ValidationResult.INSUFFICIENT_FEE_PRICE; - } - - if (this.changeAddress == null) { - return ValidationResult.NO_CHANGE_ADDRESS; - } - - if (this.tx.from.length === 0) { - return ValidationResult.NO_FROM; - } - - return ValidationResult.OK; - } -} diff --git a/packages/core/src/workflow/CreateTxConverter.ts b/packages/core/src/workflow/CreateTxConverter.ts deleted file mode 100644 index 159ac049f..000000000 --- a/packages/core/src/workflow/CreateTxConverter.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { BigAmount } from '@emeraldpay/bigamount'; -import { WeiAny } from '@emeraldpay/bigamount-crypto'; -import { BitcoinEntry, WalletEntry, isBitcoinEntry, isEthereumEntry } from '@emeraldpay/emerald-vault-core'; -import { - Blockchains, - InputUtxo, - TokenRegistry, - amountFactory, - blockchainIdToCode, - isBitcoin, - isEthereum, -} from '../blockchains'; -import { EthereumTransactionType } from '../transaction/ethereum'; -import { BitcoinTxOrigin, CreateBitcoinTx } from './CreateBitcoinTx'; -import { CreateERC20Tx } from './CreateErc20Tx'; -import { CreateEthereumTx } from './CreateEthereumTx'; -import { - AnyCreateTx, - AnyEthereumCreateTx, - AnyPlainTx, - BitcoinPlainTx, - EthereumPlainTx, - TxTarget, - isBitcoinPlainTx, - isErc20CreateTx, -} from './types'; - -export interface BitcoinFeeRange { - std: number; - min: number; - max: number; -} - -export interface EthereumFeeRange { - stdMaxGasPrice: T; - lowMaxGasPrice: T; - highMaxGasPrice: T; - stdPriorityGasPrice: T; - lowPriorityGasPrice: T; - highPriorityGasPrice: T; -} - -export type FeeRange = BitcoinFeeRange | EthereumFeeRange; - -interface ConverterOrigin { - asset: string; - changeAddress?: string; - entry: WalletEntry; - feeRange: FeeRange; - ownerAddress?: string; - transaction?: BitcoinPlainTx | EthereumPlainTx; -} - -interface DataProvider { - getBalance(entry: WalletEntry, asset: string, ownerAddress?: string): BigAmount; - getUtxo(entry: BitcoinEntry): InputUtxo[]; -} - -export class CreateTxConverter implements ConverterOrigin { - readonly asset: string; - readonly changeAddress?: string; - readonly entry: WalletEntry; - readonly feeRange: FeeRange; - readonly ownerAddress?: string; - readonly transaction?: BitcoinPlainTx | EthereumPlainTx; - - private readonly dataProvider: DataProvider; - private readonly tokenRegistry: TokenRegistry; - - constructor(origin: ConverterOrigin, dataProvider: DataProvider, tokenRegistry: TokenRegistry) { - const { asset, changeAddress, entry, feeRange, ownerAddress, transaction } = origin; - - this.asset = asset; - this.changeAddress = changeAddress; - this.entry = entry; - this.feeRange = feeRange; - this.ownerAddress = ownerAddress; - this.transaction = transaction; - - this.dataProvider = dataProvider; - this.tokenRegistry = tokenRegistry; - } - - static fromPlainTx(transaction: AnyPlainTx, origin: BitcoinTxOrigin, tokenRegistry: TokenRegistry): AnyCreateTx { - if (isBitcoinPlainTx(transaction)) { - return CreateTxConverter.fromBitcoinPlainTx(origin, transaction); - } - - return CreateTxConverter.fromEthereumPlainTx(transaction, tokenRegistry); - } - - static fromBitcoinPlainTx(origin: BitcoinTxOrigin, transaction: BitcoinPlainTx): CreateBitcoinTx { - return CreateBitcoinTx.fromPlain(origin, transaction); - } - - static fromEthereumPlainTx(transaction: EthereumPlainTx, tokenRegistry: TokenRegistry): AnyEthereumCreateTx { - if (tokenRegistry.hasAddress(transaction.blockchain, transaction.asset)) { - return CreateERC20Tx.fromPlain(tokenRegistry, transaction); - } - - return CreateEthereumTx.fromPlain(transaction); - } - - static isBitcoinFeeRange(feeRange: unknown): feeRange is BitcoinFeeRange { - return feeRange != null && typeof feeRange === 'object' && 'std' in feeRange && typeof feeRange.std === 'number'; - } - - static isEthereumFeeRange(feeRange: unknown): feeRange is EthereumFeeRange { - return ( - feeRange != null && - typeof feeRange === 'object' && - 'stdMaxGasPrice' in feeRange && - feeRange.stdMaxGasPrice != null - ); - } - - get createTx(): AnyCreateTx { - const { asset, changeAddress, entry, feeRange, ownerAddress, tokenRegistry, transaction } = this; - const { getBalance, getUtxo } = this.dataProvider; - - const blockchain = blockchainIdToCode(entry.blockchain); - const { coinTicker, eip1559: supportEip1559 = false } = Blockchains[blockchain].params; - - let createTx: AnyCreateTx; - - if ( - transaction == null || - (isBitcoin(blockchain) && isEthereum(transaction.blockchain)) || - (isEthereum(blockchain) && isBitcoin(transaction.blockchain)) - ) { - if (isBitcoinEntry(entry)) { - createTx = new CreateBitcoinTx({ - blockchain, - changeAddress, - entryId: entry.id, - utxo: getUtxo(entry), - }); - - if (CreateTxConverter.isBitcoinFeeRange(feeRange)) { - createTx.feePrice = feeRange.std; - } - } else { - if (tokenRegistry.hasAddress(blockchain, asset)) { - createTx = new CreateERC20Tx(tokenRegistry, asset, blockchain); - createTx.totalBalance = getBalance(entry, coinTicker) as WeiAny; - createTx.totalTokenBalance = getBalance(entry, asset, ownerAddress); - createTx.transferFrom = ownerAddress; - } else { - createTx = new CreateEthereumTx(null, blockchain); - createTx.totalBalance = getBalance(entry, asset) as WeiAny; - } - - createTx.from = entry.address?.value; - - if (CreateTxConverter.isEthereumFeeRange(feeRange)) { - createTx.gasPrice = feeRange.stdMaxGasPrice; - createTx.maxGasPrice = feeRange.stdMaxGasPrice; - createTx.priorityGasPrice = feeRange.stdPriorityGasPrice; - } - - createTx.type = supportEip1559 ? EthereumTransactionType.EIP1559 : EthereumTransactionType.LEGACY; - } - } else { - if (isBitcoinPlainTx(transaction)) { - if (isEthereumEntry(entry)) { - throw new Error('Ethereum entry provided for Bitcoin transaction'); - } - - createTx = CreateTxConverter.fromBitcoinPlainTx( - { - blockchain, - changeAddress, - entryId: entry.id, - utxo: getUtxo(entry), - }, - transaction, - ); - } else { - if (isBitcoinEntry(entry)) { - throw new Error('Bitcoin entry provided for Ethereum transaction'); - } - - createTx = CreateTxConverter.fromEthereumPlainTx(transaction, tokenRegistry); - - if (asset !== createTx.getAsset() || blockchain !== createTx.blockchain) { - const type = supportEip1559 ? createTx.type : EthereumTransactionType.LEGACY; - - let newCreateTx: AnyEthereumCreateTx; - - if (tokenRegistry.hasAddress(blockchain, asset)) { - newCreateTx = new CreateERC20Tx(tokenRegistry, asset, blockchain, type); - newCreateTx.totalBalance = getBalance(entry, coinTicker) as WeiAny; - newCreateTx.totalTokenBalance = getBalance(entry, asset, newCreateTx.transferFrom); - - newCreateTx.transferFrom = isErc20CreateTx(createTx, tokenRegistry) - ? createTx.transferFrom ?? ownerAddress - : ownerAddress; - } else { - newCreateTx = new CreateEthereumTx(null, blockchain, type); - newCreateTx.totalBalance = getBalance(entry, asset) as WeiAny; - } - - newCreateTx.from = entry.address?.value; - newCreateTx.to = createTx.to; - - if (blockchain === createTx.blockchain && (createTx.gasPrice?.isPositive() ?? false)) { - newCreateTx.gasPrice = createTx.gasPrice; - newCreateTx.maxGasPrice = createTx.maxGasPrice; - newCreateTx.priorityGasPrice = createTx.priorityGasPrice; - } else if (CreateTxConverter.isEthereumFeeRange(feeRange)) { - newCreateTx.gasPrice = feeRange.stdMaxGasPrice; - newCreateTx.maxGasPrice = feeRange.stdMaxGasPrice; - newCreateTx.priorityGasPrice = feeRange.stdPriorityGasPrice; - } - - if (createTx.target === TxTarget.SEND_ALL) { - newCreateTx.target = TxTarget.SEND_ALL; - - if (!newCreateTx.rebalance()) { - newCreateTx.target = TxTarget.MANUAL; - - newCreateTx.amount = isErc20CreateTx(newCreateTx, tokenRegistry) - ? tokenRegistry.byAddress(blockchain, newCreateTx.getAsset()).getAmount(0) - : amountFactory(blockchain)(0); - } - } - - return newCreateTx; - } - - const gasPrice = createTx.gasPrice ?? createTx.maxGasPrice; - - if ((gasPrice?.isZero() ?? true) && CreateTxConverter.isEthereumFeeRange(feeRange)) { - createTx.gasPrice = feeRange.stdMaxGasPrice; - createTx.maxGasPrice = feeRange.stdMaxGasPrice; - createTx.priorityGasPrice = feeRange.stdPriorityGasPrice; - } - - if ( - transaction.from !== entry.address?.value || - transaction.transferFrom !== ownerAddress || - (transaction.transferFrom == null && ownerAddress != null) - ) { - createTx.from = entry.address?.value; - - if (isErc20CreateTx(createTx, tokenRegistry)) { - createTx.transferFrom = ownerAddress; - - createTx.totalBalance = getBalance(entry, coinTicker) as WeiAny; - createTx.totalTokenBalance = getBalance(entry, asset, createTx.transferFrom); - } else { - createTx.totalBalance = getBalance(entry, asset) as WeiAny; - } - - if (createTx.target === TxTarget.SEND_ALL && !createTx.rebalance()) { - createTx.target = TxTarget.MANUAL; - - createTx.amount = isErc20CreateTx(createTx, tokenRegistry) - ? tokenRegistry.byAddress(blockchain, createTx.getAsset()).getAmount(0) - : amountFactory(blockchain)(0); - } - } - } - } - - return createTx; - } -} diff --git a/packages/core/src/workflow/index.ts b/packages/core/src/workflow/index.ts deleted file mode 100644 index 997e32cbb..000000000 --- a/packages/core/src/workflow/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint sort-exports/sort-exports: error */ - -export { - AnyCreateTx, - AnyEthereumCreateTx, - AnyPlainTx, - BitcoinPlainTx, - EthereumPlainTx, - TxTarget, - ValidationResult, - isAnyEthereumCreateTx, - isBitcoinCreateTx, - isBitcoinPlainTx, - isErc20CreateTx, - isEthereumCreateTx, -} from './types'; - -export { ApproveTarget, CreateErc20ApproveTx, Erc20ApproveTxDetails } from './CreateErc20ApproveTx'; -export { BitcoinFeeRange, CreateTxConverter, EthereumFeeRange, FeeRange } from './CreateTxConverter'; -export { BitcoinTx, BitcoinTxDetails, CreateBitcoinTx } from './CreateBitcoinTx'; -export { CreateBitcoinCancelTx } from './CreateBitcoinCancelTx'; -export { CreateERC20Tx, ERC20TxDetails } from './CreateErc20Tx'; -export { CreateErc20WrappedTx, Erc20WrappedTxDetails } from './CreateErc20WrappedTx'; -export { CreateEthereumTx, TxDetails } from './CreateEthereumTx'; - -export { TxSigner } from './TxSigner'; diff --git a/packages/core/src/workflow/types.ts b/packages/core/src/workflow/types.ts deleted file mode 100644 index 9badc4c7f..000000000 --- a/packages/core/src/workflow/types.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { BigAmount } from '@emeraldpay/bigamount'; -import { SatoshiAny, WeiAny } from '@emeraldpay/bigamount-crypto'; -import BigNumber from 'bignumber.js'; -import { BlockchainCode, TokenRegistry, isBitcoin } from '../blockchains'; -import { CreateBitcoinTx } from './CreateBitcoinTx'; -import { CreateERC20Tx } from './CreateErc20Tx'; -import { CreateEthereumTx } from './CreateEthereumTx'; - -export enum ValidationResult { - INSUFFICIENT_FEE_PRICE, - INSUFFICIENT_FUNDS, - INSUFFICIENT_TOKEN_FUNDS, - NO_CHANGE_ADDRESS, - NO_AMOUNT, - NO_FROM, - NO_TO, - OK, -} - -export enum TxTarget { - MANUAL, - SEND_ALL, -} - -/** - * TODO Make unified interface for all create tx classes - */ -export interface EthereumTx { - getAmount(): T; - getAsset(): string; - getTotalBalance(): T; - setAmount(amount: T | BigNumber, tokenSymbol?: string): void; - setTotalBalance(total: T): void; -} - -export type AnyEthereumCreateTx = CreateEthereumTx | CreateERC20Tx; -export type AnyCreateTx = CreateBitcoinTx | AnyEthereumCreateTx; - -export function isBitcoinCreateTx(createTx: AnyCreateTx): createTx is CreateBitcoinTx { - return 'amount' in createTx && SatoshiAny.is(createTx.amount); -} - -export function isEthereumCreateTx(createTx: AnyCreateTx): createTx is CreateEthereumTx { - return 'amount' in createTx && WeiAny.is(createTx.amount); -} - -export function isErc20CreateTx(createTx: AnyCreateTx, tokenRegistry: TokenRegistry): createTx is CreateERC20Tx { - return ( - 'getAsset' in createTx && - typeof createTx.getAsset === 'function' && - tokenRegistry.hasAddress(createTx.blockchain, createTx.getAsset()) - ); -} - -export function isAnyEthereumCreateTx( - createTx: AnyCreateTx, - tokenRegistry: TokenRegistry, -): createTx is AnyEthereumCreateTx { - return isEthereumCreateTx(createTx) || isErc20CreateTx(createTx, tokenRegistry); -} - -export interface BitcoinPlainTx { - amount: string; - blockchain: BlockchainCode; - target: number; - to?: string; - vkbPrice: number; -} - -export interface EthereumPlainTx { - amount: string; - asset: string; - blockchain: BlockchainCode; - from?: string; - gas: number; - gasPrice?: string; - maxGasPrice?: string; - priorityGasPrice?: string; - target: number; - to?: string; - totalBalance?: string; - totalTokenBalance?: string; - transferFrom?: string; - type: string; -} - -export type AnyPlainTx = BitcoinPlainTx | EthereumPlainTx; - -export function isBitcoinPlainTx(transaction: BitcoinPlainTx | EthereumPlainTx): transaction is BitcoinPlainTx { - return isBitcoin(transaction.blockchain); -} diff --git a/packages/react-app/src/app/screen/Screen/Screen.tsx b/packages/react-app/src/app/screen/Screen/Screen.tsx index bbc42a049..66d3f087c 100644 --- a/packages/react-app/src/app/screen/Screen/Screen.tsx +++ b/packages/react-app/src/app/screen/Screen/Screen.tsx @@ -15,7 +15,6 @@ import ReceiveScreen from '../../../receive/ReceiveScreen'; import Settings from '../../../settings/Settings'; import { BroadcastEthTx } from '../../../transaction/BroadcastEthTx'; import CreateApproveTransaction from '../../../transaction/CreateApproveTransaction'; -import CreateCancelTransaction from '../../../transaction/CreateCancelTransaction'; import CreateConvertTransaction from '../../../transaction/CreateConvertTransaction'; import CreateRecoverTransaction from '../../../transaction/CreateRecoverTransaction'; import CreateSpeedUpTransaction from '../../../transaction/CreateSpeedUpTransaction'; @@ -69,8 +68,6 @@ const Screen: React.FC = ({ restoreData, screenItem, term return ; case screen.Pages.CREATE_TX_CONVERT: return ; - case screen.Pages.CREATE_TX_CANCEL: - return ; case screen.Pages.CREATE_TX_SPEED_UP: return ; case screen.Pages.CREATE_TX_RECOVER: diff --git a/packages/react-app/src/transaction/CreateApproveTransaction/SetupApproveTransaction.tsx b/packages/react-app/src/transaction/CreateApproveTransaction/SetupApproveTransaction.tsx index 20db0b0dd..7fb4e4b50 100644 --- a/packages/react-app/src/transaction/CreateApproveTransaction/SetupApproveTransaction.tsx +++ b/packages/react-app/src/transaction/CreateApproveTransaction/SetupApproveTransaction.tsx @@ -13,7 +13,6 @@ import { blockchainIdToCode, workflow, } from '@emeraldwallet/core'; -import { ApproveTarget, ValidationResult } from '@emeraldwallet/core/lib/workflow'; import { Allowance, FEE_KEYS, @@ -166,10 +165,10 @@ const SetupApproveTransaction: React.FC = const { allowance, spenderAddress } = initialAllowance ?? {}; let amount: TokenAmount | undefined; - let target: ApproveTarget | undefined; + let target: workflow.ApproveTarget | undefined; if (allowance?.number.isGreaterThanOrEqualTo(MAX_DISPLAY_ALLOWANCE)) { - target = ApproveTarget.INFINITE; + target = workflow.ApproveTarget.INFINITE; } else { amount = allowance; } @@ -180,6 +179,7 @@ const SetupApproveTransaction: React.FC = approveBy: address, allowFor: spenderAddress, blockchain: currentBlockchain, + meta: { type: workflow.TxMetaType.ETHEREUM_APPROVE }, token: currentToken, totalBalance: balances[currentEntry.id], totalTokenBalance: getTokenBalance(currentEntry, currentToken.address), @@ -190,7 +190,7 @@ const SetupApproveTransaction: React.FC = }); const [allowInfinite, setAllowInfinite] = React.useState( - workflow.CreateErc20ApproveTx.fromPlain(approveTx).target === ApproveTarget.INFINITE, + workflow.CreateErc20ApproveTx.fromPlain(approveTx).target === workflow.ApproveTarget.INFINITE, ); const fetchFees = (): Promise => @@ -271,7 +271,7 @@ const SetupApproveTransaction: React.FC = if (allowance != null && tx.amount.isZero()) { if (allowance.number.isGreaterThanOrEqualTo(MAX_DISPLAY_ALLOWANCE)) { - tx.target = ApproveTarget.INFINITE; + tx.target = workflow.ApproveTarget.INFINITE; setAllowInfinite(true); } else { @@ -289,7 +289,7 @@ const SetupApproveTransaction: React.FC = const tx = workflow.CreateErc20ApproveTx.fromPlain(approveTx); - tx.target = allowed ? ApproveTarget.INFINITE : ApproveTarget.MAX_AVAILABLE; + tx.target = allowed ? workflow.ApproveTarget.INFINITE : workflow.ApproveTarget.MAX_AVAILABLE; setApproveTx(tx.dump()); }; @@ -305,7 +305,7 @@ const SetupApproveTransaction: React.FC = const onMaxAmount = (callback: (value: BigAmount) => void): void => { const tx = workflow.CreateErc20ApproveTx.fromPlain(approveTx); - tx.target = ApproveTarget.MAX_AVAILABLE; + tx.target = workflow.ApproveTarget.MAX_AVAILABLE; callback(tx.amount); @@ -486,7 +486,7 @@ const SetupApproveTransaction: React.FC =