@0xsequence/relayer 2.3.35 → 3.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +3862 -0
  3. package/LICENSE +0 -17
  4. package/README.md +1 -2
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3 -0
  8. package/dist/preconditions/codec.d.ts +12 -0
  9. package/dist/preconditions/codec.d.ts.map +1 -0
  10. package/dist/preconditions/codec.js +125 -0
  11. package/dist/preconditions/index.d.ts +4 -0
  12. package/dist/preconditions/index.d.ts.map +1 -0
  13. package/dist/preconditions/index.js +3 -0
  14. package/dist/preconditions/selectors.d.ts +7 -0
  15. package/dist/preconditions/selectors.d.ts.map +1 -0
  16. package/dist/preconditions/selectors.js +27 -0
  17. package/dist/preconditions/types.d.ts +70 -0
  18. package/dist/preconditions/types.d.ts.map +1 -0
  19. package/dist/preconditions/types.js +203 -0
  20. package/dist/relayer/index.d.ts +45 -0
  21. package/dist/relayer/index.d.ts.map +1 -0
  22. package/dist/relayer/index.js +3 -0
  23. package/dist/relayer/relayer.d.ts +26 -0
  24. package/dist/relayer/relayer.d.ts.map +1 -0
  25. package/dist/relayer/relayer.js +7 -0
  26. package/dist/relayer/rpc-relayer/index.d.ts +38 -0
  27. package/dist/relayer/rpc-relayer/index.d.ts.map +1 -0
  28. package/dist/relayer/rpc-relayer/index.js +375 -0
  29. package/dist/{declarations/src → relayer}/rpc-relayer/relayer.gen.d.ts +218 -178
  30. package/dist/relayer/rpc-relayer/relayer.gen.d.ts.map +1 -0
  31. package/dist/relayer/rpc-relayer/relayer.gen.js +1246 -0
  32. package/dist/relayer/standard/abi.d.ts +73 -0
  33. package/dist/relayer/standard/abi.d.ts.map +1 -0
  34. package/dist/relayer/standard/abi.js +10 -0
  35. package/dist/relayer/standard/eip6963.d.ts +31 -0
  36. package/dist/relayer/standard/eip6963.d.ts.map +1 -0
  37. package/dist/relayer/standard/eip6963.js +51 -0
  38. package/dist/relayer/standard/index.d.ts +5 -0
  39. package/dist/relayer/standard/index.d.ts.map +1 -0
  40. package/dist/relayer/standard/index.js +4 -0
  41. package/dist/relayer/standard/local.d.ts +60 -0
  42. package/dist/relayer/standard/local.d.ts.map +1 -0
  43. package/dist/relayer/standard/local.js +285 -0
  44. package/dist/relayer/standard/pk-relayer.d.ts +28 -0
  45. package/dist/relayer/standard/pk-relayer.d.ts.map +1 -0
  46. package/dist/relayer/standard/pk-relayer.js +112 -0
  47. package/dist/relayer/standard/sequence.d.ts +27 -0
  48. package/dist/relayer/standard/sequence.d.ts.map +1 -0
  49. package/dist/relayer/standard/sequence.js +84 -0
  50. package/package.json +28 -25
  51. package/src/index.ts +3 -111
  52. package/src/preconditions/codec.ts +190 -0
  53. package/src/preconditions/index.ts +3 -0
  54. package/src/preconditions/selectors.ts +38 -0
  55. package/src/preconditions/types.ts +201 -0
  56. package/src/relayer/index.ts +60 -0
  57. package/src/relayer/relayer.ts +37 -0
  58. package/src/relayer/rpc-relayer/index.ts +449 -0
  59. package/src/relayer/rpc-relayer/relayer.gen.ts +2268 -0
  60. package/src/relayer/standard/abi.ts +13 -0
  61. package/src/relayer/standard/eip6963.ts +74 -0
  62. package/src/relayer/standard/index.ts +4 -0
  63. package/src/relayer/standard/local.ts +353 -0
  64. package/src/relayer/standard/pk-relayer.ts +138 -0
  65. package/src/relayer/standard/sequence.ts +110 -0
  66. package/test/preconditions/codec.test.ts +531 -0
  67. package/test/preconditions/preconditions.test.ts +283 -0
  68. package/test/preconditions/selectors.test.ts +415 -0
  69. package/test/preconditions/types.test.ts +443 -0
  70. package/test/relayer/relayer.test.ts +355 -0
  71. package/tsconfig.json +10 -0
  72. package/dist/0xsequence-relayer.cjs.d.ts +0 -2
  73. package/dist/0xsequence-relayer.cjs.dev.js +0 -1626
  74. package/dist/0xsequence-relayer.cjs.js +0 -7
  75. package/dist/0xsequence-relayer.cjs.prod.js +0 -1626
  76. package/dist/0xsequence-relayer.esm.js +0 -1613
  77. package/dist/declarations/src/index.d.ts +0 -42
  78. package/dist/declarations/src/local-relayer.d.ts +0 -35
  79. package/dist/declarations/src/provider-relayer.d.ts +0 -47
  80. package/dist/declarations/src/rpc-relayer/index.d.ts +0 -72
  81. package/src/local-relayer.ts +0 -125
  82. package/src/provider-relayer.ts +0 -284
  83. package/src/rpc-relayer/index.ts +0 -380
  84. package/src/rpc-relayer/relayer.gen.ts +0 -1900
