@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
text/typescript
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 });
}
}