UNPKG

@arbitrum/sdk

Version:

Typescript library client-side interactions with Arbitrum

195 lines (194 loc) 8.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getBlockRangesForL1Block = exports.isArbitrumChain = exports.isDefined = exports.getTransactionReceipt = exports.getBaseFee = exports.wait = void 0; exports.getFirstBlockForL1Block = getFirstBlockForL1Block; exports.getNativeTokenDecimals = getNativeTokenDecimals; exports.scaleFrom18DecimalsToNativeTokenDecimals = scaleFrom18DecimalsToNativeTokenDecimals; exports.scaleFromNativeTokenDecimalsTo18Decimals = scaleFromNativeTokenDecimalsTo18Decimals; const ethers_1 = require("ethers"); const errors_1 = require("../dataEntities/errors"); const arbProvider_1 = require("./arbProvider"); const ArbSys__factory_1 = require("../abi/factories/ArbSys__factory"); const constants_1 = require("../dataEntities/constants"); const networks_1 = require("../dataEntities/networks"); const ERC20__factory_1 = require("../abi/factories/ERC20__factory"); const wait = (ms) => new Promise(res => setTimeout(res, ms)); exports.wait = wait; const getBaseFee = async (provider) => { const baseFee = (await provider.getBlock('latest')).baseFeePerGas; if (!baseFee) { throw new errors_1.ArbSdkError('Latest block did not contain base fee, ensure provider is connected to a network that supports EIP 1559.'); } return baseFee; }; exports.getBaseFee = getBaseFee; /** * Waits for a transaction receipt if confirmations or timeout is provided * Otherwise tries to fetch straight away. * @param provider * @param txHash * @param confirmations * @param timeout * @returns */ const getTransactionReceipt = async (provider, txHash, confirmations, timeout) => { if (confirmations || timeout) { try { const receipt = await provider.waitForTransaction(txHash, confirmations, timeout); return receipt || null; } catch (err) { if (err.message.includes('timeout exceeded')) { // return null return null; } else throw err; } } else { const receipt = await provider.getTransactionReceipt(txHash); return receipt || null; } }; exports.getTransactionReceipt = getTransactionReceipt; const isDefined = (val) => typeof val !== 'undefined' && val !== null; exports.isDefined = isDefined; const isArbitrumChain = async (provider) => { try { await ArbSys__factory_1.ArbSys__factory.connect(constants_1.ARB_SYS_ADDRESS, provider).arbOSVersion(); } catch (error) { return false; } return true; }; exports.isArbitrumChain = isArbitrumChain; /** * This function performs a binary search to find the first Arbitrum block that corresponds to a given L1 block number. * The function returns a Promise that resolves to a number if a block is found, or undefined otherwise. * * @param {JsonRpcProvider} arbitrumProvider - The Arbitrum provider to use for the search. * @param {number} forL1Block - The L1 block number to search for. * @param {boolean} [allowGreater=false] - Whether to allow the search to go past the specified `forL1Block`. * @param {number|string} minArbitrumBlock - The minimum Arbitrum block number to start the search from. Cannot be below the network's Nitro genesis block. * @param {number|string} [maxArbitrumBlock='latest'] - The maximum Arbitrum block number to end the search at. Can be a `number` or `'latest'`. `'latest'` is the current block. * @returns {Promise<number | undefined>} - A Promise that resolves to a number if a block is found, or undefined otherwise. */ async function getFirstBlockForL1Block({ arbitrumProvider, forL1Block, allowGreater = false, minArbitrumBlock, maxArbitrumBlock = 'latest', }) { if (!(await (0, exports.isArbitrumChain)(arbitrumProvider))) { // Provider is L1. return forL1Block; } const arbProvider = new arbProvider_1.ArbitrumProvider(arbitrumProvider); const currentArbBlock = await arbProvider.getBlockNumber(); const arbitrumChainId = (await arbProvider.getNetwork()).chainId; const nitroGenesisBlock = (0, networks_1.getNitroGenesisBlock)(arbitrumChainId); async function getL1Block(forL2Block) { const { l1BlockNumber } = await arbProvider.getBlock(forL2Block); return l1BlockNumber; } if (!minArbitrumBlock) { minArbitrumBlock = nitroGenesisBlock; } if (maxArbitrumBlock === 'latest') { maxArbitrumBlock = currentArbBlock; } if (minArbitrumBlock >= maxArbitrumBlock) { throw new Error(`'minArbitrumBlock' (${minArbitrumBlock}) must be lower than 'maxArbitrumBlock' (${maxArbitrumBlock}).`); } if (minArbitrumBlock < nitroGenesisBlock) { throw new Error(`'minArbitrumBlock' (${minArbitrumBlock}) cannot be below the Nitro genesis block, which is ${nitroGenesisBlock} for the current network.`); } let start = minArbitrumBlock; let end = maxArbitrumBlock; let resultForTargetBlock; let resultForGreaterBlock; while (start <= end) { // Calculate the midpoint of the current range. const mid = start + Math.floor((end - start) / 2); const l1Block = await getL1Block(mid); // If the midpoint matches the target, we've found a match. // Adjust the range to search for the first occurrence. if (l1Block === forL1Block) { end = mid - 1; } else if (l1Block < forL1Block) { start = mid + 1; } else { end = mid - 1; } // Stores last valid Arbitrum block corresponding to the current, or greater, L1 block. if (l1Block) { if (l1Block === forL1Block) { resultForTargetBlock = mid; } if (allowGreater && l1Block > forL1Block) { resultForGreaterBlock = mid; } } } return resultForTargetBlock !== null && resultForTargetBlock !== void 0 ? resultForTargetBlock : resultForGreaterBlock; } const getBlockRangesForL1Block = async (props) => { const arbProvider = new arbProvider_1.ArbitrumProvider(props.arbitrumProvider); const currentArbitrumBlock = await arbProvider.getBlockNumber(); if (!props.maxArbitrumBlock || props.maxArbitrumBlock === 'latest') { props.maxArbitrumBlock = currentArbitrumBlock; } const result = await Promise.all([ getFirstBlockForL1Block(Object.assign(Object.assign({}, props), { allowGreater: false })), getFirstBlockForL1Block(Object.assign(Object.assign({}, props), { forL1Block: props.forL1Block + 1, allowGreater: true })), ]); if (!result[0]) { // If there's no start of the range, there won't be the end either. return [undefined, undefined]; } if (result[0] && result[1]) { // If both results are defined, we can assume that the previous Arbitrum block for the end of the range will be for 'forL1Block'. return [result[0], result[1] - 1]; } return [result[0], props.maxArbitrumBlock]; }; exports.getBlockRangesForL1Block = getBlockRangesForL1Block; async function getNativeTokenDecimals({ parentProvider, childNetwork, }) { const nativeTokenAddress = childNetwork.nativeToken; if (!nativeTokenAddress || nativeTokenAddress === ethers_1.constants.AddressZero) { return 18; } const nativeTokenContract = ERC20__factory_1.ERC20__factory.connect(nativeTokenAddress, parentProvider); try { return await nativeTokenContract.decimals(); } catch (_a) { return 0; } } function scaleFrom18DecimalsToNativeTokenDecimals({ amount, decimals, }) { // do nothing for 18 decimals if (decimals === 18) { return amount; } if (decimals < 18) { const scaledAmount = amount.div(ethers_1.BigNumber.from(10).pow(ethers_1.BigNumber.from(18 - decimals))); // round up if necessary if (scaledAmount .mul(ethers_1.BigNumber.from(10).pow(ethers_1.BigNumber.from(18 - decimals))) .lt(amount)) { return scaledAmount.add(ethers_1.BigNumber.from(1)); } return scaledAmount; } // decimals > 18 return amount.mul(ethers_1.BigNumber.from(10).pow(ethers_1.BigNumber.from(decimals - 18))); } function scaleFromNativeTokenDecimalsTo18Decimals({ amount, decimals, }) { if (decimals < 18) { return amount.mul(ethers_1.BigNumber.from(10).pow(18 - decimals)); } else if (decimals > 18) { return amount.div(ethers_1.BigNumber.from(10).pow(decimals - 18)); } return amount; }