@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.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +54 -21
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- package/dist/solana-ZYO63LY5.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
|
+
}
|