@sip-protocol/sdk 0.7.3 → 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 (264) 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 +47556 -19603
  7. package/dist/browser.mjs +628 -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-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  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-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  24. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48396 -19623
  28. package/dist/index.mjs +537 -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 +252 -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 +47 -6
  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 +186 -33
  98. package/src/chains/solana/providers/index.ts +31 -0
  99. package/src/chains/solana/providers/interface.ts +61 -18
  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 +338 -67
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +83 -66
  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 +57 -6
  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 +23 -0
  116. package/src/compliance/range-sas.ts +398 -33
  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 +686 -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 +254 -4
  143. package/src/privacy-backends/interface.ts +649 -6
  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.ts +13 -17
  148. package/src/privacy-backends/private-swap.ts +570 -0
  149. package/src/privacy-backends/rate-limiter.ts +683 -0
  150. package/src/privacy-backends/registry.ts +414 -2
  151. package/src/privacy-backends/router.ts +283 -3
  152. package/src/privacy-backends/shadowwire.ts +449 -0
  153. package/src/privacy-backends/sip-native.ts +3 -0
  154. package/src/privacy-logger.ts +191 -0
  155. package/src/production-safety.ts +373 -0
  156. package/src/proofs/aggregator.ts +1029 -0
  157. package/src/proofs/browser-composer.ts +1150 -0
  158. package/src/proofs/browser.ts +113 -25
  159. package/src/proofs/cache/index.ts +127 -0
  160. package/src/proofs/cache/interface.ts +545 -0
  161. package/src/proofs/cache/key-generator.ts +188 -0
  162. package/src/proofs/cache/lru-cache.ts +481 -0
  163. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  164. package/src/proofs/cache/persistent-cache.ts +788 -0
  165. package/src/proofs/compliance-proof.ts +872 -0
  166. package/src/proofs/composer/base.ts +923 -0
  167. package/src/proofs/composer/index.ts +25 -0
  168. package/src/proofs/composer/interface.ts +518 -0
  169. package/src/proofs/composer/types.ts +383 -0
  170. package/src/proofs/converters/halo2.ts +452 -0
  171. package/src/proofs/converters/index.ts +208 -0
  172. package/src/proofs/converters/interface.ts +363 -0
  173. package/src/proofs/converters/kimchi.ts +462 -0
  174. package/src/proofs/converters/noir.ts +451 -0
  175. package/src/proofs/fallback.ts +888 -0
  176. package/src/proofs/halo2.ts +42 -0
  177. package/src/proofs/index.ts +471 -0
  178. package/src/proofs/interface.ts +13 -0
  179. package/src/proofs/kimchi.ts +42 -0
  180. package/src/proofs/lazy.ts +1004 -0
  181. package/src/proofs/mock.ts +25 -1
  182. package/src/proofs/noir.ts +110 -29
  183. package/src/proofs/orchestrator.ts +960 -0
  184. package/src/proofs/parallel/concurrency.ts +297 -0
  185. package/src/proofs/parallel/dependency-graph.ts +602 -0
  186. package/src/proofs/parallel/executor.ts +420 -0
  187. package/src/proofs/parallel/index.ts +131 -0
  188. package/src/proofs/parallel/interface.ts +685 -0
  189. package/src/proofs/parallel/worker-pool.ts +644 -0
  190. package/src/proofs/providers/halo2.ts +560 -0
  191. package/src/proofs/providers/index.ts +34 -0
  192. package/src/proofs/providers/kimchi.ts +641 -0
  193. package/src/proofs/validator.ts +881 -0
  194. package/src/proofs/verifier.ts +867 -0
  195. package/src/quantum/index.ts +112 -0
  196. package/src/quantum/winternitz-vault.ts +639 -0
  197. package/src/quantum/wots.ts +611 -0
  198. package/src/settlement/backends/direct-chain.ts +1 -0
  199. package/src/settlement/index.ts +9 -0
  200. package/src/settlement/router.ts +732 -46
  201. package/src/solana/index.ts +72 -0
  202. package/src/solana/jito-relayer.ts +687 -0
  203. package/src/solana/noir-verifier-types.ts +430 -0
  204. package/src/solana/noir-verifier.ts +816 -0
  205. package/src/stealth/address-derivation.ts +193 -0
  206. package/src/stealth/ed25519.ts +431 -0
  207. package/src/stealth/index.ts +233 -0
  208. package/src/stealth/meta-address.ts +221 -0
  209. package/src/stealth/secp256k1.ts +368 -0
  210. package/src/stealth/utils.ts +194 -0
  211. package/src/stealth.ts +50 -1504
  212. package/src/sync/index.ts +106 -0
  213. package/src/sync/manager.ts +504 -0
  214. package/src/sync/mock-provider.ts +318 -0
  215. package/src/sync/oblivious.ts +625 -0
  216. package/src/tokens/index.ts +15 -0
  217. package/src/tokens/registry.ts +301 -0
  218. package/src/utils/deprecation.ts +94 -0
  219. package/src/utils/index.ts +9 -0
  220. package/src/wallet/ethereum/index.ts +68 -0
  221. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  222. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  223. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  224. package/src/wallet/ethereum/types.ts +3 -1
  225. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  226. package/src/wallet/hardware/index.ts +10 -0
  227. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  228. package/src/wallet/index.ts +71 -0
  229. package/src/wallet/near/adapter.ts +626 -0
  230. package/src/wallet/near/index.ts +86 -0
  231. package/src/wallet/near/meteor-wallet.ts +1153 -0
  232. package/src/wallet/near/my-near-wallet.ts +790 -0
  233. package/src/wallet/near/wallet-selector.ts +702 -0
  234. package/src/wallet/solana/adapter.ts +6 -4
  235. package/src/wallet/solana/index.ts +13 -0
  236. package/src/wallet/solana/privacy-adapter.ts +567 -0
  237. package/src/wallet/sui/types.ts +6 -4
  238. package/src/zcash/rpc-client.ts +13 -6
  239. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  240. package/dist/chunk-3INS3PR5.mjs +0 -884
  241. package/dist/chunk-3OVABDRH.mjs +0 -17096
  242. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  243. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  244. package/dist/chunk-E6SZWREQ.mjs +0 -57
  245. package/dist/chunk-F6F73W35.mjs +0 -16166
  246. package/dist/chunk-G33LB27A.mjs +0 -16166
  247. package/dist/chunk-HGU6HZRC.mjs +0 -231
  248. package/dist/chunk-L2K34JCU.mjs +0 -1496
  249. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  250. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  251. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  252. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  253. package/dist/constants-VOI7BSLK.mjs +0 -27
  254. package/dist/index-B71aXVzk.d.ts +0 -13264
  255. package/dist/index-BYZbDjal.d.ts +0 -11390
  256. package/dist/index-CHB3KuOB.d.mts +0 -11859
  257. package/dist/index-CzWPI6Le.d.ts +0 -11859
  258. package/dist/index-pOIIuwfV.d.mts +0 -13264
  259. package/dist/index-xbWjohNq.d.mts +0 -11390
  260. package/dist/solana-4O4K45VU.mjs +0 -46
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-NDABAZ6P.mjs +0 -56
  263. package/dist/solana-Q4NAVBTS.mjs +0 -46
  264. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,790 @@
