@0xsequence/relayer 2.3.39 → 3.0.0-beta.10

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 (83) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +3926 -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 +3 -2
  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/{rpc-relayer → relayer/rpc-relayer}/relayer.gen.ts +483 -258
  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 -1865
  74. package/dist/0xsequence-relayer.cjs.js +0 -7
  75. package/dist/0xsequence-relayer.cjs.prod.js +0 -1865
  76. package/dist/0xsequence-relayer.esm.js +0 -1852
  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
@@ -0,0 +1,449 @@
1
+ import {
2
+ Relayer as GenRelayer,
3
+ SendMetaTxnReturn as RpcSendMetaTxnReturn,
4
+ MetaTxn as RpcMetaTxn,
5
+ FeeTokenType,
6
+ FeeToken as RpcFeeToken,
7
+ TransactionPrecondition,
8
+ ETHTxnStatus,
9
+ } from './relayer.gen.js'
10
+ import { Address, Hex, Bytes, AbiFunction } from 'ox'
11
+ import { Constants, Payload, Network } from '@0xsequence/wallet-primitives'
12
+ import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
13
+ import { decodePrecondition } from '../../preconditions/index.js'
14
+ import {
15
+ erc20BalanceOf,
16
+ erc20Allowance,
17
+ erc721OwnerOf,
18
+ erc721GetApproved,
19
+ erc1155BalanceOf,
20
+ erc1155IsApprovedForAll,
21
+ } from '../standard/abi.js'
22
+ import { PublicClient, createPublicClient, http, Chain } from 'viem'
23
+ import * as chains from 'viem/chains'
24
+
25
+ export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>
26
+
27
+ /**
28
+ * Convert a Sequence Network to a viem Chain
29
+ */
30
+ const networkToChain = (network: Network.Network): Chain => {
31
+ return {
32
+ id: network.chainId,
33
+ name: network.title || network.name,
34
+ nativeCurrency: {
35
+ name: network.nativeCurrency.name,
36
+ symbol: network.nativeCurrency.symbol,
37
+ decimals: network.nativeCurrency.decimals,
38
+ },
39
+ rpcUrls: {
40
+ default: {
41
+ http: [network.rpcUrl],
42
+ },
43
+ },
44
+ blockExplorers: network.blockExplorer
45
+ ? {
46
+ default: {
47
+ name: network.blockExplorer.name || 'Explorer',
48
+ url: network.blockExplorer.url,
49
+ },
50
+ }
51
+ : undefined,
52
+ contracts: network.ensAddress
53
+ ? {
54
+ ensUniversalResolver: {
55
+ address: network.ensAddress as `0x${string}`,
56
+ },
57
+ }
58
+ : undefined,
59
+ } as Chain
60
+ }
61
+
62
+ export const getChain = (chainId: number): Chain => {
63
+ // First try to get the chain from Sequence's network configurations
64
+ const sequenceNetwork = Network.getNetworkFromChainId(chainId)
65
+ if (sequenceNetwork) {
66
+ return networkToChain(sequenceNetwork)
67
+ }
68
+
69
+ // Fall back to viem's built-in chains
70
+ const viemChain = Object.values(chains).find((c: any) => typeof c === 'object' && 'id' in c && c.id === chainId)
71
+ if (viemChain) {
72
+ return viemChain as Chain
73
+ }
74
+
75
+ throw new Error(`Chain with id ${chainId} not found in Sequence networks or viem chains`)
76
+ }
77
+
78
+ export class RpcRelayer implements Relayer {
79
+ public readonly kind: 'relayer' = 'relayer'
80
+ public readonly type = 'rpc'
81
+ public readonly id: string
82
+ public readonly chainId: number
83
+ private client: GenRelayer
84
+ private fetch: Fetch
85
+ private provider: PublicClient
86
+ private readonly projectAccessKey?: string
87
+
88
+ constructor(hostname: string, chainId: number, rpcUrl: string, fetchImpl?: Fetch, projectAccessKey?: string) {
89
+ this.id = `rpc:${hostname}`
90
+ this.chainId = chainId
91
+ this.projectAccessKey = projectAccessKey
92
+ const effectiveFetch = fetchImpl || (typeof window !== 'undefined' ? window.fetch.bind(window) : undefined)
93
+ if (!effectiveFetch) {
94
+ throw new Error('Fetch implementation is required but not available in this environment.')
95
+ }
96
+ this.fetch = effectiveFetch
97
+ this.client = new GenRelayer(hostname, this.fetch)
98
+
99
+ // Get the chain from the chainId
100
+ const chain = getChain(chainId)
101
+
102
+ // Create viem PublicClient with the provided RPC URL
103
+ this.provider = createPublicClient({
104
+ chain,
105
+ transport: http(rpcUrl),
106
+ })
107
+ }
108
+
109
+ isAvailable(_wallet: Address.Address, chainId: number): Promise<boolean> {
110
+ return Promise.resolve(this.chainId === chainId)
111
+ }
112
+
113
+ async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: RpcFeeToken[]; paymentAddress?: Address.Address }> {
114
+ try {
115
+ const { isFeeRequired, tokens, paymentAddress } = await this.client.feeTokens()
116
+ if (isFeeRequired) {
117
+ Address.assert(paymentAddress)
118
+ return {
119
+ isFeeRequired,
120
+ tokens,
121
+ paymentAddress,
122
+ }
123
+ }
124
+ // Not required
125
+ return {
126
+ isFeeRequired,
127
+ }
128
+ } catch (e) {
129
+ console.warn('RpcRelayer.feeTokens failed:', e)
130
+ return { isFeeRequired: false }
131
+ }
132
+ }
133
+
134
+ async feeOptions(
135
+ wallet: Address.Address,
136
+ chainId: number,
137
+ calls: Payload.Call[],
138
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
139
+ const callsStruct: Payload.Calls = { type: 'call', space: 0n, nonce: 0n, calls: calls }
140
+ const data = Payload.encode(callsStruct)
141
+
142
+ try {
143
+ const result = await this.client.feeOptions(
144
+ {
145
+ wallet: wallet,
146
+ to: wallet,
147
+ data: Bytes.toHex(data),
148
+ },
149
+ { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) },
150
+ )
151
+
152
+ const quote = result.quote ? ({ _tag: 'FeeQuote', _quote: result.quote } as FeeQuote) : undefined
153
+ const options = result.options.map((option) => ({
154
+ token: {
155
+ ...option.token,
156
+ contractAddress: this.mapRpcFeeTokenToAddress(option.token),
157
+ },
158
+ to: option.to,
159
+ value: option.value,
160
+ gasLimit: option.gasLimit,
161
+ }))
162
+
163
+ return { options, quote }
164
+ } catch (e) {
165
+ console.warn('RpcRelayer.feeOptions failed:', e)
166
+ return { options: [] }
167
+ }
168
+ }
169
+
170
+ async sendMetaTxn(
171
+ walletAddress: Address.Address,
172
+ to: Address.Address,
173
+ data: Hex.Hex,
174
+ chainId: number,
175
+ quote?: FeeQuote,
176
+ preconditions?: TransactionPrecondition[],
177
+ ): Promise<{ opHash: Hex.Hex }> {
178
+ console.log('sendMetaTxn', walletAddress, to, data, chainId, quote, preconditions)
179
+ const rpcCall: RpcMetaTxn = {
180
+ walletAddress: walletAddress,
181
+ contract: to,
182
+ input: data,
183
+ }
184
+
185
+ const result: RpcSendMetaTxnReturn = await this.client.sendMetaTxn(
186
+ {
187
+ call: rpcCall,
188
+ quote: quote ? JSON.stringify(quote._quote) : undefined,
189
+ preconditions: preconditions,
190
+ },
191
+ { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) },
192
+ )
193
+
194
+ if (!result.status) {
195
+ console.error('RpcRelayer.relay failed', result)
196
+ throw new Error(`Relay failed: TxnHash ${result.txnHash}`)
197
+ }
198
+
199
+ return { opHash: Hex.fromString(result.txnHash) }
200
+ }
201
+
202
+ async relay(
203
+ to: Address.Address,
204
+ data: Hex.Hex,
205
+ chainId: number,
206
+ quote?: FeeQuote,
207
+ preconditions?: TransactionPrecondition[],
208
+ ): Promise<{ opHash: Hex.Hex }> {
209
+ console.log('relay', to, data, chainId, quote, preconditions)
210
+ const rpcCall: RpcMetaTxn = {
211
+ walletAddress: to,
212
+ contract: to,
213
+ input: data,
214
+ }
215
+
216
+ const result: RpcSendMetaTxnReturn = await this.client.sendMetaTxn(
217
+ {
218
+ call: rpcCall,
219
+ quote: quote ? JSON.stringify(quote._quote) : undefined,
220
+ preconditions: preconditions,
221
+ },
222
+ { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) },
223
+ )
224
+
225
+ if (!result.status) {
226
+ console.error('RpcRelayer.relay failed', result)
227
+ throw new Error(`Relay failed: TxnHash ${result.txnHash}`)
228
+ }
229
+
230
+ return { opHash: `0x${result.txnHash}` }
231
+ }
232
+
233
+ async status(opHash: Hex.Hex, chainId: number): Promise<OperationStatus> {
234
+ try {
235
+ const cleanedOpHash = opHash.startsWith('0x') ? opHash.substring(2) : opHash
236
+ const result = await this.client.getMetaTxnReceipt({ metaTxID: cleanedOpHash })
237
+ const receipt = result.receipt
238
+
239
+ if (!receipt) {
240
+ console.warn(`RpcRelayer.status: receipt not found for opHash ${opHash}`)
241
+ return { status: 'unknown' }
242
+ }
243
+
244
+ if (!receipt.status) {
245
+ console.warn(`RpcRelayer.status: receipt status not found for opHash ${opHash}`)
246
+ return { status: 'unknown' }
247
+ }
248
+
249
+ switch (receipt.status as ETHTxnStatus) {
250
+ case ETHTxnStatus.QUEUED:
251
+ case ETHTxnStatus.PENDING_PRECONDITION:
252
+ case ETHTxnStatus.SENT:
253
+ return { status: 'pending' }
254
+ case ETHTxnStatus.SUCCEEDED:
255
+ return { status: 'confirmed', transactionHash: receipt.txnHash as Hex.Hex, data: result }
256
+ case ETHTxnStatus.FAILED:
257
+ case ETHTxnStatus.PARTIALLY_FAILED:
258
+ return {
259
+ status: 'failed',
260
+ transactionHash: receipt.txnHash ? (receipt.txnHash as Hex.Hex) : undefined,
261
+ reason: receipt.revertReason || 'Relayer reported failure',
262
+ data: result,
263
+ }
264
+ case ETHTxnStatus.DROPPED:
265
+ return { status: 'failed', reason: 'Transaction dropped' }
266
+ case ETHTxnStatus.UNKNOWN:
267
+ default:
268
+ return { status: 'unknown' }
269
+ }
270
+ } catch (error) {
271
+ console.error(`RpcRelayer.status failed for opHash ${opHash}:`, error)
272
+ return { status: 'failed', reason: 'Failed to fetch status' }
273
+ }
274
+ }
275
+
276
+ async checkPrecondition(precondition: TransactionPrecondition): Promise<boolean> {
277
+ const decoded = decodePrecondition(precondition)
278
+
279
+ if (!decoded) {
280
+ return false
281
+ }
282
+
283
+ switch (decoded.type()) {
284
+ case 'native-balance': {
285
+ const native = decoded as any
286
+ try {
287
+ const balance = await this.provider.getBalance({ address: native.address.toString() as `0x${string}` })
288
+ const minWei = native.min !== undefined ? BigInt(native.min) : undefined
289
+ const maxWei = native.max !== undefined ? BigInt(native.max) : undefined
290
+
291
+ if (minWei !== undefined && maxWei !== undefined) {
292
+ return balance >= minWei && balance <= maxWei
293
+ }
294
+ if (minWei !== undefined) {
295
+ return balance >= minWei
296
+ }
297
+ if (maxWei !== undefined) {
298
+ return balance <= maxWei
299
+ }
300
+ // If no min or max specified, this is an invalid precondition
301
+ console.warn('Native balance precondition has neither min nor max specified')
302
+ return false
303
+ } catch (error) {
304
+ console.error('Error checking native balance:', error)
305
+ return false
306
+ }
307
+ }
308
+
309
+ case 'erc20-balance': {
310
+ const erc20 = decoded as any
311
+ try {
312
+ const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address.toString()])
313
+ const result = await this.provider.call({
314
+ to: erc20.token.toString() as `0x${string}`,
315
+ data: data as `0x${string}`,
316
+ })
317
+ const balance = BigInt(result.toString())
318
+ const minWei = erc20.min !== undefined ? BigInt(erc20.min) : undefined
319
+ const maxWei = erc20.max !== undefined ? BigInt(erc20.max) : undefined
320
+
321
+ if (minWei !== undefined && maxWei !== undefined) {
322
+ return balance >= minWei && balance <= maxWei
323
+ }
324
+ if (minWei !== undefined) {
325
+ return balance >= minWei
326
+ }
327
+ if (maxWei !== undefined) {
328
+ return balance <= maxWei
329
+ }
330
+ console.warn('ERC20 balance precondition has neither min nor max specified')
331
+ return false
332
+ } catch (error) {
333
+ console.error('Error checking ERC20 balance:', error)
334
+ return false
335
+ }
336
+ }
337
+
338
+ case 'erc20-approval': {
339
+ const erc20 = decoded as any
340
+ try {
341
+ const data = AbiFunction.encodeData(erc20Allowance, [erc20.address.toString(), erc20.operator.toString()])
342
+ const result = await this.provider.call({
343
+ to: erc20.token.toString() as `0x${string}`,
344
+ data: data as `0x${string}`,
345
+ })
346
+ const allowance = BigInt(result.toString())
347
+ const minAllowance = BigInt(erc20.min)
348
+ return allowance >= minAllowance
349
+ } catch (error) {
350
+ console.error('Error checking ERC20 approval:', error)
351
+ return false
352
+ }
353
+ }
354
+
355
+ case 'erc721-ownership': {
356
+ const erc721 = decoded as any
357
+ try {
358
+ const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId])
359
+ const result = await this.provider.call({
360
+ to: erc721.token.toString() as `0x${string}`,
361
+ data: data as `0x${string}`,
362
+ })
363
+ const resultHex = result.toString() as `0x${string}`
364
+ const owner = resultHex.slice(-40)
365
+ const isOwner = owner.toLowerCase() === erc721.address.toString().slice(2).toLowerCase()
366
+ const expectedOwnership = erc721.owned !== undefined ? erc721.owned : true
367
+ return isOwner === expectedOwnership
368
+ } catch (error) {
369
+ console.error('Error checking ERC721 ownership:', error)
370
+ return false
371
+ }
372
+ }
373
+
374
+ case 'erc721-approval': {
375
+ const erc721 = decoded as any
376
+ try {
377
+ const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId])
378
+ const result = await this.provider.call({
379
+ to: erc721.token.toString() as `0x${string}`,
380
+ data: data as `0x${string}`,
381
+ })
382
+ const resultHex = result.toString() as `0x${string}`
383
+ const approved = resultHex.slice(-40)
384
+ return approved.toLowerCase() === erc721.operator.toString().slice(2).toLowerCase()
385
+ } catch (error) {
386
+ console.error('Error checking ERC721 approval:', error)
387
+ return false
388
+ }
389
+ }
390
+
391
+ case 'erc1155-balance': {
392
+ const erc1155 = decoded as any
393
+ try {
394
+ const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address.toString(), erc1155.tokenId])
395
+ const result = await this.provider.call({
396
+ to: erc1155.token.toString() as `0x${string}`,
397
+ data: data as `0x${string}`,
398
+ })
399
+ const balance = BigInt(result.toString())
400
+ const minWei = erc1155.min !== undefined ? BigInt(erc1155.min) : undefined
401
+ const maxWei = erc1155.max !== undefined ? BigInt(erc1155.max) : undefined
402
+
403
+ if (minWei !== undefined && maxWei !== undefined) {
404
+ return balance >= minWei && balance <= maxWei
405
+ }
406
+ if (minWei !== undefined) {
407
+ return balance >= minWei
408
+ }
409
+ if (maxWei !== undefined) {
410
+ return balance <= maxWei
411
+ }
412
+ console.warn('ERC1155 balance precondition has neither min nor max specified')
413
+ return false
414
+ } catch (error) {
415
+ console.error('Error checking ERC1155 balance:', error)
416
+ return false
417
+ }
418
+ }
419
+
420
+ case 'erc1155-approval': {
421
+ const erc1155 = decoded as any
422
+ try {
423
+ const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [
424
+ erc1155.address.toString(),
425
+ erc1155.operator.toString(),
426
+ ])
427
+ const result = await this.provider.call({
428
+ to: erc1155.token.toString() as `0x${string}`,
429
+ data: data as `0x${string}`,
430
+ })
431
+ return BigInt(result.toString()) === 1n
432
+ } catch (error) {
433
+ console.error('Error checking ERC1155 approval:', error)
434
+ return false
435
+ }
436
+ }
437
+
438
+ default:
439
+ return false
440
+ }
441
+ }
442
+
443
+ private mapRpcFeeTokenToAddress(rpcToken: RpcFeeToken): Address.Address {
444
+ if (rpcToken.type === FeeTokenType.ERC20_TOKEN && rpcToken.contractAddress) {
445
+ return Address.from(rpcToken.contractAddress)
446
+ }
447
+ return Constants.ZeroAddress // Default to zero address for native token or unsupported types
448
+ }
449
+ }