@xchainjs/xchain-evm
Advanced tools
Comparing version 0.2.2 to 0.3.0
@@ -22,3 +22,3 @@ import { Provider, TransactionResponse } from '@ethersproject/abstract-provider'; | ||
explorerProviders: ExplorerProviders; | ||
dataProviders: OnlineDataProviders; | ||
dataProviders: OnlineDataProviders[]; | ||
}; | ||
@@ -275,3 +275,6 @@ /** | ||
getFees(params: TxParams): Promise<Fees>; | ||
protected roundRobinGetBalance(address: Address, assets?: Asset[]): Promise<Balance[]>; | ||
protected roundRobinGetTransactionData(txId: string, assetAddress?: string): Promise<Tx>; | ||
protected roundRobinGetTransactions(params: TxHistoryParams): Promise<TxsPage>; | ||
} | ||
export { Client }; |
export * from './types'; | ||
export * from './client'; | ||
export * from './providers'; | ||
export { call, estimateApprove, estimateCall, getApprovalAmount, getTokenAddress, getPrefix, isApproved, strip0x, validateAddress, MAX_APPROVAL, } from './utils'; | ||
@@ -5,0 +4,0 @@ export declare const abi: { |
@@ -1,6 +0,5 @@ | ||
import { BaseXChainClient, Network, FeeOption, checkFeeBounds, standardFeeRates, FeeType, TxType } from '@xchainjs/xchain-client'; | ||
import { baseAmount, eqAsset, assetToString, bnOrZero, assetFromString } from '@xchainjs/xchain-util'; | ||
import { BaseXChainClient, Network, FeeOption, checkFeeBounds, standardFeeRates, FeeType } from '@xchainjs/xchain-client'; | ||
import { baseAmount, eqAsset, assetToString } from '@xchainjs/xchain-util'; | ||
import { ethers, Wallet, BigNumber } from 'ethers'; | ||
import { HDNode, toUtf8Bytes } from 'ethers/lib/utils'; | ||
import axios from 'axios'; | ||
@@ -32,3 +31,3 @@ /****************************************************************************** | ||
var erc20ABI$1 = [ | ||
var erc20ABI = [ | ||
{ | ||
@@ -275,3 +274,3 @@ inputs: [ | ||
*/ | ||
const validateAddress$1 = (address) => { | ||
const validateAddress = (address) => { | ||
try { | ||
@@ -389,3 +388,3 @@ ethers.utils.getAddress(address); | ||
const txAmount = ethers.BigNumber.from((_a = amount === null || amount === void 0 ? void 0 : amount.amount().toFixed()) !== null && _a !== void 0 ? _a : 1); | ||
const contract = new ethers.Contract(contractAddress, erc20ABI$1, provider); | ||
const contract = new ethers.Contract(contractAddress, erc20ABI, provider); | ||
const allowance = yield contract.allowance(fromAddress, spenderAddress); | ||
@@ -551,3 +550,3 @@ return txAmount.lte(allowance); | ||
validateAddress(address) { | ||
return validateAddress$1(address); | ||
return validateAddress(address); | ||
} | ||
@@ -564,6 +563,3 @@ /** | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const prov = this.dataProviders[this.network]; | ||
if (!prov) | ||
throw Error('Provider unidefined'); | ||
return yield prov.getBalance(address, assets); | ||
return yield this.roundRobinGetBalance(address, assets); | ||
}); | ||
@@ -587,6 +583,3 @@ } | ||
}; | ||
const prov = this.dataProviders[this.network]; | ||
if (!prov) | ||
throw Error('Provider unidefined'); | ||
return yield prov.getTransactions(filteredParams); | ||
return yield this.roundRobinGetTransactions(filteredParams); | ||
}); | ||
@@ -606,6 +599,3 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const prov = this.dataProviders[this.network]; | ||
if (!prov) | ||
throw Error('Provider unidefined'); | ||
return yield prov.getTransactionData(txId, assetAddress); | ||
return yield this.roundRobinGetTransactionData(txId, assetAddress); | ||
}); | ||
@@ -707,3 +697,3 @@ } | ||
const valueToApprove = getApprovalAmount(amount); | ||
const contract = new ethers.Contract(contractAddress, erc20ABI$1, this.getProvider()); | ||
const contract = new ethers.Contract(contractAddress, erc20ABI, this.getProvider()); | ||
/* as same as ethers.TransactionResponse expected by `sendTransaction` */ | ||
@@ -733,3 +723,3 @@ const unsignedTx = yield contract.populateTransaction.approve(spenderAddress, valueToApprove); | ||
fromAddress, | ||
abi: erc20ABI$1, | ||
abi: erc20ABI, | ||
amount, | ||
@@ -800,3 +790,3 @@ }); | ||
contractAddress: assetAddress, | ||
abi: erc20ABI$1, | ||
abi: erc20ABI, | ||
funcName: 'transfer', | ||
@@ -877,3 +867,3 @@ funcParams: [recipient, txAmount, Object.assign({}, overrides)], | ||
throw Error(`Can't get address from asset ${assetToString(theAsset)}`); | ||
const contract = new ethers.Contract(assetAddress, erc20ABI$1, this.getProvider()); | ||
const contract = new ethers.Contract(assetAddress, erc20ABI, this.getProvider()); | ||
gasEstimate = yield contract.estimateGas.transfer(recipient, txAmount, { | ||
@@ -938,561 +928,45 @@ from: this.getAddress(), | ||
} | ||
} | ||
var erc20ABI = [ | ||
{ | ||
inputs: [ | ||
], | ||
stateMutability: "nonpayable", | ||
type: "constructor" | ||
}, | ||
{ | ||
anonymous: false, | ||
inputs: [ | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "owner", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "spender", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: false, | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "Approval", | ||
type: "event" | ||
}, | ||
{ | ||
anonymous: false, | ||
inputs: [ | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "from", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "to", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: false, | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "Transfer", | ||
type: "event" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "address", | ||
name: "", | ||
type: "address" | ||
} | ||
], | ||
name: "allowance", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "spender", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "approve", | ||
outputs: [ | ||
{ | ||
internalType: "bool", | ||
name: "success", | ||
type: "bool" | ||
} | ||
], | ||
stateMutability: "nonpayable", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "", | ||
type: "address" | ||
} | ||
], | ||
name: "balanceOf", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "decimals", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "name", | ||
outputs: [ | ||
{ | ||
internalType: "string", | ||
name: "", | ||
type: "string" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "symbol", | ||
outputs: [ | ||
{ | ||
internalType: "string", | ||
name: "", | ||
type: "string" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "totalSupply", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "to", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "transfer", | ||
outputs: [ | ||
{ | ||
internalType: "bool", | ||
name: "success", | ||
type: "bool" | ||
} | ||
], | ||
stateMutability: "nonpayable", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "from", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "address", | ||
name: "to", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "transferFrom", | ||
outputs: [ | ||
{ | ||
internalType: "bool", | ||
name: "success", | ||
type: "bool" | ||
} | ||
], | ||
stateMutability: "nonpayable", | ||
type: "function" | ||
} | ||
]; | ||
ethers.BigNumber.from(2).pow(256).sub(1); | ||
/** | ||
* Validate the given address. | ||
* | ||
* @param {Address} address | ||
* @returns {boolean} `true` or `false` | ||
*/ | ||
const validateAddress = (address) => { | ||
try { | ||
ethers.utils.getAddress(address); | ||
return true; | ||
} | ||
catch (error) { | ||
return false; | ||
} | ||
}; | ||
const getApiKeyQueryParameter = (apiKey) => (!!apiKey ? `&apiKey=${apiKey}` : ''); | ||
/** | ||
* Filter self txs | ||
* | ||
* @returns {T[]} | ||
* | ||
**/ | ||
const filterSelfTxs = (txs) => { | ||
const filterTxs = txs.filter((tx) => tx.from !== tx.to); | ||
let selfTxs = txs.filter((tx) => tx.from === tx.to); | ||
while (selfTxs.length) { | ||
const selfTx = selfTxs[0]; | ||
filterTxs.push(selfTx); | ||
selfTxs = selfTxs.filter((tx) => tx.hash !== selfTx.hash); | ||
} | ||
return filterTxs; | ||
}; | ||
/** | ||
* Check if the symbol is valid. | ||
* | ||
* @param {string|null|undefined} symbol | ||
* @returns {boolean} `true` or `false`. | ||
*/ | ||
const validateSymbol = (symbol) => (symbol ? symbol.length >= 3 : false); | ||
/** | ||
* Get transactions from ETH transaction | ||
* | ||
* @param {ETHTransactionInfo} tx | ||
* @returns {Tx} The parsed transaction. | ||
*/ | ||
const getTxFromEthTransaction = (tx, gasAsset, decimals) => { | ||
return { | ||
asset: gasAsset, | ||
from: [ | ||
{ | ||
from: tx.from, | ||
amount: baseAmount(tx.value, decimals), | ||
}, | ||
], | ||
to: [ | ||
{ | ||
to: tx.to, | ||
amount: baseAmount(tx.value, decimals), | ||
}, | ||
], | ||
date: new Date(parseInt(tx.timeStamp) * 1000), | ||
type: TxType.Transfer, | ||
hash: tx.hash, | ||
}; | ||
}; | ||
/** | ||
* Get transactions from token tx | ||
* | ||
* @param {TokenTransactionInfo} tx | ||
* @returns {Tx|null} The parsed transaction. | ||
*/ | ||
const getTxFromTokenTransaction = (tx, chain, decimals) => { | ||
const decimal = parseInt(tx.tokenDecimal) || decimals; | ||
const symbol = tx.tokenSymbol; | ||
const address = tx.contractAddress; | ||
if (validateSymbol(symbol) && validateAddress(address)) { | ||
const tokenAsset = assetFromString(`${chain}.${symbol}-${address}`); | ||
if (tokenAsset) { | ||
return { | ||
asset: tokenAsset, | ||
from: [ | ||
{ | ||
from: tx.from, | ||
amount: baseAmount(tx.value, decimal), | ||
}, | ||
], | ||
to: [ | ||
{ | ||
to: tx.to, | ||
amount: baseAmount(tx.value, decimal), | ||
}, | ||
], | ||
date: new Date(parseInt(tx.timeStamp) * 1000), | ||
type: TxType.Transfer, | ||
hash: tx.hash, | ||
}; | ||
} | ||
} | ||
return null; | ||
}; | ||
/** | ||
* Get ETH transaction history | ||
* | ||
* @see https://etherscan.io/apis#accounts | ||
* | ||
* @param {string} baseUrl The etherscan node url. | ||
* @param {string} address The address. | ||
* @param {TransactionHistoryParam} params The search options. | ||
* @param {string} apiKey The etherscan API key. (optional) | ||
* @returns {ETHTransactionInfo[]} The ETH transaction history | ||
*/ | ||
const getGasAssetTransactionHistory = ({ gasAsset, gasDecimals, baseUrl, address, page, offset, startblock, endblock, apiKey, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
let url = baseUrl + `/api?module=account&action=txlist&sort=desc` + getApiKeyQueryParameter(apiKey); | ||
if (address) | ||
url += `&address=${address}`; | ||
if (offset) | ||
url += `&offset=${offset}`; | ||
if (page) | ||
url += `&page=${page}`; | ||
if (startblock) | ||
url += `&startblock=${startblock}`; | ||
if (endblock) | ||
url += `&endblock=${endblock}`; | ||
const result = (yield axios.get(url)).data.result; | ||
if (JSON.stringify(result).includes('Invalid API Key')) | ||
throw new Error('Invalid API Key'); | ||
if (typeof result !== 'object') | ||
throw new Error(result); | ||
return filterSelfTxs(result) | ||
.filter((tx) => !bnOrZero(tx.value).isZero()) | ||
.map((tx) => getTxFromEthTransaction(tx, gasAsset, gasDecimals)); | ||
}); | ||
/** | ||
* Get token transaction history | ||
* | ||
* @see https://etherscan.io/apis#accounts | ||
* | ||
* @param {string} baseUrl The etherscan node url. | ||
* @param {string} address The address. | ||
* @param {TransactionHistoryParam} params The search options. | ||
* @param {string} apiKey The etherscan API key. (optional) | ||
* @returns {Tx[]} The token transaction history | ||
*/ | ||
const getTokenTransactionHistory = ({ gasDecimals, baseUrl, address, assetAddress, page, offset, startblock, endblock, apiKey, chain, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
let url = baseUrl + `/api?module=account&action=tokentx&sort=desc` + getApiKeyQueryParameter(apiKey); | ||
if (address) | ||
url += `&address=${address}`; | ||
if (assetAddress) | ||
url += `&contractaddress=${assetAddress}`; | ||
if (offset) | ||
url += `&offset=${offset}`; | ||
if (page) | ||
url += `&page=${page}`; | ||
if (startblock) | ||
url += `&startblock=${startblock}`; | ||
if (endblock) | ||
url += `&endblock=${endblock}`; | ||
const result = (yield axios.get(url)).data.result; | ||
if (JSON.stringify(result).includes('Invalid API Key')) | ||
throw new Error('Invalid API Key'); | ||
return filterSelfTxs(result) | ||
.filter((tx) => !bnOrZero(tx.value).isZero()) | ||
.reduce((acc, cur) => { | ||
const tx = getTxFromTokenTransaction(cur, chain, gasDecimals); | ||
return tx ? [...acc, tx] : acc; | ||
}, []); | ||
}); | ||
class EtherscanProvider { | ||
constructor(provider, baseUrl, apiKey, chain, nativeAsset, nativeAssetDecimals) { | ||
this.provider = provider; | ||
this.baseUrl = baseUrl; | ||
this.apiKey = apiKey; | ||
this.chain = chain; | ||
this.nativeAsset = nativeAsset; | ||
this.nativeAssetDecimals = nativeAssetDecimals; | ||
this.nativeAsset; | ||
this.chain; | ||
} | ||
getBalance(address, assets) { | ||
roundRobinGetBalance(address, assets) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
//validate assets are for the correct chain | ||
assets === null || assets === void 0 ? void 0 : assets.forEach((i) => { | ||
if (i.chain !== this.chain) | ||
throw Error(`${assetToString(i)} is not an asset of ${this.chain}`); | ||
}); | ||
const balances = []; | ||
if (assets) { | ||
for (const asset of assets) { | ||
if (asset.symbol === this.nativeAsset.symbol) { | ||
balances.push(yield this.getNativeAssetBalance(address)); | ||
} | ||
else { | ||
const splitSymbol = asset.symbol.split('-'); | ||
const tokenSymbol = splitSymbol[0]; | ||
const contractAddress = splitSymbol[1]; | ||
balances.push(yield this.getTokenBalance(address, contractAddress, tokenSymbol)); | ||
} | ||
for (const provider of this.dataProviders) { | ||
try { | ||
const prov = provider[this.network]; | ||
if (prov) | ||
return yield prov.getBalance(address, assets); | ||
} | ||
} | ||
else { | ||
//get nativeAsset | ||
balances.push(yield this.getNativeAssetBalance(address)); | ||
// Get All Erc-20 txs | ||
const response = (yield axios.get(`${this.baseUrl}/api?module=account&action=tokentx&address=${address}&startblock=0&sort=asc&apikey=${this.apiKey}`)).data; | ||
const erc20TokenTxs = this.getUniqueContractAddresses(response.result); | ||
for (const erc20Token of erc20TokenTxs) { | ||
balances.push(yield this.getTokenBalance(address, erc20Token.contractAddress, erc20Token.tokenSymbol)); | ||
catch (error) { | ||
console.warn(error); | ||
} | ||
} | ||
return balances; | ||
throw Error('no provider able to get balance'); | ||
}); | ||
} | ||
getNativeAssetBalance(address) { | ||
roundRobinGetTransactionData(txId, assetAddress) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const gasAssetBalance = yield this.provider.getBalance(address); | ||
const amount = baseAmount(gasAssetBalance.toString(), this.nativeAssetDecimals); | ||
return { | ||
asset: this.nativeAsset, | ||
amount, | ||
}; | ||
}); | ||
} | ||
getTokenBalance(address, contractAddress, tokenTicker) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const asset = { | ||
chain: this.chain, | ||
symbol: `${tokenTicker}-${contractAddress}`, | ||
ticker: tokenTicker, | ||
synth: false, | ||
}; | ||
const contract = new ethers.Contract(contractAddress, erc20ABI, this.provider); | ||
const balance = (yield contract.balanceOf(address)).toString(); | ||
const decimals = (yield contract.decimals()).toString(); | ||
const amount = baseAmount(balance, Number.parseInt(decimals)); | ||
return { | ||
asset, | ||
amount, | ||
}; | ||
}); | ||
} | ||
getUniqueContractAddresses(array) { | ||
const mySet = new Set(); | ||
return array.filter((x) => { | ||
const key = x.contractAddress, isNew = !mySet.has(key); | ||
if (isNew) | ||
mySet.add(key); | ||
return isNew; | ||
}); | ||
} | ||
getTransactions(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const offset = (params === null || params === void 0 ? void 0 : params.offset) || 0; | ||
const limit = (params === null || params === void 0 ? void 0 : params.limit) || 10; | ||
const assetAddress = params === null || params === void 0 ? void 0 : params.asset; | ||
const maxCount = 10000; | ||
let transactions; | ||
if (assetAddress) { | ||
transactions = yield getTokenTransactionHistory({ | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
address: params === null || params === void 0 ? void 0 : params.address, | ||
assetAddress, | ||
page: 0, | ||
offset: maxCount, | ||
apiKey: this.apiKey, | ||
chain: this.chain, | ||
}); | ||
for (const provider of this.dataProviders) { | ||
try { | ||
const prov = provider[this.network]; | ||
if (prov) | ||
return yield prov.getTransactionData(txId, assetAddress); | ||
} | ||
catch (error) { | ||
console.warn(error); | ||
} | ||
} | ||
else { | ||
transactions = yield getGasAssetTransactionHistory({ | ||
gasAsset: this.nativeAsset, | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
address: params === null || params === void 0 ? void 0 : params.address, | ||
page: 0, | ||
offset: maxCount, | ||
apiKey: this.apiKey, | ||
}); | ||
} | ||
return { | ||
total: transactions.length, | ||
txs: transactions.filter((_, index) => index >= offset && index < offset + limit), | ||
}; | ||
throw Error('no provider able to GetTransactionData'); | ||
}); | ||
} | ||
getTransactionData(txHash, assetAddress) { | ||
var _a, _b; | ||
roundRobinGetTransactions(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let tx; | ||
const txInfo = yield this.provider.getTransaction(txHash); | ||
if (txInfo) { | ||
if (assetAddress) { | ||
tx = | ||
(_a = (yield getTokenTransactionHistory({ | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
assetAddress, | ||
startblock: txInfo.blockNumber, | ||
endblock: txInfo.blockNumber, | ||
apiKey: this.apiKey, | ||
chain: this.chain, | ||
})).filter((info) => info.hash === txHash)[0]) !== null && _a !== void 0 ? _a : null; | ||
for (const provider of this.dataProviders) { | ||
try { | ||
const prov = provider[this.network]; | ||
if (prov) | ||
return yield prov.getTransactions(params); | ||
} | ||
else { | ||
tx = | ||
(_b = (yield getGasAssetTransactionHistory({ | ||
gasAsset: this.nativeAsset, | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
startblock: txInfo.blockNumber, | ||
endblock: txInfo.blockNumber, | ||
apiKey: this.apiKey, | ||
address: txInfo.from, | ||
})).filter((info) => info.hash === txHash)[0]) !== null && _b !== void 0 ? _b : null; | ||
catch (error) { | ||
console.warn(error); | ||
} | ||
} | ||
if (!tx) | ||
throw new Error('Could not get transaction history'); | ||
return tx; | ||
throw Error('no provider able to GetTransactions'); | ||
}); | ||
@@ -1954,6 +1428,6 @@ } | ||
router: routerABI, | ||
erc20: erc20ABI$1, | ||
erc20: erc20ABI, | ||
}; | ||
export { Client, EtherscanProvider, MAX_APPROVAL, abi, call, estimateApprove, estimateCall, getApprovalAmount, getPrefix, getTokenAddress, isApproved, strip0x, validateAddress$1 as validateAddress }; | ||
export { Client, MAX_APPROVAL, abi, call, estimateApprove, estimateCall, getApprovalAmount, getPrefix, getTokenAddress, isApproved, strip0x, validateAddress }; | ||
//# sourceMappingURL=index.esm.js.map |
615
lib/index.js
@@ -9,8 +9,3 @@ 'use strict'; | ||
var utils = require('ethers/lib/utils'); | ||
var axios = require('axios'); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios); | ||
/****************************************************************************** | ||
@@ -41,3 +36,3 @@ Copyright (c) Microsoft Corporation. | ||
var erc20ABI$1 = [ | ||
var erc20ABI = [ | ||
{ | ||
@@ -284,3 +279,3 @@ inputs: [ | ||
*/ | ||
const validateAddress$1 = (address) => { | ||
const validateAddress = (address) => { | ||
try { | ||
@@ -398,3 +393,3 @@ ethers.ethers.utils.getAddress(address); | ||
const txAmount = ethers.ethers.BigNumber.from((_a = amount === null || amount === void 0 ? void 0 : amount.amount().toFixed()) !== null && _a !== void 0 ? _a : 1); | ||
const contract = new ethers.ethers.Contract(contractAddress, erc20ABI$1, provider); | ||
const contract = new ethers.ethers.Contract(contractAddress, erc20ABI, provider); | ||
const allowance = yield contract.allowance(fromAddress, spenderAddress); | ||
@@ -560,3 +555,3 @@ return txAmount.lte(allowance); | ||
validateAddress(address) { | ||
return validateAddress$1(address); | ||
return validateAddress(address); | ||
} | ||
@@ -573,6 +568,3 @@ /** | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const prov = this.dataProviders[this.network]; | ||
if (!prov) | ||
throw Error('Provider unidefined'); | ||
return yield prov.getBalance(address, assets); | ||
return yield this.roundRobinGetBalance(address, assets); | ||
}); | ||
@@ -596,6 +588,3 @@ } | ||
}; | ||
const prov = this.dataProviders[this.network]; | ||
if (!prov) | ||
throw Error('Provider unidefined'); | ||
return yield prov.getTransactions(filteredParams); | ||
return yield this.roundRobinGetTransactions(filteredParams); | ||
}); | ||
@@ -615,6 +604,3 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const prov = this.dataProviders[this.network]; | ||
if (!prov) | ||
throw Error('Provider unidefined'); | ||
return yield prov.getTransactionData(txId, assetAddress); | ||
return yield this.roundRobinGetTransactionData(txId, assetAddress); | ||
}); | ||
@@ -716,3 +702,3 @@ } | ||
const valueToApprove = getApprovalAmount(amount); | ||
const contract = new ethers.ethers.Contract(contractAddress, erc20ABI$1, this.getProvider()); | ||
const contract = new ethers.ethers.Contract(contractAddress, erc20ABI, this.getProvider()); | ||
/* as same as ethers.TransactionResponse expected by `sendTransaction` */ | ||
@@ -742,3 +728,3 @@ const unsignedTx = yield contract.populateTransaction.approve(spenderAddress, valueToApprove); | ||
fromAddress, | ||
abi: erc20ABI$1, | ||
abi: erc20ABI, | ||
amount, | ||
@@ -809,3 +795,3 @@ }); | ||
contractAddress: assetAddress, | ||
abi: erc20ABI$1, | ||
abi: erc20ABI, | ||
funcName: 'transfer', | ||
@@ -886,3 +872,3 @@ funcParams: [recipient, txAmount, Object.assign({}, overrides)], | ||
throw Error(`Can't get address from asset ${xchainUtil.assetToString(theAsset)}`); | ||
const contract = new ethers.ethers.Contract(assetAddress, erc20ABI$1, this.getProvider()); | ||
const contract = new ethers.ethers.Contract(assetAddress, erc20ABI, this.getProvider()); | ||
gasEstimate = yield contract.estimateGas.transfer(recipient, txAmount, { | ||
@@ -947,561 +933,45 @@ from: this.getAddress(), | ||
} | ||
} | ||
var erc20ABI = [ | ||
{ | ||
inputs: [ | ||
], | ||
stateMutability: "nonpayable", | ||
type: "constructor" | ||
}, | ||
{ | ||
anonymous: false, | ||
inputs: [ | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "owner", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "spender", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: false, | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "Approval", | ||
type: "event" | ||
}, | ||
{ | ||
anonymous: false, | ||
inputs: [ | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "from", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: true, | ||
internalType: "address", | ||
name: "to", | ||
type: "address" | ||
}, | ||
{ | ||
indexed: false, | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "Transfer", | ||
type: "event" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "address", | ||
name: "", | ||
type: "address" | ||
} | ||
], | ||
name: "allowance", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "spender", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "approve", | ||
outputs: [ | ||
{ | ||
internalType: "bool", | ||
name: "success", | ||
type: "bool" | ||
} | ||
], | ||
stateMutability: "nonpayable", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "", | ||
type: "address" | ||
} | ||
], | ||
name: "balanceOf", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "decimals", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "name", | ||
outputs: [ | ||
{ | ||
internalType: "string", | ||
name: "", | ||
type: "string" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "symbol", | ||
outputs: [ | ||
{ | ||
internalType: "string", | ||
name: "", | ||
type: "string" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
], | ||
name: "totalSupply", | ||
outputs: [ | ||
{ | ||
internalType: "uint256", | ||
name: "", | ||
type: "uint256" | ||
} | ||
], | ||
stateMutability: "view", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "to", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "transfer", | ||
outputs: [ | ||
{ | ||
internalType: "bool", | ||
name: "success", | ||
type: "bool" | ||
} | ||
], | ||
stateMutability: "nonpayable", | ||
type: "function" | ||
}, | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address", | ||
name: "from", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "address", | ||
name: "to", | ||
type: "address" | ||
}, | ||
{ | ||
internalType: "uint256", | ||
name: "value", | ||
type: "uint256" | ||
} | ||
], | ||
name: "transferFrom", | ||
outputs: [ | ||
{ | ||
internalType: "bool", | ||
name: "success", | ||
type: "bool" | ||
} | ||
], | ||
stateMutability: "nonpayable", | ||
type: "function" | ||
} | ||
]; | ||
ethers.ethers.BigNumber.from(2).pow(256).sub(1); | ||
/** | ||
* Validate the given address. | ||
* | ||
* @param {Address} address | ||
* @returns {boolean} `true` or `false` | ||
*/ | ||
const validateAddress = (address) => { | ||
try { | ||
ethers.ethers.utils.getAddress(address); | ||
return true; | ||
} | ||
catch (error) { | ||
return false; | ||
} | ||
}; | ||
const getApiKeyQueryParameter = (apiKey) => (!!apiKey ? `&apiKey=${apiKey}` : ''); | ||
/** | ||
* Filter self txs | ||
* | ||
* @returns {T[]} | ||
* | ||
**/ | ||
const filterSelfTxs = (txs) => { | ||
const filterTxs = txs.filter((tx) => tx.from !== tx.to); | ||
let selfTxs = txs.filter((tx) => tx.from === tx.to); | ||
while (selfTxs.length) { | ||
const selfTx = selfTxs[0]; | ||
filterTxs.push(selfTx); | ||
selfTxs = selfTxs.filter((tx) => tx.hash !== selfTx.hash); | ||
} | ||
return filterTxs; | ||
}; | ||
/** | ||
* Check if the symbol is valid. | ||
* | ||
* @param {string|null|undefined} symbol | ||
* @returns {boolean} `true` or `false`. | ||
*/ | ||
const validateSymbol = (symbol) => (symbol ? symbol.length >= 3 : false); | ||
/** | ||
* Get transactions from ETH transaction | ||
* | ||
* @param {ETHTransactionInfo} tx | ||
* @returns {Tx} The parsed transaction. | ||
*/ | ||
const getTxFromEthTransaction = (tx, gasAsset, decimals) => { | ||
return { | ||
asset: gasAsset, | ||
from: [ | ||
{ | ||
from: tx.from, | ||
amount: xchainUtil.baseAmount(tx.value, decimals), | ||
}, | ||
], | ||
to: [ | ||
{ | ||
to: tx.to, | ||
amount: xchainUtil.baseAmount(tx.value, decimals), | ||
}, | ||
], | ||
date: new Date(parseInt(tx.timeStamp) * 1000), | ||
type: xchainClient.TxType.Transfer, | ||
hash: tx.hash, | ||
}; | ||
}; | ||
/** | ||
* Get transactions from token tx | ||
* | ||
* @param {TokenTransactionInfo} tx | ||
* @returns {Tx|null} The parsed transaction. | ||
*/ | ||
const getTxFromTokenTransaction = (tx, chain, decimals) => { | ||
const decimal = parseInt(tx.tokenDecimal) || decimals; | ||
const symbol = tx.tokenSymbol; | ||
const address = tx.contractAddress; | ||
if (validateSymbol(symbol) && validateAddress(address)) { | ||
const tokenAsset = xchainUtil.assetFromString(`${chain}.${symbol}-${address}`); | ||
if (tokenAsset) { | ||
return { | ||
asset: tokenAsset, | ||
from: [ | ||
{ | ||
from: tx.from, | ||
amount: xchainUtil.baseAmount(tx.value, decimal), | ||
}, | ||
], | ||
to: [ | ||
{ | ||
to: tx.to, | ||
amount: xchainUtil.baseAmount(tx.value, decimal), | ||
}, | ||
], | ||
date: new Date(parseInt(tx.timeStamp) * 1000), | ||
type: xchainClient.TxType.Transfer, | ||
hash: tx.hash, | ||
}; | ||
} | ||
} | ||
return null; | ||
}; | ||
/** | ||
* Get ETH transaction history | ||
* | ||
* @see https://etherscan.io/apis#accounts | ||
* | ||
* @param {string} baseUrl The etherscan node url. | ||
* @param {string} address The address. | ||
* @param {TransactionHistoryParam} params The search options. | ||
* @param {string} apiKey The etherscan API key. (optional) | ||
* @returns {ETHTransactionInfo[]} The ETH transaction history | ||
*/ | ||
const getGasAssetTransactionHistory = ({ gasAsset, gasDecimals, baseUrl, address, page, offset, startblock, endblock, apiKey, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
let url = baseUrl + `/api?module=account&action=txlist&sort=desc` + getApiKeyQueryParameter(apiKey); | ||
if (address) | ||
url += `&address=${address}`; | ||
if (offset) | ||
url += `&offset=${offset}`; | ||
if (page) | ||
url += `&page=${page}`; | ||
if (startblock) | ||
url += `&startblock=${startblock}`; | ||
if (endblock) | ||
url += `&endblock=${endblock}`; | ||
const result = (yield axios__default["default"].get(url)).data.result; | ||
if (JSON.stringify(result).includes('Invalid API Key')) | ||
throw new Error('Invalid API Key'); | ||
if (typeof result !== 'object') | ||
throw new Error(result); | ||
return filterSelfTxs(result) | ||
.filter((tx) => !xchainUtil.bnOrZero(tx.value).isZero()) | ||
.map((tx) => getTxFromEthTransaction(tx, gasAsset, gasDecimals)); | ||
}); | ||
/** | ||
* Get token transaction history | ||
* | ||
* @see https://etherscan.io/apis#accounts | ||
* | ||
* @param {string} baseUrl The etherscan node url. | ||
* @param {string} address The address. | ||
* @param {TransactionHistoryParam} params The search options. | ||
* @param {string} apiKey The etherscan API key. (optional) | ||
* @returns {Tx[]} The token transaction history | ||
*/ | ||
const getTokenTransactionHistory = ({ gasDecimals, baseUrl, address, assetAddress, page, offset, startblock, endblock, apiKey, chain, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
let url = baseUrl + `/api?module=account&action=tokentx&sort=desc` + getApiKeyQueryParameter(apiKey); | ||
if (address) | ||
url += `&address=${address}`; | ||
if (assetAddress) | ||
url += `&contractaddress=${assetAddress}`; | ||
if (offset) | ||
url += `&offset=${offset}`; | ||
if (page) | ||
url += `&page=${page}`; | ||
if (startblock) | ||
url += `&startblock=${startblock}`; | ||
if (endblock) | ||
url += `&endblock=${endblock}`; | ||
const result = (yield axios__default["default"].get(url)).data.result; | ||
if (JSON.stringify(result).includes('Invalid API Key')) | ||
throw new Error('Invalid API Key'); | ||
return filterSelfTxs(result) | ||
.filter((tx) => !xchainUtil.bnOrZero(tx.value).isZero()) | ||
.reduce((acc, cur) => { | ||
const tx = getTxFromTokenTransaction(cur, chain, gasDecimals); | ||
return tx ? [...acc, tx] : acc; | ||
}, []); | ||
}); | ||
class EtherscanProvider { | ||
constructor(provider, baseUrl, apiKey, chain, nativeAsset, nativeAssetDecimals) { | ||
this.provider = provider; | ||
this.baseUrl = baseUrl; | ||
this.apiKey = apiKey; | ||
this.chain = chain; | ||
this.nativeAsset = nativeAsset; | ||
this.nativeAssetDecimals = nativeAssetDecimals; | ||
this.nativeAsset; | ||
this.chain; | ||
} | ||
getBalance(address, assets) { | ||
roundRobinGetBalance(address, assets) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
//validate assets are for the correct chain | ||
assets === null || assets === void 0 ? void 0 : assets.forEach((i) => { | ||
if (i.chain !== this.chain) | ||
throw Error(`${xchainUtil.assetToString(i)} is not an asset of ${this.chain}`); | ||
}); | ||
const balances = []; | ||
if (assets) { | ||
for (const asset of assets) { | ||
if (asset.symbol === this.nativeAsset.symbol) { | ||
balances.push(yield this.getNativeAssetBalance(address)); | ||
} | ||
else { | ||
const splitSymbol = asset.symbol.split('-'); | ||
const tokenSymbol = splitSymbol[0]; | ||
const contractAddress = splitSymbol[1]; | ||
balances.push(yield this.getTokenBalance(address, contractAddress, tokenSymbol)); | ||
} | ||
for (const provider of this.dataProviders) { | ||
try { | ||
const prov = provider[this.network]; | ||
if (prov) | ||
return yield prov.getBalance(address, assets); | ||
} | ||
} | ||
else { | ||
//get nativeAsset | ||
balances.push(yield this.getNativeAssetBalance(address)); | ||
// Get All Erc-20 txs | ||
const response = (yield axios__default["default"].get(`${this.baseUrl}/api?module=account&action=tokentx&address=${address}&startblock=0&sort=asc&apikey=${this.apiKey}`)).data; | ||
const erc20TokenTxs = this.getUniqueContractAddresses(response.result); | ||
for (const erc20Token of erc20TokenTxs) { | ||
balances.push(yield this.getTokenBalance(address, erc20Token.contractAddress, erc20Token.tokenSymbol)); | ||
catch (error) { | ||
console.warn(error); | ||
} | ||
} | ||
return balances; | ||
throw Error('no provider able to get balance'); | ||
}); | ||
} | ||
getNativeAssetBalance(address) { | ||
roundRobinGetTransactionData(txId, assetAddress) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const gasAssetBalance = yield this.provider.getBalance(address); | ||
const amount = xchainUtil.baseAmount(gasAssetBalance.toString(), this.nativeAssetDecimals); | ||
return { | ||
asset: this.nativeAsset, | ||
amount, | ||
}; | ||
}); | ||
} | ||
getTokenBalance(address, contractAddress, tokenTicker) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const asset = { | ||
chain: this.chain, | ||
symbol: `${tokenTicker}-${contractAddress}`, | ||
ticker: tokenTicker, | ||
synth: false, | ||
}; | ||
const contract = new ethers.ethers.Contract(contractAddress, erc20ABI, this.provider); | ||
const balance = (yield contract.balanceOf(address)).toString(); | ||
const decimals = (yield contract.decimals()).toString(); | ||
const amount = xchainUtil.baseAmount(balance, Number.parseInt(decimals)); | ||
return { | ||
asset, | ||
amount, | ||
}; | ||
}); | ||
} | ||
getUniqueContractAddresses(array) { | ||
const mySet = new Set(); | ||
return array.filter((x) => { | ||
const key = x.contractAddress, isNew = !mySet.has(key); | ||
if (isNew) | ||
mySet.add(key); | ||
return isNew; | ||
}); | ||
} | ||
getTransactions(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const offset = (params === null || params === void 0 ? void 0 : params.offset) || 0; | ||
const limit = (params === null || params === void 0 ? void 0 : params.limit) || 10; | ||
const assetAddress = params === null || params === void 0 ? void 0 : params.asset; | ||
const maxCount = 10000; | ||
let transactions; | ||
if (assetAddress) { | ||
transactions = yield getTokenTransactionHistory({ | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
address: params === null || params === void 0 ? void 0 : params.address, | ||
assetAddress, | ||
page: 0, | ||
offset: maxCount, | ||
apiKey: this.apiKey, | ||
chain: this.chain, | ||
}); | ||
for (const provider of this.dataProviders) { | ||
try { | ||
const prov = provider[this.network]; | ||
if (prov) | ||
return yield prov.getTransactionData(txId, assetAddress); | ||
} | ||
catch (error) { | ||
console.warn(error); | ||
} | ||
} | ||
else { | ||
transactions = yield getGasAssetTransactionHistory({ | ||
gasAsset: this.nativeAsset, | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
address: params === null || params === void 0 ? void 0 : params.address, | ||
page: 0, | ||
offset: maxCount, | ||
apiKey: this.apiKey, | ||
}); | ||
} | ||
return { | ||
total: transactions.length, | ||
txs: transactions.filter((_, index) => index >= offset && index < offset + limit), | ||
}; | ||
throw Error('no provider able to GetTransactionData'); | ||
}); | ||
} | ||
getTransactionData(txHash, assetAddress) { | ||
var _a, _b; | ||
roundRobinGetTransactions(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let tx; | ||
const txInfo = yield this.provider.getTransaction(txHash); | ||
if (txInfo) { | ||
if (assetAddress) { | ||
tx = | ||
(_a = (yield getTokenTransactionHistory({ | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
assetAddress, | ||
startblock: txInfo.blockNumber, | ||
endblock: txInfo.blockNumber, | ||
apiKey: this.apiKey, | ||
chain: this.chain, | ||
})).filter((info) => info.hash === txHash)[0]) !== null && _a !== void 0 ? _a : null; | ||
for (const provider of this.dataProviders) { | ||
try { | ||
const prov = provider[this.network]; | ||
if (prov) | ||
return yield prov.getTransactions(params); | ||
} | ||
else { | ||
tx = | ||
(_b = (yield getGasAssetTransactionHistory({ | ||
gasAsset: this.nativeAsset, | ||
gasDecimals: this.nativeAssetDecimals, | ||
baseUrl: this.baseUrl, | ||
startblock: txInfo.blockNumber, | ||
endblock: txInfo.blockNumber, | ||
apiKey: this.apiKey, | ||
address: txInfo.from, | ||
})).filter((info) => info.hash === txHash)[0]) !== null && _b !== void 0 ? _b : null; | ||
catch (error) { | ||
console.warn(error); | ||
} | ||
} | ||
if (!tx) | ||
throw new Error('Could not get transaction history'); | ||
return tx; | ||
throw Error('no provider able to GetTransactions'); | ||
}); | ||
@@ -1963,7 +1433,6 @@ } | ||
router: routerABI, | ||
erc20: erc20ABI$1, | ||
erc20: erc20ABI, | ||
}; | ||
exports.Client = Client; | ||
exports.EtherscanProvider = EtherscanProvider; | ||
exports.MAX_APPROVAL = MAX_APPROVAL; | ||
@@ -1979,3 +1448,3 @@ exports.abi = abi; | ||
exports.strip0x = strip0x; | ||
exports.validateAddress = validateAddress$1; | ||
exports.validateAddress = validateAddress; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@xchainjs/xchain-evm", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "Genereic EVM client for XChainJS", | ||
@@ -40,2 +40,3 @@ "keywords": [ | ||
"@xchainjs/xchain-util": "^0.13.0", | ||
"@xchainjs/xchain-evm-providers": "^0.1.0", | ||
"axios": "^1.3.6", | ||
@@ -51,2 +52,2 @@ "ethers": "^5.7.2" | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
206992
6
12
3450