Skip to content

Teleporter tutorial update #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/l1-l3-teleport/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ See [`monitor-deposit-status.ts`](./scripts/monitor-deposit-status.ts) for monit

See [`monitor-eth-deposit-status.ts`](./scripts//monitor-eth-deposit-status.ts) for monitoring ETH deposits.

See [`recover-deposit.ts](./scripts/recover-deposit.ts) for recovering a failed ERC20 deposit. (Assumes `DEVNET_PRIVATE_KEY` is funded on L2 and L3)

See [`recover-eth-deposit.ts`](./scripts/recover-eth-deposit.ts) for recovering a failed ETH deposit. (Assumes `DEVNET_PRIVATE_KEY` is funded on L2 and L3)

### Paying for retryables
When initiating an ERC20 deposit, there are multiple ways retryable fees can be paid.

Expand All @@ -35,6 +39,10 @@ yarn initiate-eth-deposit --help
yarn monitor-deposit-status <txHash>

yarn monitor-eth-deposit-status <txHash>

yarn recover-deposit <txHash>

yarn recover-eth-deposit <txHash>
```

## Configure environment variables
Expand Down
2 changes: 1 addition & 1 deletion packages/l1-l3-teleport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"monitor-eth-deposit-status": "ts-node scripts/monitor-eth-deposit-status.ts"
},
"dependencies": {
"godzillaba-arbitrum-sdk": "^3.3.3-teleport",
"@arbitrum/sdk": "3.5.1-teleporter.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
"yargs": "^17.7.2"
Expand Down
24 changes: 12 additions & 12 deletions packages/l1-l3-teleport/scripts/initiate-deposit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { providers, Wallet, BigNumber, ethers } from "ethers"
import { providers, Wallet, BigNumber } from "ethers"
import { arbLog, requireEnvVariables } from 'arb-shared-dependencies'
import { Erc20L1L3Bridger, getL2Network } from "godzillaba-arbitrum-sdk"
import { ERC20__factory } from "godzillaba-arbitrum-sdk/dist/lib/abi/factories/ERC20__factory"
import { Erc20L1L3Bridger, getL2Network } from "@arbitrum/sdk"
import { ERC20__factory } from "@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory"
import yargs from 'yargs/yargs'
import { hideBin } from "yargs/helpers"

Expand All @@ -17,7 +17,7 @@ const l3Provider = new providers.JsonRpcProvider(process.env.L3RPC)
const l1Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l1Provider)

const main = async (params: {
l1Token: string, amount: BigNumber, l3Recipient?: string, skipFeeToken?: boolean
l1Token: string, amount: BigNumber, l3Recipient?: string, skipGasToken?: boolean
}) => {
await arbLog(`Bridging tokens from L1 to L3`)

Expand All @@ -44,12 +44,12 @@ const main = async (params: {
const l2ChainId = (await l2Provider.getNetwork()).chainId
const l3ChainId = (await l3Provider.getNetwork()).chainId
const signerAddress = await l1Signer.getAddress()
const l3FeeTokenL1Address = await bridger.l1FeeTokenAddress(l1Provider, l2Provider)
const l3GasTokenL1Addr = await bridger.getGasTokenOnL1(l1Provider, l2Provider)
const feeTokenSymbol = bridger.l2FeeTokenAddress ? await ERC20__factory.connect(bridger.l2FeeTokenAddress, l2Provider).symbol() : 'ETH'
console.log('L1 Token:', params.l1Token)
console.log('L3 Fee Token:', feeTokenSymbol || 'ETH')
console.log('L3 Fee Token Address on L2:', bridger.l2FeeTokenAddress || 'ETH')
console.log('L3 Fee Token Address on L1:', l3FeeTokenL1Address)
console.log('L3 Gas Token:', feeTokenSymbol || 'ETH')
console.log('L3 Gas Token Address on L2:', bridger.l2FeeTokenAddress || 'ETH')
console.log('L3 Gas Token Address on L1:', l3GasTokenL1Addr)
console.log('Recipient:', params.l3Recipient || signerAddress)
console.log('Amount:', params.amount.toString())
console.log('L1 chain id:', l1ChainId)
Expand All @@ -66,13 +66,13 @@ const main = async (params: {
amount: params.amount,
l2Provider,
l3Provider,
to: params.l3Recipient, // optional, defaults to signer's address
destinationAddress: params.l3Recipient, // optional, defaults to signer's address
/**
* Optional, defaults to false.
* Skip paying the L2->L3 fee if the L3 doesn't use ETH for fees
* This has no effect if the L3 uses ETH for fees.
*/
skipFeeToken: params.skipFeeToken
skipGasToken: params.skipGasToken
})
console.log('Done')

Expand Down Expand Up @@ -132,7 +132,7 @@ const args = yargs(hideBin(process.argv))
type: 'string',
description: 'L3 recipient address'
},
skipFeeToken: {
skipGasToken: {
type: 'boolean',
description: 'Skip paying the L2->L3 fee if the L3 doesn\'t use ETH for fees.\nThis has no effect if the L3 uses ETH for fees.',
default: false
Expand All @@ -145,7 +145,7 @@ main({
l1Token: args.l1Token,
amount: BigNumber.from(args.amount),
l3Recipient: args.l3Recipient,
skipFeeToken: args.skipFeeToken
skipGasToken: args.skipGasToken
}).then(() => process.exit(0)).catch(error => {
console.error(error)
process.exit(1)
Expand Down
5 changes: 2 additions & 3 deletions packages/l1-l3-teleport/scripts/initiate-eth-deposit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { providers, Wallet, BigNumber } from "ethers"
import { arbLog, requireEnvVariables } from 'arb-shared-dependencies'
import { Erc20L1L3Bridger, EthL1L3Bridger, getL2Network } from "godzillaba-arbitrum-sdk"
import { ERC20__factory } from "godzillaba-arbitrum-sdk/dist/lib/abi/factories/ERC20__factory"
import { EthL1L3Bridger, getL2Network } from "@arbitrum/sdk"
import yargs from 'yargs/yargs'
import { hideBin } from "yargs/helpers"

Expand Down Expand Up @@ -49,7 +48,7 @@ const main = async (params: {
amount: params.amount,
l2Provider,
l3Provider,
to: params.l3Recipient, // optional, defaults to signer's address
destinationAddress: params.l3Recipient, // optional, defaults to signer's address
})
console.log('Done')
/**
Expand Down
21 changes: 7 additions & 14 deletions packages/l1-l3-teleport/scripts/monitor-deposit-status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { providers } from "ethers"
import { arbLog, requireEnvVariables } from 'arb-shared-dependencies'
import { Erc20L1L3Bridger, L1ToL2MessageStatus, getL2Network } from "godzillaba-arbitrum-sdk"
import { Erc20L1L3Bridger, L1ToL2MessageStatus, getL2Network } from "@arbitrum/sdk"

// Importing configuration //
require('dotenv').config()
Expand Down Expand Up @@ -33,20 +33,13 @@ const main = async (txHash: string) => {
* Get deposit status
*/
console.log('Getting deposit status...')
const depositStatus = await bridger.getDepositMessages({ txHash, l1Provider, l2Provider, l3Provider })



/**
* If any of these retryables fail (i.e. FUNDS_DEPOSITED_ON_L2), manually redeem them in the order displayed below
* Note that anyone can manually redeem these retryables, not just the sender of the deposit
* If the user opts to skip payment for the L2->L3 retryable, depositStatus.l2l3TokenBridge MUST be redeemed manually
*/
console.log(`L1 to L2 Fee Token Bridge: ${statusToText[await depositStatus.l1l2FeeTokenBridge?.status() || 'NA']}`)
console.log(`L1 to L2 Token Bridge: ${statusToText[await depositStatus.l1l2TokenBridge.status()]}`)
console.log(`L2 Forwarder Factory Call: ${statusToText[await depositStatus.l2ForwarderFactory.status()]}`)
console.log(`L2 to L3 Token Bridge: ${statusToText[await depositStatus.l2l3TokenBridge?.status() || L1ToL2MessageStatus.NOT_YET_CREATED]}`)
const depositStatus = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider })
console.log(`L1 to L2 Fee Token Bridge: ${statusToText[await depositStatus.l1l2FeeTokenBridgeRetryable?.status() || 'NA']}`)
console.log(`L1 to L2 Token Bridge: ${statusToText[await depositStatus.l1l2TokenBridgeRetryable.status()]}`)
console.log(`L2 Forwarder Factory Call: ${statusToText[await depositStatus.l2ForwarderFactoryRetryable.status()]}`)
console.log(`L2 to L3 Token Bridge: ${statusToText[await depositStatus.l2l3TokenBridgeRetryable?.status() || L1ToL2MessageStatus.NOT_YET_CREATED]}`)
console.log(`Completed: ${depositStatus.completed}`)
console.log(`L2 Forwarder Factory Call Frontran: ${depositStatus.l2ForwarderFactoryRetryableFrontRan}`)
}

if (!process.argv[2]) {
Expand Down
6 changes: 2 additions & 4 deletions packages/l1-l3-teleport/scripts/monitor-eth-deposit-status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { providers } from "ethers"
import { arbLog, requireEnvVariables } from 'arb-shared-dependencies'
import { Erc20L1L3Bridger, EthL1L3Bridger, L1ToL2MessageStatus, getL2Network } from "godzillaba-arbitrum-sdk"
import { EthL1L3Bridger, L1ToL2MessageStatus, getL2Network } from "@arbitrum/sdk"

// Importing configuration //
require('dotenv').config()
Expand Down Expand Up @@ -33,9 +33,7 @@ const main = async (txHash: string) => {
* Get deposit status
*/
console.log('Getting deposit status...')
const depositStatus = await bridger.getDepositMessages({ txHash, l1Provider, l2Provider, l3Provider })


const depositStatus = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider })

/**
* If any of these retryables fail (i.e. FUNDS_DEPOSITED_ON_L2), manually redeem them in the order displayed below
Expand Down
93 changes: 93 additions & 0 deletions packages/l1-l3-teleport/scripts/recover-deposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { providers, Wallet } from "ethers"
import { arbLog, requireEnvVariables } from 'arb-shared-dependencies'
import { Erc20L1L3Bridger, L1ToL2MessageReader, L1ToL2MessageStatus, L1ToL2MessageWriter, getL2Network } from "@arbitrum/sdk"

// Importing configuration //
require('dotenv').config()
requireEnvVariables(['L1RPC', 'L2RPC', 'L3RPC', 'DEVNET_PRIVKEY'])

// Initial setup //
const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC)
const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC)
const l3Provider = new providers.JsonRpcProvider(process.env.L3RPC)

const l2Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l2Provider)
const l3Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l3Provider)

const main = async (txHash: string) => {
await arbLog(`Recovering a failed deposit`)

/**
* Use L3 Network to initialize a bridger
*/
const l3Network = await getL2Network(l3Provider)
const bridger = new Erc20L1L3Bridger(l3Network)

const status = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider })

if (status.completed) {
console.log('Teleportation already completed')
return
}

if (status.l2ForwarderFactoryRetryableFrontRan) {
console.log('L2 Forwarder Factory Call was frontran, funds were picked up by another teleportation that may or may not have completed.')
console.log('Please check the status of all teleportations that share the same L2Forwarder and token')
console.log('If all those teleportations have (status.completed || status.l2ForwarderFactoryRetryableFrontRan), then all deposits have safely arrived on L2')
return
}

const getWriter = (signer: Wallet, msg: L1ToL2MessageReader) => {
return new L1ToL2MessageWriter(signer, msg.chainId, msg.sender, msg.messageNumber, msg.l1BaseFee, msg.messageData)
}

// the order of check / redeem is important

if (await status.l1l2FeeTokenBridgeRetryable?.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
console.log('Redeeming l1l2FeeTokenBridgeRetryable...')
await getWriter(l2Signer, status.l1l2FeeTokenBridgeRetryable!).redeem()
console.log('Redeemed l1l2FeeTokenBridgeRetryable')
}

if (await status.l1l2TokenBridgeRetryable.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
console.log('Redeeming l1l2TokenBridgeRetryable...')
await getWriter(l2Signer, status.l1l2TokenBridgeRetryable).redeem()
console.log('Redeemed l1l2TokenBridgeRetryable')
}

if (await status.l2ForwarderFactoryRetryable.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
console.log('Redeeming l2ForwarderFactoryRetryable...')

try {
await getWriter(l2Signer, status.l2ForwarderFactoryRetryable).redeem()
} catch (e) {
console.error(e)
console.log('Failed to redeem l2ForwarderFactoryRetryable')
console.log('If the l2ForwarderFactoryRetryable cannot be redeemed, the teleportation is stuck')
console.log('The sender of the teleport must call L2Forwarder::rescue to recover the funds')
console.log('If the sender of the teleport is a contract on L1, this needs to be done via a L1->L2 retryable')
console.log('If the sender of the teleport is an EOA, this can be done via a L2 transaction')
return
}
console.log('Redeemed l2ForwarderFactoryRetryable')

console.log('l2l3TokenBridgeRetryable should be created after redeeming l2ForwarderFactoryRetryable')
console.log('Please run this script again to redeem l2l3TokenBridgeRetryable if it fails to redeem automatically')
}

if (await status.l2l3TokenBridgeRetryable?.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
console.log('Redeeming l2l3TokenBridgeRetryable...')
// note that the l3 signer is used here, l2 signer used elsewhere
await getWriter(l3Signer, status.l2l3TokenBridgeRetryable!).redeem()
console.log('Redeemed l2l3TokenBridgeRetryable')

console.log('All retryables redeemed, teleportation completed!')
}
}

if (!process.argv[2]) {
console.error('Please provide a transaction hash')
process.exit(1)
}

main(process.argv[2])
61 changes: 61 additions & 0 deletions packages/l1-l3-teleport/scripts/recover-eth-deposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { providers, Wallet } from "ethers"
import { arbLog, requireEnvVariables } from 'arb-shared-dependencies'
import { Erc20L1L3Bridger, EthL1L3Bridger, L1ToL2MessageReader, L1ToL2MessageStatus, L1ToL2MessageWriter, getL2Network } from "@arbitrum/sdk"

// Importing configuration //
require('dotenv').config()
requireEnvVariables(['L1RPC', 'L2RPC', 'L3RPC', 'DEVNET_PRIVKEY'])

// Initial setup //
const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC)
const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC)
const l3Provider = new providers.JsonRpcProvider(process.env.L3RPC)

