Skip to content
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

wip stuff #1183

Draft
wants to merge 5 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
69 changes: 69 additions & 0 deletions packages/arb-token-bridge-ui/src/__experiments__/BridgeTransfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Provider } from '@ethersproject/providers'
import { ContractReceipt, ContractTransaction } from 'ethers'

type Chain = 'source_chain' | 'destination_chain'
type TxStatus = 'pending' | 'success' | 'error'

export type BridgeTransferStatus = `${Chain}_tx_${TxStatus}`
export type BridgeTransferFetchStatusFunctionResult =
Promise<BridgeTransferStatus>

export abstract class BridgeTransfer {
// status
public status: BridgeTransferStatus

// source chain
public sourceChainProvider: Provider
public sourceChainTx: ContractTransaction
public sourceChainTxReceipt?: ContractReceipt

// destination chain
public destinationChainProvider: Provider
public destinationChainTx?: ContractTransaction
public destinationChainTxReceipt?: ContractReceipt

protected constructor(props: {
status: BridgeTransferStatus
sourceChainTx: ContractTransaction
sourceChainTxReceipt?: ContractReceipt
sourceChainProvider: Provider
destinationChainProvider: Provider
}) {
this.status = props.status
this.sourceChainTx = props.sourceChainTx
this.sourceChainTxReceipt = props.sourceChainTxReceipt
this.sourceChainProvider = props.sourceChainProvider
this.destinationChainProvider = props.destinationChainProvider
}

/**
* Checks if the bridge transfer status provided is final.
*
* @param status Status to be checked.
*/
protected abstract isStatusFinal(status: BridgeTransferStatus): boolean

/**
* Fetches the current status of the bridge transfer.
*/
public abstract fetchStatus(): BridgeTransferFetchStatusFunctionResult

public pollForStatus(props: {
intervalMs?: number
onChange: (bridgeTransfer: BridgeTransfer) => void
}): void {
const intervalId = setInterval(async () => {
const status = await this.fetchStatus()
const statusChanged = this.status !== status
this.status = status

if (statusChanged) {
props.onChange(this)
}

if (this.isStatusFinal(this.status)) {
clearInterval(intervalId)
}
}, props.intervalMs ?? 15_000)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Provider } from '@ethersproject/providers'
import { BigNumber, ContractTransaction, Signer } from 'ethers'

import { BridgeTransfer } from './BridgeTransfer'

export type BridgeTransferStarterConstructorProps = {
sourceChainProvider: Provider
sourceChainErc20ContractAddress?: string
destinationChainProvider: Provider
}

export type BridgeTransferStarterApproveFunctionProps = {
amount?: BigNumber
sourceChainSigner: Signer
}

export type BridgeTransferStarterStartFunctionProps = {
amount: BigNumber
sourceChainSigner: Signer
destinationChainAddress?: string
}

export abstract class BridgeTransferStarter {
protected sourceChainProvider: Provider
protected sourceChainErc20ContractAddress?: string
protected destinationChainProvider: Provider

constructor(props: BridgeTransferStarterConstructorProps) {
this.sourceChainProvider = props.sourceChainProvider
this.sourceChainErc20ContractAddress = props.sourceChainErc20ContractAddress
this.destinationChainProvider = props.destinationChainProvider
}

public abstract requiresApproval(
props: BridgeTransferStarterStartFunctionProps
): Promise<boolean>

public abstract approve(
props: BridgeTransferStarterApproveFunctionProps
): Promise<ContractTransaction>

public abstract start(
props: BridgeTransferStarterStartFunctionProps
): Promise<BridgeTransfer>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
BridgeTransferStarter,
BridgeTransferStarterConstructorProps
} from './BridgeTransferStarter'

import { Erc20DepositStarter } from './Erc20DepositStarter'

export class BridgeTransferStarterFactory {
public static async create(
props: BridgeTransferStarterConstructorProps
): Promise<BridgeTransferStarter> {
return new Erc20DepositStarter(props)
}
}
118 changes: 118 additions & 0 deletions packages/arb-token-bridge-ui/src/__experiments__/Erc20Deposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { ContractTransaction, ContractReceipt } from 'ethers'
import { Provider } from '@ethersproject/providers'
import { L1ContractCallTransactionReceipt } from '@arbitrum/sdk/dist/lib/message/L1Transaction'

import {
BridgeTransfer,
BridgeTransferStatus,
BridgeTransferFetchStatusFunctionResult
} from './BridgeTransfer'
import { L1ToL2MessageStatus } from '@arbitrum/sdk'

export class Erc20Deposit extends BridgeTransfer {
private constructor(props: {
status: BridgeTransferStatus
sourceChainTx: ContractTransaction
sourceChainTxReceipt?: ContractReceipt
sourceChainProvider: Provider
destinationChainProvider: Provider
}) {
super(props)
}

public static async fromSourceChainTx(props: {
sourceChainTx: ContractTransaction
sourceChainProvider: Provider
destinationChainProvider: Provider
}) {
const sourceChainTxReceipt =
await props.sourceChainProvider.getTransactionReceipt(
props.sourceChainTx.hash
)

let status: BridgeTransferStatus

if (sourceChainTxReceipt) {
status =
sourceChainTxReceipt.status === 0
? 'source_chain_tx_error'
: 'source_chain_tx_success'
} else {
status = 'source_chain_tx_pending'
}

return new Erc20Deposit({ ...props, status, sourceChainTxReceipt })
}

public static async fromSourceChainTxHash(props: {
sourceChainTxHash: string
sourceChainProvider: Provider
destinationChainProvider: Provider
}) {
const sourceChainTx = await props.sourceChainProvider.getTransaction(
props.sourceChainTxHash
)

const erc20Deposit = await Erc20Deposit.fromSourceChainTx({
...props,
sourceChainTx
})

await erc20Deposit.updateStatus()

return erc20Deposit
}

protected isStatusFinal(status: BridgeTransferStatus): boolean {
if (status === 'source_chain_tx_error') {
return true
}

return false
}

public async updateStatus(): Promise<void> {
this.status = await this.fetchStatus()
}

public async fetchStatus(): BridgeTransferFetchStatusFunctionResult {
// we don't have a source chain tx receipt yet
if (!this.sourceChainTxReceipt) {
// let's fetch it
this.sourceChainTxReceipt =
await this.sourceChainProvider.getTransactionReceipt(
this.sourceChainTx.hash
)

// still nothing
if (!this.sourceChainTxReceipt) {
return 'source_chain_tx_pending'
}
}

// okay now we have tx receipt
const [message] = await new L1ContractCallTransactionReceipt(
this.sourceChainTxReceipt
).getL1ToL2Messages(this.destinationChainProvider)

// message not yet created
if (typeof message === 'undefined') {
return this.sourceChainTxReceipt.status === 1
? 'source_chain_tx_success'
: 'source_chain_tx_error'
}

const successfulRedeem = await message.getSuccessfulRedeem()

if (successfulRedeem.status === L1ToL2MessageStatus.REDEEMED) {
this.destinationChainTxReceipt = successfulRedeem.l2TxReceipt
return 'destination_chain_tx_success'
}

if (successfulRedeem.status === L1ToL2MessageStatus.NOT_YET_CREATED) {
return 'source_chain_tx_success'
}

return 'destination_chain_tx_error'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Erc20Bridger } from '@arbitrum/sdk'

import {
BridgeTransferStarter,
BridgeTransferStarterApproveFunctionProps,
BridgeTransferStarterStartFunctionProps
} from './BridgeTransferStarter'
import { Erc20Deposit } from './Erc20Deposit'
import { ContractTransaction } from 'ethers'
import { BridgeTransfer } from './BridgeTransfer'
import { getL1TokenAllowance } from '../util/TokenUtils'

export class Erc20DepositStarter extends BridgeTransferStarter {
public async requiresApproval(
props: BridgeTransferStarterStartFunctionProps
): Promise<boolean> {
const account = await props.sourceChainSigner.getAddress()

if (typeof this.sourceChainErc20ContractAddress === 'undefined') {
throw new Error('unexpected')
}

const allowance = await getL1TokenAllowance({
account,
erc20L1Address: this.sourceChainErc20ContractAddress,
l1Provider: this.sourceChainProvider,
l2Provider: this.destinationChainProvider
})

return allowance.lt(props.amount)
}

public async approve(
props: BridgeTransferStarterApproveFunctionProps
): Promise<ContractTransaction> {
const erc20Bridger = await Erc20Bridger.fromProvider(
this.destinationChainProvider
)

if (typeof this.sourceChainErc20ContractAddress === 'undefined') {
throw new Error('unexpected')
}

return erc20Bridger.approveToken({
amount: props.amount,
l1Signer: props.sourceChainSigner,
erc20L1Address: this.sourceChainErc20ContractAddress
})
}

public async start(
props: BridgeTransferStarterStartFunctionProps
): Promise<BridgeTransfer> {
const erc20Bridger = await Erc20Bridger.fromProvider(
this.destinationChainProvider
)

if (typeof this.sourceChainErc20ContractAddress === 'undefined') {
throw new Error('unexpected')
}

const tx = await erc20Bridger.deposit({
amount: props.amount,
l1Signer: props.sourceChainSigner,
erc20L1Address: this.sourceChainErc20ContractAddress,
l2Provider: this.destinationChainProvider
})

return Erc20Deposit.fromSourceChainTx({
sourceChainTx: tx,
sourceChainProvider: this.sourceChainProvider,
destinationChainProvider: this.destinationChainProvider
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Provider, TransactionReceipt } from '@ethersproject/providers'

import { L2TransactionReceipt } from '@arbitrum/sdk/dist/lib/message/L2Transaction'

import {
BridgeTransfer,
BridgeTransferStatusFunctionProps,
BridgeTransferStatusFunctionResult
} from './BridgeTransfer'
import { Signer } from 'ethers'
import { L2ToL1Message } from '@arbitrum/sdk'

export class EthDeposit extends BridgeTransfer {
protected fromChainProvider: Provider
protected fromChainTxReceipt: L2TransactionReceipt

private constructor(props: {
fromChainProvider: Provider
fromChainTxReceipt: L2TransactionReceipt
}) {
super()

this.fromChainProvider = props.fromChainProvider
this.fromChainTxReceipt = props.fromChainTxReceipt
}

public static async create(props: {
fromChainProvider: Provider
fromChainTxHash: string
}) {
const txReceipt = await props.fromChainProvider.getTransactionReceipt(
props.fromChainTxHash
)

return new EthDeposit({
fromChainProvider: props.fromChainProvider,
fromChainTxReceipt: txReceipt as L2TransactionReceipt
})
}

public async status(
props: BridgeTransferStatusFunctionProps
): Promise<BridgeTransferStatusFunctionResult> {
// return 'from_chain_tx_error'
// const [message] = await this.fromChainTxReceipt.getEthDeposits(
// props.toChainProvider
// )

// not yet created
// if (typeof message === 'undefined') {
// return 'from_chain_tx_success'
// }

return 'to_chain_tx_success'
}

public async complete(props: {
toChainSigner: Signer
toChainProvider: Provider
}) {
const [event] = this.fromChainTxReceipt.getL2ToL1Events()

if (typeof event === 'undefined') {
throw new Error('event not found')
}

return L2ToL1Message.fromEvent(
props.toChainSigner,
event,
props.toChainProvider
).execute(this.fromChainProvider)
}
}
Loading
Loading