PHP WebShell
Текущая директория: /opt/BitGoJS/modules/abstract-eth/src/lib
Просмотр файла: utils.ts
import { Buffer } from 'buffer';
import request from 'superagent';
import assert from 'assert';
import {
addHexPrefix,
bufferToHex,
bufferToInt,
generateAddress,
isValidAddress,
setLengthLeft,
stripHexPrefix,
toBuffer,
generateAddress2,
padToEven,
} from 'ethereumjs-util';
import { BaseCoin, BaseNetwork, coins, ContractAddressDefinedToken, EthereumNetwork } from '@bitgo/statics';
import EthereumAbi from 'ethereumjs-abi';
import EthereumCommon from '@ethereumjs/common';
import BN from 'bn.js';
import BigNumber from 'bignumber.js';
import {
ActivateMethodId,
BuildTransactionError,
LockMethodId,
SigningError,
TransactionType,
UnlockMethodId,
UnvoteMethodId,
VoteMethodId,
WithdrawMethodId,
} from '@bitgo/sdk-core';
import {
ERC1155TransferData,
ERC721TransferData,
FlushTokensData,
NativeTransferData,
SignatureParts,
TokenTransferData,
TransferData,
TxData,
WalletInitializationData,
ForwarderInitializationData,
} from './iface';
import { KeyPair } from './keyPair';
import {
createForwarderMethodId,
ERC1155BatchTransferTypeMethodId,
ERC1155BatchTransferTypes,
ERC1155SafeTransferTypeMethodId,
ERC1155SafeTransferTypes,
ERC721SafeTransferTypeMethodId,
ERC721SafeTransferTypes,
flushCoinsMethodId,
flushCoinsTypes,
flushForwarderTokensMethodId,
flushTokensTypes,
sendMultisigMethodId,
sendMultisigTokenMethodId,
sendMultiSigTokenTypes,
sendMultiSigTypes,
walletInitializationFirstBytes,
v1CreateForwarderMethodId,
walletSimpleConstructor,
createV1WalletTypes,
v1CreateWalletMethodId,
createV1ForwarderTypes,
recoveryWalletInitializationFirstBytes,
defaultForwarderVersion,
createV4ForwarderTypes,
v4CreateForwarderMethodId,
flushTokensTypesv4,
flushForwarderTokensMethodIdV4,
sendMultiSigTokenTypesFirstSigner,
sendMultiSigTypesFirstSigner,
} from './walletUtil';
import { EthTransactionData } from './types';
/**
* @param network
*/
export function getCommon(network: EthereumNetwork): EthereumCommon {
return EthereumCommon.forCustomChain(
// use the mainnet config as a base, override chain ids and network name
'mainnet',
{
name: network.type,
networkId: network.chainId,
chainId: network.chainId,
},
'london'
);
}
/**
* Signs the transaction using the appropriate algorithm
* and the provided common for the blockchain
*
* @param {TxData} transactionData the transaction data to sign
* @param {KeyPair} keyPair the signer's keypair
* @param {EthereumCommon} customCommon the network's custom common
* @returns {string} the transaction signed and encoded
*/
export async function signInternal(
transactionData: TxData,
keyPair: KeyPair,
customCommon: EthereumCommon
): Promise<string> {
if (!keyPair.getKeys().prv) {
throw new SigningError('Missing private key');
}
const ethTx = EthTransactionData.fromJson(transactionData, customCommon);
ethTx.sign(keyPair);
return ethTx.toSerialized();
}
/**
* Signs the transaction using the appropriate algorithm
*
* @param {TxData} transactionData the transaction data to sign
* @param {KeyPair} keyPair the signer's keypair
* @returns {string} the transaction signed and encoded
*/
export async function sign(transactionData: TxData, keyPair: KeyPair): Promise<string> {
return signInternal(transactionData, keyPair, getCommon(coins.get('teth').network as EthereumNetwork));
}
/**
* Returns the contract method encoded data
*
* @param {string} to destination address
* @param {number} value Amount to tranfer
* @param {string} data aditional method call data
* @param {number} expireTime expiration time for the transaction in seconds
* @param {number} sequenceId sequence id
* @param {string} signature signature of the call
* @returns {string} -- the contract method encoded data
*/
export function sendMultiSigData(
to: string,
value: string,
data: string,
expireTime: number,
sequenceId: number,
signature: string
): string {
const params = [to, value, toBuffer(data), expireTime, sequenceId, toBuffer(signature)];
const method = EthereumAbi.methodID('sendMultiSig', sendMultiSigTypes);
const args = EthereumAbi.rawEncode(sendMultiSigTypes, params);
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}
/**
* Returns the contract method encoded data
*
* @param {string} to destination address
* @param {number} value Amount to tranfer
* @param {string} tokenContractAddress the address of the erc20 token contract
* @param {number} expireTime expiration time for the transaction in seconds
* @param {number} sequenceId sequence id
* @param {string} signature signature of the call
* @returns {string} -- the contract method encoded data
*/
export function sendMultiSigTokenData(
to: string,
value: string,
tokenContractAddress: string,
expireTime: number,
sequenceId: number,
signature: string
): string {
const params = [to, value, tokenContractAddress, expireTime, sequenceId, toBuffer(signature)];
const method = EthereumAbi.methodID('sendMultiSigToken', sendMultiSigTokenTypes);
const args = EthereumAbi.rawEncode(sendMultiSigTokenTypes, params);
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}
/**
* Get the data required to make a flush tokens contract call
*
* @param forwarderAddress The forwarder address to flush
* @param tokenAddress The token address to flush from
*/
export function flushTokensData(forwarderAddress: string, tokenAddress: string, forwarderVersion: number): string {
let params: string[];
let method: Uint8Array;
let args: Uint8Array;
if (forwarderVersion >= 4) {
params = [tokenAddress];
method = EthereumAbi.methodID('flushTokens', flushTokensTypesv4);
args = EthereumAbi.rawEncode(flushTokensTypesv4, params);
} else {
params = [forwarderAddress, tokenAddress];
method = EthereumAbi.methodID('flushForwarderTokens', flushTokensTypes);
args = EthereumAbi.rawEncode(flushTokensTypes, params);
}
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}
/**
* Get the data required to make a flush native coins contract call
*/
export function flushCoinsData(): string {
const params = [];
const method = EthereumAbi.methodID('flush', flushCoinsTypes);
const args = EthereumAbi.rawEncode(flushCoinsTypes, params);
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}
/**
* Returns the create forwarder method calling data
*
* @returns {string} - the createForwarder method encoded
*/
export function getAddressInitializationData(): string {
return createForwarderMethodId;
}
/**
* Returns whether or not the string is a valid Eth address
*
* @param {string} address - the tx hash to validate
* @returns {boolean} - the validation result
*/
export function isValidEthAddress(address: string): boolean {
return isValidAddress(address);
}
/**
* Returns whether or not the string is a valid amount number
*
* @param {string} amount - the string to validate
* @returns {boolean} - the validation result
*/
export function isValidAmount(amount: string): boolean {
const bigNumberAmount = new BigNumber(amount);
return bigNumberAmount.isInteger() && bigNumberAmount.isGreaterThanOrEqualTo(0);
}
/**
* Returns the smart contract encoded data
*
* @param {string} data The wallet creation data to decode
* @returns {string[]} - The list of signer addresses
*/
export function decodeWalletCreationData(data: string): WalletInitializationData {
if (!(data.startsWith(walletInitializationFirstBytes) || data.startsWith(v1CreateWalletMethodId))) {
throw new BuildTransactionError(`Invalid wallet bytecode: ${data}`);
}
if (data.startsWith(walletInitializationFirstBytes)) {
const dataBuffer = Buffer.from(data.slice(2), 'hex');
// the last 160 bytes contain the serialized address array
const serializedSigners = dataBuffer.slice(-160);
const resultEncodedParameters = EthereumAbi.rawDecode(walletSimpleConstructor, serializedSigners);
if (resultEncodedParameters.length !== 1) {
throw new BuildTransactionError(`Could not decode wallet constructor bytecode: ${resultEncodedParameters}`);
}
const addresses: BN[] = resultEncodedParameters[0];
if (addresses.length !== 3) {
throw new BuildTransactionError(`invalid number of addresses in parsed constructor: ${addresses}`);
}
// sometimes ethereumjs-abi removes 0 padding at the start of addresses,
// so we should pad until they are the standard 20 bytes
const paddedAddresses = addresses.map((address) => stripHexPrefix(address.toString('hex')).padStart(40, '0'));
return { owners: paddedAddresses.map((address) => addHexPrefix(address)) };
} else {
const decodedDataForWalletCreation = getRawDecoded(
createV1WalletTypes,
getBufferedByteCode(v1CreateWalletMethodId, data)
);
const addresses = decodedDataForWalletCreation[0] as string[];
const saltBuffer = decodedDataForWalletCreation[1];
const salt = bufferToHex(saltBuffer as Buffer);
const paddedAddresses = addresses.map((address) => stripHexPrefix(address.toString()).padStart(40, '0'));
const owners = paddedAddresses.map((address) => addHexPrefix(address));
return {
owners,
salt,
};
}
}
/**
* Decode the given ABI-encoded transfer data and return parsed fields
*
* @param data The data to decode
* @param isFirstSigner whether transaction is being built for a first signer
* @returns parsed transfer data
*/
export function decodeTransferData(data: string, isFirstSigner?: boolean): TransferData {
if (data.startsWith(sendMultisigMethodId)) {
return decodeNativeTransferData(data, isFirstSigner);
} else if (data.startsWith(sendMultisigTokenMethodId)) {
return decodeTokenTransferData(data, isFirstSigner);
} else {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
}
/**
* Decode the given ABI-encoded transfer data for the sendMultisigToken function and return parsed fields
*
* @param data The data to decode
* @param isFirstSigner whether transaction is being built for a first signer
* @returns parsed token transfer data
*/
export function decodeTokenTransferData(data: string, isFirstSigner?: boolean): TokenTransferData {
if (!data.startsWith(sendMultisigTokenMethodId)) {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
let to: RecursiveBufferOrString | undefined;
let amount: RecursiveBufferOrString | undefined;
let tokenContractAddress: RecursiveBufferOrString | undefined;
let expireTime: RecursiveBufferOrString | undefined;
let sequenceId: RecursiveBufferOrString | undefined;
let signature: RecursiveBufferOrString | undefined;
let prefix: RecursiveBufferOrString | undefined;
if (!isFirstSigner) {
[to, amount, tokenContractAddress, expireTime, sequenceId, signature] = getRawDecoded(
sendMultiSigTokenTypes,
getBufferedByteCode(sendMultisigTokenMethodId, data)
);
} else {
[prefix, to, amount, tokenContractAddress, expireTime, sequenceId] = getRawDecoded(
sendMultiSigTokenTypesFirstSigner,
getBufferedByteCode(sendMultisigTokenMethodId, data)
);
}
return {
operationHashPrefix: isFirstSigner ? (prefix as string) : undefined,
to: addHexPrefix(to as string),
amount: new BigNumber(bufferToHex(amount as Buffer)).toFixed(),
expireTime: bufferToInt(expireTime as Buffer),
sequenceId: bufferToInt(sequenceId as Buffer),
signature: bufferToHex(signature as Buffer),
tokenContractAddress: addHexPrefix(tokenContractAddress as string),
};
}
export function decodeERC721TransferData(data: string): ERC721TransferData {
if (!data.startsWith(sendMultisigMethodId)) {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
const [to, amount, internalData, expireTime, sequenceId, signature] = getRawDecoded(
sendMultiSigTypes,
getBufferedByteCode(sendMultisigMethodId, data)
);
const internalDataHex = bufferToHex(internalData as Buffer);
if (!internalDataHex.startsWith(ERC721SafeTransferTypeMethodId)) {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
const [from, receiver, tokenId, userSentData] = getRawDecoded(
ERC721SafeTransferTypes,
getBufferedByteCode(ERC721SafeTransferTypeMethodId, internalDataHex)
);
return {
to: addHexPrefix(receiver as string),
from: addHexPrefix(from as string),
expireTime: bufferToInt(expireTime as Buffer),
amount: new BigNumber(bufferToHex(amount as Buffer)).toFixed(),
tokenId: new BigNumber(bufferToHex(tokenId as Buffer)).toFixed(),
sequenceId: bufferToInt(sequenceId as Buffer),
signature: bufferToHex(signature as Buffer),
tokenContractAddress: addHexPrefix(to as string),
userData: bufferToHex(userSentData as Buffer),
};
}
export function decodeERC1155TransferData(data: string): ERC1155TransferData {
let from, receiver, userSentData;
let tokenIds: string[];
let values: string[];
if (!data.startsWith(sendMultisigMethodId)) {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
const [to, amount, internalData, expireTime, sequenceId, signature] = getRawDecoded(
sendMultiSigTypes,
getBufferedByteCode(sendMultisigMethodId, data)
);
const internalDataHex = bufferToHex(internalData as Buffer);
if (internalDataHex.startsWith(ERC1155SafeTransferTypeMethodId)) {
let tokenId;
let value;
[from, receiver, tokenId, value, userSentData] = getRawDecoded(
ERC1155SafeTransferTypes,
getBufferedByteCode(ERC1155SafeTransferTypeMethodId, internalDataHex)
);
tokenIds = [new BigNumber(bufferToHex(tokenId)).toFixed()];
values = [new BigNumber(bufferToHex(value)).toFixed()];
} else if (bufferToHex(internalData as Buffer).startsWith(ERC1155BatchTransferTypeMethodId)) {
let tempTokenIds, tempValues;
[from, receiver, tempTokenIds, tempValues, userSentData] = getRawDecoded(
ERC1155BatchTransferTypes,
getBufferedByteCode(ERC1155BatchTransferTypeMethodId, internalDataHex)
);
tokenIds = tempTokenIds.map((x) => new BigNumber(bufferToHex(x)).toFixed());
values = tempValues.map((x) => new BigNumber(bufferToHex(x)).toFixed());
} else {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
return {
to: addHexPrefix(receiver),
from: addHexPrefix(from),
expireTime: bufferToInt(expireTime as Buffer),
amount: new BigNumber(bufferToHex(amount as Buffer)).toFixed(),
tokenIds,
values,
sequenceId: bufferToInt(sequenceId as Buffer),
signature: bufferToHex(signature as Buffer),
tokenContractAddress: addHexPrefix(to as string),
userData: userSentData,
};
}
/**
* Decode the given ABI-encoded transfer data for the sendMultisig function and return parsed fields
*
* @param data The data to decode
* @param isFirstSigner whether transaction is being built for a first signer
* @returns parsed transfer data
*/
export function decodeNativeTransferData(data: string, isFirstSigner?: boolean): NativeTransferData {
if (!data.startsWith(sendMultisigMethodId)) {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
let to: RecursiveBufferOrString | undefined;
let amount: RecursiveBufferOrString | undefined;
let internalData: RecursiveBufferOrString | undefined;
let expireTime: RecursiveBufferOrString | undefined;
let sequenceId: RecursiveBufferOrString | undefined;
let signature: RecursiveBufferOrString | undefined;
let prefix: RecursiveBufferOrString | undefined;
if (!isFirstSigner) {
[to, amount, internalData, expireTime, sequenceId, signature] = getRawDecoded(
sendMultiSigTypes,
getBufferedByteCode(sendMultisigMethodId, data)
);
} else {
[prefix, to, amount, internalData, expireTime, sequenceId] = getRawDecoded(
sendMultiSigTypesFirstSigner,
getBufferedByteCode(sendMultisigMethodId, data)
);
}
return {
operationHashPrefix: isFirstSigner ? (prefix as string) : undefined,
to: addHexPrefix(to as string),
amount: new BigNumber(bufferToHex(amount as Buffer)).toFixed(),
expireTime: bufferToInt(expireTime as Buffer),
sequenceId: bufferToInt(sequenceId as Buffer),
signature: bufferToHex(signature as Buffer),
data: bufferToHex(internalData as Buffer),
};
}
/**
* Decode the given ABI-encoded flush tokens data and return parsed fields
*
* @param data The data to decode
* @param to Optional to parameter of tx
* @returns parsed transfer data
*/
export function decodeFlushTokensData(data: string, to?: string): FlushTokensData {
if (data.startsWith(flushForwarderTokensMethodId)) {
const [forwarderAddress, tokenAddress] = getRawDecoded(
flushTokensTypes,
getBufferedByteCode(flushForwarderTokensMethodId, data)
);
return {
forwarderAddress: addHexPrefix(forwarderAddress as string),
tokenAddress: addHexPrefix(tokenAddress as string),
};
} else if (data.startsWith(flushForwarderTokensMethodIdV4)) {
const [tokenAddress] = getRawDecoded(flushTokensTypesv4, getBufferedByteCode(flushForwarderTokensMethodIdV4, data));
if (!to) {
throw new BuildTransactionError(`Missing to address: ${to}`);
}
return {
forwarderAddress: to,
tokenAddress: addHexPrefix(tokenAddress as string),
forwarderVersion: 4,
};
} else {
throw new BuildTransactionError(`Invalid transfer bytecode: ${data}`);
}
}
/**
* Classify the given transaction data based as a transaction type.
* ETH transactions are defined by the first 8 bytes of the transaction data, also known as the method id
*
* @param {string} data The data to classify the transaction with
* @returns {TransactionType} The classified transaction type
*/
export function classifyTransaction(data: string): TransactionType {
if (data.length < 10) {
// contract calls must have at least 4 bytes (method id) and '0x'
// if it doesn't have enough data to be a contract call it must be a single sig send
return TransactionType.SingleSigSend;
}
// TODO(STLX-1970): validate if we are going to constraint to some methods allowed
let transactionType = transactionTypesMap[data.slice(0, 10).toLowerCase()];
if (transactionType === undefined) {
transactionType = TransactionType.ContractCall;
}
return transactionType;
}
/**
* A transaction types map according to the starting part of the encoded data
*/
const transactionTypesMap = {
[walletInitializationFirstBytes]: TransactionType.WalletInitialization,
[recoveryWalletInitializationFirstBytes]: TransactionType.RecoveryWalletDeployment,
[v1CreateWalletMethodId]: TransactionType.WalletInitialization,
[createForwarderMethodId]: TransactionType.AddressInitialization,
[v1CreateForwarderMethodId]: TransactionType.AddressInitialization,
[v4CreateForwarderMethodId]: TransactionType.AddressInitialization,
[sendMultisigMethodId]: TransactionType.Send,
[flushForwarderTokensMethodId]: TransactionType.FlushTokens,
[flushForwarderTokensMethodIdV4]: TransactionType.FlushTokens,
[flushCoinsMethodId]: TransactionType.FlushCoins,
[sendMultisigTokenMethodId]: TransactionType.Send,
[LockMethodId]: TransactionType.StakingLock,
[VoteMethodId]: TransactionType.StakingVote,
[ActivateMethodId]: TransactionType.StakingActivate,
[UnvoteMethodId]: TransactionType.StakingUnvote,
[UnlockMethodId]: TransactionType.StakingUnlock,
[WithdrawMethodId]: TransactionType.StakingWithdraw,
};
/**
*
* @param {number} num number to be converted to hex
* @returns {string} the hex number
*/
export function numberToHexString(num: number): string {
const hex = num.toString(16);
return hex.length % 2 === 0 ? '0x' + hex : '0x0' + hex;
}
/**
*
* @param {string} hex The hex string to be converted
* @returns {number} the resulting number
*/
export function hexStringToNumber(hex: string): number {
return parseInt(hex.slice(2), 16);
}
/**
* Generates an address of the forwarder address to be deployed
*
* @param {string} contractAddress the address which is creating this new address
* @param {number} contractCounter the nonce of the contract address
* @returns {string} the calculated forwarder contract address
*/
export function calculateForwarderAddress(contractAddress: string, contractCounter: number): string {
const forwarderAddress = generateAddress(
Buffer.from(stripHexPrefix(contractAddress), 'hex'),
Buffer.from(padToEven(stripHexPrefix(numberToHexString(contractCounter))), 'hex')
);
return addHexPrefix(forwarderAddress.toString('hex'));
}
/**
* Calculate the forwarder v1 address that will be generated if `creatorAddress` creates it with salt `salt`
* and initcode `inicode using the create2 opcode
* @param {string} creatorAddress The address that is sending the tx to create a new address, hex string
* @param {string} salt The salt to create the address with using create2, hex string
* @param {string} initcode The initcode that will be deployed to the address, hex string
* @return {string} The calculated address
*/
export function calculateForwarderV1Address(creatorAddress: string, salt: string, initcode: string): string {
const forwarderV1Address = generateAddress2(
Buffer.from(stripHexPrefix(creatorAddress), 'hex'),
Buffer.from(stripHexPrefix(salt), 'hex'),
Buffer.from(padToEven(stripHexPrefix(initcode)), 'hex')
);
return addHexPrefix(forwarderV1Address.toString('hex'));
}
/**
* Take the implementation address for the proxy contract, and get the binary initcode for the associated proxy
* @param {string} implementationAddress The address of the implementation contract for the proxy
* @return {string} Binary hex string of the proxy
*/
export function getProxyInitcode(implementationAddress: string): string {
const target = stripHexPrefix(implementationAddress.toLowerCase()).padStart(40, '0');
// bytecode of the proxy, from:
// https://github.com/BitGo/eth-multisig-v4/blob/d546a937f90f93e83b3423a5bf933d1d77c677c3/contracts/CloneFactory.sol#L42-L56
return `0x3d602d80600a3d3981f3363d3d373d3d3d363d73${target}5af43d82803e903d91602b57fd5bf3`;
}
/**
* Convert the given signature parts to a string representation
*
* @param {SignatureParts} sig The signature to convert to string
* @returns {string} String representation of the signature
*/
export function toStringSig(sig: SignatureParts): string {
return bufferToHex(
Buffer.concat([
setLengthLeft(Buffer.from(stripHexPrefix(sig.r), 'hex'), 32),
setLengthLeft(Buffer.from(stripHexPrefix(sig.s), 'hex'), 32),
toBuffer(sig.v),
])
);
}
/**
* Return whether or not the given tx data has a signature
*
* @param {TxData} txData The transaction data to check for signature
* @returns {boolean} true if the tx has a signature, else false
*/
export function hasSignature(txData: TxData): boolean {
return (
txData.v !== undefined &&
txData.r !== undefined &&
txData.s !== undefined &&
txData.v.length > 0 &&
txData.r.length > 0 &&
txData.s.length > 0
);
}
type RecursiveBufferOrString = string | Buffer | BN | RecursiveBufferOrString[];
/**
* Get the raw data decoded for some types
*
* @param {string[]} types ABI types definition
* @param {Buffer} serializedArgs encoded args
* @returns {Buffer[]} the decoded raw
*/
export function getRawDecoded(types: string[], serializedArgs: Buffer): RecursiveBufferOrString[] {
function normalize(v: unknown, i: number): unknown {
if (BN.isBN(v)) {
return v;
} else if (typeof v === 'string' || Buffer.isBuffer(v)) {
return v;
} else if (Array.isArray(v)) {
return v.map(normalize);
} else {
throw new Error(`For ${types}[${i}] got ${typeof v}`);
}
}
return EthereumAbi.rawDecode(types, serializedArgs).map(normalize);
}
/**
* Get the buffered bytecode from rawData using a methodId as delimiter
*
* @param {string} methodId the hex encoded method Id
* @param {string} rawData the hex encoded raw data
* @returns {Buffer} data buffered bytecode
*/
export function getBufferedByteCode(methodId: string, rawData: string): Buffer {
const splitBytecode = rawData.split(methodId);
if (splitBytecode.length !== 2) {
throw new BuildTransactionError(`Invalid send bytecode: ${rawData}`);
}
if (splitBytecode[1].length % 2 !== 0) {
throw new BuildTransactionError(`Invalid send bytecode: ${rawData} (wrong lenght)`);
}
return Buffer.from(splitBytecode[1], 'hex');
}
/**
* Get the statics coin object matching a given contract address if it exists
*
* @param tokenContractAddress The contract address to match against
* @param network - the coin network
* @param family - the coin family
* @returns statics BaseCoin object for the matching token
*/
export function getToken(
tokenContractAddress: string,
network: BaseNetwork,
family: string
): Readonly<BaseCoin> | undefined {
// filter the coins array to find the token with the matching contract address, network and coin family
// coin family is needed to avoid causing issues when a token has same contract address on two different chains
const tokens = coins.filter((coin) => {
if (coin instanceof ContractAddressDefinedToken) {
return (
coin.network.type === network.type &&
coin.family === family &&
coin.contractAddress.toLowerCase() === tokenContractAddress.toLowerCase()
);
}
return false;
});
// if length of tokens is 1, return the first, else return undefined
// Can't directly index into tokens, or call `length`, so we use map to get an array
const tokensArray = tokens.map((token) => token);
if (tokensArray.length >= 1) {
// there should never be two tokens with the same contract address, so we assert that here
assert(tokensArray.length === 1);
return tokensArray[0];
}
return undefined;
}
/**
* Returns the create wallet method calling data for v1 wallets
*
* @param {string[]} walletOwners - wallet owner addresses for wallet initialization transactions
* @param {string} salt - The salt for wallet initialization transactions
* @returns {string} - the createWallet method encoded
*/
export function getV1WalletInitializationData(walletOwners: string[], salt: string): string {
const saltBuffer = setLengthLeft(toBuffer(salt), 32);
const params = [walletOwners, saltBuffer];
const method = EthereumAbi.methodID('createWallet', createV1WalletTypes);
const args = EthereumAbi.rawEncode(createV1WalletTypes, params);
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}
/**
* Returns the create address method calling data for v1, v2, v4 forwarders
*
* @param {string} baseAddress - The address of the wallet contract
* @param {string} salt - The salt for address initialization transactions
* @param {string} feeAddress - The fee address for the enterprise
* @returns {string} - the createForwarder method encoded
*/
export function getV1AddressInitializationData(baseAddress: string, salt: string, feeAddress?: string): string {
const saltBuffer = setLengthLeft(toBuffer(salt), 32);
const { createForwarderParams, createForwarderTypes } = getCreateForwarderParamsAndTypes(
baseAddress,
saltBuffer,
feeAddress
);
const method = EthereumAbi.methodID('createForwarder', createForwarderTypes);
const args = EthereumAbi.rawEncode(createForwarderTypes, createForwarderParams);
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}
/**
* Returns the create address method calling data for all forwarder versions
*
* @param {number} forwarderVersion - The version of the forwarder to create
* @param {string} baseAddress - The address of the wallet contract
* @param {string} salt - The salt for address initialization transactions
* @param {string} feeAddress - The fee address for the enterprise
* @returns {string} - the createForwarder method encoded
*
*/
export function getAddressInitDataAllForwarderVersions(
forwarderVersion: number,
baseAddress: string,
salt: string,
feeAddress?: string
): string {
if (forwarderVersion === defaultForwarderVersion) {
return getAddressInitializationData();
} else {
return getV1AddressInitializationData(baseAddress, salt, feeAddress);
}
}
/**
* Returns the createForwarderTypes and createForwarderParams for all forwarder versions
*
* @param {string} baseAddress - The address of the wallet contract
* @param {Buffer} saltBuffer - The salt for address initialization transaction
* @param {string} feeAddress - The fee address for the enterprise
* @returns {createForwarderParams: (string | Buffer)[], createForwarderTypes: string[]}
*/
export function getCreateForwarderParamsAndTypes(
baseAddress: string,
saltBuffer: Buffer,
feeAddress?: string
): { createForwarderParams: (string | Buffer)[]; createForwarderTypes: string[] } {
let createForwarderParams = [baseAddress, saltBuffer];
let createForwarderTypes = createV1ForwarderTypes;
if (feeAddress) {
createForwarderParams = [baseAddress, feeAddress, saltBuffer];
createForwarderTypes = createV4ForwarderTypes;
}
return { createForwarderParams, createForwarderTypes };
}
/**
* Decode the given ABI-encoded create forwarder data and return parsed fields
*
* @param data The data to decode
* @returns parsed transfer data
*/
export function decodeForwarderCreationData(data: string): ForwarderInitializationData {
if (
!(
data.startsWith(v4CreateForwarderMethodId) ||
data.startsWith(v1CreateForwarderMethodId) ||
data.startsWith(createForwarderMethodId)
)
) {
throw new BuildTransactionError(`Invalid address bytecode: ${data}`);
}
if (data.startsWith(createForwarderMethodId)) {
return {
baseAddress: undefined,
addressCreationSalt: undefined,
feeAddress: undefined,
};
} else if (data.startsWith(v1CreateForwarderMethodId)) {
const [baseAddress, saltBuffer] = getRawDecoded(
createV1ForwarderTypes,
getBufferedByteCode(v1CreateForwarderMethodId, data)
);
return {
baseAddress: addHexPrefix(baseAddress as string),
addressCreationSalt: bufferToHex(saltBuffer as Buffer),
feeAddress: undefined,
} as const;
} else {
const [baseAddress, feeAddress, saltBuffer] = getRawDecoded(
createV4ForwarderTypes,
getBufferedByteCode(v4CreateForwarderMethodId, data)
);
return {
baseAddress: addHexPrefix(baseAddress as string),
addressCreationSalt: bufferToHex(saltBuffer as Buffer),
feeAddress: addHexPrefix(feeAddress as string),
} as const;
}
}
/**
* Make a query to explorer for information such as balance, token balance, solidity calls
* @param {Object} query key-value pairs of parameters to append after /api
* @param {string} token the API token to use for the request
* @param {string} explorerUrl the URL of the explorer
* @returns {Promise<Object>} response from explorer
*/
export async function recoveryBlockchainExplorerQuery(
query: Record<string, string>,
explorerUrl: string,
token?: string
): Promise<Record<string, unknown>> {
if (token) {
query.apikey = token;
}
const response = await request.get(`${explorerUrl}/api`).query(query);
if (!response.ok) {
throw new Error('could not reach explorer');
}
if (response.body.status === '0' && response.body.message === 'NOTOK') {
throw new Error('Explorer rate limit reached');
}
return response.body;
}
/**
* Default expire time for a contract call (1 week)
* @returns {number} Time in seconds
*/
export function getDefaultExpireTime(): number {
return Math.floor(new Date().getTime() / 1000) + 60 * 60 * 24 * 7;
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!