Bridging L1 and L2 (Portal)
Thanos에서 Portal은 L1의 OptimismPortal
컨트랙트와 L2의 L2ToL1MessagePasser
컨트랙트를 사용하여 네트워크 간 자산의 입출금을 지원합니다. 단, Portal을 통해 전송할 수 있는 자산은 NativeToken
으로 한정되며 CrossChainMessenger
를 사용하는 것보다 더 낮은 가스비로 거래를 처리할 수 있습니다.
본 문서에서는 입금과 출금 시에 사용되는 Portal의 함수들을 살펴보고 Portal을 import하여 NativeToken
을 L1과 L2 간에 입출금하는 방법을 예제를 통해 단계별로 설명합니다.
여기를 통해 Portal에 대한 자세한 코드를 확인할 수 있습니다. Thanos SDK를 활용하여 L1과 L2 간에 NativeToken
을 입출금하는 예제 코드는 여기에서 확인할 수 있습니다.
생성자 (Constructor)
생성자는 CrossChainProvider
인스턴스를 초기화하는 데 필요한 정보를 설정합니다. 각 생성자는 L1과 L2 간의 상호작용을 원활하게 처리하기 위해 필수적인 요소들로 구성됩니다.
l1SignerOrProvider
: L1과 상호작용하기 위한 서명자 또는 제공자l2SignerOrProvider
: L2와 상호작용하기 위한 서명자 또는 제공자l1ChainId
: L1 체인의 고유 식별자l2ChainId
: L2 체인의 고유 식별자depositConfirmationBlocks
: 입금이 확인되기까지 필요한 블록 수l1BlockTimeSeconds
: L1 체인의 블록 생성 시간 (초)contracts
: 오버라이드할 수 있는 특정 계약 주소
/**
* Creates a new CrossChainProvider instance.
*
* @param opts Options for the provider.
* @param opts.l1SignerOrProvider Signer or Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l2SignerOrProvider Signer or Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.l2ChainId Chain ID for the L2 chain.
* @param opts.depositConfirmationBlocks Optional number of blocks before a deposit is confirmed.
* @param opts.l1BlockTimeSeconds Optional estimated block time in seconds for the L1 chain.
* @param opts.contracts Optional contract address overrides.
*/
constructor(opts: {
l1SignerOrProvider: SignerOrProviderLike
l2SignerOrProvider: SignerOrProviderLike
l1ChainId: NumberLike
l2ChainId: NumberLike
depositConfirmationBlocks?: NumberLike
l1BlockTimeSeconds?: NumberLike
contracts?: DeepPartial<OEContractsLike>
})
Set Up
Ethereum 블록체인과 상호작용하는 ethers
라이브러리와 @tokamak-network/thanos-sdk
패키지를 import 합니다.
또한, Portal의 생성자를 통해 L1과 L2간의 상호작용을 위한 인스턴스를 생성하고 Portal에서 제공하는 다양한 함수를 사용할 준비를 합니다.
import { ethers } from 'ethers'
import { Portals } from '@tokamak-network/thanos-sdk'
const l1Provider = new ethers.providers.StaticJsonRpcProvider(
process.env.L1_URL
)
const l2Provider = new ethers.providers.StaticJsonRpcProvider(
process.env.L2_URL
)
const l1ChainId = (await l1Provider.getNetwork()).chainId
const l2ChainId = (await l2Provider.getNetwork()).chainId
const l1Wallet = new ethers.Wallet(privateKey, l1Provider)
const l2Wallet = new ethers.Wallet(privateKey, l2Provider)
const l1Contracts = {
AddressManager: addressManager,
OptimismPortal: optimismPortal,
L2OutputOracle: l2OutputOracle,
}
const portals = new Portals({
contracts: {
l1: l1Contracts,
},
l1ChainId,
l2ChainId,
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2Wallet,
})
입금 (Deposit)
NativeToken 생성: L1에서
NativeToken
컨트랙트와 상호작용하기 위한 인스턴스를 생성합니다. 해당 인스턴스를 사용하여 이후 토큰의 승인 및 전송 작업을 수행할 수 있습니다. 컨트랙트의 주소와 ERC20 표준 ABI를 사용하여NativeToken
컨트랙트 객체를 생성하며 이를 기반으로OptimismPortal
컨트랙트와의 상호작용이 가능해집니다.
// NativeTokenContract in L1 (like TON)
const l2NativeTokenContract = new ethers.Contract(
l2NativeToken,
erc20ABI,
l1Wallet
)
사전 승인: L1에서
NativeToken
을 입금하기 전,OptimismPortal
컨트랙트가 해당 토큰을 사용할 수 있도록 사전에 승인을 받아야 합니다. 이를 위해approve
함수를 호출하여 지정된 수량만큼의 토큰을OptimismPortal
에서 처리할 수 있도록 허용합니다. 이 절차는 입금 과정에서 필수이며 승인 이후에 입금 트랜잭션이 성공적으로 진행됩니다.
// approve
const approveTx = await l2NativeTokenContract.approve(optimismPortal, amount)
await approveTx.wait()
console.log('approveTx:', approveTx.hash)
입금 실행:
portals.depositTransaction
함수를 사용하여 L2로NativeToken
을 입금합니다. 이 과정에서to
,value
,gasLimit
,data
와 같은 매개변수를 설정하여 트랜잭션을 실행합니다. 트랜잭션이 블록에 포함되면depositReceipt을
통해 트랜잭션 해시를 확인할 수 있습니다.
to
: 입금을 진행할 L2 주소value
: 입금 금액gasLimit
: 트랜잭션 실행 시 필요한 가스 한도 설정,data.length * 16 + 21000
보다 커야합니다.data
: 추가로 전달한 데이터 (기본 값은0x
)
// deposit NativeToken in L1
const depositTx = await portals.depositTransaction({
to: l2Wallet.address,
value: BigNumber.from(amount),
gasLimit: BigNumber.from('200000'), // It will be greater than data.length * 16 + 21000
data: '0x',
})
const depositReceipt = await depositTx.wait()
console.log('depositTx:', depositReceipt.transactionHash)
입금 확인: 입금이 성공적으로 처리되었는지 확인하기 위해
portals.waitingDepositTransactionRelayed
함수를 사용합니다. 이 함수는 트랜잭션이 L2에 도달하고 최종적으로 처리되었는지 확인 후 관련된 트랜잭션 해시를 반환합니다.getTransactionReceipt
를 통해 입금이 올바르게 처리되었는지 확인합니다.
// get relayedTxHash in L2
const relayedTxHash = await portals.waitingDepositTransactionRelayed(
depositReceipt,
{}
)
console.log('relayedTxHash:', relayedTxHash)
const depositedTxReceipt = await l2Provider.getTransactionReceipt(
relayedTxHash
)
출금 (Withdrawal)
출금 시작:
portals.initiateWithdrawal
함수를 사용하여 L2에서 L1으로 출금을 시작합니다. 이 함수는 출금 트랜잭션을 생성하고target
,value
,gasLimit
,data
와 같은 매개변수를 사용하여 출금을 요청합니다.withdrawalReceipt
을 통해 트랜잭션이 완료될 때까지 대기합니다.
target
: 출금을 진행할 L1 주소value
: 출금 금액gasLimit
: 가스 한도 설정data
: 추가로 전달한 데이터 (기본 값은0x
)
const withdrawalTx = await portals.initiateWithdrawal({
target: l1Wallet.address,
value: BigNumber.from(amount),
gasLimit: BigNumber.from('200000'),
data: '0x12345678',
})
const withdrawalReceipt = await withdrawalTx.wait()
릴레이 대기:
portals.waitForWithdrawalTxReadyForRelayUsingL2Tx
함수를 통해 L2 트랜잭션 해시를 기반으로 출금 트랜잭션이 L1으로 릴레이가 될 때까지 대기합니다.
await portals.waitForWithdrawalTxReadyForRelayUsingL2Tx(
withdrawalReceipt.transactionHash
)
출금 증명: 출금 트랜잭션이 L2에서 L1으로 릴레이될 준비가 되었음을 확인한 후
portals.proveWithdrawalTransactionUsingL2Tx
함수를 통해 L2 트랜잭션 해시를 기반으로 출금 트랜잭션을 증명합니다. 이 단계에서는 출금 트랜잭션의 유효성을 확인하기 위해 증명 트랜잭션을 제출합니다.
const proveTransaction = await portals.proveWithdrawalTransactionUsingL2Tx(
withdrawalReceipt.transactionHash
)
await proveTransaction.wait()
확정 대기:
portals.waitForFinalizationUsingL2Tx
함수를 사용하여 출금 트랜잭션이 확정될 때까지 대기합니다. 출금 트랜잭션의 확정이 완료되면 출금이 성공적으로 처리됩니다.
await portals.waitForFinalizationUsingL2Tx(withdrawalReceipt.transactionHash)
출금 확정:
portals.finalizeWithdrawalTransactionUsingL2Tx
함수를 사용하여 L2 트랜잭션 해시를 기반으로 출금 트랜잭션을 확정합니다. 이 단계에서 출금 트랜잭션의 최종 처리가 완료되며finalizedTransactionReceipt
을 확인합니다.
const finalizedTransaction =
await portals.finalizeWithdrawalTransactionUsingL2Tx(
withdrawalReceipt.transactionHash
)
const finalizedTransactionReceipt = await finalizedTransaction.wait()
console.log('finalized transaction receipt:', finalizedTransactionReceipt)
토큰 전송: 출금이 확정된 후
OptimismPortal
컨트랙트를 이용하여 사용자의 지갑 주소로 토큰을 전송합니다. 이 과정은 확정된 출금 트랜잭션에 따라 L1으로 토큰을 전송하는 작업을 수행합니다.
// Transferfrom (after finalization, user have to transferFrom to get his token)
const transferTx = await l2NativeTokenContract.transferFrom(
l1Contracts.OptimismPortal,
l1Wallet.address,
amount
)
await transferTx.wait()
트랜잭션 상태 확인 (getMessageStatus)
getMessageStatus
는 OptimismPortal
및 L2ToL1MessagePasser
를 사용할 때 L1과 L2 간의 입출금 트랜잭션의 상태를 확인하는 데 사용됩니다.
OptimismPortal
트랜잭션 생성:
portals.depositTransaction
함수를 사용하여 L1에서 L2로NativeToken
입금 트랜잭션을 생성합니다.
const depositTx = await portals.depositTransaction({
to: l2Wallet.address,
value: BigNumber.from(amount),
gasLimit: BigNumber.from('200000'),
data: '0x',
})
트랜잭션 대기:
depositTx.wait()
을 호출하여 트랜잭션이 완료되면 트랜잭션 영수증인depositReceipt
를 받습니다. 이 영수증에는transactionHash
등 중요한 정보가 포함됩니다.
const depositReceipt = await depositTx.wait()
console.log('depositTx:', depositReceipt.transactionHash)
릴레이 해시: 트랜잭션이 L1에서 성공적으로 처리한 후 해당 트랜잭션이 L2로 릴레이될 때까지 기다려야 합니다.
portals.waitingDepositTransactionRelayed
함수를 사용하여 L1에서 제출된 트랜잭션이 L2에 성공적으로 릴레이되었는지 확인하고relayedTxHash
를 가져옵니다. 이 해시는 L2에서 발생한 트랜잭션을 추적하는 데 사용됩니다.
const relayedTxHash = await portals.waitingDepositTransactionRelayed(
depositReceipt,
{}
)
상태 확인:
portals.getMessageStatus
를 사용하여 입금 트랜잭션의 상태를 확인하고 L2에서 성공적으로 릴레이되었는지 확인합니다.
const status = await portals.getMessageStatus(depositReceipt)
console.log('deposit status relayed:', status === MessageStatus.RELAYED)
이 흐름을 통해 L1에서 L2로 NativeToken
을 입금한 후 getMessageStatus
를 사용하여 트랜잭션의 최종 상태를 모니터링하여 전체 입금 과정이 정상적으로 처리되었는지 확인할 수 있습니다.
L2ToL1MessagePasser
출금 트랜잭션 생성 및 제출:
portals.initiateWithdrawal
을 통해 L2에서 L1으로 출금 트랜잭션을 생성합니다. 생성된 트랜잭션의 결과는withdrawalReceipt
으로 반환되며 이후 출금 트랜잭션의 상태를 추적하는 데 사용됩니다.
const withdrawalTx = await portals.initiateWithdrawal({
target: l1Wallet.address,
value: BigNumber.from(amount),
gasLimit: BigNumber.from('200000'),
data: '0x12345678',
})
const withdrawalReceipt = await withdrawalTx.wait()
출금 메세지 정보 계산:
portals.calculateWithdrawalMessage
는 출금 메시지에 대한 정보를 계산합니다. 이 정보는 출금 트랜잭션의 다음 단계인 증명 및 확정 프로세스에서 활용됩니다.
const withdrawalMessageInfo = await portals.calculateWithdrawalMessage(
withdrawalReceipt
)
console.log('withdrawalMessageInfo:', withdrawalMessageInfo)
출금 트랜잭션 상태 확인:
portals.getMessageStatus
를 사용하여 출금 트랜잭션의 상태를 확인합니다. 이 단계에서는 L2에서 root가 성공적으로 게시되었는지 확인합니다.
let status = await portals.getMessageStatus(withdrawalReceipt)
console.log('[Withdrawal Status] check publish L2 root:', status)
릴레이 준비 상태 대기: 출금 트랜잭션이 L1으로 릴레이될 준비가 될 때까지
portals.waitForWithdrawalTxReadyForRelay
함수를 사용하여 대기합니다. 이 과정은 L2에서 생성된 출금 메시지가 L1으로 전송될 준비가 되었음을 나타냅니다.
await portals.waitForWithdrawalTxReadyForRelay(withdrawalReceipt)
출금 트랜잭션 상태 확인 1:
getMessageStatus
를 다시 호출하여 출금 트랜잭션이 L1에서 증명될 준비가 되었는지 확인합니다.
status = await portals.getMessageStatus(withdrawalReceipt)
console.log('[Withdrawal Status] check ready for proving:', status)
출금 트랜잭션 증명:
portals.proveWithdrawalTransaction
을 사용하여 L1에서 출금 트랜잭션을 증명합니다. 이 단계는 출금 트랜잭션을 L1에서 확인하는 과정입니다.
const proveTransaction = await portals.proveWithdrawalTransaction(
withdrawalMessageInfo
)
await proveTransaction.wait()
출금 트랜잭션 상태 확인 2: 트랜잭션이 L1에서 챌린징 상태에 있는지
getMessageStatus
로 확인합니다. 이 상태는 트랜잭션이 아직 처리 중임을 나타냅니다.
status = await portals.getMessageStatus(withdrawalReceipt)
console.log('[Withdrawal Status] check in challenging:', status)
확정 대기: 트랜잭션이 최종적으로 완료될 때까지
portals.waitForFinalization
을 호출해 대기합니다. 이후, 트랜잭션의 확정 프로세스를 수행합니다.
await portals.waitForFinalization(withdrawalMessageInfo)
const finalizedTransaction = await portals.finalizeWithdrawalTransaction(
withdrawalMessageInfo
출금 트랜잭션 확정 및 상태 확인:
portals.finalizeWithdrawalTransaction
을 사용하여 출금 트랜잭션을 확정합니다. 이 과정은 트랜잭션이 성공적으로 완료되었음을 나타냅니다. 마지막으로getMessageStatus
를 호출하여 출금 트랜잭션이 L1에서 성공적으로 릴레이되었는지 확인합니다.
const finalizedTransactionReceipt = await finalizedTransaction.wait()
console.log('finalized transaction receipt:', finalizedTransactionReceipt)
status = await portals.getMessageStatus(withdrawalReceipt)
console.log('[Withdrawal Status] check relayed:', status)
Last updated