Skip to content
Merged
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
68 changes: 56 additions & 12 deletions packages/wallet/core/src/relayer/local.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Constants, Payload } from '@0xsequence/wallet-primitives'
import { AbiFunction, Address, Bytes, Hex } from 'ox'
import { AbiFunction, Address, Bytes, Hex, TransactionReceipt } from 'ox'
import { FeeOption, FeeQuote, OperationStatus, Relayer } from './relayer.js'

type GenericProviderTransactionReceipt = 'success' | 'failed' | 'unknown'

export interface GenericProvider {
sendTransaction(args: { to: string; data: string }): Promise<string>
sendTransaction(args: { to: string; data: string }, chainId: bigint): Promise<string>
getTransactionReceipt(txHash: string, chainId: bigint): Promise<GenericProviderTransactionReceipt>
}

export class LocalRelayer implements Relayer {
Expand All @@ -18,15 +21,33 @@ export class LocalRelayer implements Relayer {
return undefined
}

const trySwitchChain = async (chainId: bigint) => {
try {
await eth.request({
method: 'wallet_switchEthereumChain',
params: [
{
chainId: `0x${chainId.toString(16)}`,
},
],
})
} catch (error) {
// Log and continue
console.error('Error switching chain', error)
}
}

return new LocalRelayer({
sendTransaction: async (args) => {
sendTransaction: async (args, chainId) => {
const accounts: string[] = await eth.request({ method: 'eth_requestAccounts' })
const from = accounts[0]
if (!from) {
console.warn('No account selected, skipping local relayer')
return undefined
}

await trySwitchChain(chainId)

const tx = await eth.request({
method: 'eth_sendTransaction',
params: [
Expand All @@ -39,6 +60,20 @@ export class LocalRelayer implements Relayer {
})
return tx
},
getTransactionReceipt: async (txHash, chainId) => {
await trySwitchChain(chainId)

const rpcReceipt = await eth.request({ method: 'eth_getTransactionReceipt', params: [txHash] })
if (rpcReceipt) {
const receipt = TransactionReceipt.fromRpc(rpcReceipt)
if (receipt?.status === 'success') {
return 'success'
} else if (receipt?.status === 'reverted') {
return 'failed'
}
}
return 'unknown'
},
})
}

Expand All @@ -65,17 +100,26 @@ export class LocalRelayer implements Relayer {
}

async relay(to: Address.Address, data: Hex.Hex, chainId: bigint, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
const hash = Payload.hash(to, chainId, this.decodeCalls(data))

await this.provider.sendTransaction({
to,
data,
})
const txHash = await this.provider.sendTransaction(
{
to,
data,
},
chainId,
)
Hex.assert(txHash)

return { opHash: Hex.fromBytes(hash) }
return { opHash: txHash }
}

status(opHash: Hex.Hex, chainId: bigint): Promise<OperationStatus> {
throw new Error('Method not implemented.')
async status(opHash: Hex.Hex, chainId: bigint): Promise<OperationStatus> {
const receipt = await this.provider.getTransactionReceipt(opHash, chainId)
if (receipt === 'unknown') {
// Could be pending but we don't know
return { status: 'unknown' }
}
return receipt === 'success'
? { status: 'confirmed', transactionHash: opHash }
: { status: 'failed', reason: 'failed' }
}
}
25 changes: 22 additions & 3 deletions packages/wallet/core/src/relayer/pk-relayer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Payload } from '@0xsequence/wallet-primitives'
import { Address, Hex, Provider, Secp256k1, TransactionEnvelopeEip1559 } from 'ox'
import { Address, Hex, Provider, Secp256k1, TransactionEnvelopeEip1559, TransactionReceipt } from 'ox'
import { LocalRelayer } from './local.js'
import { FeeOption, FeeQuote, OperationStatus, Relayer } from './relayer.js'

Expand All @@ -13,7 +13,12 @@ export class PkRelayer implements Relayer {
) {
const relayerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey }))
this.relayer = new LocalRelayer({
sendTransaction: async (args) => {
sendTransaction: async (args, chainId) => {
const providerChainId = BigInt(await this.provider.request({ method: 'eth_chainId' }))
if (providerChainId !== chainId) {
throw new Error('Provider chain id does not match relayer chain id')
}

const oxArgs = { ...args, to: args.to as `0x${string}`, data: args.data as `0x${string}` }
// Estimate gas with a safety buffer
const estimatedGas = BigInt(await this.provider.request({ method: 'eth_estimateGas', params: [oxArgs] }))
Expand All @@ -40,7 +45,6 @@ export class PkRelayer implements Relayer {
)

// Build the relay envelope
const chainId = BigInt(await this.provider.request({ method: 'eth_chainId' }))
const relayEnvelope = TransactionEnvelopeEip1559.from({
chainId: Number(chainId),
type: 'eip1559',
Expand All @@ -66,6 +70,21 @@ export class PkRelayer implements Relayer {
})
return tx
},
getTransactionReceipt: async (txHash: string, chainId: bigint) => {
Hex.assert(txHash)

const providerChainId = BigInt(await this.provider.request({ method: 'eth_chainId' }))
if (providerChainId !== chainId) {
throw new Error('Provider chain id does not match relayer chain id')
}

const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] })
if (!rpcReceipt) {
return 'unknown'
}
const receipt = TransactionReceipt.fromRpc(rpcReceipt)
return receipt.status === 'success' ? 'success' : 'failed'
},
})
}

Expand Down