@@ -0,0 +1,13 @@
1
+ import { AbiFunction } from 'ox'
2
+
3
+ // ERC20 ABI functions
4
+ export const erc20BalanceOf = AbiFunction.from('function balanceOf(address) returns (uint256)')
5
+ export const erc20Allowance = AbiFunction.from('function allowance(address,address) returns (uint256)')
6
+
7
+ // ERC721 ABI functions
8
+ export const erc721OwnerOf = AbiFunction.from('function ownerOf(uint256) returns (address)')
9
+ export const erc721GetApproved = AbiFunction.from('function getApproved(uint256) returns (address)')
10
+
11
+ // ERC1155 ABI functions
12
+ export const erc1155BalanceOf = AbiFunction.from('function balanceOf(address,uint256) returns (uint256)')
13
+ export const erc1155IsApprovedForAll = AbiFunction.from('function isApprovedForAll(address,address) returns (bool)')
@@ -0,0 +1,74 @@
1
+ import { createStore, EIP6963ProviderInfo, EIP6963ProviderDetail } from 'mipd'
2
+ import { EIP1193ProviderAdapter, LocalRelayer } from './local.js'
3
+ import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
4
+ import { Address, Hex } from 'ox'
5
+ import { Payload } from '@0xsequence/wallet-primitives'
6
+ import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js'
7
+
8
+ export class EIP6963Relayer implements Relayer {
9
+ public readonly kind: 'relayer' = 'relayer'
10
+ public readonly type = 'eip6963'
11
+ public readonly id: string
12
+ public readonly info: EIP6963ProviderInfo
13
+ private readonly relayer: LocalRelayer
14
+
15
+ constructor(detail: EIP6963ProviderDetail) {
16
+ this.info = detail.info
17
+ this.id = detail.info.uuid
18
+
19
+ this.relayer = new LocalRelayer(new EIP1193ProviderAdapter(detail.provider))
20
+ }
21
+
22
+ isAvailable(wallet: Address.Address, chainId: number): Promise<boolean> {
23
+ return this.relayer.isAvailable(wallet, chainId)
24
+ }
25
+
26
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
27
+ return this.relayer.feeTokens()
28
+ }
29
+
30
+ feeOptions(
31
+ wallet: Address.Address,
32
+ chainId: number,
33
+ calls: Payload.Call[],
34
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
35
+ return this.relayer.feeOptions(wallet, chainId, calls)
36
+ }
37
+
38
+ async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
39
+ return this.relayer.relay(to, data, chainId)
40
+ }
41
+
42
+ status(opHash: Hex.Hex, chainId: number): Promise<OperationStatus> {
43
+ return this.relayer.status(opHash, chainId)
44
+ }
45
+
46
+ async checkPrecondition(precondition: TransactionPrecondition): Promise<boolean> {
47
+ return this.relayer.checkPrecondition(precondition)
48
+ }
49
+ }
50
+
51
+ // Global store instance
52
+ let store: ReturnType<typeof createStore> | undefined
53
+
54
+ export function getEIP6963Store() {
55
+ if (!store) {
56
+ store = createStore()
57
+ }
58
+ return store
59
+ }
60
+
61
+ let relayers: Map<string, EIP6963Relayer> = new Map()
62
+
63
+ export function getRelayers(): EIP6963Relayer[] {
64
+ const store = getEIP6963Store()
65
+ const providers = store.getProviders()
66
+
67
+ for (const detail of providers) {
68
+ if (!relayers.has(detail.info.uuid)) {
69
+ relayers.set(detail.info.uuid, new EIP6963Relayer(detail))
70
+ }
71
+ }
72
+
73
+ return Array.from(relayers.values())
74
+ }
@@ -0,0 +1,4 @@
1
+ export * from './local.js'
2
+ export * from './pk-relayer.js'
3
+ export * from './sequence.js'
4
+ export * as EIP6963 from './eip6963.js'
@@ -0,0 +1,353 @@
1
+ import { Constants, Payload } from '@0xsequence/wallet-primitives'
2
+ import { EIP1193Provider } from 'mipd'
3
+ import { AbiFunction, Address, Bytes, Hex, TransactionReceipt } from 'ox'
4
+ import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
5
+ import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js'
6
+ import { decodePrecondition } from '../../preconditions/index.js'
7
+ import {
8
+ erc20BalanceOf,
9
+ erc20Allowance,
10
+ erc721OwnerOf,
11
+ erc721GetApproved,
12
+ erc1155BalanceOf,
13
+ erc1155IsApprovedForAll,
14
+ } from './abi.js'
15
+
16
+ type GenericProviderTransactionReceipt = 'success' | 'failed' | 'unknown'
17
+
18
+ export interface GenericProvider {
19
+ sendTransaction(args: { to: Address.Address; data: Hex.Hex }, chainId: number): Promise<string | undefined>
20
+ getBalance(address: Address.Address): Promise<bigint>
21
+ call(args: { to: Address.Address; data: Hex.Hex }): Promise<string>
22
+ getTransactionReceipt(txHash: Hex.Hex, chainId: number): Promise<GenericProviderTransactionReceipt>
23
+ }
24
+
25
+ export class LocalRelayer implements Relayer {
26
+ public readonly kind: 'relayer' = 'relayer'
27
+ public readonly type = 'local'
28
+ public readonly id = 'local'
29
+
30
+ constructor(public readonly provider: GenericProvider) {}
31
+
32
+ isAvailable(_wallet: Address.Address, _chainId: number): Promise<boolean> {
33
+ return Promise.resolve(true)
34
+ }
35
+
36
+ static createFromWindow(window: Window): LocalRelayer | undefined {
37
+ const eth = (window as any).ethereum
38
+ if (!eth) {
39
+ console.warn('Window.ethereum not found, skipping local relayer')
40
+ return undefined
41
+ }
42
+
43
+ return new LocalRelayer(new EIP1193ProviderAdapter(eth))
44
+ }
45
+
46
+ static createFromProvider(provider: EIP1193Provider): LocalRelayer {
47
+ return new LocalRelayer(new EIP1193ProviderAdapter(provider))
48
+ }
49
+
50
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
51
+ return Promise.resolve({
52
+ isFeeRequired: false,
53
+ })
54
+ }
55
+
56
+ feeOptions(
57
+ wallet: Address.Address,
58
+ chainId: number,
59
+ calls: Payload.Call[],
60
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
61
+ return Promise.resolve({ options: [] })
62
+ }
63
+
64
+ private decodeCalls(data: Hex.Hex): Payload.Calls {
65
+ const executeSelector = AbiFunction.getSelector(Constants.EXECUTE)
66
+
67
+ let packedPayload
68
+ if (data.startsWith(executeSelector)) {
69
+ const decode = AbiFunction.decodeData(Constants.EXECUTE, data)
70
+ packedPayload = decode[0]
71
+ } else {
72
+ packedPayload = data
73
+ }
74
+
75
+ return Payload.decode(Bytes.fromHex(packedPayload))
76
+ }
77
+
78
+ async relay(
79
+ to: Address.Address,
80
+ data: Hex.Hex,
81
+ chainId: number,
82
+ quote?: FeeQuote,
83
+ preconditions?: TransactionPrecondition[],
84
+ checkInterval: number = 5000,
85
+ ): Promise<{ opHash: Hex.Hex }> {
86
+ // Helper function to check all preconditions
87
+ const checkAllPreconditions = async (): Promise<boolean> => {
88
+ if (!preconditions || preconditions.length === 0) {
89
+ return true
90
+ }
91
+
92
+ for (const precondition of preconditions) {
93
+ const isValid = await this.checkPrecondition(precondition)
94
+ if (!isValid) {
95
+ return false
96
+ }
97
+ }
98
+ return true
99
+ }
100
+
101
+ // Check preconditions immediately
102
+ if (await checkAllPreconditions()) {
103
+ // If all preconditions are met, relay the transaction
104
+ const txHash = await this.provider.sendTransaction(
105
+ {
106
+ to,
107
+ data,
108
+ },
109
+ chainId,
110
+ )
111
+
112
+ // TODO: Return the opHash instead, but solve the `status` function
113
+ // to properly fetch the receipt from an opHash instead of a txHash
114
+ return { opHash: txHash as Hex.Hex }
115
+ }
116
+
117
+ // If not all preconditions are met, set up event listeners and polling
118
+ return new Promise((resolve, reject) => {
119
+ let timeoutId: NodeJS.Timeout
120
+ let isResolved = false
121
+
122
+ // Function to check and relay
123
+ const checkAndRelay = async () => {
124
+ try {
125
+ if (isResolved) return
126
+
127
+ if (await checkAllPreconditions()) {
128
+ isResolved = true
129
+ clearTimeout(timeoutId)
130
+ const txHash = await this.provider.sendTransaction(
131
+ {
132
+ to,
133
+ data,
134
+ },
135
+ chainId,
136
+ )
137
+ resolve({ opHash: txHash as Hex.Hex })
138
+ } else {
139
+ // Schedule next check
140
+ timeoutId = setTimeout(checkAndRelay, checkInterval)
141
+ }
142
+ } catch (error) {
143
+ isResolved = true
144
+ clearTimeout(timeoutId)
145
+ reject(error)
146
+ }
147
+ }
148
+
149
+ // Start checking
150
+ timeoutId = setTimeout(checkAndRelay, checkInterval)
151
+
152
+ // Cleanup function
153
+ return () => {
154
+ isResolved = true
155
+ clearTimeout(timeoutId)
156
+ }
157
+ })
158
+ }
159
+
160
+ async status(opHash: Hex.Hex, chainId: number): Promise<OperationStatus> {
161
+ const receipt = await this.provider.getTransactionReceipt(opHash, chainId)
162
+ if (receipt === 'unknown') {
163
+ // Could be pending but we don't know
164
+ return { status: 'unknown' }
165
+ }
166
+ return receipt === 'success'
167
+ ? { status: 'confirmed', transactionHash: opHash }
168
+ : { status: 'failed', reason: 'failed' }
169
+ }
170
+
171
+ async checkPrecondition(precondition: TransactionPrecondition): Promise<boolean> {
172
+ const decoded = decodePrecondition(precondition)
173
+
174
+ if (!decoded) {
175
+ return false
176
+ }
177
+
178
+ switch (decoded.type()) {
179
+ case 'native-balance': {
180
+ const native = decoded as any
181
+ const balance = await this.provider.getBalance(native.address.toString())
182
+ if (native.min !== undefined && balance < native.min) {
183
+ return false
184
+ }
185
+ if (native.max !== undefined && balance > native.max) {
186
+ return false
187
+ }
188
+ return true
189
+ }
190
+
191
+ case 'erc20-balance': {
192
+ const erc20 = decoded as any
193
+ const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address.toString()])
194
+ const result = await this.provider.call({
195
+ to: erc20.token.toString(),
196
+ data,
197
+ })
198
+ const balance = BigInt(result)
199
+ if (erc20.min !== undefined && balance < erc20.min) {
200
+ return false
201
+ }
202
+ if (erc20.max !== undefined && balance > erc20.max) {
203
+ return false
204
+ }
205
+ return true
206
+ }
207
+
208
+ case 'erc20-approval': {
209
+ const erc20 = decoded as any
210
+ const data = AbiFunction.encodeData(erc20Allowance, [erc20.address.toString(), erc20.operator.toString()])
211
+ const result = await this.provider.call({
212
+ to: erc20.token.toString(),
213
+ data,
214
+ })
215
+ const allowance = BigInt(result)
216
+ return allowance >= erc20.min
217
+ }
218
+
219
+ case 'erc721-ownership': {
220
+ const erc721 = decoded as any
221
+ const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId])
222
+ const result = await this.provider.call({
223
+ to: erc721.token.toString(),
224
+ data,
225
+ })
226
+ const owner = '0x' + result.slice(26)
227
+ const isOwner = owner.toLowerCase() === erc721.address.toString().toLowerCase()
228
+ return erc721.owned === undefined ? isOwner : erc721.owned === isOwner
229
+ }
230
+
231
+ case 'erc721-approval': {
232
+ const erc721 = decoded as any
233
+ const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId])
234
+ const result = await this.provider.call({
235
+ to: erc721.token.toString(),
236
+ data,
237
+ })
238
+ const approved = '0x' + result.slice(26)
239
+ return approved.toLowerCase() === erc721.operator.toString().toLowerCase()
240
+ }
241
+
242
+ case 'erc1155-balance': {
243
+ const erc1155 = decoded as any
244
+ const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address.toString(), erc1155.tokenId])
245
+ const result = await this.provider.call({
246
+ to: erc1155.token.toString(),
247
+ data,
248
+ })
249
+ const balance = BigInt(result)
250
+ if (erc1155.min !== undefined && balance < erc1155.min) {
251
+ return false
252
+ }
253
+ if (erc1155.max !== undefined && balance > erc1155.max) {
254
+ return false
255
+ }
256
+ return true
257
+ }
258
+
259
+ case 'erc1155-approval': {
260
+ const erc1155 = decoded as any
261
+ const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [
262
+ erc1155.address.toString(),
263
+ erc1155.operator.toString(),
264
+ ])
265
+ const result = await this.provider.call({
266
+ to: erc1155.token.toString(),
267
+ data,
268
+ })
269
+ return BigInt(result) === 1n
270
+ }
271
+
272
+ default:
273
+ return false
274
+ }
275
+ }
276
+ }
277
+
278
+ export class EIP1193ProviderAdapter implements GenericProvider {
279
+ constructor(private readonly provider: EIP1193Provider) {}
280
+
281
+ private async trySwitchChain(chainId: number) {
282
+ try {
283
+ await this.provider.request({
284
+ method: 'wallet_switchEthereumChain',
285
+ params: [
286
+ {
287
+ chainId: `0x${chainId.toString(16)}`,
288
+ },
289
+ ],
290
+ })
291
+ } catch (error) {
292
+ // Log and continue
293
+ console.error('Error switching chain', error)
294
+ }
295
+ }
296
+
297
+ async sendTransaction(args: { to: Address.Address; data: Hex.Hex }, chainId: number) {
298
+ const accounts: Address.Address[] = await this.provider.request({ method: 'eth_requestAccounts' })
299
+ const from = accounts[0]
300
+
301
+ if (!from) {
302
+ console.warn('No account selected, skipping local relayer')
303
+ return undefined
304
+ }
305
+
306
+ await this.trySwitchChain(chainId)
307
+
308
+ const tx = await this.provider.request({
309
+ method: 'eth_sendTransaction',
310
+ params: [
311
+ {
312
+ from,
313
+ to: args.to,
314
+ data: args.data,
315
+ },
316
+ ],
317
+ })
318
+
319
+ return tx
320
+ }
321
+
322
+ async getBalance(address: Address.Address) {
323
+ const balance = await this.provider.request({
324
+ method: 'eth_getBalance',
325
+ params: [address, 'latest'],
326
+ })
327
+ return BigInt(balance)
328
+ }
329
+
330
+ async call(args: { to: Address.Address; data: Hex.Hex }) {
331
+ return await this.provider.request({
332
+ method: 'eth_call',
333
+ params: [args, 'latest'],
334
+ })
335
+ }
336
+
337
+ async getTransactionReceipt(txHash: Hex.Hex, chainId: number) {
338
+ await this.trySwitchChain(chainId)
339
+
340
+ const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] })
341
+
342
+ if (rpcReceipt) {
343
+ const receipt = TransactionReceipt.fromRpc(rpcReceipt as any)
344
+ if (receipt?.status === 'success') {
345
+ return 'success'
346
+ } else if (receipt?.status === 'reverted') {
347
+ return 'failed'
348
+ }
349
+ }
350
+
351
+ return 'unknown'
352
+ }
353
+ }
@@ -0,0 +1,138 @@
1
+ import { Payload, Precondition } from '@0xsequence/wallet-primitives'
2
+ import { Address, Hex, Provider, Secp256k1, TransactionEnvelopeEip1559, TransactionReceipt } from 'ox'
3
+ import { LocalRelayer } from './local.js'
4
+ import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
5
+ import { FeeToken } from '../rpc-relayer/relayer.gen.js'
6
+
7
+ export class PkRelayer implements Relayer {
8
+ public readonly kind: 'relayer' = 'relayer'
9
+ public readonly type = 'pk'
10
+ public readonly id = 'pk'
11
+ private readonly relayer: LocalRelayer
12
+
13
+ constructor(
14
+ privateKey: Hex.Hex,
15
+ private readonly provider: Provider.Provider,
16
+ ) {
17
+ const relayerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey }))
18
+ this.relayer = new LocalRelayer({
19
+ sendTransaction: async (args, chainId) => {
20
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
21
+ if (providerChainId !== chainId) {
22
+ throw new Error('Provider chain id does not match relayer chain id')
23
+ }
24
+
25
+ const oxArgs = { ...args, to: args.to as `0x${string}`, data: args.data as `0x${string}` }
26
+ // Estimate gas with a safety buffer
27
+ const estimatedGas = BigInt(await this.provider.request({ method: 'eth_estimateGas', params: [oxArgs] }))
28
+ const safeGasLimit = estimatedGas > 21000n ? (estimatedGas * 12n) / 10n : 50000n
29
+
30
+ // Get base fee and priority fee
31
+ const baseFee = BigInt(await this.provider.request({ method: 'eth_gasPrice' }))
32
+ const priorityFee = 100000000n // 0.1 gwei priority fee
33
+ const maxFeePerGas = baseFee + priorityFee
34
+
35
+ // Check sender have enough balance
36
+ const senderBalance = BigInt(
37
+ await this.provider.request({ method: 'eth_getBalance', params: [relayerAddress, 'latest'] }),
38
+ )
39
+ if (senderBalance < maxFeePerGas * safeGasLimit) {
40
+ console.log('Sender balance:', senderBalance.toString(), 'wei')
41
+ throw new Error('Sender has insufficient balance to pay for gas')
42
+ }
43
+ const nonce = BigInt(
44
+ await this.provider.request({
45
+ method: 'eth_getTransactionCount',
46
+ params: [relayerAddress, 'latest'],
47
+ }),
48
+ )
49
+
50
+ // Build the relay envelope
51
+ const relayEnvelope = TransactionEnvelopeEip1559.from({
52
+ chainId: Number(chainId),
53
+ type: 'eip1559',
54
+ from: relayerAddress,
55
+ to: oxArgs.to,
56
+ data: oxArgs.data,
57
+ gas: safeGasLimit,
58
+ maxFeePerGas: maxFeePerGas,
59
+ maxPriorityFeePerGas: priorityFee,
60
+ nonce: nonce,
61
+ value: 0n,
62
+ })
63
+ const relayerSignature = Secp256k1.sign({
64
+ payload: TransactionEnvelopeEip1559.getSignPayload(relayEnvelope),
65
+ privateKey: privateKey,
66
+ })
67
+ const signedRelayEnvelope = TransactionEnvelopeEip1559.from(relayEnvelope, {
68
+ signature: relayerSignature,
69
+ })
70
+ const tx = await this.provider.request({
71
+ method: 'eth_sendRawTransaction',
72
+ params: [TransactionEnvelopeEip1559.serialize(signedRelayEnvelope)],
73
+ })
74
+ return tx
75
+ },
76
+ getBalance: async (address: string): Promise<bigint> => {
77
+ const balanceHex = await this.provider.request({
78
+ method: 'eth_getBalance',
79
+ params: [address as Address.Address, 'latest'],
80
+ })
81
+ return BigInt(balanceHex)
82
+ },
83
+ call: async (args: { to: string; data: string }): Promise<string> => {
84
+ const callArgs = { to: args.to as `0x${string}`, data: args.data as `0x${string}` }
85
+ return await this.provider.request({ method: 'eth_call', params: [callArgs, 'latest'] })
86
+ },
87
+ getTransactionReceipt: async (txHash: string, chainId: number) => {
88
+ Hex.assert(txHash)
89
+
90
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
91
+ if (providerChainId !== chainId) {
92
+ throw new Error('Provider chain id does not match relayer chain id')
93
+ }
94
+
95
+ const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] })
96
+ if (!rpcReceipt) {
97
+ return 'unknown'
98
+ }
99
+ const receipt = TransactionReceipt.fromRpc(rpcReceipt)
100
+ return receipt.status === 'success' ? 'success' : 'failed'
101
+ },
102
+ })
103
+ }
104
+
105
+ async isAvailable(_wallet: Address.Address, chainId: number): Promise<boolean> {
106
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
107
+ return providerChainId === chainId
108
+ }
109
+
110
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
111
+ return this.relayer.feeTokens()
112
+ }
113
+
114
+ feeOptions(
115
+ wallet: Address.Address,
116
+ chainId: number,
117
+ calls: Payload.Call[],
118
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
119
+ return this.relayer.feeOptions(wallet, chainId, calls)
120
+ }
121
+
122
+ async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
123
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
124
+ if (providerChainId !== chainId) {
125
+ throw new Error('Provider chain id does not match relayer chain id')
126
+ }
127
+ return this.relayer.relay(to, data, chainId)
128
+ }
129
+
130
+ status(opHash: Hex.Hex, chainId: number): Promise<OperationStatus> {
131
+ return this.relayer.status(opHash, chainId)
132
+ }
133
+
134
+ async checkPrecondition(precondition: Precondition.Precondition): Promise<boolean> {
135
+ // TODO: Implement precondition check
136
+ return true
137
+ }
138
+ }
@@ -0,0 +1,110 @@
1
+ import { ETHTxnStatus, TransactionPrecondition, Relayer as Service, FeeToken } from '../rpc-relayer/relayer.gen.js'
2
+ import { Payload } from '@0xsequence/wallet-primitives'
3
+ import { AbiFunction, Address, Bytes, Hex } from 'ox'
4
+ import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
5
+ export class SequenceRelayer implements Relayer {
6
+ public readonly kind: 'relayer' = 'relayer'
7
+ public readonly type = 'sequence'
8
+ readonly id = 'sequence'
9
+
10
+ private readonly service: Service
11
+
12
+ constructor(host: string) {
13
+ this.service = new Service(host, fetch)
14
+ }
15
+
16
+ async isAvailable(_wallet: Address.Address, _chainId: number): Promise<boolean> {
17
+ return true
18
+ }
19
+
20
+ async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
21
+ const { isFeeRequired, tokens, paymentAddress } = await this.service.feeTokens()
22
+ if (isFeeRequired) {
23
+ Address.assert(paymentAddress)
24
+ return {
25
+ isFeeRequired,
26
+ tokens,
27
+ paymentAddress,
28
+ }
29
+ }
30
+ // Not required
31
+ return {
32
+ isFeeRequired,
33
+ }
34
+ }
35
+
36
+ async feeOptions(
37
+ wallet: Address.Address,
38
+ _chainId: number,
39
+ calls: Payload.Call[],
40
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
41
+ const to = wallet // TODO: this might be the guest module
42
+ const execute = AbiFunction.from('function execute(bytes calldata _payload, bytes calldata _signature)')
43
+ const payload = Payload.encode({ type: 'call', space: 0n, nonce: 0n, calls }, to)
44
+ const signature = '0x0001' // TODO: use a stub signature
45
+ const data = AbiFunction.encodeData(execute, [Bytes.toHex(payload), signature])
46
+
47
+ const { options, quote } = await this.service.feeOptions({ wallet, to, data })
48
+
49
+ return {
50
+ options,
51
+ quote: quote ? { _tag: 'FeeQuote', _quote: quote } : undefined,
52
+ }
53
+ }
54
+
55
+ async checkPrecondition(precondition: TransactionPrecondition): Promise<boolean> {
56
+ // TODO: implement
57
+ return false
58
+ }
59
+
60
+ async relay(to: Address.Address, data: Hex.Hex, _chainId: number, quote?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
61
+ const walletAddress = to // TODO: pass wallet address or stop requiring it
62
+
63
+ const { txnHash } = await this.service.sendMetaTxn({
64
+ call: { walletAddress, contract: to, input: data },
65
+ quote: quote && (quote._quote as string),
66
+ })
67
+
68
+ return { opHash: `0x${txnHash}` }
69
+ }
70
+
71
+ async status(opHash: Hex.Hex, _chainId: number): Promise<OperationStatus> {
72
+ try {
73
+ const {
74
+ receipt: { status, revertReason, txnReceipt },
75
+ } = await this.service.getMetaTxnReceipt({ metaTxID: opHash })
76
+
77
+ switch (status) {
78
+ case ETHTxnStatus.UNKNOWN:
79
+ return { status: 'unknown' }
80
+
81
+ case ETHTxnStatus.DROPPED:
82
+ return { status: 'failed', reason: revertReason ?? status }
83
+
84
+ case ETHTxnStatus.QUEUED:
85
+ return { status: 'pending' }
86
+
87
+ case ETHTxnStatus.SENT:
88
+ return { status: 'pending' }
89
+
90
+ case ETHTxnStatus.SUCCEEDED: {
91
+ const receipt = JSON.parse(txnReceipt)
92
+ const transactionHash = receipt.transactionHash
93
+ Hex.assert(transactionHash)
94
+ return { status: 'confirmed', transactionHash }
95
+ }
96
+
97
+ case ETHTxnStatus.PARTIALLY_FAILED:
98
+ return { status: 'failed', reason: revertReason ?? status }
99
+
100
+ case ETHTxnStatus.FAILED:
101
+ return { status: 'failed', reason: revertReason ?? status }
102
+
103
+ default:
104
+ throw new Error(`unknown transaction status '${status}'`)
105
+ }
106
+ } catch {
107
+ return { status: 'pending' }
108
+ }
109
+ }
110
+ }