@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,536 @@
1
+ /**
2
+ * EIP-5564 Stealth Address Announcement Parser
3
+ *
4
+ * Parses and creates announcements for stealth address payments on Ethereum.
5
+ * Implements the EIP-5564 announcement event format.
6
+ *
7
+ * ## EIP-5564 Announcement Format
8
+ *
9
+ * ```solidity
10
+ * event Announcement(
11
+ * uint256 indexed schemeId,
12
+ * address indexed stealthAddress,
13
+ * address indexed caller,
14
+ * bytes ephemeralPubKey,
15
+ * bytes metadata
16
+ * )
17
+ * ```
18
+ *
19
+ * @see https://eips.ethereum.org/EIPS/eip-5564
20
+ * @packageDocumentation
21
+ */
22
+
23
+ import type { HexString, StealthAddress } from '@sip-protocol/types'
24
+ import { ValidationError } from '../../errors'
25
+ import { isValidHexLength } from '../../validation'
26
+ import type {
27
+ EthereumAnnouncement,
28
+ AnnouncementMetadata,
29
+ AnnouncementEvent,
30
+ } from './types'
31
+ import {
32
+ ANNOUNCEMENT_EVENT_SIGNATURE,
33
+ SECP256K1_SCHEME_ID,
34
+ VIEW_TAG_MAX,
35
+ VIEW_TAG_MIN,
36
+ isValidEthAddress,
37
+ } from './constants'
38
+
39
+ // ─── Constants ────────────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Zero address constant
43
+ */
44
+ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as HexString
45
+
46
+ /**
47
+ * Metadata version byte
48
+ */
49
+ export const METADATA_VERSION = 0x01
50
+
51
+ // ─── Announcement Parsing ───────────────────────────────────────────────────
52
+
53
+ /**
54
+ * Parse an EIP-5564 announcement event log
55
+ *
56
+ * @param log - The raw event log from eth_getLogs
57
+ * @returns Parsed announcement
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const logs = await provider.getLogs({
62
+ * address: EIP5564_ANNOUNCER_ADDRESS,
63
+ * topics: [ANNOUNCEMENT_EVENT_SIGNATURE],
64
+ * fromBlock,
65
+ * toBlock,
66
+ * })
67
+ *
68
+ * for (const log of logs) {
69
+ * const announcement = parseAnnouncementLog(log)
70
+ * console.log(announcement.stealthAddress)
71
+ * }
72
+ * ```
73
+ */
74
+ export function parseAnnouncementLog(log: {
75
+ address: HexString
76
+ topics: HexString[]
77
+ data: HexString
78
+ blockNumber: number
79
+ transactionHash: HexString
80
+ logIndex: number
81
+ }): EthereumAnnouncement {
82
+ // Validate topics
83
+ if (log.topics.length < 4) {
84
+ throw new ValidationError(
85
+ `expected 4 topics, got ${log.topics.length}`,
86
+ 'log.topics'
87
+ )
88
+ }
89
+
90
+ // Topic 0: Event signature (already filtered)
91
+ // Topic 1: schemeId (indexed uint256)
92
+ // Topic 2: stealthAddress (indexed address, padded to 32 bytes)
93
+ // Topic 3: caller (indexed address, padded to 32 bytes)
94
+
95
+ const schemeId = parseInt(log.topics[1], 16)
96
+ const stealthAddress = `0x${log.topics[2].slice(-40)}` as HexString
97
+ const caller = `0x${log.topics[3].slice(-40)}` as HexString
98
+
99
+ // Parse data (non-indexed: ephemeralPubKey bytes, metadata bytes)
100
+ const { ephemeralPublicKey, viewTag, metadata } = parseAnnouncementData(log.data)
101
+
102
+ return {
103
+ schemeId,
104
+ stealthAddress,
105
+ caller,
106
+ ephemeralPublicKey,
107
+ viewTag,
108
+ metadata,
109
+ txHash: log.transactionHash,
110
+ blockNumber: log.blockNumber,
111
+ logIndex: log.logIndex,
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Parse the data field of an announcement event
117
+ *
118
+ * @param data - The hex-encoded data field
119
+ * @returns Parsed ephemeral key, view tag, and metadata
120
+ */
121
+ export function parseAnnouncementData(data: HexString): {
122
+ ephemeralPublicKey: HexString
123
+ viewTag: number
124
+ metadata?: HexString
125
+ } {
126
+ // Remove 0x prefix
127
+ const hex = data.startsWith('0x') ? data.slice(2) : data
128
+
129
+ if (hex.length < 128) {
130
+ throw new ValidationError(
131
+ 'announcement data too short (need at least 64 bytes for offsets)',
132
+ 'data'
133
+ )
134
+ }
135
+
136
+ // ABI-encoded bytes layout:
137
+ // - bytes[0:32]: offset to ephemeralPubKey
138
+ // - bytes[32:64]: offset to metadata
139
+ // - bytes[64:...]: encoded data
140
+
141
+ const ephemeralOffset = parseInt(hex.slice(0, 64), 16) * 2
142
+ const metadataOffset = parseInt(hex.slice(64, 128), 16) * 2
143
+
144
+ // Parse ephemeral public key
145
+ const ephemeralLength = parseInt(hex.slice(ephemeralOffset, ephemeralOffset + 64), 16)
146
+ const ephemeralStart = ephemeralOffset + 64
147
+ const ephemeralHex = hex.slice(ephemeralStart, ephemeralStart + ephemeralLength * 2)
148
+ const ephemeralPublicKey = `0x${ephemeralHex}` as HexString
149
+
150
+ // Extract view tag (first byte of hash of shared secret, stored in first byte of metadata or derived)
151
+ // In EIP-5564, view tag is the first byte of the ephemeral public key hash
152
+ // For simplicity, we compute it as first byte of ephemeral key
153
+ const viewTag = parseInt(ephemeralHex.slice(0, 2), 16)
154
+
155
+ // Parse metadata (if present and different from ephemeral)
156
+ let metadata: HexString | undefined
157
+ if (metadataOffset !== ephemeralOffset && metadataOffset < hex.length) {
158
+ const metadataLength = parseInt(hex.slice(metadataOffset, metadataOffset + 64), 16)
159
+ if (metadataLength > 0) {
160
+ const metadataStart = metadataOffset + 64
161
+ const metadataHex = hex.slice(metadataStart, metadataStart + metadataLength * 2)
162
+ metadata = `0x${metadataHex}` as HexString
163
+ }
164
+ }
165
+
166
+ return {
167
+ ephemeralPublicKey,
168
+ viewTag,
169
+ metadata,
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Parse announcement metadata
175
+ *
176
+ * @param metadata - The metadata hex string
177
+ * @returns Parsed metadata fields
178
+ */
179
+ export function parseAnnouncementMetadata(
180
+ metadata: HexString
181
+ ): AnnouncementMetadata {
182
+ const hex = metadata.startsWith('0x') ? metadata.slice(2) : metadata
183
+
184
+ // Metadata format (flexible, version-prefixed):
185
+ // byte 0: version
186
+ // bytes 1-20: token address (20 bytes, zero for ETH)
187
+ // bytes 21-53: amount commitment (33 bytes, compressed point)
188
+ // bytes 54-85: blinding hash (32 bytes)
189
+ // bytes 86+: extra data
190
+
191
+ const result: AnnouncementMetadata = {}
192
+
193
+ if (hex.length < 2) {
194
+ return result
195
+ }
196
+
197
+ const version = parseInt(hex.slice(0, 2), 16)
198
+
199
+ if (version === METADATA_VERSION && hex.length >= 42) {
200
+ // Token address (20 bytes)
201
+ const tokenHex = hex.slice(2, 42)
202
+ if (tokenHex !== '0'.repeat(40)) {
203
+ result.tokenAddress = `0x${tokenHex}` as HexString
204
+ }
205
+
206
+ // Amount commitment (33 bytes, if present)
207
+ if (hex.length >= 108) {
208
+ result.amountCommitment = `0x${hex.slice(42, 108)}` as HexString
209
+ }
210
+
211
+ // Blinding hash (32 bytes, if present)
212
+ if (hex.length >= 172) {
213
+ result.blindingHash = `0x${hex.slice(108, 172)}` as HexString
214
+ }
215
+
216
+ // Extra data (remaining bytes)
217
+ if (hex.length > 172) {
218
+ result.extraData = `0x${hex.slice(172)}` as HexString
219
+ }
220
+ }
221
+
222
+ return result
223
+ }
224
+
225
+ // ─── Announcement Creation ──────────────────────────────────────────────────
226
+
227
+ /**
228
+ * Create announcement metadata bytes
229
+ *
230
+ * @param options - Metadata options
231
+ * @returns Encoded metadata hex string
232
+ */
233
+ export function createAnnouncementMetadata(options: {
234
+ tokenAddress?: HexString
235
+ amountCommitment?: HexString
236
+ blindingHash?: HexString
237
+ extraData?: HexString
238
+ }): HexString {
239
+ const parts: string[] = []
240
+
241
+ // Version byte
242
+ parts.push(METADATA_VERSION.toString(16).padStart(2, '0'))
243
+
244
+ // Token address (20 bytes, zero-padded)
245
+ if (options.tokenAddress) {
246
+ const addr = options.tokenAddress.slice(2).toLowerCase()
247
+ parts.push(addr.padStart(40, '0'))
248
+ } else {
249
+ parts.push('0'.repeat(40))
250
+ }
251
+
252
+ // Amount commitment (33 bytes)
253
+ if (options.amountCommitment) {
254
+ const commitment = options.amountCommitment.slice(2)
255
+ parts.push(commitment.padStart(66, '0'))
256
+ }
257
+
258
+ // Blinding hash (32 bytes)
259
+ if (options.blindingHash) {
260
+ const hash = options.blindingHash.slice(2)
261
+ parts.push(hash.padStart(64, '0'))
262
+ }
263
+
264
+ // Extra data
265
+ if (options.extraData) {
266
+ parts.push(options.extraData.slice(2))
267
+ }
268
+
269
+ return `0x${parts.join('')}` as HexString
270
+ }
271
+
272
+ /**
273
+ * Encode announcement call data for the Announcer contract
274
+ *
275
+ * @param schemeId - The EIP-5564 scheme ID (1 for secp256k1)
276
+ * @param stealthAddress - The stealth Ethereum address
277
+ * @param ephemeralPublicKey - The ephemeral public key (33 bytes compressed)
278
+ * @param metadata - Optional metadata bytes
279
+ * @returns Encoded call data
280
+ */
281
+ export function encodeAnnouncementCallData(
282
+ schemeId: number,
283
+ stealthAddress: HexString,
284
+ ephemeralPublicKey: HexString,
285
+ metadata?: HexString
286
+ ): HexString {
287
+ // Function selector: announce(uint256,address,bytes,bytes)
288
+ const selector = '0x3f62a9e6'
289
+
290
+ // Encode parameters
291
+ const schemeIdHex = schemeId.toString(16).padStart(64, '0')
292
+ const stealthAddressHex = stealthAddress.slice(2).padStart(64, '0')
293
+
294
+ // Dynamic data offsets
295
+ const ephemeralOffset = (4 * 32).toString(16).padStart(64, '0') // After 4 static params
296
+ const metadataOffset = metadata
297
+ ? ((4 * 32 + 32 + Math.ceil((ephemeralPublicKey.slice(2).length / 2 + 32) / 32) * 32))
298
+ .toString(16)
299
+ .padStart(64, '0')
300
+ : ephemeralOffset // Point to same location if no metadata
301
+
302
+ // Encode ephemeral public key bytes
303
+ const ephemeralBytes = ephemeralPublicKey.slice(2)
304
+ const ephemeralLength = (ephemeralBytes.length / 2).toString(16).padStart(64, '0')
305
+ const ephemeralPadded = ephemeralBytes.padEnd(
306
+ Math.ceil(ephemeralBytes.length / 64) * 64,
307
+ '0'
308
+ )
309
+
310
+ // Encode metadata bytes
311
+ let metadataEncoded = ''
312
+ if (metadata) {
313
+ const metadataBytes = metadata.slice(2)
314
+ const metadataLength = (metadataBytes.length / 2).toString(16).padStart(64, '0')
315
+ const metadataPadded = metadataBytes.padEnd(
316
+ Math.ceil(metadataBytes.length / 64) * 64,
317
+ '0'
318
+ )
319
+ metadataEncoded = metadataLength + metadataPadded
320
+ } else {
321
+ metadataEncoded = '0'.repeat(64) // Zero length
322
+ }
323
+
324
+ return `${selector}${schemeIdHex}${stealthAddressHex}${ephemeralOffset}${metadataOffset}${ephemeralLength}${ephemeralPadded}${metadataEncoded}` as HexString
325
+ }
326
+
327
+ // ─── Announcement Filtering ─────────────────────────────────────────────────
328
+
329
+ /**
330
+ * Filter announcements by scheme ID
331
+ *
332
+ * @param announcements - Announcements to filter
333
+ * @param schemeId - Scheme ID to filter for (default: secp256k1 = 1)
334
+ * @returns Filtered announcements
335
+ */
336
+ export function filterBySchemeId(
337
+ announcements: EthereumAnnouncement[],
338
+ schemeId: number = SECP256K1_SCHEME_ID
339
+ ): EthereumAnnouncement[] {
340
+ return announcements.filter((a) => a.schemeId === schemeId)
341
+ }
342
+
343
+ /**
344
+ * Filter announcements by view tag (quick pre-filter before full check)
345
+ *
346
+ * @param announcements - Announcements to filter
347
+ * @param viewTag - Expected view tag
348
+ * @returns Announcements with matching view tag
349
+ */
350
+ export function filterByViewTag(
351
+ announcements: EthereumAnnouncement[],
352
+ viewTag: number
353
+ ): EthereumAnnouncement[] {
354
+ if (viewTag < VIEW_TAG_MIN || viewTag > VIEW_TAG_MAX) {
355
+ throw new ValidationError(
356
+ `viewTag must be between ${VIEW_TAG_MIN} and ${VIEW_TAG_MAX}`,
357
+ 'viewTag'
358
+ )
359
+ }
360
+
361
+ return announcements.filter((a) => a.viewTag === viewTag)
362
+ }
363
+
364
+ /**
365
+ * Filter announcements by block range
366
+ *
367
+ * @param announcements - Announcements to filter
368
+ * @param fromBlock - Start block (inclusive)
369
+ * @param toBlock - End block (inclusive)
370
+ * @returns Filtered announcements
371
+ */
372
+ export function filterByBlockRange(
373
+ announcements: EthereumAnnouncement[],
374
+ fromBlock: number,
375
+ toBlock: number
376
+ ): EthereumAnnouncement[] {
377
+ return announcements.filter(
378
+ (a) =>
379
+ a.blockNumber !== undefined &&
380
+ a.blockNumber >= fromBlock &&
381
+ a.blockNumber <= toBlock
382
+ )
383
+ }
384
+
385
+ /**
386
+ * Filter announcements by token (from metadata)
387
+ *
388
+ * @param announcements - Announcements to filter
389
+ * @param tokenAddress - Token contract address (use ZERO_ADDRESS for ETH)
390
+ * @returns Filtered announcements
391
+ */
392
+ export function filterByToken(
393
+ announcements: EthereumAnnouncement[],
394
+ tokenAddress: HexString
395
+ ): EthereumAnnouncement[] {
396
+ const normalizedToken = tokenAddress.toLowerCase()
397
+
398
+ return announcements.filter((a) => {
399
+ if (!a.metadata) {
400
+ // No metadata = native ETH
401
+ return normalizedToken === ZERO_ADDRESS.toLowerCase()
402
+ }
403
+
404
+ const parsed = parseAnnouncementMetadata(a.metadata)
405
+ const announcementToken = parsed.tokenAddress?.toLowerCase() || ZERO_ADDRESS.toLowerCase()
406
+
407
+ return announcementToken === normalizedToken
408
+ })
409
+ }
410
+
411
+ // ─── Announcement Conversion ────────────────────────────────────────────────
412
+
413
+ /**
414
+ * Convert an announcement to a StealthAddress object
415
+ *
416
+ * @param announcement - The announcement
417
+ * @returns StealthAddress object for further processing
418
+ */
419
+ export function announcementToStealthAddress(
420
+ announcement: EthereumAnnouncement
421
+ ): StealthAddress {
422
+ return {
423
+ address: announcement.stealthAddress,
424
+ ephemeralPublicKey: announcement.ephemeralPublicKey,
425
+ viewTag: announcement.viewTag,
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Create an announcement from a stealth address result
431
+ *
432
+ * @param stealthAddress - The stealth address
433
+ * @param stealthEthAddress - The derived Ethereum address
434
+ * @param caller - The sender's address
435
+ * @param metadata - Optional metadata
436
+ * @returns Partial announcement (without tx details)
437
+ */
438
+ export function createAnnouncementFromStealth(
439
+ stealthAddress: StealthAddress,
440
+ stealthEthAddress: HexString,
441
+ caller: HexString,
442
+ metadata?: HexString
443
+ ): Omit<EthereumAnnouncement, 'txHash' | 'blockNumber' | 'logIndex' | 'timestamp'> {
444
+ return {
445
+ schemeId: SECP256K1_SCHEME_ID,
446
+ stealthAddress: stealthEthAddress,
447
+ caller,
448
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
449
+ viewTag: stealthAddress.viewTag,
450
+ metadata,
451
+ }
452
+ }
453
+
454
+ // ─── Validation ─────────────────────────────────────────────────────────────
455
+
456
+ /**
457
+ * Validate an announcement structure
458
+ *
459
+ * @param announcement - The announcement to validate
460
+ * @returns True if valid
461
+ */
462
+ export function isValidAnnouncement(announcement: EthereumAnnouncement): boolean {
463
+ // Check scheme ID
464
+ if (announcement.schemeId !== SECP256K1_SCHEME_ID) {
465
+ return false
466
+ }
467
+
468
+ // Check stealth address
469
+ if (!isValidEthAddress(announcement.stealthAddress)) {
470
+ return false
471
+ }
472
+
473
+ // Check caller
474
+ if (!isValidEthAddress(announcement.caller)) {
475
+ return false
476
+ }
477
+
478
+ // Check ephemeral public key (33 bytes = 66 hex chars + 0x)
479
+ if (!isValidHexLength(announcement.ephemeralPublicKey, 33)) {
480
+ return false
481
+ }
482
+
483
+ // Check view tag
484
+ if (
485
+ announcement.viewTag < VIEW_TAG_MIN ||
486
+ announcement.viewTag > VIEW_TAG_MAX
487
+ ) {
488
+ return false
489
+ }
490
+
491
+ return true
492
+ }
493
+
494
+ /**
495
+ * Get the event topic for filtering logs
496
+ *
497
+ * @returns The Announcement event signature topic
498
+ */
499
+ export function getAnnouncementEventTopic(): HexString {
500
+ return ANNOUNCEMENT_EVENT_SIGNATURE as HexString
501
+ }
502
+
503
+ /**
504
+ * Build topics array for eth_getLogs filtering
505
+ *
506
+ * @param schemeId - Optional scheme ID filter
507
+ * @param stealthAddress - Optional stealth address filter
508
+ * @param caller - Optional caller filter
509
+ * @returns Topics array for eth_getLogs
510
+ */
511
+ export function buildAnnouncementTopics(options?: {
512
+ schemeId?: number
513
+ stealthAddress?: HexString
514
+ caller?: HexString
515
+ }): (HexString | null)[] {
516
+ const topics: (HexString | null)[] = [
517
+ ANNOUNCEMENT_EVENT_SIGNATURE as HexString, // Topic 0: event signature
518
+ null, // Topic 1: schemeId
519
+ null, // Topic 2: stealthAddress
520
+ null, // Topic 3: caller
521
+ ]
522
+
523
+ if (options?.schemeId !== undefined) {
524
+ topics[1] = `0x${options.schemeId.toString(16).padStart(64, '0')}` as HexString
525
+ }
526
+
527
+ if (options?.stealthAddress) {
528
+ topics[2] = `0x${options.stealthAddress.slice(2).padStart(64, '0')}` as HexString
529
+ }
530
+
531
+ if (options?.caller) {
532
+ topics[3] = `0x${options.caller.slice(2).padStart(64, '0')}` as HexString
533
+ }
534
+
535
+ return topics
536
+ }