UNPKG

@bcpros/crypto-wallet-core

Version:

A multi-currency support library for address derivation, private key creation, and transaction creation

144 lines (131 loc) 4.65 kB
import { ethers } from 'ethers'; import Web3 from 'web3'; import { AbiItem, toHex, toBN } from 'web3-utils'; import { Constants } from '../../constants'; import { EVM_CHAIN_DEFAULT_TESTNET as defaultTestnet, EVM_CHAIN_NETWORK_TO_CHAIN_ID as chainIds } from '../../constants/chains'; import { Key } from '../../derivation'; import { MULTISENDAbi } from '../erc20/abi'; export class ETHTxProvider { chain: string; constructor(chain = 'ETH') { this.chain = chain; } create(params: { recipients: Array<{ address: string; amount: string }>; nonce: number; gasPrice?: number; data: string; gasLimit: number; network: string; chainId?: number; contractAddress?: string; maxGasFee?: number; priorityGasFee?: number; }) { const { recipients, nonce, gasPrice, gasLimit, network, contractAddress, maxGasFee, priorityGasFee } = params; let { data } = params; let to; let amount; if (recipients.length > 1) { if (!contractAddress) { throw new Error('Multiple recipients requires use of multi-send contract, please specify contractAddress'); } const addresses = []; const amounts = []; amount = toBN(0); for (let recipient of recipients) { addresses.push(recipient.address); amounts.push(toBN(recipient.amount)); amount = amount.add(toBN(recipient.amount)); } const multisendContract = this.getMultiSendContract(contractAddress); data = data || multisendContract.methods.sendEth(addresses, amounts).encodeABI(); to = contractAddress; } else { to = recipients[0].address; amount = recipients[0].amount; } let { chainId } = params; chainId = chainId || this.getChainId(network); let txData: any = { nonce: toHex(nonce), gasLimit: toHex(gasLimit), to, data, value: toHex(amount), chainId }; if (maxGasFee) { txData.maxFeePerGas = toHex(maxGasFee); txData.maxPriorityFeePerGas = toHex(priorityGasFee || this.getPriorityFeeMinimum(chainId)); txData.type = 2; } else { txData.gasPrice = toHex(gasPrice); } return ethers.utils.serializeTransaction(txData); } getMultiSendContract(tokenContractAddress: string) { const web3 = new Web3(); return new web3.eth.Contract(MULTISENDAbi as AbiItem[], tokenContractAddress); } getPriorityFeeMinimum(chainId: number) { const chain = Constants.EVM_CHAIN_ID_TO_CHAIN[chainId]; return Constants.FEE_MINIMUMS[chain]?.priority || 0; } getChainId(network: string) { if (network === 'testnet') { network = defaultTestnet[this.chain]; } return chainIds[`${this.chain}_${network}`] || chainIds[`${this.chain}_mainnet`]; } getSignatureObject(params: { tx: string; key: Key }) { const { tx, key } = params; // To complain with new ethers let k = key.privKey; if (k.substr(0, 2) != '0x') { k = '0x' + k; } const signingKey = new ethers.utils.SigningKey(k); const signDigest = signingKey.signDigest.bind(signingKey); return signDigest(ethers.utils.keccak256(tx)); } getSignature(params: { tx: string; key: Key }) { const signatureHex = ethers.utils.joinSignature(this.getSignatureObject(params)); return signatureHex; } getHash(params: { tx: string }) { const { tx } = params; // tx must be signed, for hash to exist return ethers.utils.parseTransaction(tx).hash; } applySignature(params: { tx: string; signature: any }) { let { tx, signature } = params; const parsedTx = ethers.utils.parseTransaction(tx); const { nonce, gasPrice, gasLimit, to, value, data, chainId, maxFeePerGas, maxPriorityFeePerGas } = parsedTx; let txData: any = { nonce, gasPrice, gasLimit, to, value, data, chainId }; if (maxFeePerGas) { txData.maxFeePerGas = maxFeePerGas; txData.maxPriorityFeePerGas = maxPriorityFeePerGas; txData.type = 2; } else if (!gasPrice || !gasPrice.toNumber()) { throw new Error('either gasPrice or maxFeePerGas is required'); } if (typeof signature == 'string') { signature = ethers.utils.splitSignature(signature); } const signedTx = ethers.utils.serializeTransaction(txData, signature); const parsedTxSigned = ethers.utils.parseTransaction(signedTx); if (!parsedTxSigned.hash) { throw new Error('Signature invalid'); } return signedTx; } sign(params: { tx: string; key: Key }) { const { tx, key } = params; const signature = this.getSignatureObject({ tx, key }); return this.applySignature({ tx, signature }); } }