const l2Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l2Provider)
const l3Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l3Provider)

const main = async (txHash: string) => {
await arbLog(`Recovering a failed ETH deposit`)

/**
* Use L3 Network to initialize a bridger
*/
const l3Network = await getL2Network(l3Provider)
const bridger = new EthL1L3Bridger(l3Network)

const status = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider })

if (status.completed) {
console.log('Teleportation already completed')
return
}

const getWriter = (signer: Wallet, msg: L1ToL2MessageReader) => {
return new L1ToL2MessageWriter(signer, msg.chainId, msg.sender, msg.messageNumber, msg.l1BaseFee, msg.messageData)
}

// the order of check / redeem is important

if (await status.l2Retryable.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
console.log('Redeeming l2Retryable...')
await getWriter(l2Signer, status.l2Retryable).redeem()
console.log('Redeemed l2Retryable')

console.log('l3Retryable should be created after redeeming l2Retryable')
console.log('Please run this script again to redeem l3Retryable if it fails to redeem automatically')
}

if (await status.l3Retryable?.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) {
console.log('Redeeming l3Retryable...')
await getWriter(l3Signer, status.l3Retryable!).redeem()
console.log('Redeemed l3Retryable')
console.log('All retryables redeemed, teleportation completed!')
}
}

if (!process.argv[2]) {
console.error('Please provide a transaction hash')
process.exit(1)
}

