@arbitrum/sdk
Version:
Typescript library client-side interactions with Arbitrum
735 lines (734 loc) • 38 kB
JavaScript
/*
* Copyright 2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-env node */
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminErc20Bridger = exports.Erc20Bridger = void 0;
const constants_1 = require("@ethersproject/constants");
const logger_1 = require("@ethersproject/logger");
const ethers_1 = require("ethers");
const L1GatewayRouter__factory_1 = require("../abi/factories/L1GatewayRouter__factory");
const L2GatewayRouter__factory_1 = require("../abi/factories/L2GatewayRouter__factory");
const L1WethGateway__factory_1 = require("../abi/factories/L1WethGateway__factory");
const L2ArbitrumGateway__factory_1 = require("../abi/factories/L2ArbitrumGateway__factory");
const ERC20__factory_1 = require("../abi/factories/ERC20__factory");
const L2GatewayToken__factory_1 = require("../abi/factories/L2GatewayToken__factory");
const ICustomToken__factory_1 = require("../abi/factories/ICustomToken__factory");
const IArbToken__factory_1 = require("../abi/factories/IArbToken__factory");
const ParentToChildMessageGasEstimator_1 = require("../message/ParentToChildMessageGasEstimator");
const signerOrProvider_1 = require("../dataEntities/signerOrProvider");
const networks_1 = require("../dataEntities/networks");
const errors_1 = require("../dataEntities/errors");
const constants_2 = require("../dataEntities/constants");
const eventFetcher_1 = require("../utils/eventFetcher");
const assetBridger_1 = require("./assetBridger");
const ParentTransaction_1 = require("../message/ParentTransaction");
const ChildTransaction_1 = require("../message/ChildTransaction");
const transactionRequest_1 = require("../dataEntities/transactionRequest");
const utils_1 = require("ethers/lib/utils");
const retryableData_1 = require("../dataEntities/retryableData");
const lib_1 = require("../utils/lib");
const L2ERC20Gateway__factory_1 = require("../abi/factories/L2ERC20Gateway__factory");
const calldata_1 = require("../utils/calldata");
/**
* Bridger for moving ERC20 tokens back and forth between parent-to-child
*/
class Erc20Bridger extends assetBridger_1.AssetBridger {
/**
* Bridger for moving ERC20 tokens back and forth between parent-to-child
*/
constructor(childNetwork) {
super(childNetwork);
(0, networks_1.assertArbitrumNetworkHasTokenBridge)(childNetwork);
this.childNetwork = childNetwork;
}
/**
* Instantiates a new Erc20Bridger from a child provider
* @param childProvider
* @returns
*/
static async fromProvider(childProvider) {
return new Erc20Bridger(await (0, networks_1.getArbitrumNetwork)(childProvider));
}
/**
* Get the address of the parent gateway for this token
* @param erc20ParentAddress
* @param parentProvider
* @returns
*/
async getParentGatewayAddress(erc20ParentAddress, parentProvider) {
await this.checkParentNetwork(parentProvider);
return await L1GatewayRouter__factory_1.L1GatewayRouter__factory.connect(this.childNetwork.tokenBridge.parentGatewayRouter, parentProvider).getGateway(erc20ParentAddress);
}
/**
* Get the address of the child gateway for this token
* @param erc20ParentAddress
* @param childProvider
* @returns
*/
async getChildGatewayAddress(erc20ParentAddress, childProvider) {
await this.checkChildNetwork(childProvider);
return await L2GatewayRouter__factory_1.L2GatewayRouter__factory.connect(this.childNetwork.tokenBridge.childGatewayRouter, childProvider).getGateway(erc20ParentAddress);
}
/**
* Creates a transaction request for approving the custom gas token to be spent by the relevant gateway on the parent network
* @param params
*/
async getApproveGasTokenRequest(params) {
if (this.nativeTokenIsEth) {
throw new Error('chain uses ETH as its native/gas token');
}
const txRequest = await this.getApproveTokenRequest(params);
// just reuse the approve token request but direct it towards the native token contract
return Object.assign(Object.assign({}, txRequest), { to: this.nativeToken });
}
/**
* Approves the custom gas token to be spent by the relevant gateway on the parent network
* @param params
*/
async approveGasToken(params) {
if (this.nativeTokenIsEth) {
throw new Error('chain uses ETH as its native/gas token');
}
await this.checkParentNetwork(params.parentSigner);
const approveGasTokenRequest = this.isApproveParams(params)
? await this.getApproveGasTokenRequest(Object.assign(Object.assign({}, params), { parentProvider: signerOrProvider_1.SignerProviderUtils.getProviderOrThrow(params.parentSigner) }))
: params.txRequest;
return params.parentSigner.sendTransaction(Object.assign(Object.assign({}, approveGasTokenRequest), params.overrides));
}
/**
* Get a tx request to approve tokens for deposit to the bridge.
* The tokens will be approved for the relevant gateway.
* @param params
* @returns
*/
async getApproveTokenRequest(params) {
// you approve tokens to the gateway that the router will use
const gatewayAddress = await this.getParentGatewayAddress(params.erc20ParentAddress, signerOrProvider_1.SignerProviderUtils.getProviderOrThrow(params.parentProvider));
const iErc20Interface = ERC20__factory_1.ERC20__factory.createInterface();
const data = iErc20Interface.encodeFunctionData('approve', [
gatewayAddress,
params.amount || Erc20Bridger.MAX_APPROVAL,
]);
return {
to: params.erc20ParentAddress,
data,
value: ethers_1.BigNumber.from(0),
};
}
isApproveParams(params) {
return params.erc20ParentAddress != undefined;
}
/**
* Approve tokens for deposit to the bridge. The tokens will be approved for the relevant gateway.
* @param params
* @returns
*/
async approveToken(params) {
await this.checkParentNetwork(params.parentSigner);
const approveRequest = this.isApproveParams(params)
? await this.getApproveTokenRequest(Object.assign(Object.assign({}, params), { parentProvider: signerOrProvider_1.SignerProviderUtils.getProviderOrThrow(params.parentSigner) }))
: params.txRequest;
return await params.parentSigner.sendTransaction(Object.assign(Object.assign({}, approveRequest), params.overrides));
}
/**
* Get the child network events created by a withdrawal
* @param childProvider
* @param gatewayAddress
* @param parentTokenAddress
* @param fromAddress
* @param filter
* @returns
*/
async getWithdrawalEvents(childProvider, gatewayAddress, filter, parentTokenAddress, fromAddress, toAddress) {
await this.checkChildNetwork(childProvider);
const eventFetcher = new eventFetcher_1.EventFetcher(childProvider);
const events = (await eventFetcher.getEvents(L2ArbitrumGateway__factory_1.L2ArbitrumGateway__factory, contract => contract.filters.WithdrawalInitiated(null, // parentToken
fromAddress || null, // _from
toAddress || null // _to
), Object.assign(Object.assign({}, filter), { address: gatewayAddress }))).map(a => (Object.assign({ txHash: a.transactionHash }, a.event)));
return parentTokenAddress
? events.filter(log => log.l1Token.toLocaleLowerCase() ===
parentTokenAddress.toLocaleLowerCase())
: events;
}
/**
* Does the provided address look like a weth gateway
* @param potentialWethGatewayAddress
* @param parentProvider
* @returns
*/
async looksLikeWethGateway(potentialWethGatewayAddress, parentProvider) {
try {
const potentialWethGateway = L1WethGateway__factory_1.L1WethGateway__factory.connect(potentialWethGatewayAddress, parentProvider);
await potentialWethGateway.callStatic.l1Weth();
return true;
}
catch (err) {
if (err instanceof Error &&
err.code ===
logger_1.Logger.errors.CALL_EXCEPTION) {
return false;
}
else {
throw err;
}
}
}
/**
* Is this a known or unknown WETH gateway
* @param gatewayAddress
* @param parentProvider
* @returns
*/
async isWethGateway(gatewayAddress, parentProvider) {
const wethAddress = this.childNetwork.tokenBridge.parentWethGateway;
if (this.childNetwork.isCustom) {
// For custom network, we do an ad-hoc check to see if it's a WETH gateway
if (await this.looksLikeWethGateway(gatewayAddress, parentProvider)) {
return true;
}
// ...otherwise we directly check it against the config file
}
else if (wethAddress === gatewayAddress) {
return true;
}
return false;
}
/**
* Get the child network token contract at the provided address
* Note: This function just returns a typed ethers object for the provided address, it doesn't
* check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity
* of any of the underlying functions on that contract.
* @param childProvider
* @param childTokenAddr
* @returns
*/
getChildTokenContract(childProvider, childTokenAddr) {
return L2GatewayToken__factory_1.L2GatewayToken__factory.connect(childTokenAddr, childProvider);
}
/**
* Get the parent token contract at the provided address
* Note: This function just returns a typed ethers object for the provided address, it doesnt
* check the underlying form of the contract bytecode to see if it's an erc20, and doesn't ensure the validity
* of any of the underlying functions on that contract.
* @param parentProvider
* @param parentTokenAddr
* @returns
*/
getParentTokenContract(parentProvider, parentTokenAddr) {
return ERC20__factory_1.ERC20__factory.connect(parentTokenAddr, parentProvider);
}
/**
* Get the corresponding child network token address for the provided parent network token
* @param erc20ParentAddress
* @param parentProvider
* @returns
*/
async getChildErc20Address(erc20ParentAddress, parentProvider) {
await this.checkParentNetwork(parentProvider);
const parentGatewayRouter = L1GatewayRouter__factory_1.L1GatewayRouter__factory.connect(this.childNetwork.tokenBridge.parentGatewayRouter, parentProvider);
return await parentGatewayRouter.functions
.calculateL2TokenAddress(erc20ParentAddress)
.then(([res]) => res);
}
/**
* Get the corresponding parent network address for the provided child network token
* Validates the returned address against the child network router to ensure it is correctly mapped to the provided erc20ChildChainAddress
* @param erc20ChildChainAddress
* @param childProvider
* @returns
*/
async getParentErc20Address(erc20ChildChainAddress, childProvider) {
await this.checkChildNetwork(childProvider);
// child network WETH contract doesn't have the parentAddress method on it
if (erc20ChildChainAddress.toLowerCase() ===
this.childNetwork.tokenBridge.childWeth.toLowerCase()) {
return this.childNetwork.tokenBridge.parentWeth;
}
const arbERC20 = L2GatewayToken__factory_1.L2GatewayToken__factory.connect(erc20ChildChainAddress, childProvider);
const parentAddress = await arbERC20.functions
.l1Address()
.then(([res]) => res);
// check that this l1 address is indeed registered to this child token
const childGatewayRouter = L2GatewayRouter__factory_1.L2GatewayRouter__factory.connect(this.childNetwork.tokenBridge.childGatewayRouter, childProvider);
const childAddress = await childGatewayRouter.calculateL2TokenAddress(parentAddress);
if (childAddress.toLowerCase() !== erc20ChildChainAddress.toLowerCase()) {
throw new errors_1.ArbSdkError(`Unexpected parent address. Parent address from token is not registered to the provided child address. ${parentAddress} ${childAddress} ${erc20ChildChainAddress}`);
}
return parentAddress;
}
/**
* Whether the token has been disabled on the router
* @param parentTokenAddress
* @param parentProvider
* @returns
*/
async isDepositDisabled(parentTokenAddress, parentProvider) {
await this.checkParentNetwork(parentProvider);
const parentGatewayRouter = L1GatewayRouter__factory_1.L1GatewayRouter__factory.connect(this.childNetwork.tokenBridge.parentGatewayRouter, parentProvider);
return ((await parentGatewayRouter.l1TokenToGateway(parentTokenAddress)) ===
constants_2.DISABLED_GATEWAY);
}
applyDefaults(params) {
return Object.assign(Object.assign({}, params), { excessFeeRefundAddress: params.excessFeeRefundAddress || params.from, callValueRefundAddress: params.callValueRefundAddress || params.from, destinationAddress: params.destinationAddress || params.from });
}
/**
* Get the call value for the deposit transaction request
* @param depositParams
* @returns
*/
getDepositRequestCallValue(depositParams) {
// the call value should be zero when paying with a custom gas token,
// as the fee amount is packed inside the last parameter (`data`) of the call to `outboundTransfer`, see `getDepositRequestOutboundTransferInnerData`
if (!this.nativeTokenIsEth) {
return ethers_1.constants.Zero;
}
// we dont include the child call value for token deposits because
// they either have 0 call value, or their call value is withdrawn from
// a contract by the gateway (weth). So in both of these cases the child call value
// is not actually deposited in the value field
return depositParams.gasLimit
.mul(depositParams.maxFeePerGas)
.add(depositParams.maxSubmissionCost);
}
/**
* Get the `data` param for call to `outboundTransfer`
* @param depositParams
* @returns
*/
getDepositRequestOutboundTransferInnerData(depositParams, decimals) {
if (!this.nativeTokenIsEth) {
return utils_1.defaultAbiCoder.encode(['uint256', 'bytes', 'uint256'], [
// maxSubmissionCost
depositParams.maxSubmissionCost, // will be zero
// callHookData
'0x',
// nativeTokenTotalFee
(0, lib_1.scaleFrom18DecimalsToNativeTokenDecimals)({
amount: depositParams.gasLimit
.mul(depositParams.maxFeePerGas)
.add(depositParams.maxSubmissionCost), // will be zero
decimals,
}),
]);
}
return utils_1.defaultAbiCoder.encode(['uint256', 'bytes'], [
// maxSubmissionCost
depositParams.maxSubmissionCost,
// callHookData
'0x',
]);
}
/**
* Get the arguments for calling the deposit function
* @param params
* @returns
*/
async getDepositRequest(params) {
await this.checkParentNetwork(params.parentProvider);
await this.checkChildNetwork(params.childProvider);
const defaultedParams = this.applyDefaults(params);
const { amount, destinationAddress, erc20ParentAddress, parentProvider, childProvider, retryableGasOverrides, } = defaultedParams;
const parentGatewayAddress = await this.getParentGatewayAddress(erc20ParentAddress, parentProvider);
let tokenGasOverrides = retryableGasOverrides;
// we also add a hardcoded minimum gas limit for custom gateway deposits
if (parentGatewayAddress === this.childNetwork.tokenBridge.parentCustomGateway) {
if (!tokenGasOverrides)
tokenGasOverrides = {};
if (!tokenGasOverrides.gasLimit)
tokenGasOverrides.gasLimit = {};
if (!tokenGasOverrides.gasLimit.min) {
tokenGasOverrides.gasLimit.min =
Erc20Bridger.MIN_CUSTOM_DEPOSIT_GAS_LIMIT;
}
}
const decimals = await (0, lib_1.getNativeTokenDecimals)({
parentProvider,
childNetwork: this.childNetwork,
});
const depositFunc = (depositParams) => {
depositParams.maxSubmissionCost =
params.maxSubmissionCost || depositParams.maxSubmissionCost;
const iGatewayRouter = L1GatewayRouter__factory_1.L1GatewayRouter__factory.createInterface();
const innerData = this.getDepositRequestOutboundTransferInnerData(depositParams, decimals);
const functionData = defaultedParams.excessFeeRefundAddress !== defaultedParams.from
? iGatewayRouter.encodeFunctionData('outboundTransferCustomRefund', [
erc20ParentAddress,
defaultedParams.excessFeeRefundAddress,
destinationAddress,
amount,
depositParams.gasLimit,
depositParams.maxFeePerGas,
innerData,
])
: iGatewayRouter.encodeFunctionData('outboundTransfer', [
erc20ParentAddress,
destinationAddress,
amount,
depositParams.gasLimit,
depositParams.maxFeePerGas,
innerData,
]);
return {
data: functionData,
to: this.childNetwork.tokenBridge.parentGatewayRouter,
from: defaultedParams.from,
value: this.getDepositRequestCallValue(depositParams),
};
};
const gasEstimator = new ParentToChildMessageGasEstimator_1.ParentToChildMessageGasEstimator(childProvider);
const estimates = await gasEstimator.populateFunctionParams(depositFunc, parentProvider, tokenGasOverrides);
return {
txRequest: {
to: this.childNetwork.tokenBridge.parentGatewayRouter,
data: estimates.data,
value: estimates.value,
from: params.from,
},
retryableData: Object.assign(Object.assign({}, estimates.retryable), estimates.estimates),
isValid: async () => {
const reEstimates = await gasEstimator.populateFunctionParams(depositFunc, parentProvider, tokenGasOverrides);
return ParentToChildMessageGasEstimator_1.ParentToChildMessageGasEstimator.isValid(estimates.estimates, reEstimates.estimates);
},
};
}
/**
* Execute a token deposit from parent to child network
* @param params
* @returns
*/
async deposit(params) {
var _a;
await this.checkParentNetwork(params.parentSigner);
// Although the types prevent should alert callers that value is not
// a valid override, it is possible that they pass it in anyway as it's a common override
// We do a safety check here
if ((_a = params.overrides) === null || _a === void 0 ? void 0 : _a.value) {
throw new errors_1.ArbSdkError('Parent call value should be set through `l1CallValue` param');
}
const parentProvider = signerOrProvider_1.SignerProviderUtils.getProviderOrThrow(params.parentSigner);
const erc20ParentAddress = (0, transactionRequest_1.isParentToChildTransactionRequest)(params)
? (0, calldata_1.getErc20ParentAddressFromParentToChildTxRequest)(params)
: params.erc20ParentAddress;
const isRegistered = await this.isRegistered({
erc20ParentAddress,
parentProvider,
childProvider: params.childProvider,
});
if (!isRegistered) {
const parentChainId = (await parentProvider.getNetwork()).chainId;
throw new Error(`Token ${erc20ParentAddress} on chain ${parentChainId} is not registered on the gateways`);
}
const tokenDeposit = (0, transactionRequest_1.isParentToChildTransactionRequest)(params)
? params
: await this.getDepositRequest(Object.assign(Object.assign({}, params), { parentProvider, from: await params.parentSigner.getAddress() }));
const tx = await params.parentSigner.sendTransaction(Object.assign(Object.assign({}, tokenDeposit.txRequest), params.overrides));
return ParentTransaction_1.ParentTransactionReceipt.monkeyPatchContractCallWait(tx);
}
/**
* Get the arguments for calling the token withdrawal function
* @param params
* @returns
*/
async getWithdrawalRequest(params) {
const to = params.destinationAddress;
const routerInterface = L2GatewayRouter__factory_1.L2GatewayRouter__factory.createInterface();
const functionData =
// we need to do this since typechain doesnt seem to correctly create
// encodeFunctionData for functions with overrides
routerInterface.encodeFunctionData('outboundTransfer(address,address,uint256,bytes)', [
params.erc20ParentAddress,
to,
params.amount,
'0x',
]);
return {
txRequest: {
data: functionData,
to: this.childNetwork.tokenBridge.childGatewayRouter,
value: ethers_1.BigNumber.from(0),
from: params.from,
},
// todo: do proper estimation
estimateParentGasLimit: async (parentProvider) => {
if (await (0, lib_1.isArbitrumChain)(parentProvider)) {
// values for L3 are dependent on the L1 base fee, so hardcoding can never be accurate
// however, this is only an estimate used for display, so should be good enough
//
// measured with token withdrawals from Rari then added some padding
return ethers_1.BigNumber.from(8000000);
}
const parentGatewayAddress = await this.getParentGatewayAddress(params.erc20ParentAddress, parentProvider);
// The WETH gateway is the only deposit that requires callvalue in the Child user-tx (i.e., the recently un-wrapped ETH)
// Here we check if this is a WETH deposit, and include the callvalue for the gas estimate query if so
const isWeth = await this.isWethGateway(parentGatewayAddress, parentProvider);
// measured 157421 - add some padding
return isWeth ? ethers_1.BigNumber.from(190000) : ethers_1.BigNumber.from(160000);
},
};
}
/**
* Withdraw tokens from child to parent network
* @param params
* @returns
*/
async withdraw(params) {
if (!signerOrProvider_1.SignerProviderUtils.signerHasProvider(params.childSigner)) {
throw new errors_1.MissingProviderArbSdkError('childSigner');
}
await this.checkChildNetwork(params.childSigner);
const withdrawalRequest = (0, transactionRequest_1.isChildToParentTransactionRequest)(params)
? params
: await this.getWithdrawalRequest(Object.assign(Object.assign({}, params), { from: await params.childSigner.getAddress() }));
const tx = await params.childSigner.sendTransaction(Object.assign(Object.assign({}, withdrawalRequest.txRequest), params.overrides));
return ChildTransaction_1.ChildTransactionReceipt.monkeyPatchWait(tx);
}
/**
* Checks if the token has been properly registered on both gateways. Mostly useful for tokens that use a custom gateway.
*
* @param {Object} params
* @param {string} params.erc20ParentAddress
* @param {Provider} params.parentProvider
* @param {Provider} params.childProvider
* @returns
*/
async isRegistered({ erc20ParentAddress, parentProvider, childProvider, }) {
const parentStandardGatewayAddressFromChainConfig = this.childNetwork.tokenBridge.parentErc20Gateway;
const parentGatewayAddressFromParentGatewayRouter = await this.getParentGatewayAddress(erc20ParentAddress, parentProvider);
// token uses standard gateway; no need to check further
if (parentStandardGatewayAddressFromChainConfig.toLowerCase() ===
parentGatewayAddressFromParentGatewayRouter.toLowerCase()) {
return true;
}
const childTokenAddressFromParentGatewayRouter = await this.getChildErc20Address(erc20ParentAddress, parentProvider);
const childGatewayAddressFromChildRouter = await this.getChildGatewayAddress(erc20ParentAddress, childProvider);
const childTokenAddressFromChildGateway = await L2ERC20Gateway__factory_1.L2ERC20Gateway__factory.connect(childGatewayAddressFromChildRouter, childProvider).calculateL2TokenAddress(erc20ParentAddress);
return (childTokenAddressFromParentGatewayRouter.toLowerCase() ===
childTokenAddressFromChildGateway.toLowerCase());
}
}
exports.Erc20Bridger = Erc20Bridger;
Erc20Bridger.MAX_APPROVAL = constants_1.MaxUint256;
Erc20Bridger.MIN_CUSTOM_DEPOSIT_GAS_LIMIT = ethers_1.BigNumber.from(275000);
/**
* Admin functionality for the token bridge
*/
class AdminErc20Bridger extends Erc20Bridger {
percentIncrease(num, increase) {
return num.add(num.mul(increase).div(100));
}
getApproveGasTokenForCustomTokenRegistrationRequest(params) {
if (this.nativeTokenIsEth) {
throw new Error('chain uses ETH as its native/gas token');
}
const iErc20Interface = ERC20__factory_1.ERC20__factory.createInterface();
const data = iErc20Interface.encodeFunctionData('approve', [
params.erc20ParentAddress,
params.amount || Erc20Bridger.MAX_APPROVAL,
]);
return {
data,
value: ethers_1.BigNumber.from(0),
to: this.nativeToken,
};
}
async approveGasTokenForCustomTokenRegistration(params) {
if (this.nativeTokenIsEth) {
throw new Error('chain uses ETH as its native/gas token');
}
await this.checkParentNetwork(params.parentSigner);
const approveGasTokenRequest = this.isApproveParams(params)
? this.getApproveGasTokenForCustomTokenRegistrationRequest(Object.assign(Object.assign({}, params), { parentProvider: signerOrProvider_1.SignerProviderUtils.getProviderOrThrow(params.parentSigner) }))
: params.txRequest;
return params.parentSigner.sendTransaction(Object.assign(Object.assign({}, approveGasTokenRequest), params.overrides));
}
/**
* Register a custom token on the Arbitrum bridge
* See https://developer.offchainlabs.com/docs/bridging_assets#the-arbitrum-generic-custom-gateway for more details
* @param parentTokenAddress Address of the already deployed parent token. Must inherit from https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-peripherals/tokenbridge/ethereum/icustomtoken.
* @param childTokenAddress Address of the already deployed child token. Must inherit from https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-peripherals/tokenbridge/arbitrum/iarbtoken.
* @param parentSigner The signer with the rights to call `registerTokenOnL2` on the parent token
* @param childProvider Arbitrum rpc provider
* @returns
*/
async registerCustomToken(parentTokenAddress, childTokenAddress, parentSigner, childProvider) {
if (!signerOrProvider_1.SignerProviderUtils.signerHasProvider(parentSigner)) {
throw new errors_1.MissingProviderArbSdkError('parentSigner');
}
await this.checkParentNetwork(parentSigner);
await this.checkChildNetwork(childProvider);
const parentProvider = parentSigner.provider;
const parentSenderAddress = await parentSigner.getAddress();
const parentToken = ICustomToken__factory_1.ICustomToken__factory.connect(parentTokenAddress, parentSigner);
const childToken = IArbToken__factory_1.IArbToken__factory.connect(childTokenAddress, childProvider);
// sanity checks
await parentToken.deployed();
await childToken.deployed();
if (!this.nativeTokenIsEth) {
const nativeTokenContract = ERC20__factory_1.ERC20__factory.connect(this.nativeToken, parentProvider);
const allowance = await nativeTokenContract.allowance(parentSenderAddress, parentToken.address);
const maxFeePerGasOnChild = (await childProvider.getFeeData())
.maxFeePerGas;
const maxFeePerGasOnChildWithBuffer = this.percentIncrease(maxFeePerGasOnChild, ethers_1.BigNumber.from(500));
// hardcode gas limit to 60k
const estimatedGasFee = ethers_1.BigNumber.from(60000).mul(maxFeePerGasOnChildWithBuffer);
if (allowance.lt(estimatedGasFee)) {
throw new Error(`Insufficient allowance. Please increase spending for: owner - ${parentSenderAddress}, spender - ${parentToken.address}.`);
}
}
const parentAddressFromChild = await childToken.l1Address();
if (parentAddressFromChild !== parentTokenAddress) {
throw new errors_1.ArbSdkError(`child token does not have parent address set. Set address: ${parentAddressFromChild}, expected address: ${parentTokenAddress}.`);
}
const nativeTokenDecimals = await (0, lib_1.getNativeTokenDecimals)({
parentProvider,
childNetwork: this.childNetwork,
});
const from = await parentSigner.getAddress();
const encodeFuncData = (setTokenGas, setGatewayGas, maxFeePerGas) => {
// if we set maxFeePerGas to be the error triggering param then it will
// always trigger for the setToken call and never make it ti setGateways
// so we here we just use the gas limit to trigger retryable data
const doubleFeePerGas = maxFeePerGas.eq(retryableData_1.RetryableDataTools.ErrorTriggeringParams.maxFeePerGas)
? retryableData_1.RetryableDataTools.ErrorTriggeringParams.maxFeePerGas.mul(2)
: maxFeePerGas;
const setTokenDeposit = setTokenGas.gasLimit
.mul(doubleFeePerGas)
.add(setTokenGas.maxSubmissionCost);
const setGatewayDeposit = setGatewayGas.gasLimit
.mul(doubleFeePerGas)
.add(setGatewayGas.maxSubmissionCost);
const data = parentToken.interface.encodeFunctionData('registerTokenOnL2', [
childTokenAddress,
setTokenGas.maxSubmissionCost,
setGatewayGas.maxSubmissionCost,
setTokenGas.gasLimit,
setGatewayGas.gasLimit,
doubleFeePerGas,
(0, lib_1.scaleFrom18DecimalsToNativeTokenDecimals)({
amount: setTokenDeposit,
decimals: nativeTokenDecimals,
}),
(0, lib_1.scaleFrom18DecimalsToNativeTokenDecimals)({
amount: setGatewayDeposit,
decimals: nativeTokenDecimals,
}),
parentSenderAddress,
]);
return {
data,
value: this.nativeTokenIsEth
? setTokenDeposit.add(setGatewayDeposit)
: ethers_1.BigNumber.from(0),
to: parentToken.address,
from,
};
};
const gEstimator = new ParentToChildMessageGasEstimator_1.ParentToChildMessageGasEstimator(childProvider);
const setTokenEstimates2 = await gEstimator.populateFunctionParams((params) => encodeFuncData({
gasLimit: params.gasLimit,
maxSubmissionCost: params.maxSubmissionCost,
}, {
gasLimit: retryableData_1.RetryableDataTools.ErrorTriggeringParams.gasLimit,
maxSubmissionCost: ethers_1.BigNumber.from(1),
}, params.maxFeePerGas), parentProvider);
const setGatewayEstimates2 = await gEstimator.populateFunctionParams((params) => encodeFuncData({
gasLimit: setTokenEstimates2.estimates.gasLimit,
maxSubmissionCost: setTokenEstimates2.estimates.maxSubmissionCost,
}, {
gasLimit: params.gasLimit,
maxSubmissionCost: params.maxSubmissionCost,
}, params.maxFeePerGas), parentProvider);
const registerTx = await parentSigner.sendTransaction({
to: parentToken.address,
data: setGatewayEstimates2.data,
value: setGatewayEstimates2.value,
});
return ParentTransaction_1.ParentTransactionReceipt.monkeyPatchWait(registerTx);
}
/**
* Get all the gateway set events on the Parent gateway router
* @param parentProvider The provider for the parent network
* @param filter An object containing fromBlock and toBlock to filter events
* @returns An array of GatewaySetEvent event arguments
*/
async getParentGatewaySetEvents(parentProvider, filter) {
await this.checkParentNetwork(parentProvider);
const parentGatewayRouterAddress = this.childNetwork.tokenBridge.parentGatewayRouter;
const eventFetcher = new eventFetcher_1.EventFetcher(parentProvider);
return (await eventFetcher.getEvents(L1GatewayRouter__factory_1.L1GatewayRouter__factory, t => t.filters.GatewaySet(), Object.assign(Object.assign({}, filter), { address: parentGatewayRouterAddress }))).map(a => a.event);
}
/**
* Get all the gateway set events on the child gateway router
* @param childProvider The provider for the child network
* @param filter An object containing fromBlock and toBlock to filter events
* @param customNetworkChildGatewayRouter Optional address of the custom network child gateway router
* @returns An array of GatewaySetEvent event arguments
* @throws {ArbSdkError} If the network is custom and customNetworkChildGatewayRouter is not provided
*/
async getChildGatewaySetEvents(childProvider, filter, customNetworkChildGatewayRouter) {
if (this.childNetwork.isCustom && !customNetworkChildGatewayRouter) {
throw new errors_1.ArbSdkError('Must supply customNetworkChildGatewayRouter for custom network ');
}
await this.checkChildNetwork(childProvider);
const childGatewayRouterAddress = customNetworkChildGatewayRouter ||
this.childNetwork.tokenBridge.childGatewayRouter;
const eventFetcher = new eventFetcher_1.EventFetcher(childProvider);
return (await eventFetcher.getEvents(L2GatewayRouter__factory_1.L2GatewayRouter__factory, t => t.filters.GatewaySet(), Object.assign(Object.assign({}, filter), { address: childGatewayRouterAddress }))).map(a => a.event);
}
/**
* Register the provided token addresses against the provided gateways
* @param parentSigner
* @param childProvider
* @param tokenGateways
* @returns
*/
async setGateways(parentSigner, childProvider, tokenGateways, options) {
if (!signerOrProvider_1.SignerProviderUtils.signerHasProvider(parentSigner)) {
throw new errors_1.MissingProviderArbSdkError('parentSigner');
}
await this.checkParentNetwork(parentSigner);
await this.checkChildNetwork(childProvider);
const from = await parentSigner.getAddress();
const parentGatewayRouter = L1GatewayRouter__factory_1.L1GatewayRouter__factory.connect(this.childNetwork.tokenBridge.parentGatewayRouter, parentSigner);
const setGatewaysFunc = (params) => {
return {
data: parentGatewayRouter.interface.encodeFunctionData('setGateways', [
tokenGateways.map(tG => tG.tokenAddr),
tokenGateways.map(tG => tG.gatewayAddr),
params.gasLimit,
params.maxFeePerGas,
params.maxSubmissionCost,
]),
from,
value: params.gasLimit
.mul(params.maxFeePerGas)
.add(params.maxSubmissionCost),
to: parentGatewayRouter.address,
};
};
const gEstimator = new ParentToChildMessageGasEstimator_1.ParentToChildMessageGasEstimator(childProvider);
const estimates = await gEstimator.populateFunctionParams(setGatewaysFunc, parentSigner.provider, options);
const res = await parentSigner.sendTransaction({
to: estimates.to,
data: estimates.data,
value: estimates.estimates.deposit,
});
return ParentTransaction_1.ParentTransactionReceipt.monkeyPatchContractCallWait(res);
}
}
exports.AdminErc20Bridger = AdminErc20Bridger;