1
+ /**
2
+ * ERC-20 Token Helper for Privacy Transfers
3
+ *
4
+ * Handles token allowances, EIP-2612 permit signatures,
5
+ * and metadata fetching for privacy-preserving token transfers.
6
+ *
7
+ * @module chains/ethereum/token
8
+ */
9
+
10
+ import type { HexString } from '@sip-protocol/types'
11
+ import { EthereumRpcClient } from './rpc'
12
+ import { estimateTokenTransferGas, type DetailedGasEstimate } from './gas-estimation'
13
+ import type { EthereumNetwork } from './constants'
14
+ import { DEFAULT_GAS_LIMITS } from './constants'
15
+
16
+ // ─── Types ────────────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * ERC-20 token metadata
20
+ */
21
+ export interface TokenMetadata {
22
+ /**
23
+ * Token name
24
+ */
25
+ name: string
26
+
27
+ /**
28
+ * Token symbol
29
+ */
30
+ symbol: string
31
+
32
+ /**
33
+ * Token decimals
34
+ */
35
+ decimals: number
36
+
37
+ /**
38
+ * Token contract address
39
+ */
40
+ address: HexString
41
+ }
42
+
43
+ /**
44
+ * Token allowance info
45
+ */
46
+ export interface TokenAllowance {
47
+ /**
48
+ * Owner address
49
+ */
50
+ owner: HexString
51
+
52
+ /**
53
+ * Spender address
54
+ */
55
+ spender: HexString
56
+
57
+ /**
58
+ * Current allowance
59
+ */
60
+ allowance: bigint
61
+
62
+ /**
63
+ * Token address
64
+ */
65
+ tokenAddress: HexString
66
+ }
67
+
68
+ /**
69
+ * EIP-2612 permit data
70
+ */
71
+ export interface PermitData {
72
+ /**
73
+ * Owner (signer) address
74
+ */
75
+ owner: HexString
76
+
77
+ /**
78
+ * Spender address
79
+ */
80
+ spender: HexString
81
+
82
+ /**
83
+ * Permit value
84
+ */
85
+ value: bigint
86
+
87
+ /**
88
+ * Permit nonce
89
+ */
90
+ nonce: bigint
91
+
92
+ /**
93
+ * Permit deadline (Unix timestamp)
94
+ */
95
+ deadline: bigint
96
+
97
+ /**
98
+ * Token address
99
+ */
100
+ tokenAddress: HexString
101
+ }
102
+
103
+ /**
104
+ * EIP-712 typed data for permit signing
105
+ */
106
+ export interface PermitTypedData {
107
+ /**
108
+ * Domain separator data
109
+ */
110
+ domain: {
111
+ name: string
112
+ version: string
113
+ chainId: number
114
+ verifyingContract: HexString
115
+ }
116
+
117
+ /**
118
+ * Types
119
+ */
120
+ types: {
121
+ Permit: Array<{
122
+ name: string
123
+ type: string
124
+ }>
125
+ }
126
+
127
+ /**
128
+ * Primary type
129
+ */
130
+ primaryType: 'Permit'
131
+
132
+ /**
133
+ * Message to sign
134
+ */
135
+ message: {
136
+ owner: HexString
137
+ spender: HexString
138
+ value: string
139
+ nonce: string
140
+ deadline: string
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Prepared approval transaction
146
+ */
147
+ export interface PreparedApproval {
148
+ /**
149
+ * Transaction data
150
+ */
151
+ tx: {
152
+ to: HexString
153
+ data: HexString
154
+ value: bigint
155
+ }
156
+
157
+ /**
158
+ * Token address
159
+ */
160
+ tokenAddress: HexString
161
+
162
+ /**
163
+ * Spender address
164
+ */
165
+ spender: HexString
166
+
167
+ /**
168
+ * Approval amount
169
+ */
170
+ amount: bigint
171
+
172
+ /**
173
+ * Estimated gas
174
+ */
175
+ estimatedGas: bigint
176
+ }
177
+
178
+ /**
179
+ * Token transfer check result
180
+ */
181
+ export interface TransferCheck {
182
+ /**
183
+ * Whether transfer is possible
184
+ */
185
+ canTransfer: boolean
186
+
187
+ /**
188
+ * Current token balance
189
+ */
190
+ balance: bigint
191
+
192
+ /**
193
+ * Current allowance for spender
194
+ */
195
+ allowance: bigint
196
+
197
+ /**
198
+ * Whether approval is needed
199
+ */
200
+ needsApproval: boolean
201
+
202
+ /**
203
+ * Amount of additional approval needed
204
+ */
205
+ additionalApprovalNeeded: bigint
206
+
207
+ /**
208
+ * Transfer amount
209
+ */
210
+ amount: bigint
211
+
212
+ /**
213
+ * Estimated gas for transfer (including approval if needed)
214
+ */
215
+ gasEstimate: DetailedGasEstimate
216
+ }
217
+
218
+ // ─── Constants ────────────────────────────────────────────────────────────────
219
+
220
+ /**
221
+ * ERC-20 function selectors
222
+ */
223
+ const ERC20_SELECTORS = {
224
+ /**
225
+ * name() returns (string)
226
+ */
227
+ name: '0x06fdde03',
228
+
229
+ /**
230
+ * symbol() returns (string)
231
+ */
232
+ symbol: '0x95d89b41',
233
+
234
+ /**
235
+ * decimals() returns (uint8)
236
+ */
237
+ decimals: '0x313ce567',
238
+
239
+ /**
240
+ * totalSupply() returns (uint256)
241
+ */
242
+ totalSupply: '0x18160ddd',
243
+
244
+ /**
245
+ * balanceOf(address) returns (uint256)
246
+ */
247
+ balanceOf: '0x70a08231',
248
+
249
+ /**
250
+ * allowance(address,address) returns (uint256)
251
+ */
252
+ allowance: '0xdd62ed3e',
253
+
254
+ /**
255
+ * approve(address,uint256) returns (bool)
256
+ */
257
+ approve: '0x095ea7b3',
258
+
259
+ /**
260
+ * transfer(address,uint256) returns (bool)
261
+ */
262
+ transfer: '0xa9059cbb',
263
+
264
+ /**
265
+ * transferFrom(address,address,uint256) returns (bool)
266
+ */
267
+ transferFrom: '0x23b872dd',
268
+
269
+ /**
270
+ * permit(address,address,uint256,uint256,uint8,bytes32,bytes32)
271
+ */
272
+ permit: '0xd505accf',
273
+
274
+ /**
275
+ * nonces(address) returns (uint256)
276
+ */
277
+ nonces: '0x7ecebe00',
278
+
279
+ /**
280
+ * DOMAIN_SEPARATOR() returns (bytes32)
281
+ */
282
+ domainSeparator: '0x3644e515',
283
+ } as const
284
+
285
+ /**
286
+ * Maximum uint256 value for unlimited approval
287
+ */
288
+ export const MAX_UINT256 = 2n ** 256n - 1n
289
+
290
+ // ─── Token Helper Class ──────────────────────────────────────────────────────
291
+
292
+ /**
293
+ * ERC-20 Token Helper
294
+ *
295
+ * Provides utilities for working with ERC-20 tokens in privacy transfers:
296
+ * - Metadata fetching (name, symbol, decimals)
297
+ * - Balance and allowance checking
298
+ * - Approval transaction building
299
+ * - EIP-2612 permit support
300
+ *
301
+ * @example Basic usage
302
+ * ```typescript
303
+ * const helper = new TokenHelper('mainnet')
304
+ *
305
+ * // Get token info
306
+ * const metadata = await helper.getTokenMetadata(usdcAddress)
307
+ * console.log(metadata.symbol) // 'USDC'
308
+ *
309
+ * // Check if transfer is possible
310
+ * const check = await helper.checkTransfer({
311
+ * owner: senderAddress,
312
+ * spender: routerAddress,
313
+ * tokenAddress: usdcAddress,
314
+ * amount: 100_000_000n,
315
+ * })
316
+ *
317
+ * if (check.needsApproval) {
318
+ * const approval = helper.buildApproval(...)
319
+ * // Sign and submit approval
320
+ * }
321
+ * ```
322
+ */
323
+ export class TokenHelper {
324
+ private rpc: EthereumRpcClient
325
+ private network: EthereumNetwork
326
+ private metadataCache: Map<string, TokenMetadata> = new Map()
327
+
328
+ constructor(
329
+ network: EthereumNetwork = 'mainnet',
330
+ options?: {
331
+ rpcUrl?: string
332
+ }
333
+ ) {
334
+ this.network = network
335
+ this.rpc = new EthereumRpcClient(network, options)
336
+ }
337
+
338
+ // ─── Metadata Methods ────────────────────────────────────────────────────────
339
+
340
+ /**
341
+ * Get token metadata (name, symbol, decimals)
342
+ *
343
+ * Results are cached for performance.
344
+ *
345
+ * @param tokenAddress - Token contract address
346
+ * @returns Token metadata
347
+ */
348
+ async getTokenMetadata(tokenAddress: HexString): Promise<TokenMetadata> {
349
+ const cached = this.metadataCache.get(tokenAddress.toLowerCase())
350
+ if (cached) {
351
+ return cached
352
+ }
353
+
354
+ // Fetch name, symbol, decimals in parallel
355
+ const [name, symbol, decimals] = await Promise.all([
356
+ this.getTokenName(tokenAddress),
357
+ this.getTokenSymbol(tokenAddress),
358
+ this.getTokenDecimals(tokenAddress),
359
+ ])
360
+
361
+ const metadata: TokenMetadata = {
362
+ name,
363
+ symbol,
364
+ decimals,
365
+ address: tokenAddress,
366
+ }
367
+
368
+ this.metadataCache.set(tokenAddress.toLowerCase(), metadata)
369
+ return metadata
370
+ }
371
+
372
+ /**
373
+ * Get token name
374
+ *
375
+ * @param tokenAddress - Token contract address
376
+ * @returns Token name
377
+ */
378
+ async getTokenName(tokenAddress: HexString): Promise<string> {
379
+ try {
380
+ const result = await this.rpc.call({
381
+ to: tokenAddress,
382
+ data: ERC20_SELECTORS.name as HexString,
383
+ })
384
+ return this.decodeString(result)
385
+ } catch {
386
+ return 'Unknown Token'
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Get token symbol
392
+ *
393
+ * @param tokenAddress - Token contract address
394
+ * @returns Token symbol
395
+ */
396
+ async getTokenSymbol(tokenAddress: HexString): Promise<string> {
397
+ try {
398
+ const result = await this.rpc.call({
399
+ to: tokenAddress,
400
+ data: ERC20_SELECTORS.symbol as HexString,
401
+ })
402
+ return this.decodeString(result)
403
+ } catch {
404
+ return 'UNKNOWN'
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Get token decimals
410
+ *
411
+ * @param tokenAddress - Token contract address
412
+ * @returns Token decimals (default: 18)
413
+ */
414
+ async getTokenDecimals(tokenAddress: HexString): Promise<number> {
415
+ try {
416
+ const result = await this.rpc.call({
417
+ to: tokenAddress,
418
+ data: ERC20_SELECTORS.decimals as HexString,
419
+ })
420
+ return parseInt(result, 16)
421
+ } catch {
422
+ return 18 // Default to 18 if call fails
423
+ }
424
+ }
425
+
426
+ // ─── Balance Methods ─────────────────────────────────────────────────────────
427
+
428
+ /**
429
+ * Get token balance
430
+ *
431
+ * @param tokenAddress - Token contract address
432
+ * @param owner - Address to check balance of
433
+ * @returns Token balance
434
+ */
435
+ async getBalance(tokenAddress: HexString, owner: HexString): Promise<bigint> {
436
+ const data = `${ERC20_SELECTORS.balanceOf}${owner.slice(2).padStart(64, '0')}` as HexString
437
+
438
+ const result = await this.rpc.call({
439
+ to: tokenAddress,
440
+ data,
441
+ })
442
+
443
+ return BigInt(result)
444
+ }
445
+
446
+ // ─── Allowance Methods ───────────────────────────────────────────────────────
447
+
448
+ /**
449
+ * Get token allowance
450
+ *
451
+ * @param tokenAddress - Token contract address
452
+ * @param owner - Token owner address
453
+ * @param spender - Spender address
454
+ * @returns Current allowance
455
+ */
456
+ async getAllowance(
457
+ tokenAddress: HexString,
458
+ owner: HexString,
459
+ spender: HexString
460
+ ): Promise<bigint> {
461
+ const ownerParam = owner.slice(2).padStart(64, '0')
462
+ const spenderParam = spender.slice(2).padStart(64, '0')
463
+ const data = `${ERC20_SELECTORS.allowance}${ownerParam}${spenderParam}` as HexString
464
+
465
+ const result = await this.rpc.call({
466
+ to: tokenAddress,
467
+ data,
468
+ })
469
+
470
+ return BigInt(result)
471
+ }
472
+
473
+ /**
474
+ * Build an approval transaction
475
+ *
476
+ * @param tokenAddress - Token contract address
477
+ * @param spender - Spender address to approve
478
+ * @param amount - Amount to approve (use MAX_UINT256 for unlimited)
479
+ * @returns Prepared approval transaction
480
+ */
481
+ buildApproval(
482
+ tokenAddress: HexString,
483
+ spender: HexString,
484
+ amount: bigint = MAX_UINT256
485
+ ): PreparedApproval {
486
+ const spenderParam = spender.slice(2).padStart(64, '0')
487
+ const amountParam = amount.toString(16).padStart(64, '0')
488
+ const data = `${ERC20_SELECTORS.approve}${spenderParam}${amountParam}` as HexString
489
+
490
+ return {
491
+ tx: {
492
+ to: tokenAddress,
493
+ data,
494
+ value: 0n,
495
+ },
496
+ tokenAddress,
497
+ spender,
498
+ amount,
499
+ estimatedGas: DEFAULT_GAS_LIMITS.erc20Approve,
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Check if transfer is possible and what approvals are needed
505
+ *
506
+ * @param params - Check parameters
507
+ * @returns Transfer check result
508
+ */
509
+ async checkTransfer(params: {
510
+ owner: HexString
511
+ spender: HexString
512
+ tokenAddress: HexString
513
+ amount: bigint
514
+ }): Promise<TransferCheck> {
515
+ // Fetch balance and allowance in parallel
516
+ const [balance, allowance] = await Promise.all([
517
+ this.getBalance(params.tokenAddress, params.owner),
518
+ this.getAllowance(params.tokenAddress, params.owner, params.spender),
519
+ ])
520
+
521
+ const needsApproval = allowance < params.amount
522
+ const additionalApprovalNeeded = needsApproval
523
+ ? params.amount - allowance
524
+ : 0n
525
+
526
+ const canTransfer = balance >= params.amount
527
+
528
+ // Estimate gas
529
+ const gasEstimate = estimateTokenTransferGas(this.network, needsApproval)
530
+
531
+ return {
532
+ canTransfer,
533
+ balance,
534
+ allowance,
535
+ needsApproval,
536
+ additionalApprovalNeeded,
537
+ amount: params.amount,
538
+ gasEstimate,
539
+ }
540
+ }
541
+
542
+ // ─── Permit Methods ──────────────────────────────────────────────────────────
543
+
544
+ /**
545
+ * Check if token supports EIP-2612 permit
546
+ *
547
+ * @param tokenAddress - Token contract address
548
+ * @returns True if permit is supported
549
+ */
550
+ async supportsPermit(tokenAddress: HexString): Promise<boolean> {
551
+ try {
552
+ // Try to call nonces() - if it exists, permit is likely supported
553
+ await this.rpc.call({
554
+ to: tokenAddress,
555
+ data: `${ERC20_SELECTORS.nonces}${'0'.padStart(64, '0')}` as HexString,
556
+ })
557
+ return true
558
+ } catch {
559
+ return false
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Get permit nonce for an address
565
+ *
566
+ * @param tokenAddress - Token contract address
567
+ * @param owner - Address to get nonce for
568
+ * @returns Permit nonce
569
+ */
570
+ async getPermitNonce(tokenAddress: HexString, owner: HexString): Promise<bigint> {
571
+ const ownerParam = owner.slice(2).padStart(64, '0')
572
+ const data = `${ERC20_SELECTORS.nonces}${ownerParam}` as HexString
573
+
574
+ const result = await this.rpc.call({
575
+ to: tokenAddress,
576
+ data,
577
+ })
578
+
579
+ return BigInt(result)
580
+ }
581
+
582
+ /**
583
+ * Build EIP-712 typed data for permit signing
584
+ *
585
+ * @param params - Permit parameters
586
+ * @returns Typed data for signing
587
+ */
588
+ async buildPermitTypedData(params: {
589
+ tokenAddress: HexString
590
+ owner: HexString
591
+ spender: HexString
592
+ value: bigint
593
+ deadline?: bigint
594
+ }): Promise<PermitTypedData> {
595
+ // Get token metadata and nonce
596
+ const [metadata, nonce] = await Promise.all([
597
+ this.getTokenMetadata(params.tokenAddress),
598
+ this.getPermitNonce(params.tokenAddress, params.owner),
599
+ ])
600
+
601
+ // Default deadline: 1 hour from now
602
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1000) + 3600)
603
+
604
+ return {
605
+ domain: {
606
+ name: metadata.name,
607
+ version: '1',
608
+ chainId: this.rpc.getChainId(),
609
+ verifyingContract: params.tokenAddress,
610
+ },
611
+ types: {
612
+ Permit: [
613
+ { name: 'owner', type: 'address' },
614
+ { name: 'spender', type: 'address' },
615
+ { name: 'value', type: 'uint256' },
616
+ { name: 'nonce', type: 'uint256' },
617
+ { name: 'deadline', type: 'uint256' },
618
+ ],
619
+ },
620
+ primaryType: 'Permit',
621
+ message: {
622
+ owner: params.owner,
623
+ spender: params.spender,
624
+ value: params.value.toString(),
625
+ nonce: nonce.toString(),
626
+ deadline: deadline.toString(),
627
+ },
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Build permit call data from signature
633
+ *
634
+ * @param params - Permit parameters with signature
635
+ * @returns Call data for permit function
636
+ */
637
+ buildPermitCallData(params: {
638
+ owner: HexString
639
+ spender: HexString
640
+ value: bigint
641
+ deadline: bigint
642
+ v: number
643
+ r: HexString
644
+ s: HexString
645
+ }): HexString {
646
+ const ownerParam = params.owner.slice(2).padStart(64, '0')
647
+ const spenderParam = params.spender.slice(2).padStart(64, '0')
648
+ const valueParam = params.value.toString(16).padStart(64, '0')
649
+ const deadlineParam = params.deadline.toString(16).padStart(64, '0')
650
+ const vParam = params.v.toString(16).padStart(64, '0')
651
+ const rParam = params.r.slice(2).padStart(64, '0')
652
+ const sParam = params.s.slice(2).padStart(64, '0')
653
+
654
+ return `${ERC20_SELECTORS.permit}${ownerParam}${spenderParam}${valueParam}${deadlineParam}${vParam}${rParam}${sParam}` as HexString
655
+ }
656
+
657
+ // ─── Utility Methods ─────────────────────────────────────────────────────────
658
+
659
+ /**
660
+ * Format token amount with decimals
661
+ *
662
+ * @param amount - Raw amount
663
+ * @param decimals - Token decimals
664
+ * @returns Formatted string
665
+ */
666
+ formatAmount(amount: bigint, decimals: number): string {
667
+ const divisor = 10n ** BigInt(decimals)
668
+ const whole = amount / divisor
669
+ const fraction = amount % divisor
670
+
671
+ if (fraction === 0n) {
672
+ return whole.toString()
673
+ }
674
+
675
+ const fractionStr = fraction.toString().padStart(decimals, '0')
676
+ const trimmed = fractionStr.replace(/0+$/, '')
677
+
678
+ return `${whole}.${trimmed}`
679
+ }
680
+
681
+ /**
682
+ * Parse token amount from string
683
+ *
684
+ * @param amount - Amount string (e.g., "100.5")
685
+ * @param decimals - Token decimals
686
+ * @returns Raw amount
687
+ */
688
+ parseAmount(amount: string, decimals: number): bigint {
689
+ const [whole, fraction = ''] = amount.split('.')
690
+ const paddedFraction = fraction.padEnd(decimals, '0').slice(0, decimals)
691
+ const combined = `${whole}${paddedFraction}`
692
+
693
+ return BigInt(combined)
694
+ }
695
+
696
+ /**
697
+ * Get network
698
+ */
699
+ getNetwork(): EthereumNetwork {
700
+ return this.network
701
+ }
702
+
703
+ /**
704
+ * Clear metadata cache
705
+ */
706
+ clearCache(): void {
707
+ this.metadataCache.clear()
708
+ }
709
+
710
+ // ─── Private Methods ─────────────────────────────────────────────────────────
711
+
712
+ /**
713
+ * Decode ABI-encoded string
714
+ */
715
+ private decodeString(data: HexString): string {
716
+ // Remove 0x prefix
717
+ const hex = data.slice(2)
718
+
719
+ // ABI-encoded string: offset (32 bytes) + length (32 bytes) + data
720
+ if (hex.length < 128) {
721
+ // Might be raw bytes32 string (some older tokens)
722
+ return this.decodeBytesString(hex)
723
+ }
724
+
725
+ // Skip offset (first 32 bytes)
726
+ // Get length (next 32 bytes)
727
+ const lengthHex = hex.slice(64, 128)
728
+ const length = parseInt(lengthHex, 16)
729
+
730
+ if (length === 0 || length > 256) {
731
+ return this.decodeBytesString(hex)
732
+ }
733
+
734
+ // Get string data
735
+ const stringHex = hex.slice(128, 128 + length * 2)
736
+
737
+ // Decode hex to string
738
+ let result = ''
739
+ for (let i = 0; i < stringHex.length; i += 2) {
740
+ const charCode = parseInt(stringHex.substr(i, 2), 16)
741
+ if (charCode === 0) break
742
+ result += String.fromCharCode(charCode)
743
+ }
744
+
745
+ return result
746
+ }
747
+
748
+ /**
749
+ * Decode bytes32 string (for non-standard tokens)
750
+ */
751
+ private decodeBytesString(hex: string): string {
752
+ let result = ''
753
+ for (let i = 0; i < Math.min(hex.length, 64); i += 2) {
754
+ const charCode = parseInt(hex.substr(i, 2), 16)
755
+ if (charCode === 0) break
756
+ result += String.fromCharCode(charCode)
757
+ }
758
+ return result.trim() || 'Unknown'
759
+ }
760
+ }
761
+
762
+ // ─── Factory Functions ────────────────────────────────────────────────────────
763
+
764
+ /**
765
+ * Create a token helper for a network
766
+ *
767
+ * @param network - Target network
768
+ * @param rpcUrl - Optional custom RPC URL
769
+ * @returns Token helper
770
+ */
771
+ export function createTokenHelper(
772
+ network: EthereumNetwork = 'mainnet',
773
+ rpcUrl?: string
774
+ ): TokenHelper {
775
+ return new TokenHelper(network, { rpcUrl })
776
+ }
777
+
778
+ /**
779
+ * Create a mainnet token helper
780
+ */
781
+ export function createMainnetTokenHelper(rpcUrl?: string): TokenHelper {
782
+ return new TokenHelper('mainnet', { rpcUrl })
783
+ }
784
+
785
+ /**
786
+ * Create a Sepolia testnet token helper
787
+ */
788
+ export function createSepoliaTokenHelper(rpcUrl?: string): TokenHelper {
789
+ return new TokenHelper('sepolia', { rpcUrl })
790
+ }