@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
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Token mints, RPC endpoints, and configuration for Solana same-chain privacy.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
SOLANA_RPC_ENDPOINTS as SOLANA_RPC_CONFIG,
|
|
9
|
+
SOLANA_EXPLORER_ENDPOINTS as SOLANA_EXPLORER_CONFIG,
|
|
10
|
+
} from '../../config/endpoints'
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Common SPL token mint addresses on Solana mainnet
|
|
9
14
|
*/
|
|
@@ -28,13 +33,14 @@ export const SOLANA_TOKEN_DECIMALS: Record<string, number> = {
|
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* RPC endpoints for Solana clusters
|
|
36
|
+
* Localnet is configurable via SOLANA_LOCALNET_RPC environment variable
|
|
31
37
|
*/
|
|
32
38
|
export const SOLANA_RPC_ENDPOINTS = {
|
|
33
|
-
'mainnet-beta':
|
|
34
|
-
mainnet:
|
|
35
|
-
devnet:
|
|
36
|
-
testnet:
|
|
37
|
-
localnet:
|
|
39
|
+
'mainnet-beta': SOLANA_RPC_CONFIG.mainnet,
|
|
40
|
+
mainnet: SOLANA_RPC_CONFIG.mainnet,
|
|
41
|
+
devnet: SOLANA_RPC_CONFIG.devnet,
|
|
42
|
+
testnet: SOLANA_RPC_CONFIG.testnet,
|
|
43
|
+
localnet: SOLANA_RPC_CONFIG.localnet,
|
|
38
44
|
} as const
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -44,13 +50,14 @@ export type SolanaCluster = keyof typeof SOLANA_RPC_ENDPOINTS
|
|
|
44
50
|
|
|
45
51
|
/**
|
|
46
52
|
* Explorer URLs for transaction viewing
|
|
53
|
+
* Localnet is configurable via SOLANA_LOCALNET_EXPLORER environment variable
|
|
47
54
|
*/
|
|
48
55
|
export const SOLANA_EXPLORER_URLS = {
|
|
49
|
-
'mainnet-beta':
|
|
50
|
-
mainnet:
|
|
51
|
-
devnet:
|
|
52
|
-
testnet:
|
|
53
|
-
localnet:
|
|
56
|
+
'mainnet-beta': SOLANA_EXPLORER_CONFIG.mainnet,
|
|
57
|
+
mainnet: SOLANA_EXPLORER_CONFIG.mainnet,
|
|
58
|
+
devnet: `${SOLANA_EXPLORER_CONFIG.devnet}?cluster=devnet`,
|
|
59
|
+
testnet: `${SOLANA_EXPLORER_CONFIG.testnet}?cluster=testnet`,
|
|
60
|
+
localnet: SOLANA_EXPLORER_CONFIG.localnet,
|
|
54
61
|
} as const
|
|
55
62
|
|
|
56
63
|
/**
|
|
@@ -75,6 +82,43 @@ export const ESTIMATED_TX_FEE_LAMPORTS = 5000n
|
|
|
75
82
|
*/
|
|
76
83
|
export const ATA_RENT_LAMPORTS = 2039280n
|
|
77
84
|
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Named constants for magic numbers
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
/** Solana address minimum length (base58 encoded 32-byte public keys) */
|
|
90
|
+
export const SOLANA_ADDRESS_MIN_LENGTH = 32
|
|
91
|
+
|
|
92
|
+
/** Solana address maximum length (base58 encoded 32-byte public keys) */
|
|
93
|
+
export const SOLANA_ADDRESS_MAX_LENGTH = 44
|
|
94
|
+
|
|
95
|
+
/** View tag minimum value */
|
|
96
|
+
export const VIEW_TAG_MIN = 0
|
|
97
|
+
|
|
98
|
+
/** View tag maximum value (1 byte) */
|
|
99
|
+
export const VIEW_TAG_MAX = 255
|
|
100
|
+
|
|
101
|
+
/** Ed25519 key size in bytes */
|
|
102
|
+
export const ED25519_KEY_BYTES = 32
|
|
103
|
+
|
|
104
|
+
/** Ed25519 key hex length including '0x' prefix */
|
|
105
|
+
export const ED25519_KEY_HEX_LENGTH = 66
|
|
106
|
+
|
|
107
|
+
/** Default scan limit for pagination */
|
|
108
|
+
export const DEFAULT_SCAN_LIMIT = 100
|
|
109
|
+
|
|
110
|
+
/** Helius DAS API page limit */
|
|
111
|
+
export const HELIUS_DAS_PAGE_LIMIT = 1000
|
|
112
|
+
|
|
113
|
+
/** Helius maximum pages for pagination */
|
|
114
|
+
export const HELIUS_MAX_PAGES = 100
|
|
115
|
+
|
|
116
|
+
/** Helius API key minimum length */
|
|
117
|
+
export const HELIUS_API_KEY_MIN_LENGTH = 8
|
|
118
|
+
|
|
119
|
+
/** Webhook batch processing limit */
|
|
120
|
+
export const WEBHOOK_MAX_BATCH_SIZE = 100
|
|
121
|
+
|
|
78
122
|
/**
|
|
79
123
|
* Get explorer URL for a transaction
|
|
80
124
|
*/
|
|
@@ -94,8 +138,78 @@ export function getTokenMint(symbol: string): string | undefined {
|
|
|
94
138
|
}
|
|
95
139
|
|
|
96
140
|
/**
|
|
97
|
-
* Get token decimals from symbol
|
|
141
|
+
* Get token decimals from symbol (Solana-specific, legacy)
|
|
142
|
+
*
|
|
143
|
+
* @deprecated Use `getTokenDecimals(symbol, 'solana')` from the main SDK export
|
|
144
|
+
* which throws on unknown tokens instead of silently returning 9.
|
|
98
145
|
*/
|
|
99
|
-
export function
|
|
146
|
+
export function getSolanaTokenDecimals(symbol: string): number {
|
|
100
147
|
return SOLANA_TOKEN_DECIMALS[symbol] ?? 9 // Default to 9 (SOL decimals)
|
|
101
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sanitize a URL by masking potential credentials
|
|
152
|
+
*
|
|
153
|
+
* Removes or masks:
|
|
154
|
+
* - API keys in query parameters (api-key, apiKey, key, token, x-token)
|
|
155
|
+
* - Credentials in URL path (common for QuickNode, Triton)
|
|
156
|
+
* - Basic auth credentials (user:pass@host)
|
|
157
|
+
*
|
|
158
|
+
* @param url - URL string to sanitize
|
|
159
|
+
* @returns Sanitized URL safe for logging/error messages
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* sanitizeUrl('https://api.helius.xyz?api-key=secret123')
|
|
163
|
+
* // => 'https://api.helius.xyz?api-key=***'
|
|
164
|
+
*
|
|
165
|
+
* sanitizeUrl('https://example.quiknode.pro/abc123def456')
|
|
166
|
+
* // => 'https://example.quiknode.pro/***'
|
|
167
|
+
*/
|
|
168
|
+
export function sanitizeUrl(url: string): string {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = new URL(url)
|
|
171
|
+
|
|
172
|
+
// Remove basic auth credentials
|
|
173
|
+
if (parsed.username || parsed.password) {
|
|
174
|
+
parsed.username = '***'
|
|
175
|
+
parsed.password = ''
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Mask sensitive query parameters (case-insensitive)
|
|
179
|
+
const sensitivePatterns = ['api-key', 'apikey', 'api_key', 'key', 'token', 'x-token', 'xtoken', 'secret', 'auth']
|
|
180
|
+
const keysToMask: string[] = []
|
|
181
|
+
for (const [key] of parsed.searchParams) {
|
|
182
|
+
const keyLower = key.toLowerCase()
|
|
183
|
+
if (sensitivePatterns.some((pattern) => keyLower === pattern || keyLower.includes(pattern))) {
|
|
184
|
+
keysToMask.push(key)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const key of keysToMask) {
|
|
188
|
+
parsed.searchParams.set(key, '***')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Mask path segments that look like API keys/tokens
|
|
192
|
+
// QuickNode: /abc123def456 (32+ char alphanumeric)
|
|
193
|
+
// Triton: /x-token-value
|
|
194
|
+
const pathParts = parsed.pathname.split('/')
|
|
195
|
+
const maskedParts = pathParts.map((part) => {
|
|
196
|
+
// Skip empty parts and common path segments
|
|
197
|
+
if (!part || part.length < 16) return part
|
|
198
|
+
// If part looks like a token (long alphanumeric string), mask it
|
|
199
|
+
if (/^[a-zA-Z0-9_-]{16,}$/.test(part)) {
|
|
200
|
+
return '***'
|
|
201
|
+
}
|
|
202
|
+
return part
|
|
203
|
+
})
|
|
204
|
+
parsed.pathname = maskedParts.join('/')
|
|
205
|
+
|
|
206
|
+
return parsed.toString()
|
|
207
|
+
} catch {
|
|
208
|
+
// If URL parsing fails, do basic string sanitization
|
|
209
|
+
return url
|
|
210
|
+
.replace(/api-key=[^&]+/gi, 'api-key=***')
|
|
211
|
+
.replace(/apikey=[^&]+/gi, 'apikey=***')
|
|
212
|
+
.replace(/token=[^&]+/gi, 'token=***')
|
|
213
|
+
.replace(/\/[a-zA-Z0-9_-]{16,}(\/|$)/g, '/***$1')
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solana Ephemeral Keypair Management
|
|
3
|
+
*
|
|
4
|
+
* Provides secure ephemeral keypair generation and management for stealth
|
|
5
|
+
* payments. Each stealth transfer requires a fresh ephemeral keypair.
|
|
6
|
+
*
|
|
7
|
+
* Security considerations:
|
|
8
|
+
* - Ephemeral private keys are wiped from memory after use
|
|
9
|
+
* - Keys are never persisted to storage
|
|
10
|
+
* - Batch generation uses cryptographically secure randomness
|
|
11
|
+
*
|
|
12
|
+
* @module chains/solana/ephemeral-keys
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
16
|
+
import { randomBytes, bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
17
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
18
|
+
import { secureWipe } from '../../secure-memory'
|
|
19
|
+
import { ed25519PublicKeyToSolanaAddress } from '../../stealth'
|
|
20
|
+
import type { HexString } from '@sip-protocol/types'
|
|
21
|
+
|
|
22
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* An ephemeral keypair for stealth transfers
|
|
26
|
+
*/
|
|
27
|
+
export interface EphemeralKeypair {
|
|
28
|
+
/**
|
|
29
|
+
* Ephemeral private key (hex)
|
|
30
|
+
* @security SENSITIVE - must be wiped after shared secret computation
|
|
31
|
+
*/
|
|
32
|
+
privateKey: HexString
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ephemeral public key (hex, ed25519 format)
|
|
36
|
+
*/
|
|
37
|
+
publicKey: HexString
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ephemeral public key in Solana base58 format
|
|
41
|
+
*/
|
|
42
|
+
publicKeyBase58: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Result of using an ephemeral keypair for stealth address generation
|
|
47
|
+
*/
|
|
48
|
+
export interface EphemeralKeyUsageResult {
|
|
49
|
+
/**
|
|
50
|
+
* Shared secret derived from ECDH
|
|
51
|
+
*/
|
|
52
|
+
sharedSecret: HexString
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* View tag (first byte of shared secret hash)
|
|
56
|
+
*/
|
|
57
|
+
viewTag: number
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stealth address (hex, ed25519 format)
|
|
61
|
+
*/
|
|
62
|
+
stealthAddress: HexString
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Stealth address in Solana base58 format
|
|
66
|
+
*/
|
|
67
|
+
stealthAddressBase58: string
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Ephemeral public key used (for announcement)
|
|
71
|
+
*/
|
|
72
|
+
ephemeralPublicKey: HexString
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Ephemeral public key in Solana base58 format
|
|
76
|
+
*/
|
|
77
|
+
ephemeralPublicKeyBase58: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Managed ephemeral keypair with automatic secure disposal
|
|
82
|
+
*/
|
|
83
|
+
export interface ManagedEphemeralKeypair extends EphemeralKeypair {
|
|
84
|
+
/**
|
|
85
|
+
* Whether this keypair has been used (and thus disposed)
|
|
86
|
+
*/
|
|
87
|
+
isDisposed: boolean
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Securely dispose of this keypair
|
|
91
|
+
* Called automatically after use, but can be called manually
|
|
92
|
+
*/
|
|
93
|
+
dispose(): void
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Use this keypair to generate a stealth address
|
|
97
|
+
* Automatically disposes the keypair after use
|
|
98
|
+
*/
|
|
99
|
+
useForStealthAddress(
|
|
100
|
+
recipientSpendingKey: HexString,
|
|
101
|
+
recipientViewingKey: HexString
|
|
102
|
+
): EphemeralKeyUsageResult
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Options for batch ephemeral key generation
|
|
107
|
+
*/
|
|
108
|
+
export interface BatchGenerationOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Number of keypairs to generate
|
|
111
|
+
*/
|
|
112
|
+
count: number
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether to add entropy mixing between generations
|
|
116
|
+
* @default true
|
|
117
|
+
*/
|
|
118
|
+
entropyMixing?: boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Core Generation ──────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generate a single ephemeral keypair
|
|
125
|
+
*
|
|
126
|
+
* @returns Fresh ephemeral keypair
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const ephemeral = generateEphemeralKeypair()
|
|
131
|
+
* console.log('Public key:', ephemeral.publicKeyBase58)
|
|
132
|
+
* // Use for stealth transfer, then dispose
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function generateEphemeralKeypair(): EphemeralKeypair {
|
|
136
|
+
const privateKeyBytes = randomBytes(32)
|
|
137
|
+
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes)
|
|
138
|
+
|
|
139
|
+
const privateKey = `0x${bytesToHex(privateKeyBytes)}` as HexString
|
|
140
|
+
const publicKey = `0x${bytesToHex(publicKeyBytes)}` as HexString
|
|
141
|
+
const publicKeyBase58 = ed25519PublicKeyToSolanaAddress(publicKey)
|
|
142
|
+
|
|
143
|
+
// Wipe the raw bytes (caller owns the hex string now)
|
|
144
|
+
secureWipe(privateKeyBytes)
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
privateKey,
|
|
148
|
+
publicKey,
|
|
149
|
+
publicKeyBase58,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generate a managed ephemeral keypair with automatic disposal
|
|
155
|
+
*
|
|
156
|
+
* The managed keypair tracks its usage state and automatically wipes
|
|
157
|
+
* the private key from memory after use.
|
|
158
|
+
*
|
|
159
|
+
* @returns Managed ephemeral keypair
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* const managed = generateManagedEphemeralKeypair()
|
|
164
|
+
*
|
|
165
|
+
* // Use for stealth address generation (auto-disposes)
|
|
166
|
+
* const result = managed.useForStealthAddress(
|
|
167
|
+
* recipientSpendingKey,
|
|
168
|
+
* recipientViewingKey
|
|
169
|
+
* )
|
|
170
|
+
*
|
|
171
|
+
* console.log('Stealth address:', result.stealthAddressBase58)
|
|
172
|
+
* console.log('Is disposed:', managed.isDisposed) // true
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export function generateManagedEphemeralKeypair(): ManagedEphemeralKeypair {
|
|
176
|
+
// Store private key bytes for secure disposal
|
|
177
|
+
const privateKeyBytes = randomBytes(32)
|
|
178
|
+
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes)
|
|
179
|
+
|
|
180
|
+
const privateKeyHex = `0x${bytesToHex(privateKeyBytes)}` as HexString
|
|
181
|
+
const publicKeyHex = `0x${bytesToHex(publicKeyBytes)}` as HexString
|
|
182
|
+
const publicKeyBase58 = ed25519PublicKeyToSolanaAddress(publicKeyHex)
|
|
183
|
+
|
|
184
|
+
let disposed = false
|
|
185
|
+
|
|
186
|
+
const managed: ManagedEphemeralKeypair = {
|
|
187
|
+
get privateKey(): HexString {
|
|
188
|
+
if (disposed) {
|
|
189
|
+
throw new Error('Ephemeral keypair has been disposed')
|
|
190
|
+
}
|
|
191
|
+
return privateKeyHex
|
|
192
|
+
},
|
|
193
|
+
publicKey: publicKeyHex,
|
|
194
|
+
publicKeyBase58,
|
|
195
|
+
|
|
196
|
+
get isDisposed(): boolean {
|
|
197
|
+
return disposed
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
dispose(): void {
|
|
201
|
+
if (!disposed) {
|
|
202
|
+
secureWipe(privateKeyBytes)
|
|
203
|
+
disposed = true
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
useForStealthAddress(
|
|
208
|
+
recipientSpendingKey: HexString,
|
|
209
|
+
recipientViewingKey: HexString
|
|
210
|
+
): EphemeralKeyUsageResult {
|
|
211
|
+
if (disposed) {
|
|
212
|
+
throw new Error('Ephemeral keypair has been disposed')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const result = computeStealthAddress(
|
|
217
|
+
privateKeyBytes,
|
|
218
|
+
publicKeyBytes,
|
|
219
|
+
recipientSpendingKey,
|
|
220
|
+
recipientViewingKey
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
...result,
|
|
225
|
+
ephemeralPublicKey: publicKeyHex,
|
|
226
|
+
ephemeralPublicKeyBase58: publicKeyBase58,
|
|
227
|
+
}
|
|
228
|
+
} finally {
|
|
229
|
+
// Always dispose after use
|
|
230
|
+
managed.dispose()
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return managed
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Generate multiple ephemeral keypairs in batch
|
|
240
|
+
*
|
|
241
|
+
* Useful for preparing keypairs for multiple transfers or for
|
|
242
|
+
* pre-generating keypairs for performance.
|
|
243
|
+
*
|
|
244
|
+
* @param options - Batch generation options
|
|
245
|
+
* @returns Array of ephemeral keypairs
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* // Generate 10 keypairs for upcoming transfers
|
|
250
|
+
* const keypairs = batchGenerateEphemeralKeypairs({ count: 10 })
|
|
251
|
+
*
|
|
252
|
+
* // Use each keypair for a transfer
|
|
253
|
+
* for (const keypair of keypairs) {
|
|
254
|
+
* await sendPrivateTransfer({ ephemeralKeypair: keypair, ... })
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export function batchGenerateEphemeralKeypairs(
|
|
259
|
+
options: BatchGenerationOptions
|
|
260
|
+
): EphemeralKeypair[] {
|
|
261
|
+
const { count, entropyMixing = true } = options
|
|
262
|
+
|
|
263
|
+
if (count <= 0 || !Number.isInteger(count)) {
|
|
264
|
+
throw new Error('count must be a positive integer')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (count > 1000) {
|
|
268
|
+
throw new Error('count cannot exceed 1000 (security limit)')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const keypairs: EphemeralKeypair[] = []
|
|
272
|
+
|
|
273
|
+
// For entropy mixing, we hash extra randomness into each generation
|
|
274
|
+
let entropyState: Uint8Array | null = entropyMixing ? randomBytes(32) : null
|
|
275
|
+
|
|
276
|
+
for (let i = 0; i < count; i++) {
|
|
277
|
+
if (entropyMixing && entropyState) {
|
|
278
|
+
// Mix additional entropy for each keypair
|
|
279
|
+
const extraEntropy = randomBytes(32)
|
|
280
|
+
entropyState = sha256(new Uint8Array([...entropyState, ...extraEntropy]))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
keypairs.push(generateEphemeralKeypair())
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Wipe entropy state
|
|
287
|
+
if (entropyState) {
|
|
288
|
+
secureWipe(entropyState)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return keypairs
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Generate multiple managed ephemeral keypairs in batch
|
|
296
|
+
*
|
|
297
|
+
* @param options - Batch generation options
|
|
298
|
+
* @returns Array of managed ephemeral keypairs
|
|
299
|
+
*/
|
|
300
|
+
export function batchGenerateManagedEphemeralKeypairs(
|
|
301
|
+
options: BatchGenerationOptions
|
|
302
|
+
): ManagedEphemeralKeypair[] {
|
|
303
|
+
const { count, entropyMixing = true } = options
|
|
304
|
+
|
|
305
|
+
if (count <= 0 || !Number.isInteger(count)) {
|
|
306
|
+
throw new Error('count must be a positive integer')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (count > 1000) {
|
|
310
|
+
throw new Error('count cannot exceed 1000 (security limit)')
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const keypairs: ManagedEphemeralKeypair[] = []
|
|
314
|
+
|
|
315
|
+
// For entropy mixing, we hash extra randomness into each generation
|
|
316
|
+
let entropyState: Uint8Array | null = entropyMixing ? randomBytes(32) : null
|
|
317
|
+
|
|
318
|
+
for (let i = 0; i < count; i++) {
|
|
319
|
+
if (entropyMixing && entropyState) {
|
|
320
|
+
// Mix additional entropy for each keypair
|
|
321
|
+
const extraEntropy = randomBytes(32)
|
|
322
|
+
entropyState = sha256(new Uint8Array([...entropyState, ...extraEntropy]))
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
keypairs.push(generateManagedEphemeralKeypair())
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Wipe entropy state
|
|
329
|
+
if (entropyState) {
|
|
330
|
+
secureWipe(entropyState)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return keypairs
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ─── Disposal Utilities ───────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Dispose multiple ephemeral keypairs at once
|
|
340
|
+
*
|
|
341
|
+
* @param keypairs - Array of managed keypairs to dispose
|
|
342
|
+
*/
|
|
343
|
+
export function disposeEphemeralKeypairs(
|
|
344
|
+
keypairs: ManagedEphemeralKeypair[]
|
|
345
|
+
): void {
|
|
346
|
+
for (const keypair of keypairs) {
|
|
347
|
+
keypair.dispose()
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Securely wipe an ephemeral private key string from memory
|
|
353
|
+
*
|
|
354
|
+
* Note: Due to JavaScript string immutability, this creates a new
|
|
355
|
+
* Uint8Array from the hex string and wipes it. For better security,
|
|
356
|
+
* use ManagedEphemeralKeypair which maintains byte access.
|
|
357
|
+
*
|
|
358
|
+
* @param privateKeyHex - Private key hex string to wipe
|
|
359
|
+
*/
|
|
360
|
+
export function wipeEphemeralPrivateKey(privateKeyHex: HexString): void {
|
|
361
|
+
// Convert hex to bytes and wipe the bytes
|
|
362
|
+
const bytes = hexToBytes(privateKeyHex.slice(2))
|
|
363
|
+
secureWipe(bytes)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* ed25519 group order (L)
|
|
370
|
+
*/
|
|
371
|
+
const ED25519_ORDER = 2n ** 252n + 27742317777372353535851937790883648493n
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Convert bytes to bigint (little-endian for ed25519)
|
|
375
|
+
*/
|
|
376
|
+
function bytesToBigIntLE(bytes: Uint8Array): bigint {
|
|
377
|
+
let result = 0n
|
|
378
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
379
|
+
result = (result << 8n) | BigInt(bytes[i])
|
|
380
|
+
}
|
|
381
|
+
return result
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get ed25519 scalar from private key bytes
|
|
386
|
+
* Follows standard ed25519 scalar clamping
|
|
387
|
+
*/
|
|
388
|
+
function getEd25519Scalar(privateKey: Uint8Array): bigint {
|
|
389
|
+
const hash = sha256(privateKey)
|
|
390
|
+
// Clamp to valid scalar
|
|
391
|
+
hash[0] &= 248
|
|
392
|
+
hash[31] &= 127
|
|
393
|
+
hash[31] |= 64
|
|
394
|
+
return bytesToBigIntLE(hash.slice(0, 32))
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Compute stealth address from ephemeral keypair and recipient keys
|
|
399
|
+
*/
|
|
400
|
+
function computeStealthAddress(
|
|
401
|
+
ephemeralPrivateBytes: Uint8Array,
|
|
402
|
+
_ephemeralPublicBytes: Uint8Array, // Reserved for future validation
|
|
403
|
+
recipientSpendingKey: HexString,
|
|
404
|
+
recipientViewingKey: HexString
|
|
405
|
+
): Omit<EphemeralKeyUsageResult, 'ephemeralPublicKey' | 'ephemeralPublicKeyBase58'> {
|
|
406
|
+
// Parse recipient keys
|
|
407
|
+
const spendingKeyBytes = hexToBytes(recipientSpendingKey.slice(2))
|
|
408
|
+
const viewingKeyBytes = hexToBytes(recipientViewingKey.slice(2))
|
|
409
|
+
|
|
410
|
+
// Get ephemeral scalar
|
|
411
|
+
const rawEphemeralScalar = getEd25519Scalar(ephemeralPrivateBytes)
|
|
412
|
+
const ephemeralScalar = rawEphemeralScalar % ED25519_ORDER
|
|
413
|
+
if (ephemeralScalar === 0n) {
|
|
414
|
+
throw new Error('Invalid ephemeral scalar')
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Compute shared secret: S = ephemeral_scalar * P_spend
|
|
418
|
+
const spendingPoint = ed25519.ExtendedPoint.fromHex(spendingKeyBytes)
|
|
419
|
+
const sharedSecretPoint = spendingPoint.multiply(ephemeralScalar)
|
|
420
|
+
const sharedSecretBytes = sharedSecretPoint.toRawBytes()
|
|
421
|
+
|
|
422
|
+
// Hash the shared secret
|
|
423
|
+
const sharedSecretHash = sha256(sharedSecretBytes)
|
|
424
|
+
const viewTag = sharedSecretHash[0]
|
|
425
|
+
|
|
426
|
+
// Derive stealth address: P_stealth = P_view + hash(S)*G
|
|
427
|
+
const hashScalar = bytesToBigIntLE(sharedSecretHash) % ED25519_ORDER
|
|
428
|
+
if (hashScalar === 0n) {
|
|
429
|
+
throw new Error('Invalid hash scalar')
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const hashTimesG = ed25519.ExtendedPoint.BASE.multiply(hashScalar)
|
|
433
|
+
const viewingPoint = ed25519.ExtendedPoint.fromHex(viewingKeyBytes)
|
|
434
|
+
const stealthPoint = viewingPoint.add(hashTimesG)
|
|
435
|
+
const stealthAddressBytes = stealthPoint.toRawBytes()
|
|
436
|
+
|
|
437
|
+
const stealthAddress = `0x${bytesToHex(stealthAddressBytes)}` as HexString
|
|
438
|
+
const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress)
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
|
|
442
|
+
viewTag,
|
|
443
|
+
stealthAddress,
|
|
444
|
+
stealthAddressBase58,
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─── Announcement Format ──────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Format ephemeral key data for Solana memo announcement
|
|
452
|
+
*
|
|
453
|
+
* @param ephemeralPublicKeyBase58 - Ephemeral public key in base58
|
|
454
|
+
* @param viewTag - View tag (0-255)
|
|
455
|
+
* @param stealthAddressBase58 - Optional stealth address for verification
|
|
456
|
+
* @returns Formatted announcement string
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* const memo = formatEphemeralAnnouncement(
|
|
461
|
+
* result.ephemeralPublicKeyBase58,
|
|
462
|
+
* result.viewTag,
|
|
463
|
+
* result.stealthAddressBase58
|
|
464
|
+
* )
|
|
465
|
+
* // "SIP:1:7xK9...:0a:8yL0..."
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
export function formatEphemeralAnnouncement(
|
|
469
|
+
ephemeralPublicKeyBase58: string,
|
|
470
|
+
viewTag: number,
|
|
471
|
+
stealthAddressBase58?: string
|
|
472
|
+
): string {
|
|
473
|
+
const viewTagHex = viewTag.toString(16).padStart(2, '0')
|
|
474
|
+
const parts = ['SIP:1', ephemeralPublicKeyBase58, viewTagHex]
|
|
475
|
+
|
|
476
|
+
if (stealthAddressBase58) {
|
|
477
|
+
parts.push(stealthAddressBase58)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return parts.join(':')
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Parse ephemeral key data from Solana memo announcement
|
|
485
|
+
*
|
|
486
|
+
* @param announcement - Announcement string from memo
|
|
487
|
+
* @returns Parsed ephemeral data or null if invalid
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* const parsed = parseEphemeralAnnouncement('SIP:1:7xK9...:0a:8yL0...')
|
|
492
|
+
* if (parsed) {
|
|
493
|
+
* console.log('Ephemeral key:', parsed.ephemeralPublicKeyBase58)
|
|
494
|
+
* console.log('View tag:', parsed.viewTag)
|
|
495
|
+
* }
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
export function parseEphemeralAnnouncement(
|
|
499
|
+
announcement: string
|
|
500
|
+
): {
|
|
501
|
+
ephemeralPublicKeyBase58: string
|
|
502
|
+
viewTag: number
|
|
503
|
+
stealthAddressBase58?: string
|
|
504
|
+
} | null {
|
|
505
|
+
if (!announcement.startsWith('SIP:1:')) {
|
|
506
|
+
return null
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const parts = announcement.slice(6).split(':')
|
|
510
|
+
if (parts.length < 2) {
|
|
511
|
+
return null
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const ephemeralPublicKeyBase58 = parts[0]
|
|
515
|
+
const viewTagHex = parts[1]
|
|
516
|
+
const stealthAddressBase58 = parts[2]
|
|
517
|
+
|
|
518
|
+
// Validate ephemeral key (base58, 32-44 chars)
|
|
519
|
+
if (!ephemeralPublicKeyBase58 || ephemeralPublicKeyBase58.length < 32 || ephemeralPublicKeyBase58.length > 44) {
|
|
520
|
+
return null
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Validate view tag (1-2 hex chars)
|
|
524
|
+
if (!viewTagHex || viewTagHex.length > 2 || !/^[0-9a-fA-F]+$/.test(viewTagHex)) {
|
|
525
|
+
return null
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const viewTag = parseInt(viewTagHex, 16)
|
|
529
|
+
if (viewTag < 0 || viewTag > 255) {
|
|
530
|
+
return null
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Validate stealth address if present
|
|
534
|
+
if (stealthAddressBase58 && (stealthAddressBase58.length < 32 || stealthAddressBase58.length > 44)) {
|
|
535
|
+
return null
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
ephemeralPublicKeyBase58,
|
|
540
|
+
viewTag,
|
|
541
|
+
stealthAddressBase58,
|
|
542
|
+
}
|
|
543
|
+
}
|