@sip-protocol/sdk 0.7.2 → 0.7.4

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +48874 -18336
  7. package/dist/browser.mjs +674 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/chunk-YWGJ77A2.mjs +33806 -0
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/index-DXh2IGkz.d.ts +24681 -0
  24. package/dist/index-DeE1ZzA4.d.mts +24681 -0
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48676 -17318
  28. package/dist/index.mjs +583 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +276 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +201 -0
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +402 -0
  98. package/src/chains/solana/providers/index.ts +85 -0
  99. package/src/chains/solana/providers/interface.ts +221 -0
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +790 -0
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +170 -73
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +77 -7
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +37 -0
  116. package/src/compliance/range-sas.ts +956 -0
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +785 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +336 -0
  143. package/src/privacy-backends/interface.ts +906 -0
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash-types.ts +278 -0
  148. package/src/privacy-backends/privacycash.ts +456 -0
  149. package/src/privacy-backends/private-swap.ts +570 -0
  150. package/src/privacy-backends/rate-limiter.ts +683 -0
  151. package/src/privacy-backends/registry.ts +690 -0
  152. package/src/privacy-backends/router.ts +626 -0
  153. package/src/privacy-backends/shadowwire.ts +449 -0
  154. package/src/privacy-backends/sip-native.ts +256 -0
  155. package/src/privacy-logger.ts +191 -0
  156. package/src/production-safety.ts +373 -0
  157. package/src/proofs/aggregator.ts +1029 -0
  158. package/src/proofs/browser-composer.ts +1150 -0
  159. package/src/proofs/browser.ts +113 -25
  160. package/src/proofs/cache/index.ts +127 -0
  161. package/src/proofs/cache/interface.ts +545 -0
  162. package/src/proofs/cache/key-generator.ts +188 -0
  163. package/src/proofs/cache/lru-cache.ts +481 -0
  164. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  165. package/src/proofs/cache/persistent-cache.ts +788 -0
  166. package/src/proofs/compliance-proof.ts +872 -0
  167. package/src/proofs/composer/base.ts +923 -0
  168. package/src/proofs/composer/index.ts +25 -0
  169. package/src/proofs/composer/interface.ts +518 -0
  170. package/src/proofs/composer/types.ts +383 -0
  171. package/src/proofs/converters/halo2.ts +452 -0
  172. package/src/proofs/converters/index.ts +208 -0
  173. package/src/proofs/converters/interface.ts +363 -0
  174. package/src/proofs/converters/kimchi.ts +462 -0
  175. package/src/proofs/converters/noir.ts +451 -0
  176. package/src/proofs/fallback.ts +888 -0
  177. package/src/proofs/halo2.ts +42 -0
  178. package/src/proofs/index.ts +471 -0
  179. package/src/proofs/interface.ts +13 -0
  180. package/src/proofs/kimchi.ts +42 -0
  181. package/src/proofs/lazy.ts +1004 -0
  182. package/src/proofs/mock.ts +25 -1
  183. package/src/proofs/noir.ts +111 -30
  184. package/src/proofs/orchestrator.ts +960 -0
  185. package/src/proofs/parallel/concurrency.ts +297 -0
  186. package/src/proofs/parallel/dependency-graph.ts +602 -0
  187. package/src/proofs/parallel/executor.ts +420 -0
  188. package/src/proofs/parallel/index.ts +131 -0
  189. package/src/proofs/parallel/interface.ts +685 -0
  190. package/src/proofs/parallel/worker-pool.ts +644 -0
  191. package/src/proofs/providers/halo2.ts +560 -0
  192. package/src/proofs/providers/index.ts +34 -0
  193. package/src/proofs/providers/kimchi.ts +641 -0
  194. package/src/proofs/validator.ts +881 -0
  195. package/src/proofs/verifier.ts +867 -0
  196. package/src/quantum/index.ts +112 -0
  197. package/src/quantum/winternitz-vault.ts +639 -0
  198. package/src/quantum/wots.ts +611 -0
  199. package/src/settlement/backends/direct-chain.ts +1 -0
  200. package/src/settlement/index.ts +9 -0
  201. package/src/settlement/router.ts +732 -46
  202. package/src/solana/index.ts +72 -0
  203. package/src/solana/jito-relayer.ts +687 -0
  204. package/src/solana/noir-verifier-types.ts +430 -0
  205. package/src/solana/noir-verifier.ts +816 -0
  206. package/src/stealth/address-derivation.ts +193 -0
  207. package/src/stealth/ed25519.ts +431 -0
  208. package/src/stealth/index.ts +233 -0
  209. package/src/stealth/meta-address.ts +221 -0
  210. package/src/stealth/secp256k1.ts +368 -0
  211. package/src/stealth/utils.ts +194 -0
  212. package/src/stealth.ts +50 -1504
  213. package/src/surveillance/algorithms/address-reuse.ts +143 -0
  214. package/src/surveillance/algorithms/cluster.ts +247 -0
  215. package/src/surveillance/algorithms/exchange.ts +295 -0
  216. package/src/surveillance/algorithms/temporal.ts +337 -0
  217. package/src/surveillance/analyzer.ts +442 -0
  218. package/src/surveillance/index.ts +64 -0
  219. package/src/surveillance/scoring.ts +372 -0
  220. package/src/surveillance/types.ts +264 -0
  221. package/src/sync/index.ts +106 -0
  222. package/src/sync/manager.ts +504 -0
  223. package/src/sync/mock-provider.ts +318 -0
  224. package/src/sync/oblivious.ts +625 -0
  225. package/src/tokens/index.ts +15 -0
  226. package/src/tokens/registry.ts +301 -0
  227. package/src/utils/deprecation.ts +94 -0
  228. package/src/utils/index.ts +9 -0
  229. package/src/wallet/ethereum/index.ts +68 -0
  230. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  231. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  232. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  233. package/src/wallet/ethereum/types.ts +3 -1
  234. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  235. package/src/wallet/hardware/index.ts +10 -0
  236. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  237. package/src/wallet/index.ts +71 -0
  238. package/src/wallet/near/adapter.ts +626 -0
  239. package/src/wallet/near/index.ts +86 -0
  240. package/src/wallet/near/meteor-wallet.ts +1153 -0
  241. package/src/wallet/near/my-near-wallet.ts +790 -0
  242. package/src/wallet/near/wallet-selector.ts +702 -0
  243. package/src/wallet/solana/adapter.ts +6 -4
  244. package/src/wallet/solana/index.ts +13 -0
  245. package/src/wallet/solana/privacy-adapter.ts +567 -0
  246. package/src/wallet/sui/types.ts +6 -4
  247. package/src/zcash/rpc-client.ts +13 -6
  248. package/dist/chunk-3INS3PR5.mjs +0 -884
  249. package/dist/chunk-3OVABDRH.mjs +0 -17096
  250. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  251. package/dist/chunk-E6SZWREQ.mjs +0 -57
  252. package/dist/chunk-G33LB27A.mjs +0 -16166
  253. package/dist/chunk-HGU6HZRC.mjs +0 -231
  254. package/dist/chunk-L2K34JCU.mjs +0 -1496
  255. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  256. package/dist/constants-VOI7BSLK.mjs +0 -27
  257. package/dist/index-BYZbDjal.d.ts +0 -11390
  258. package/dist/index-CHB3KuOB.d.mts +0 -11859
  259. package/dist/index-CzWPI6Le.d.ts +0 -11859
  260. package/dist/index-xbWjohNq.d.mts +0 -11390
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
@@ -0,0 +1,830 @@
1
+ /**
2
+ * ERC-4337 Relayer for EVM Gas Abstraction
3
+ *
4
+ * Enables users to submit shielded EVM transactions without paying gas directly,
5
+ * using ERC-4337 account abstraction with Paymasters.
6
+ *
7
+ * This breaks the link between the user's wallet and their privacy transaction,
8
+ * providing true sender privacy on EVM chains (Base, Arbitrum, Optimism, etc.).
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { ERC4337Relayer, createPimlicoRelayer } from '@sip-protocol/sdk'
13
+ *
14
+ * // Create relayer with Pimlico bundler
15
+ * const relayer = createPimlicoRelayer({
16
+ * apiKey: process.env.PIMLICO_API_KEY!,
17
+ * chain: 'base',
18
+ * })
19
+ *
20
+ * // Relay a shielded transfer
21
+ * const result = await relayer.relayTransaction({
22
+ * to: stealthAddress,
23
+ * data: transferCalldata,
24
+ * value: 0n,
25
+ * signer,
26
+ * })
27
+ * ```
28
+ *
29
+ * @see https://eips.ethereum.org/EIPS/eip-4337
30
+ */
31
+
32
+ /**
33
+ * Supported EVM chains for ERC-4337 relaying
34
+ */
35
+ export type SupportedEVMChain =
36
+ | 'ethereum'
37
+ | 'base'
38
+ | 'arbitrum'
39
+ | 'optimism'
40
+ | 'polygon'
41
+ | 'sepolia'
42
+ | 'base-sepolia'
43
+
44
+ /**
45
+ * Chain IDs for supported networks
46
+ */
47
+ export const EVM_CHAIN_IDS: Record<SupportedEVMChain, number> = {
48
+ ethereum: 1,
49
+ base: 8453,
50
+ arbitrum: 42161,
51
+ optimism: 10,
52
+ polygon: 137,
53
+ sepolia: 11155111,
54
+ 'base-sepolia': 84532,
55
+ }
56
+
57
+ /**
58
+ * Entry point addresses for ERC-4337 v0.7
59
+ */
60
+ export const ENTRY_POINT_V07 = '0x0000000071727De22E5E9d8BAf0edAc6f37da032'
61
+
62
+ /**
63
+ * Popular bundler endpoints
64
+ */
65
+ export const BUNDLER_ENDPOINTS = {
66
+ pimlico: {
67
+ mainnet: 'https://api.pimlico.io/v2/{chain}/rpc',
68
+ testnet: 'https://api.pimlico.io/v2/{chain}/rpc',
69
+ },
70
+ stackup: {
71
+ mainnet: 'https://api.stackup.sh/v1/node/{apiKey}',
72
+ testnet: 'https://api.stackup.sh/v1/node/{apiKey}',
73
+ },
74
+ biconomy: {
75
+ mainnet: 'https://bundler.biconomy.io/api/v2/{chainId}/{apiKey}',
76
+ testnet: 'https://bundler.biconomy.io/api/v2/{chainId}/{apiKey}',
77
+ },
78
+ alchemy: {
79
+ mainnet: 'https://alchemy.com/account-abstraction/v3/{chain}/rpc',
80
+ testnet: 'https://alchemy.com/account-abstraction/v3/{chain}/rpc',
81
+ },
82
+ } as const
83
+
84
+ /**
85
+ * ERC-4337 Relayer configuration
86
+ */
87
+ export interface ERC4337RelayerConfig {
88
+ /** Bundler RPC endpoint (full URL or provider name) */
89
+ bundlerUrl?: string
90
+ /** Bundler provider (pimlico, stackup, biconomy, alchemy) */
91
+ bundlerProvider?: 'pimlico' | 'stackup' | 'biconomy' | 'alchemy'
92
+ /** API key for bundler */
93
+ apiKey: string
94
+ /** Target chain */
95
+ chain: SupportedEVMChain
96
+ /** Paymaster URL (for gas sponsorship) */
97
+ paymasterUrl?: string
98
+ /** Entry point version (default: v0.7) */
99
+ entryPointVersion?: 'v0.6' | 'v0.7'
100
+ /** Maximum gas price multiplier (default: 1.2) */
101
+ gasPriceMultiplier?: number
102
+ }
103
+
104
+ /**
105
+ * User Operation structure (ERC-4337 v0.7)
106
+ */
107
+ export interface UserOperation {
108
+ sender: string
109
+ nonce: bigint
110
+ factory?: string
111
+ factoryData?: string
112
+ callData: string
113
+ callGasLimit: bigint
114
+ verificationGasLimit: bigint
115
+ preVerificationGas: bigint
116
+ maxFeePerGas: bigint
117
+ maxPriorityFeePerGas: bigint
118
+ paymaster?: string
119
+ paymasterVerificationGasLimit?: bigint
120
+ paymasterPostOpGasLimit?: bigint
121
+ paymasterData?: string
122
+ signature: string
123
+ }
124
+
125
+ /**
126
+ * Request to relay a transaction
127
+ */
128
+ export interface RelayTransactionRequest {
129
+ /** Target contract address */
130
+ to: string
131
+ /** Call data */
132
+ data: string
133
+ /** Value to send (in wei) */
134
+ value?: bigint
135
+ /** Signer for the UserOperation */
136
+ signer: {
137
+ address: string
138
+ signMessage: (message: Uint8Array | string) => Promise<string>
139
+ }
140
+ /** Maximum fee willing to pay (optional, for fee limits) */
141
+ maxFee?: bigint
142
+ /** Wait for transaction confirmation */
143
+ waitForConfirmation?: boolean
144
+ }
145
+
146
+ /**
147
+ * Result of relaying a transaction
148
+ */
149
+ export interface RelayTransactionResult {
150
+ /** Whether the relay was successful */
151
+ success: boolean
152
+ /** User operation hash */
153
+ userOpHash?: string
154
+ /** Transaction hash (if confirmed) */
155
+ transactionHash?: string
156
+ /** Gas used */
157
+ gasUsed?: bigint
158
+ /** Actual fee paid */
159
+ actualFee?: bigint
160
+ /** Error if failed */
161
+ error?: Error
162
+ }
163
+
164
+ /**
165
+ * Bundler RPC response types
166
+ */
167
+ interface BundlerEstimateResponse {
168
+ preVerificationGas: string
169
+ verificationGasLimit: string
170
+ callGasLimit: string
171
+ paymasterVerificationGasLimit?: string
172
+ paymasterPostOpGasLimit?: string
173
+ }
174
+
175
+ interface BundlerSendResponse {
176
+ result: string // userOpHash
177
+ }
178
+
179
+ interface BundlerReceiptResponse {
180
+ result: {
181
+ userOpHash: string
182
+ entryPoint: string
183
+ sender: string
184
+ nonce: string
185
+ success: boolean
186
+ actualGasUsed: string
187
+ actualGasCost: string
188
+ receipt: {
189
+ transactionHash: string
190
+ blockNumber: string
191
+ gasUsed: string
192
+ }
193
+ } | null
194
+ }
195
+
196
+ /**
197
+ * Error codes for ERC-4337 Relayer
198
+ */
199
+ export enum ERC4337RelayerErrorCode {
200
+ BUNDLER_ERROR = 'BUNDLER_ERROR',
201
+ PAYMASTER_ERROR = 'PAYMASTER_ERROR',
202
+ SIGNATURE_ERROR = 'SIGNATURE_ERROR',
203
+ GAS_ESTIMATION_ERROR = 'GAS_ESTIMATION_ERROR',
204
+ TIMEOUT = 'TIMEOUT',
205
+ INVALID_CHAIN = 'INVALID_CHAIN',
206
+ }
207
+
208
+ /**
209
+ * ERC-4337 Relayer error
210
+ */
211
+ export class ERC4337RelayerError extends Error {
212
+ code: ERC4337RelayerErrorCode
213
+ details?: unknown
214
+
215
+ constructor(message: string, code: ERC4337RelayerErrorCode, details?: unknown) {
216
+ super(message)
217
+ this.name = 'ERC4337RelayerError'
218
+ this.code = code
219
+ this.details = details
220
+ }
221
+ }
222
+
223
+ /**
224
+ * ERC-4337 Relayer for EVM gas abstraction
225
+ *
226
+ * Submits transactions via ERC-4337 bundlers with paymaster sponsorship.
227
+ */
228
+ export class ERC4337Relayer {
229
+ private readonly bundlerUrl: string
230
+ private readonly paymasterUrl: string | null
231
+ private readonly chainId: number
232
+ private readonly entryPoint: string
233
+ private readonly gasPriceMultiplier: number
234
+
235
+ constructor(config: ERC4337RelayerConfig) {
236
+ // Validate chain
237
+ if (!EVM_CHAIN_IDS[config.chain]) {
238
+ throw new ERC4337RelayerError(
239
+ `Unsupported chain: ${config.chain}`,
240
+ ERC4337RelayerErrorCode.INVALID_CHAIN
241
+ )
242
+ }
243
+
244
+ this.chainId = EVM_CHAIN_IDS[config.chain]
245
+ this.gasPriceMultiplier = config.gasPriceMultiplier ?? 1.2
246
+
247
+ // Set entry point
248
+ this.entryPoint = ENTRY_POINT_V07
249
+
250
+ // Build bundler URL
251
+ if (config.bundlerUrl) {
252
+ this.bundlerUrl = config.bundlerUrl
253
+ } else if (config.bundlerProvider) {
254
+ const endpoints = BUNDLER_ENDPOINTS[config.bundlerProvider]
255
+ const isTestnet = config.chain.includes('sepolia')
256
+ const template = isTestnet ? endpoints.testnet : endpoints.mainnet
257
+
258
+ this.bundlerUrl = template
259
+ .replace('{chain}', config.chain)
260
+ .replace('{chainId}', this.chainId.toString())
261
+ .replace('{apiKey}', config.apiKey)
262
+
263
+ // Add API key as query param for Pimlico
264
+ if (config.bundlerProvider === 'pimlico') {
265
+ this.bundlerUrl += `?apikey=${config.apiKey}`
266
+ }
267
+ } else {
268
+ throw new ERC4337RelayerError(
269
+ 'Either bundlerUrl or bundlerProvider must be specified',
270
+ ERC4337RelayerErrorCode.BUNDLER_ERROR
271
+ )
272
+ }
273
+
274
+ // Set paymaster URL (defaults to bundler URL for integrated services)
275
+ this.paymasterUrl = config.paymasterUrl ?? this.bundlerUrl
276
+ }
277
+
278
+ /**
279
+ * Relay a transaction via ERC-4337
280
+ *
281
+ * @param request - Transaction to relay
282
+ * @returns Relay result
283
+ */
284
+ async relayTransaction(request: RelayTransactionRequest): Promise<RelayTransactionResult> {
285
+ try {
286
+ // 1. Build the UserOperation
287
+ const userOp = await this.buildUserOperation(request)
288
+
289
+ // 2. Sign the UserOperation
290
+ const signedUserOp = await this.signUserOperation(userOp, request.signer)
291
+
292
+ // 3. Submit to bundler
293
+ const userOpHash = await this.submitUserOperation(signedUserOp)
294
+
295
+ // 4. Wait for confirmation if requested
296
+ if (request.waitForConfirmation) {
297
+ const receipt = await this.waitForReceipt(userOpHash)
298
+
299
+ if (!receipt) {
300
+ return {
301
+ success: false,
302
+ userOpHash,
303
+ error: new ERC4337RelayerError(
304
+ 'Transaction timed out waiting for confirmation',
305
+ ERC4337RelayerErrorCode.TIMEOUT
306
+ ),
307
+ }
308
+ }
309
+
310
+ return {
311
+ success: receipt.success,
312
+ userOpHash,
313
+ transactionHash: receipt.receipt.transactionHash,
314
+ gasUsed: BigInt(receipt.actualGasUsed),
315
+ actualFee: BigInt(receipt.actualGasCost),
316
+ }
317
+ }
318
+
319
+ return {
320
+ success: true,
321
+ userOpHash,
322
+ }
323
+ } catch (error) {
324
+ if (error instanceof ERC4337RelayerError) {
325
+ return { success: false, error }
326
+ }
327
+ return {
328
+ success: false,
329
+ error: new ERC4337RelayerError(
330
+ error instanceof Error ? error.message : 'Unknown error',
331
+ ERC4337RelayerErrorCode.BUNDLER_ERROR,
332
+ error
333
+ ),
334
+ }
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Build a UserOperation for the transaction
340
+ */
341
+ private async buildUserOperation(request: RelayTransactionRequest): Promise<UserOperation> {
342
+ // Get gas prices from bundler
343
+ const gasPrices = await this.getGasPrices()
344
+
345
+ // Build call data for the target call
346
+ const callData = this.encodeExecuteCall(request.to, request.value ?? 0n, request.data)
347
+
348
+ // Get nonce from entry point (simplified - in production, query contract)
349
+ const nonce = await this.getNonce(request.signer.address)
350
+
351
+ // Estimate gas limits
352
+ const gasEstimate = await this.estimateGas({
353
+ sender: request.signer.address,
354
+ nonce,
355
+ callData,
356
+ maxFeePerGas: gasPrices.maxFeePerGas,
357
+ maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas,
358
+ })
359
+
360
+ // Get paymaster data if sponsoring
361
+ const paymasterData = await this.getPaymasterData({
362
+ sender: request.signer.address,
363
+ nonce,
364
+ callData,
365
+ ...gasEstimate,
366
+ maxFeePerGas: gasPrices.maxFeePerGas,
367
+ maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas,
368
+ })
369
+
370
+ return {
371
+ sender: request.signer.address,
372
+ nonce,
373
+ callData,
374
+ callGasLimit: gasEstimate.callGasLimit,
375
+ verificationGasLimit: gasEstimate.verificationGasLimit,
376
+ preVerificationGas: gasEstimate.preVerificationGas,
377
+ maxFeePerGas: gasPrices.maxFeePerGas,
378
+ maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas,
379
+ paymaster: paymasterData.paymaster,
380
+ paymasterVerificationGasLimit: paymasterData.paymasterVerificationGasLimit,
381
+ paymasterPostOpGasLimit: paymasterData.paymasterPostOpGasLimit,
382
+ paymasterData: paymasterData.paymasterData,
383
+ signature: '0x', // Will be filled in during signing
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Sign a UserOperation
389
+ */
390
+ private async signUserOperation(
391
+ userOp: UserOperation,
392
+ signer: RelayTransactionRequest['signer']
393
+ ): Promise<UserOperation> {
394
+ // Compute the UserOperation hash
395
+ const userOpHash = this.getUserOpHash(userOp)
396
+
397
+ // Sign the hash
398
+ const signature = await signer.signMessage(userOpHash)
399
+
400
+ return {
401
+ ...userOp,
402
+ signature,
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Submit UserOperation to bundler
408
+ */
409
+ private async submitUserOperation(userOp: UserOperation): Promise<string> {
410
+ const response = await fetch(this.bundlerUrl, {
411
+ method: 'POST',
412
+ headers: {
413
+ 'Content-Type': 'application/json',
414
+ },
415
+ body: JSON.stringify({
416
+ jsonrpc: '2.0',
417
+ id: 1,
418
+ method: 'eth_sendUserOperation',
419
+ params: [this.serializeUserOp(userOp), this.entryPoint],
420
+ }),
421
+ })
422
+
423
+ if (!response.ok) {
424
+ throw new ERC4337RelayerError(
425
+ `Bundler request failed: ${response.status}`,
426
+ ERC4337RelayerErrorCode.BUNDLER_ERROR
427
+ )
428
+ }
429
+
430
+ const data = (await response.json()) as BundlerSendResponse | { error: { message: string } }
431
+
432
+ if ('error' in data) {
433
+ throw new ERC4337RelayerError(
434
+ data.error.message,
435
+ ERC4337RelayerErrorCode.BUNDLER_ERROR
436
+ )
437
+ }
438
+
439
+ return data.result
440
+ }
441
+
442
+ /**
443
+ * Wait for UserOperation receipt
444
+ */
445
+ private async waitForReceipt(
446
+ userOpHash: string,
447
+ timeoutMs: number = 60000
448
+ ): Promise<BundlerReceiptResponse['result']> {
449
+ const startTime = Date.now()
450
+ const pollInterval = 2000
451
+
452
+ while (Date.now() - startTime < timeoutMs) {
453
+ const response = await fetch(this.bundlerUrl, {
454
+ method: 'POST',
455
+ headers: {
456
+ 'Content-Type': 'application/json',
457
+ },
458
+ body: JSON.stringify({
459
+ jsonrpc: '2.0',
460
+ id: 1,
461
+ method: 'eth_getUserOperationReceipt',
462
+ params: [userOpHash],
463
+ }),
464
+ })
465
+
466
+ if (response.ok) {
467
+ const data = (await response.json()) as BundlerReceiptResponse
468
+ if (data.result) {
469
+ return data.result
470
+ }
471
+ }
472
+
473
+ await new Promise((resolve) => setTimeout(resolve, pollInterval))
474
+ }
475
+
476
+ return null
477
+ }
478
+
479
+ /**
480
+ * Get current gas prices from bundler
481
+ */
482
+ private async getGasPrices(): Promise<{
483
+ maxFeePerGas: bigint
484
+ maxPriorityFeePerGas: bigint
485
+ }> {
486
+ const response = await fetch(this.bundlerUrl, {
487
+ method: 'POST',
488
+ headers: { 'Content-Type': 'application/json' },
489
+ body: JSON.stringify({
490
+ jsonrpc: '2.0',
491
+ id: 1,
492
+ method: 'pimlico_getUserOperationGasPrice',
493
+ params: [],
494
+ }),
495
+ })
496
+
497
+ if (response.ok) {
498
+ const data = await response.json()
499
+ if (data.result?.standard) {
500
+ return {
501
+ maxFeePerGas: BigInt(data.result.standard.maxFeePerGas),
502
+ maxPriorityFeePerGas: BigInt(data.result.standard.maxPriorityFeePerGas),
503
+ }
504
+ }
505
+ }
506
+
507
+ // Fallback: use reasonable defaults
508
+ return {
509
+ maxFeePerGas: BigInt(30e9), // 30 gwei
510
+ maxPriorityFeePerGas: BigInt(1e9), // 1 gwei
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Estimate gas for UserOperation
516
+ */
517
+ private async estimateGas(partialUserOp: Partial<UserOperation>): Promise<{
518
+ preVerificationGas: bigint
519
+ verificationGasLimit: bigint
520
+ callGasLimit: bigint
521
+ }> {
522
+ const response = await fetch(this.bundlerUrl, {
523
+ method: 'POST',
524
+ headers: { 'Content-Type': 'application/json' },
525
+ body: JSON.stringify({
526
+ jsonrpc: '2.0',
527
+ id: 1,
528
+ method: 'eth_estimateUserOperationGas',
529
+ params: [
530
+ {
531
+ sender: partialUserOp.sender,
532
+ nonce: `0x${(partialUserOp.nonce ?? 0n).toString(16)}`,
533
+ callData: partialUserOp.callData,
534
+ signature: '0x' + 'ff'.repeat(65), // Dummy signature for estimation
535
+ },
536
+ this.entryPoint,
537
+ ],
538
+ }),
539
+ })
540
+
541
+ if (!response.ok) {
542
+ throw new ERC4337RelayerError(
543
+ 'Gas estimation failed',
544
+ ERC4337RelayerErrorCode.GAS_ESTIMATION_ERROR
545
+ )
546
+ }
547
+
548
+ const data = await response.json() as { result: BundlerEstimateResponse } | { error: { message: string } }
549
+
550
+ if ('error' in data) {
551
+ throw new ERC4337RelayerError(
552
+ data.error.message,
553
+ ERC4337RelayerErrorCode.GAS_ESTIMATION_ERROR
554
+ )
555
+ }
556
+
557
+ // Apply gas multiplier for safety margin
558
+ const multiplier = this.gasPriceMultiplier
559
+
560
+ return {
561
+ preVerificationGas: BigInt(Math.ceil(Number(BigInt(data.result.preVerificationGas)) * multiplier)),
562
+ verificationGasLimit: BigInt(Math.ceil(Number(BigInt(data.result.verificationGasLimit)) * multiplier)),
563
+ callGasLimit: BigInt(Math.ceil(Number(BigInt(data.result.callGasLimit)) * multiplier)),
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Get paymaster data for sponsorship
569
+ */
570
+ private async getPaymasterData(partialUserOp: Partial<UserOperation>): Promise<{
571
+ paymaster?: string
572
+ paymasterVerificationGasLimit?: bigint
573
+ paymasterPostOpGasLimit?: bigint
574
+ paymasterData?: string
575
+ }> {
576
+ if (!this.paymasterUrl) {
577
+ return {}
578
+ }
579
+
580
+ const response = await fetch(this.paymasterUrl, {
581
+ method: 'POST',
582
+ headers: { 'Content-Type': 'application/json' },
583
+ body: JSON.stringify({
584
+ jsonrpc: '2.0',
585
+ id: 1,
586
+ method: 'pm_sponsorUserOperation',
587
+ params: [
588
+ {
589
+ sender: partialUserOp.sender,
590
+ nonce: `0x${(partialUserOp.nonce ?? 0n).toString(16)}`,
591
+ callData: partialUserOp.callData,
592
+ callGasLimit: `0x${(partialUserOp.callGasLimit ?? 0n).toString(16)}`,
593
+ verificationGasLimit: `0x${(partialUserOp.verificationGasLimit ?? 0n).toString(16)}`,
594
+ preVerificationGas: `0x${(partialUserOp.preVerificationGas ?? 0n).toString(16)}`,
595
+ maxFeePerGas: `0x${(partialUserOp.maxFeePerGas ?? 0n).toString(16)}`,
596
+ maxPriorityFeePerGas: `0x${(partialUserOp.maxPriorityFeePerGas ?? 0n).toString(16)}`,
597
+ signature: '0x' + 'ff'.repeat(65),
598
+ },
599
+ this.entryPoint,
600
+ { sponsorshipPolicyId: 'sp_sip_privacy' }, // Sponsorship policy
601
+ ],
602
+ }),
603
+ })
604
+
605
+ if (!response.ok) {
606
+ // Paymaster not available, user will pay gas
607
+ return {}
608
+ }
609
+
610
+ const data = await response.json()
611
+
612
+ if (data.result) {
613
+ return {
614
+ paymaster: data.result.paymaster,
615
+ paymasterVerificationGasLimit: data.result.paymasterVerificationGasLimit
616
+ ? BigInt(data.result.paymasterVerificationGasLimit)
617
+ : undefined,
618
+ paymasterPostOpGasLimit: data.result.paymasterPostOpGasLimit
619
+ ? BigInt(data.result.paymasterPostOpGasLimit)
620
+ : undefined,
621
+ paymasterData: data.result.paymasterData,
622
+ }
623
+ }
624
+
625
+ return {}
626
+ }
627
+
628
+ /**
629
+ * Get nonce for sender
630
+ */
631
+ private async getNonce(sender: string): Promise<bigint> {
632
+ // In production, this would query the EntryPoint contract
633
+ // For now, return 0 (first operation) or query bundler
634
+ const response = await fetch(this.bundlerUrl, {
635
+ method: 'POST',
636
+ headers: { 'Content-Type': 'application/json' },
637
+ body: JSON.stringify({
638
+ jsonrpc: '2.0',
639
+ id: 1,
640
+ method: 'eth_call',
641
+ params: [
642
+ {
643
+ to: this.entryPoint,
644
+ data: `0x35567e1a${sender.slice(2).padStart(64, '0')}${'0'.repeat(64)}`, // getNonce(address,uint192)
645
+ },
646
+ 'latest',
647
+ ],
648
+ }),
649
+ })
650
+
651
+ if (response.ok) {
652
+ const data = await response.json()
653
+ if (data.result && data.result !== '0x') {
654
+ return BigInt(data.result)
655
+ }
656
+ }
657
+
658
+ return 0n
659
+ }
660
+
661
+ /**
662
+ * Encode execute call for smart account
663
+ */
664
+ private encodeExecuteCall(to: string, value: bigint, data: string): string {
665
+ // Standard execute function signature
666
+ // execute(address dest, uint256 value, bytes calldata func)
667
+ const selector = '0xb61d27f6'
668
+ const encodedTo = to.slice(2).padStart(64, '0')
669
+ const encodedValue = value.toString(16).padStart(64, '0')
670
+ const dataOffset = (32 * 3).toString(16).padStart(64, '0')
671
+ const dataLength = ((data.length - 2) / 2).toString(16).padStart(64, '0')
672
+ const dataContent = data.slice(2).padEnd(Math.ceil((data.length - 2) / 64) * 64, '0')
673
+
674
+ return `${selector}${encodedTo}${encodedValue}${dataOffset}${dataLength}${dataContent}`
675
+ }
676
+
677
+ /**
678
+ * Compute UserOperation hash
679
+ */
680
+ private getUserOpHash(userOp: UserOperation): string {
681
+ // Simplified hash computation
682
+ // In production, use proper EIP-712 typed data hashing
683
+ const packed = this.packUserOp(userOp)
684
+ // For now, return a placeholder - real implementation would use keccak256
685
+ return packed
686
+ }
687
+
688
+ /**
689
+ * Pack UserOperation for hashing
690
+ */
691
+ private packUserOp(userOp: UserOperation): string {
692
+ return JSON.stringify({
693
+ sender: userOp.sender,
694
+ nonce: userOp.nonce.toString(),
695
+ callData: userOp.callData,
696
+ callGasLimit: userOp.callGasLimit.toString(),
697
+ verificationGasLimit: userOp.verificationGasLimit.toString(),
698
+ preVerificationGas: userOp.preVerificationGas.toString(),
699
+ maxFeePerGas: userOp.maxFeePerGas.toString(),
700
+ maxPriorityFeePerGas: userOp.maxPriorityFeePerGas.toString(),
701
+ paymaster: userOp.paymaster,
702
+ paymasterData: userOp.paymasterData,
703
+ chainId: this.chainId,
704
+ entryPoint: this.entryPoint,
705
+ })
706
+ }
707
+
708
+ /**
709
+ * Serialize UserOperation for RPC
710
+ */
711
+ private serializeUserOp(userOp: UserOperation): Record<string, string | undefined> {
712
+ return {
713
+ sender: userOp.sender,
714
+ nonce: `0x${userOp.nonce.toString(16)}`,
715
+ factory: userOp.factory,
716
+ factoryData: userOp.factoryData,
717
+ callData: userOp.callData,
718
+ callGasLimit: `0x${userOp.callGasLimit.toString(16)}`,
719
+ verificationGasLimit: `0x${userOp.verificationGasLimit.toString(16)}`,
720
+ preVerificationGas: `0x${userOp.preVerificationGas.toString(16)}`,
721
+ maxFeePerGas: `0x${userOp.maxFeePerGas.toString(16)}`,
722
+ maxPriorityFeePerGas: `0x${userOp.maxPriorityFeePerGas.toString(16)}`,
723
+ paymaster: userOp.paymaster,
724
+ paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit
725
+ ? `0x${userOp.paymasterVerificationGasLimit.toString(16)}`
726
+ : undefined,
727
+ paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit
728
+ ? `0x${userOp.paymasterPostOpGasLimit.toString(16)}`
729
+ : undefined,
730
+ paymasterData: userOp.paymasterData,
731
+ signature: userOp.signature,
732
+ }
733
+ }
734
+
735
+ /**
736
+ * Check if relayer is available
737
+ */
738
+ async isAvailable(): Promise<boolean> {
739
+ try {
740
+ const response = await fetch(this.bundlerUrl, {
741
+ method: 'POST',
742
+ headers: { 'Content-Type': 'application/json' },
743
+ body: JSON.stringify({
744
+ jsonrpc: '2.0',
745
+ id: 1,
746
+ method: 'eth_supportedEntryPoints',
747
+ params: [],
748
+ }),
749
+ })
750
+
751
+ if (!response.ok) return false
752
+
753
+ const data = await response.json()
754
+ return Array.isArray(data.result) && data.result.length > 0
755
+ } catch {
756
+ return false
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Get supported entry points
762
+ */
763
+ async getSupportedEntryPoints(): Promise<string[]> {
764
+ try {
765
+ const response = await fetch(this.bundlerUrl, {
766
+ method: 'POST',
767
+ headers: { 'Content-Type': 'application/json' },
768
+ body: JSON.stringify({
769
+ jsonrpc: '2.0',
770
+ id: 1,
771
+ method: 'eth_supportedEntryPoints',
772
+ params: [],
773
+ }),
774
+ })
775
+
776
+ if (response.ok) {
777
+ const data = await response.json()
778
+ return data.result ?? []
779
+ }
780
+ } catch {
781
+ // Ignore
782
+ }
783
+ return []
784
+ }
785
+ }
786
+
787
+ /**
788
+ * Create a Pimlico-backed ERC-4337 relayer
789
+ *
790
+ * @param config - Relayer configuration
791
+ * @returns Configured relayer
792
+ */
793
+ export function createPimlicoRelayer(
794
+ config: Omit<ERC4337RelayerConfig, 'bundlerProvider'>
795
+ ): ERC4337Relayer {
796
+ return new ERC4337Relayer({
797
+ ...config,
798
+ bundlerProvider: 'pimlico',
799
+ })
800
+ }
801
+
802
+ /**
803
+ * Create a Stackup-backed ERC-4337 relayer
804
+ *
805
+ * @param config - Relayer configuration
806
+ * @returns Configured relayer
807
+ */
808
+ export function createStackupRelayer(
809
+ config: Omit<ERC4337RelayerConfig, 'bundlerProvider'>
810
+ ): ERC4337Relayer {
811
+ return new ERC4337Relayer({
812
+ ...config,
813
+ bundlerProvider: 'stackup',
814
+ })
815
+ }
816
+
817
+ /**
818
+ * Create a Biconomy-backed ERC-4337 relayer
819
+ *
820
+ * @param config - Relayer configuration
821
+ * @returns Configured relayer
822
+ */
823
+ export function createBiconomyRelayer(
824
+ config: Omit<ERC4337RelayerConfig, 'bundlerProvider'>
825
+ ): ERC4337Relayer {
826
+ return new ERC4337Relayer({
827
+ ...config,
828
+ bundlerProvider: 'biconomy',
829
+ })
830
+ }