From 0c54e60eed8884deae66b1023b738458555e8f58 Mon Sep 17 00:00:00 2001 From: kf Date: Tue, 15 Feb 2022 01:58:50 +0000 Subject: [PATCH] feat(sdk): add approval fns to the SDK --- .changeset/thin-seahorses-fry.md | 5 ++ packages/sdk/src/adapters/eth-bridge.ts | 23 +++++- packages/sdk/src/adapters/standard-bridge.ts | 70 +++++++++++++++++ packages/sdk/src/cross-chain-messenger.ts | 64 +++++++++++++++- packages/sdk/src/interfaces/bridge-adapter.ts | 73 ++++++++++++++++++ .../src/interfaces/cross-chain-messenger.ts | 76 +++++++++++++++++++ 6 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 .changeset/thin-seahorses-fry.md diff --git a/.changeset/thin-seahorses-fry.md b/.changeset/thin-seahorses-fry.md new file mode 100644 index 0000000000000..8dbc70f3d9b50 --- /dev/null +++ b/.changeset/thin-seahorses-fry.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/sdk': patch +--- + +Add approval functions to the SDK diff --git a/packages/sdk/src/adapters/eth-bridge.ts b/packages/sdk/src/adapters/eth-bridge.ts index 3a5a3e1f4f97b..cb31eb55d996e 100644 --- a/packages/sdk/src/adapters/eth-bridge.ts +++ b/packages/sdk/src/adapters/eth-bridge.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { ethers, Overrides } from 'ethers' +import { ethers, Contract, Overrides, BigNumber } from 'ethers' import { TransactionRequest, BlockTag } from '@ethersproject/abstract-provider' -import { predeploys } from '@eth-optimism/contracts' +import { predeploys, getContractInterface } from '@eth-optimism/contracts' import { hexStringEquals } from '@eth-optimism/core-utils' import { @@ -17,6 +17,14 @@ import { StandardBridgeAdapter } from './standard-bridge' * Bridge adapter for the ETH bridge. */ export class ETHBridgeAdapter extends StandardBridgeAdapter { + public async approval( + l1Token: AddressLike, + l2Token: AddressLike, + signer: ethers.Signer + ): Promise { + throw new Error(`approval not necessary for ETH bridge`) + } + public async getDepositsByAddress( address: AddressLike, opts?: { @@ -104,6 +112,17 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter { } populateTransaction = { + approve: async ( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise => { + throw new Error(`approvals not necessary for ETH bridge`) + }, + deposit: async ( l1Token: AddressLike, l2Token: AddressLike, diff --git a/packages/sdk/src/adapters/standard-bridge.ts b/packages/sdk/src/adapters/standard-bridge.ts index f9956fdfa0b2a..17c87853d973a 100644 --- a/packages/sdk/src/adapters/standard-bridge.ts +++ b/packages/sdk/src/adapters/standard-bridge.ts @@ -185,6 +185,38 @@ export class StandardBridgeAdapter implements IBridgeAdapter { } } + public async approval( + l1Token: AddressLike, + l2Token: AddressLike, + signer: ethers.Signer + ): Promise { + if (!(await this.supportsTokenPair(l1Token, l2Token))) { + throw new Error(`token pair not supported by bridge`) + } + + const token = new Contract( + toAddress(l1Token), + getContractInterface('L2StandardERC20'), // Any ERC20 will do + this.messenger.l1Provider + ) + + return token.allowance(await signer.getAddress(), this.l1Bridge.address) + } + + public async approve( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + signer: Signer, + opts?: { + overrides?: Overrides + } + ): Promise { + return signer.sendTransaction( + await this.populateTransaction.approve(l1Token, l2Token, amount, opts) + ) + } + public async deposit( l1Token: AddressLike, l2Token: AddressLike, @@ -217,6 +249,31 @@ export class StandardBridgeAdapter implements IBridgeAdapter { } populateTransaction = { + approve: async ( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise => { + if (!(await this.supportsTokenPair(l1Token, l2Token))) { + throw new Error(`token pair not supported by bridge`) + } + + const token = new Contract( + toAddress(l1Token), + getContractInterface('L2StandardERC20'), // Any ERC20 will do + this.messenger.l1Provider + ) + + return token.populateTransaction.approve( + this.l1Bridge.address, + amount, + opts?.overrides || {} + ) + }, + deposit: async ( l1Token: AddressLike, l2Token: AddressLike, @@ -288,6 +345,19 @@ export class StandardBridgeAdapter implements IBridgeAdapter { } estimateGas = { + approve: async ( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise => { + return this.messenger.l1Provider.estimateGas( + await this.populateTransaction.approve(l1Token, l2Token, amount, opts) + ) + }, + deposit: async ( l1Token: AddressLike, l2Token: AddressLike, diff --git a/packages/sdk/src/cross-chain-messenger.ts b/packages/sdk/src/cross-chain-messenger.ts index f532e25749ec6..f42df1ae0ee97 100644 --- a/packages/sdk/src/cross-chain-messenger.ts +++ b/packages/sdk/src/cross-chain-messenger.ts @@ -109,7 +109,7 @@ export class CrossChainMessenger implements ICrossChainMessenger { if (Provider.isProvider(this.l1SignerOrProvider)) { return this.l1SignerOrProvider } else { - return this.l1SignerOrProvider.provider + return this.l1SignerOrProvider.provider as any } } @@ -117,7 +117,7 @@ export class CrossChainMessenger implements ICrossChainMessenger { if (Provider.isProvider(this.l2SignerOrProvider)) { return this.l2SignerOrProvider } else { - return this.l2SignerOrProvider.provider + return this.l2SignerOrProvider.provider as any } } @@ -844,6 +844,36 @@ export class CrossChainMessenger implements ICrossChainMessenger { ) } + public async approval( + l1Token: AddressLike, + l2Token: AddressLike, + opts?: { + signer?: Signer + } + ): Promise { + const bridge = await this.getBridgeForTokenPair(l1Token, l2Token) + return bridge.approval(l1Token, l2Token, opts?.signer || this.l1Signer) + } + + public async approveERC20( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + signer?: Signer + overrides?: Overrides + } + ): Promise { + return (opts?.signer || this.l1Signer).sendTransaction( + await this.populateTransaction.approveERC20( + l1Token, + l2Token, + amount, + opts + ) + ) + } + public async depositERC20( l1Token: AddressLike, l2Token: AddressLike, @@ -986,6 +1016,18 @@ export class CrossChainMessenger implements ICrossChainMessenger { ) }, + approveERC20: async ( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise => { + const bridge = await this.getBridgeForTokenPair(l1Token, l2Token) + return bridge.populateTransaction.approve(l1Token, l2Token, amount, opts) + }, + depositERC20: async ( l1Token: AddressLike, l2Token: AddressLike, @@ -1082,6 +1124,24 @@ export class CrossChainMessenger implements ICrossChainMessenger { ) }, + approveERC20: async ( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise => { + return this.l1Provider.estimateGas( + await this.populateTransaction.approveERC20( + l1Token, + l2Token, + amount, + opts + ) + ) + }, + depositERC20: async ( l1Token: AddressLike, l2Token: AddressLike, diff --git a/packages/sdk/src/interfaces/bridge-adapter.ts b/packages/sdk/src/interfaces/bridge-adapter.ts index 5992d8e7fc3b5..3a9f2c5e81082 100644 --- a/packages/sdk/src/interfaces/bridge-adapter.ts +++ b/packages/sdk/src/interfaces/bridge-adapter.ts @@ -78,6 +78,41 @@ export interface IBridgeAdapter { l2Token: AddressLike ): Promise + /** + * Queries the account's approval amount for a given L1 token. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param signer Signer to query the approval for. + * @returns Amount of tokens approved for deposits from the account. + */ + approval( + l1Token: AddressLike, + l2Token: AddressLike, + signer: Signer + ): Promise + + /** + * Approves a deposit into the L2 chain. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param amount Amount of the token to approve. + * @param signer Signer used to sign and send the transaction. + * @param opts Additional options. + * @param opts.overrides Optional transaction overrides. + * @returns Transaction response for the approval transaction. + */ + approve( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + signer: Signer, + opts?: { + overrides?: Overrides + } + ): Promise + /** * Deposits some tokens into the L2 chain. * @@ -131,6 +166,25 @@ export interface IBridgeAdapter { * Follows the pattern used by ethers.js. */ populateTransaction: { + /** + * Generates a transaction for approving some tokens to deposit into the L2 chain. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param amount Amount of the token to approve. + * @param opts Additional options. + * @param opts.overrides Optional transaction overrides. + * @returns Transaction that can be signed and executed to deposit the tokens. + */ + approve( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise + /** * Generates a transaction for depositing some tokens into the L2 chain. * @@ -181,6 +235,25 @@ export interface IBridgeAdapter { * Follows the pattern used by ethers.js. */ estimateGas: { + /** + * Estimates gas required to approve some tokens to deposit into the L2 chain. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param amount Amount of the token to approve. + * @param opts Additional options. + * @param opts.overrides Optional transaction overrides. + * @returns Gas estimate for the transaction. + */ + approve( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise + /** * Estimates gas required to deposit some tokens into the L2 chain. * diff --git a/packages/sdk/src/interfaces/cross-chain-messenger.ts b/packages/sdk/src/interfaces/cross-chain-messenger.ts index cffb1fa82c048..f58b6065b9af9 100644 --- a/packages/sdk/src/interfaces/cross-chain-messenger.ts +++ b/packages/sdk/src/interfaces/cross-chain-messenger.ts @@ -411,6 +411,44 @@ export interface ICrossChainMessenger { } ): Promise + /** + * Queries the account's approval amount for a given L1 token. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param opts Additional options. + * @param opts.signer Optional signer to get the approval for. + * @returns Amount of tokens approved for deposits from the account. + */ + approval( + l1Token: AddressLike, + l2Token: AddressLike, + opts?: { + signer?: Signer + } + ): Promise + + /** + * Approves a deposit into the L2 chain. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param amount Amount of the token to approve. + * @param opts Additional options. + * @param opts.signer Optional signer to use to send the transaction. + * @param opts.overrides Optional transaction overrides. + * @returns Transaction response for the approval transaction. + */ + approveERC20( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + signer?: Signer + overrides?: Overrides + } + ): Promise + /** * Deposits some ERC20 tokens into the L2 chain. * @@ -517,6 +555,25 @@ export interface ICrossChainMessenger { } ): Promise + /** + * Generates a transaction for approving some tokens to deposit into the L2 chain. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param amount Amount of the token to approve. + * @param opts Additional options. + * @param opts.overrides Optional transaction overrides. + * @returns Transaction response for the approval transaction. + */ + approveERC20( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise + /** * Generates a transaction for depositing some ETH into the L2 chain. * @@ -652,6 +709,25 @@ export interface ICrossChainMessenger { } ): Promise + /** + * Estimates gas required to approve some tokens to deposit into the L2 chain. + * + * @param l1Token The L1 token address. + * @param l2Token The L2 token address. + * @param amount Amount of the token to approve. + * @param opts Additional options. + * @param opts.overrides Optional transaction overrides. + * @returns Transaction response for the approval transaction. + */ + approveERC20( + l1Token: AddressLike, + l2Token: AddressLike, + amount: NumberLike, + opts?: { + overrides?: Overrides + } + ): Promise + /** * Estimates gas required to deposit some ETH into the L2 chain. *