UNPKG

@mantleio/sdk

Version:
382 lines (354 loc) 10.2 kB
/* eslint-disable @typescript-eslint/no-unused-vars */ import { ethers, Contract, Overrides, Signer, BigNumber, CallOverrides, } from 'ethers' import { BlockTag, TransactionRequest, TransactionResponse, } from '@ethersproject/abstract-provider' import { getContractInterface } from '@mantleio/contracts-bedrock' import { hexStringEquals } from '@mantleio/core-utils' import { CrossChainMessenger } from '../cross-chain-messenger' import { NumberLike, AddressLike, IERC721BridgeAdapter, TokenBridgeMessage, MessageDirection, } from '../interfaces' import { toAddress } from '../utils' /** * Bridge adapter for any token bridge that uses the standard token bridge interface. */ export class ERC721BridgeAdapter implements IERC721BridgeAdapter { public messenger: CrossChainMessenger public l1Bridge: Contract public l2Bridge: Contract /** * Creates a StandardBridgeAdapter instance. * * @param opts Options for the adapter. * @param opts.messenger Provider used to make queries related to cross-chain interactions. * @param opts.l1Bridge L1 bridge contract. * @param opts.l2Bridge L2 bridge contract. */ constructor(opts: { messenger: CrossChainMessenger l1Bridge: AddressLike l2Bridge: AddressLike }) { this.messenger = opts.messenger this.l1Bridge = new Contract( toAddress(opts.l1Bridge), getContractInterface('L1ERC721Bridge'), this.messenger.l1Provider ) this.l2Bridge = new Contract( toAddress(opts.l2Bridge), getContractInterface('L2ERC721Bridge'), this.messenger.l2Provider ) } public async getDepositsByAddress( address: AddressLike, opts?: { fromBlock?: BlockTag toBlock?: BlockTag } ): Promise<TokenBridgeMessage[]> { const events = await this.l1Bridge.queryFilter( this.l1Bridge.filters.ERC721BridgeInitiated( undefined, undefined, address ), opts?.fromBlock, opts?.toBlock ) return events .map((event) => { return { direction: MessageDirection.L1_TO_L2, from: event.args.from, to: event.args.to, l1Token: event.args.localToken, l2Token: event.args.remoteToken, tokenId: event.args.tokenId, data: event.args.extraData, logIndex: event.logIndex, blockNumber: event.blockNumber, transactionHash: event.transactionHash, } }) .sort((a, b) => { // Sort descending by block number return b.blockNumber - a.blockNumber }) } public async getWithdrawalsByAddress( address: AddressLike, opts?: { fromBlock?: BlockTag toBlock?: BlockTag } ): Promise<TokenBridgeMessage[]> { const events = await this.l2Bridge.queryFilter( this.l2Bridge.filters.ERC721BridgeInitiated( undefined, undefined, address ), opts?.fromBlock, opts?.toBlock ) return events .map((event) => { return { direction: MessageDirection.L2_TO_L1, from: event.args.from, to: event.args.to, l1Token: event.args.remoteToken, l2Token: event.args.localToken, amount: event.args.amount, data: event.args.extraData, logIndex: event.logIndex, blockNumber: event.blockNumber, transactionHash: event.transactionHash, } }) .sort((a, b) => { // Sort descending by block number return b.blockNumber - a.blockNumber }) } public async supportsTokenPair( l1Token: AddressLike, l2Token: AddressLike ): Promise<boolean> { try { const contract = new Contract( toAddress(l2Token), getContractInterface('OptimismMintableERC721'), this.messenger.l2Provider ) // Make sure the L1 token matches. const remoteL1Token = await contract.remoteToken() if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) { return false } // Make sure the L2 bridge matches. const remoteL2Bridge = await contract.bridge() if (!hexStringEquals(remoteL2Bridge, this.l2Bridge.address)) { return false } return true } catch (err) { // If the L2 token is not an L2StandardERC20, it may throw an error. If there's a call // exception then we assume that the token is not supported. Other errors are thrown. Since // the JSON-RPC API is not well-specified, we need to handle multiple possible error codes. if ( !err?.message?.toString().includes('CALL_EXCEPTION') && !err?.stack?.toString().includes('execution reverted') ) { console.error('Unexpected error when checking bridge', err) } return false } } public async approval( l1Token: AddressLike, l2Token: AddressLike, signer: ethers.Signer ): Promise<boolean> { if (!(await this.supportsTokenPair(l1Token, l2Token))) { throw new Error(`token pair not supported by bridge`) } const token = new Contract( toAddress(l1Token), getContractInterface('OptimismMintableERC721'), this.messenger.l1Provider ) return token.isApprovedForAll( await signer.getAddress(), this.l1Bridge.address ) } public async approve( l1Token: AddressLike, l2Token: AddressLike, signer: Signer, opts?: { overrides?: Overrides } ): Promise<TransactionResponse> { return signer.sendTransaction( await this.populateTransaction.approve(l1Token, l2Token, opts) ) } public async deposit( l1Token: AddressLike, l2Token: AddressLike, tokenId: NumberLike, signer: Signer, opts?: { recipient?: AddressLike l2GasLimit?: NumberLike overrides?: Overrides } ): Promise<TransactionResponse> { return signer.sendTransaction( await this.populateTransaction.deposit(l1Token, l2Token, tokenId, opts) ) } public async withdraw( l1Token: AddressLike, l2Token: AddressLike, tokenId: NumberLike, signer: Signer, opts?: { recipient?: AddressLike overrides?: Overrides } ): Promise<TransactionResponse> { return signer.sendTransaction( await this.populateTransaction.withdraw(l1Token, l2Token, tokenId, opts) ) } populateTransaction = { approve: async ( l1Token: AddressLike, l2Token: AddressLike, opts?: { overrides?: Overrides } ): Promise<TransactionRequest> => { if (!(await this.supportsTokenPair(l1Token, l2Token))) { throw new Error(`token pair not supported by bridge`) } const token = new Contract( toAddress(l1Token), getContractInterface('OptimismMintableERC721'), this.messenger.l1Provider ) return token.populateTransaction.setApprovalForAll( this.l1Bridge.address, true, opts?.overrides || {} ) }, deposit: async ( l1Token: AddressLike, l2Token: AddressLike, tokenId: NumberLike, opts?: { recipient?: AddressLike l2GasLimit?: NumberLike overrides?: Overrides } ): Promise<TransactionRequest> => { if (!(await this.supportsTokenPair(l1Token, l2Token))) { throw new Error(`token pair not supported by bridge`) } if (opts?.recipient === undefined) { return this.l1Bridge.populateTransaction.bridgeERC721( toAddress(l1Token), toAddress(l2Token), tokenId, opts?.l2GasLimit || 400000, '0x', // No data. opts?.overrides || {} ) } else { return this.l1Bridge.populateTransaction.bridgeERC721To( toAddress(l1Token), toAddress(l2Token), toAddress(opts.recipient), tokenId, opts?.l2GasLimit || 400000, '0x', // No data. opts?.overrides || {} ) } }, withdraw: async ( l1Token: AddressLike, l2Token: AddressLike, tokenId: NumberLike, opts?: { recipient?: AddressLike l1GasLimit?: NumberLike overrides?: Overrides } ): Promise<TransactionRequest> => { if (!(await this.supportsTokenPair(l1Token, l2Token))) { throw new Error(`token pair not supported by bridge`) } if (opts?.recipient === undefined) { return this.l2Bridge.populateTransaction.bridgeERC721( toAddress(l2Token), toAddress(l1Token), tokenId, opts?.l1GasLimit || 4_000_000, '0x', // No data. opts?.overrides || {} ) } else { return this.l2Bridge.populateTransaction.bridgeERC721( toAddress(l2Token), toAddress(l1Token), toAddress(opts.recipient), tokenId, opts?.l1GasLimit || 4_000_000, '0x', // No data. opts?.overrides || {} ) } }, } estimateGas = { approve: async ( l1Token: AddressLike, l2Token: AddressLike, opts?: { overrides?: CallOverrides } ): Promise<BigNumber> => { return this.messenger.l1Provider.estimateGas( await this.populateTransaction.approve(l1Token, l2Token, opts) ) }, deposit: async ( l1Token: AddressLike, l2Token: AddressLike, tokenId: NumberLike, opts?: { recipient?: AddressLike l2GasLimit?: NumberLike overrides?: CallOverrides } ): Promise<BigNumber> => { return this.messenger.l1Provider.estimateGas( await this.populateTransaction.deposit(l1Token, l2Token, tokenId, opts) ) }, withdraw: async ( l1Token: AddressLike, l2Token: AddressLike, tokenId: NumberLike, opts?: { recipient?: AddressLike overrides?: CallOverrides } ): Promise<BigNumber> => { return this.messenger.l2Provider.estimateGas( await this.populateTransaction.withdraw(l1Token, l2Token, tokenId, opts) ) }, } }