main(process.argv[2])
22 changes: 11 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
optionalDependencies:
sol2uml "2.2.0"

"@arbitrum/[email protected]":
version "3.5.1-teleporter.0"
resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.5.1-teleporter.0.tgz#68e2612cdaec43b173ee4dbb928871cca067c50f"
integrity sha512-0lJrUgQr/nTEbMzOKgl70m/1vnvVWE8WTOG+UFBN3iSMugcxGsJcNHh96xQV3qyYbo4XcWLwrRkwGg2WVuN25w==
dependencies:
"@ethersproject/address" "^5.0.8"
"@ethersproject/bignumber" "^5.1.1"
"@ethersproject/bytes" "^5.0.8"
async-mutex "^0.4.0"
ethers "^5.1.0"

"@arbitrum/sdk@^v3.1.9":
version "3.1.11"
resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.1.11.tgz#14fa67daceef7568b949e4a2695d561dd47eec5b"
Expand Down Expand Up @@ -4823,17 +4834,6 @@ globalthis@^1.0.3:
dependencies:
define-properties "^1.1.3"

godzillaba-arbitrum-sdk@^3.3.3-teleport:
version "3.3.3-teleport"
resolved "https://registry.yarnpkg.com/godzillaba-arbitrum-sdk/-/godzillaba-arbitrum-sdk-3.3.3-teleport.tgz#40786631068327231633738bfdf8e6a4a8c08281"
integrity sha512-gmzKsey8Kumfx7zieFbDZIXAndVQX4HxNq9/qXthVPx9pXy7E1rKIqEtDcaID74ixGUkKTjkHqeszJ6bWk6uJQ==
dependencies:
"@ethersproject/address" "^5.0.8"
"@ethersproject/bignumber" "^5.1.1"
"@ethersproject/bytes" "^5.0.8"
async-mutex "^0.4.0"
ethers "^5.1.0"

gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
Expand Down