@sip-protocol/sdk 0.5.1 → 0.6.1

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 (76) hide show
  1. package/README.md +58 -0
  2. package/dist/TransportWebUSB-TQ7WZ4LE.mjs +3098 -0
  3. package/dist/browser.d.mts +4 -4
  4. package/dist/browser.d.ts +4 -4
  5. package/dist/browser.js +10133 -4664
  6. package/dist/browser.mjs +32 -1
  7. package/dist/chunk-7QZPORY5.mjs +15604 -0
  8. package/dist/chunk-C2NPCUAJ.mjs +17010 -0
  9. package/dist/chunk-FCVLFUIC.mjs +16699 -0
  10. package/dist/chunk-G5UHXECN.mjs +16340 -0
  11. package/dist/chunk-GEDEIZHJ.mjs +16798 -0
  12. package/dist/chunk-KBS3OMSZ.mjs +14737 -0
  13. package/dist/chunk-MTNYSNR7.mjs +16269 -0
  14. package/dist/chunk-O5PIB2EA.mjs +16698 -0
  15. package/dist/chunk-PCFM7FQO.mjs +17010 -0
  16. package/dist/chunk-QK464ARC.mjs +16946 -0
  17. package/dist/chunk-TK3FWQNC.mjs +14737 -0
  18. package/dist/chunk-UJCSKKID.mjs +30 -0
  19. package/dist/chunk-VNBMNGC3.mjs +16698 -0
  20. package/dist/chunk-W5TUELDQ.mjs +16947 -0
  21. package/dist/index-05W_S8A7.d.mts +9237 -0
  22. package/dist/index-C5ehlFhR.d.mts +9443 -0
  23. package/dist/index-CD_zShu-.d.ts +10870 -0
  24. package/dist/index-CNzhx-WH.d.mts +9316 -0
  25. package/dist/index-CQBYdLYy.d.mts +10976 -0
  26. package/dist/index-Cg9TYEPv.d.mts +11321 -0
  27. package/dist/index-CqSppS4i.d.ts +9237 -0
  28. package/dist/index-CqZJOO8C.d.mts +11323 -0
  29. package/dist/index-CywN9Bnp.d.ts +11321 -0
  30. package/dist/index-DBa_jiZF.d.mts +9606 -0
  31. package/dist/index-DHy5ZjCD.d.ts +10976 -0
  32. package/dist/index-DLNdSQFQ.d.ts +9316 -0
  33. package/dist/index-DfsVsmxu.d.ts +11323 -0
  34. package/dist/index-Ink8HnKW.d.ts +9606 -0
  35. package/dist/index-ObjwyVDX.d.mts +10870 -0
  36. package/dist/index-h7B23m5b.d.ts +9443 -0
  37. package/dist/index-m0xbSfmT.d.mts +11318 -0
  38. package/dist/index-rWLEgvhN.d.ts +11318 -0
  39. package/dist/index.d.mts +3 -3
  40. package/dist/index.d.ts +3 -3
  41. package/dist/index.js +10112 -4637
  42. package/dist/index.mjs +32 -1
  43. package/dist/noir-DKfEzWy9.d.mts +482 -0
  44. package/dist/noir-DKfEzWy9.d.ts +482 -0
  45. package/dist/proofs/noir.d.mts +1 -1
  46. package/dist/proofs/noir.d.ts +1 -1
  47. package/dist/proofs/noir.js +12 -3
  48. package/dist/proofs/noir.mjs +13 -3
  49. package/package.json +5 -3
  50. package/src/adapters/near-intents.ts +13 -3
  51. package/src/auction/index.ts +20 -0
  52. package/src/auction/sealed-bid.ts +1037 -0
  53. package/src/compliance/derivation.ts +13 -3
  54. package/src/compliance/reports.ts +5 -4
  55. package/src/cosmos/ibc-stealth.ts +2 -2
  56. package/src/cosmos/stealth.ts +2 -2
  57. package/src/crypto.ts +79 -9
  58. package/src/governance/index.ts +19 -0
  59. package/src/governance/private-vote.ts +1116 -0
  60. package/src/index.ts +50 -2
  61. package/src/intent.ts +227 -12
  62. package/src/nft/index.ts +27 -0
  63. package/src/nft/private-nft.ts +811 -0
  64. package/src/privacy.ts +88 -2
  65. package/src/proofs/browser-utils.ts +1 -7
  66. package/src/proofs/noir.ts +34 -7
  67. package/src/settlement/backends/direct-chain.ts +14 -3
  68. package/src/sip.ts +324 -25
  69. package/src/stealth.ts +120 -9
  70. package/src/types/browser.d.ts +67 -0
  71. package/src/validation.ts +4 -2
  72. package/src/wallet/bitcoin/adapter.ts +159 -15
  73. package/src/wallet/bitcoin/types.ts +340 -15
  74. package/src/wallet/cosmos/mock.ts +16 -12
  75. package/src/wallet/hardware/ledger.ts +83 -14
  76. package/src/wallet/hardware/types.ts +2 -0
package/src/stealth.ts CHANGED
@@ -36,12 +36,66 @@ import {
36
36
  import { secureWipe, secureWipeAll } from './secure-memory'
37
37
 
38
38
  /**
39
- * Generate a new stealth meta-address keypair
39
+ * Generate a new stealth meta-address keypair for receiving private payments
40
40
  *
41
- * @param chain - Target chain for the addresses
42
- * @param label - Optional human-readable label
43
- * @returns Stealth meta-address and private keys
44
- * @throws {ValidationError} If chain is invalid
41
+ * Creates a reusable meta-address that senders can use to derive one-time stealth
42
+ * addresses. The recipient publishes the meta-address publicly, and senders generate
43
+ * unique payment addresses from it.
44
+ *
45
+ * **Security:** Private keys must be stored securely and never shared. The meta-address
46
+ * (containing only public keys) can be safely published.
47
+ *
48
+ * **Algorithm:** Uses secp256k1 elliptic curve (EIP-5564 style) for:
49
+ * - Ethereum, Polygon, Arbitrum, Optimism, Base, Bitcoin, Zcash
50
+ *
51
+ * For Solana/NEAR/Aptos/Sui chains, use {@link generateEd25519StealthMetaAddress} instead.
52
+ *
53
+ * @param chain - Target blockchain network (determines address format)
54
+ * @param label - Optional human-readable label for identification
55
+ * @returns Object containing:
56
+ * - `metaAddress`: Public keys to share with senders
57
+ * - `spendingPrivateKey`: Secret key for claiming funds (keep secure!)
58
+ * - `viewingPrivateKey`: Secret key for scanning incoming payments (keep secure!)
59
+ *
60
+ * @throws {ValidationError} If chain is invalid or not supported
61
+ *
62
+ * @example Generate stealth keys for Ethereum
63
+ * ```typescript
64
+ * import { generateStealthMetaAddress, encodeStealthMetaAddress } from '@sip-protocol/sdk'
65
+ *
66
+ * // Generate keys
67
+ * const { metaAddress, spendingPrivateKey, viewingPrivateKey } =
68
+ * generateStealthMetaAddress('ethereum', 'My Privacy Wallet')
69
+ *
70
+ * // Encode for sharing (QR code, website, etc.)
71
+ * const encoded = encodeStealthMetaAddress(metaAddress)
72
+ * console.log('Share this:', encoded)
73
+ * // Output: "sip:ethereum:0x02abc...123:0x03def...456"
74
+ *
75
+ * // Store private keys securely (e.g., encrypted keystore)
76
+ * secureStorage.save({
77
+ * spendingPrivateKey,
78
+ * viewingPrivateKey,
79
+ * })
80
+ * ```
81
+ *
82
+ * @example Multi-chain setup
83
+ * ```typescript
84
+ * // Generate different stealth keys for each chain
85
+ * const ethKeys = generateStealthMetaAddress('ethereum', 'ETH Privacy')
86
+ * const zkKeys = generateStealthMetaAddress('zcash', 'ZEC Privacy')
87
+ *
88
+ * // Publish meta-addresses
89
+ * publishToProfile({
90
+ * ethereum: encodeStealthMetaAddress(ethKeys.metaAddress),
91
+ * zcash: encodeStealthMetaAddress(zkKeys.metaAddress),
92
+ * })
93
+ * ```
94
+ *
95
+ * @see {@link generateStealthAddress} to generate payment addresses as a sender
96
+ * @see {@link encodeStealthMetaAddress} to encode for sharing
97
+ * @see {@link deriveStealthPrivateKey} to claim funds as a recipient
98
+ * @see {@link generateEd25519StealthMetaAddress} for Solana/NEAR chains
45
99
  */
46
100
  export function generateStealthMetaAddress(
47
101
  chain: ChainId,
@@ -125,11 +179,68 @@ function validateStealthMetaAddress(
125
179
  }
126
180
 
127
181
  /**
128
- * Generate a one-time stealth address for a recipient
182
+ * Generate a one-time stealth address for sending funds to a recipient
129
183
  *
130
- * @param recipientMetaAddress - Recipient's published stealth meta-address
131
- * @returns Stealth address data (address + ephemeral key for publication)
132
- * @throws {ValidationError} If recipientMetaAddress is invalid
184
+ * As a sender, use this function to create a unique, unlinkable payment address
185
+ * from the recipient's public meta-address. Each call generates a new address
186
+ * that only the recipient can link to their identity.
187
+ *
188
+ * **Privacy Properties:**
189
+ * - Address is unique per transaction (prevents on-chain linkability)
190
+ * - Only recipient can detect and claim payments
191
+ * - Third-party observers cannot link payments to the same recipient
192
+ * - View tag enables efficient payment scanning
193
+ *
194
+ * **Algorithm (EIP-5564 DKSAP):**
195
+ * 1. Generate ephemeral keypair (r, R = r*G)
196
+ * 2. Compute shared secret: S = r * P_spend
197
+ * 3. Derive stealth address: A = P_view + hash(S)*G
198
+ * 4. Publish (R, A) on-chain; keep r secret
199
+ *
200
+ * @param recipientMetaAddress - Recipient's public stealth meta-address
201
+ * @returns Object containing:
202
+ * - `stealthAddress`: One-time payment address to publish on-chain
203
+ * - `sharedSecret`: Secret for sender's records (optional, don't publish!)
204
+ *
205
+ * @throws {ValidationError} If meta-address is invalid or malformed
206
+ *
207
+ * @example Send shielded payment
208
+ * ```typescript
209
+ * import { generateStealthAddress, decodeStealthMetaAddress } from '@sip-protocol/sdk'
210
+ *
211
+ * // Recipient shares their meta-address (e.g., on website, profile)
212
+ * const recipientMetaAddr = 'sip:ethereum:0x02abc...123:0x03def...456'
213
+ *
214
+ * // Decode the meta-address
215
+ * const metaAddress = decodeStealthMetaAddress(recipientMetaAddr)
216
+ *
217
+ * // Generate one-time payment address
218
+ * const { stealthAddress } = generateStealthAddress(metaAddress)
219
+ *
220
+ * // Use the stealth address in your transaction
221
+ * await sendPayment({
222
+ * to: stealthAddress.address, // One-time address
223
+ * amount: '1000000000000000000', // 1 ETH
224
+ * ephemeralKey: stealthAddress.ephemeralPublicKey, // Publish for recipient
225
+ * viewTag: stealthAddress.viewTag, // For efficient scanning
226
+ * })
227
+ * ```
228
+ *
229
+ * @example Integrate with SIP intent
230
+ * ```typescript
231
+ * // In a shielded intent, the recipient stealth address is generated automatically
232
+ * const intent = await sip.createIntent({
233
+ * input: { asset: { chain: 'solana', symbol: 'SOL', address: null, decimals: 9 }, amount: 10n },
234
+ * output: { asset: { chain: 'ethereum', symbol: 'ETH', address: null, decimals: 18 }, minAmount: 0n, maxSlippage: 0.01 },
235
+ * privacy: PrivacyLevel.SHIELDED,
236
+ * recipientMetaAddress: 'sip:ethereum:0x02abc...123:0x03def...456',
237
+ * })
238
+ * // intent.recipientStealth contains the generated stealth address
239
+ * ```
240
+ *
241
+ * @see {@link generateStealthMetaAddress} to create meta-address as recipient
242
+ * @see {@link deriveStealthPrivateKey} for recipient to claim funds
243
+ * @see {@link checkStealthAddress} to scan for incoming payments
133
244
  */
134
245
  export function generateStealthAddress(
135
246
  recipientMetaAddress: StealthMetaAddress,
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Type declarations for non-standard browser APIs
3
+ *
4
+ * These declarations extend the standard DOM types to include
5
+ * browser-specific APIs used for device capability detection.
6
+ *
7
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory
8
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measureUserAgentSpecificMemory
9
+ */
10
+
11
+ /**
12
+ * Extend Navigator interface with non-standard properties
13
+ */
14
+ interface Navigator {
15
+ /**
16
+ * Device memory in gigabytes (rounded to preserve privacy)
17
+ *
18
+ * Only available in Chrome, Edge, Opera (Chromium-based browsers)
19
+ * Returns values like: 0.25, 0.5, 1, 2, 4, 8
20
+ *
21
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory
22
+ */
23
+ readonly deviceMemory?: number
24
+ }
25
+
26
+ /**
27
+ * Memory measurement result from Chrome-specific API
28
+ */
29
+ interface MemoryMeasurement {
30
+ /** Total bytes used */
31
+ bytes: number
32
+ /** Breakdown of memory usage by type */
33
+ breakdown: Array<{
34
+ bytes: number
35
+ types: string[]
36
+ attribution?: Array<{
37
+ url: string
38
+ scope: string
39
+ }>
40
+ }>
41
+ }
42
+
43
+ /**
44
+ * Extend Performance interface with Chrome-specific APIs
45
+ */
46
+ interface Performance {
47
+ /**
48
+ * Measures memory usage with breakdown by type
49
+ *
50
+ * Chrome-specific API for detailed memory profiling
51
+ * Requires cross-origin isolation headers in some contexts
52
+ *
53
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measureUserAgentSpecificMemory
54
+ */
55
+ measureUserAgentSpecificMemory?(): Promise<MemoryMeasurement>
56
+ }
57
+
58
+ /**
59
+ * Extend Window interface with vendor-prefixed APIs
60
+ */
61
+ interface Window {
62
+ /**
63
+ * Safari's webkit-prefixed AudioContext
64
+ * Used for audio fingerprinting fallback
65
+ */
66
+ webkitAudioContext?: typeof AudioContext
67
+ }
package/src/validation.ts CHANGED
@@ -118,9 +118,11 @@ export function isValidSlippage(value: number): boolean {
118
118
  /**
119
119
  * SIP stealth meta-address format:
120
120
  * sip:<chain>:<spendingKey>:<viewingKey>
121
- * Each key is 33 bytes compressed (66 hex chars with 0x prefix)
121
+ * Keys can be:
122
+ * - secp256k1: 33 bytes compressed (66 hex chars with 0x prefix)
123
+ * - ed25519: 32 bytes (64 hex chars with 0x prefix)
122
124
  */
123
- const STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{66}:0x[0-9a-fA-F]{66}$/
125
+ const STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{64,66}:0x[0-9a-fA-F]{64,66}$/
124
126
 
125
127
  /**
126
128
  * Check if a string is a valid stealth meta-address
@@ -271,18 +271,33 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
271
271
  }
272
272
 
273
273
  try {
274
- // First, sign the transaction
275
- const signed = await this.signTransaction(tx)
276
-
277
- // Extract the signed PSBT hex (without 0x prefix)
278
- const signedPsbt = signed.serialized.slice(2)
274
+ // Extract PSBT from transaction data
275
+ const psbtHex = tx.data as string
276
+ const options = tx.metadata?.signPsbtOptions as SignPsbtOptions | undefined
279
277
 
280
- // If PSBT is not finalized, we need to finalize it
281
- // For now, assume it's finalized (autoFinalized: true in options)
282
- // TODO: Add PSBT finalization logic here
278
+ // Sign with autoFinalized: true to get a finalized PSBT ready for broadcast
279
+ const signedPsbt = await this.provider.signPsbt(psbtHex, {
280
+ ...options,
281
+ autoFinalized: true, // Request wallet to finalize the PSBT
282
+ })
283
283
 
284
- // Broadcast the transaction
285
- const txid = await this.provider.pushTx(signedPsbt)
284
+ // Try to broadcast the finalized transaction
285
+ let txid: string
286
+
287
+ try {
288
+ // First, try to push the signed PSBT directly
289
+ // Some wallets return a raw transaction after finalization
290
+ txid = await this.provider.pushTx(signedPsbt)
291
+ } catch (pushError) {
292
+ // If pushTx fails (e.g., Xverse/Leather), try extracting raw tx
293
+ // The signed PSBT may need extraction of the final transaction
294
+ const rawTx = this.extractRawTransaction(signedPsbt)
295
+ if (rawTx) {
296
+ txid = await this.provider.pushTx(rawTx)
297
+ } else {
298
+ throw pushError
299
+ }
300
+ }
286
301
 
287
302
  return {
288
303
  txHash: ('0x' + txid) as HexString,
@@ -306,6 +321,44 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
306
321
  }
307
322
  }
308
323
 
324
+ /**
325
+ * Extract raw transaction from a finalized PSBT
326
+ *
327
+ * A finalized PSBT has all inputs signed and can be converted to a raw transaction.
328
+ * This is a simplified extraction - full implementation would use bitcoinjs-lib.
329
+ *
330
+ * @param psbtHex - Hex-encoded finalized PSBT
331
+ * @returns Raw transaction hex or undefined if extraction fails
332
+ */
333
+ private extractRawTransaction(psbtHex: string): string | undefined {
334
+ try {
335
+ // PSBT format: magic bytes (4) + separator (1) + key-value pairs
336
+ // After finalization, the PSBT contains the final scriptSig/witness for each input
337
+ // Full extraction requires parsing the PSBT structure
338
+
339
+ // For browser environments, we rely on the wallet to finalize properly
340
+ // This is a fallback that checks if the hex looks like a raw transaction
341
+ // Raw transactions start with version (4 bytes), typically 01000000 or 02000000
342
+
343
+ const cleanHex = psbtHex.startsWith('0x') ? psbtHex.slice(2) : psbtHex
344
+
345
+ // Check if it's already a raw transaction (not a PSBT)
346
+ // PSBT magic: 70736274ff (psbt + 0xff)
347
+ if (!cleanHex.startsWith('70736274ff')) {
348
+ // Might already be a raw transaction
349
+ if (cleanHex.startsWith('01000000') || cleanHex.startsWith('02000000')) {
350
+ return cleanHex
351
+ }
352
+ }
353
+
354
+ // Cannot extract without proper PSBT parsing library
355
+ // Return undefined to let the caller handle the error
356
+ return undefined
357
+ } catch {
358
+ return undefined
359
+ }
360
+ }
361
+
309
362
  /**
310
363
  * Get native BTC balance
311
364
  */
@@ -331,8 +384,10 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
331
384
  /**
332
385
  * Get token balance
333
386
  *
334
- * For Bitcoin, this would return balance of inscriptions or BRC-20 tokens
335
- * Not implemented yet - returns 0
387
+ * For Bitcoin, this returns BRC-20 token balances.
388
+ * Uses Unisat Open API for balance queries.
389
+ *
390
+ * @param asset - Asset with token symbol (e.g., 'ordi', 'sats')
336
391
  */
337
392
  async getTokenBalance(asset: Asset): Promise<bigint> {
338
393
  this.requireConnected()
@@ -344,9 +399,98 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
344
399
  )
345
400
  }
346
401
 
347
- // TODO: Implement BRC-20 token balance query
348
- // For now, return 0
349
- return 0n
402
+ if (!this._address) {
403
+ throw new WalletError('No address connected', WalletErrorCode.NOT_CONNECTED)
404
+ }
405
+
406
+ try {
407
+ // Query BRC-20 balance from indexer API
408
+ const balance = await this.queryBrc20Balance(this._address, asset.symbol)
409
+ return balance
410
+ } catch (error) {
411
+ // Return 0 if query fails (token might not exist or API unavailable)
412
+ console.warn(`Failed to query BRC-20 balance for ${asset.symbol}:`, error)
413
+ return 0n
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Query BRC-20 token balance from indexer API
419
+ *
420
+ * Uses Unisat Open API as the default indexer.
421
+ * Can be overridden by setting brc20ApiUrl in config.
422
+ *
423
+ * @param address - Bitcoin address
424
+ * @param ticker - BRC-20 token ticker (e.g., 'ordi', 'sats')
425
+ */
426
+ private async queryBrc20Balance(address: string, ticker: string): Promise<bigint> {
427
+ // Unisat Open API endpoint for BRC-20 balances
428
+ // API docs: https://open-api.unisat.io/swagger.html
429
+ const apiUrl = `https://open-api.unisat.io/v1/indexer/address/${address}/brc20/${ticker.toLowerCase()}/info`
430
+
431
+ try {
432
+ const response = await fetch(apiUrl, {
433
+ method: 'GET',
434
+ headers: {
435
+ 'Content-Type': 'application/json',
436
+ // Note: Production usage may require API key
437
+ // 'Authorization': `Bearer ${apiKey}`
438
+ },
439
+ })
440
+
441
+ if (!response.ok) {
442
+ if (response.status === 404) {
443
+ // Token not found or no balance
444
+ return 0n
445
+ }
446
+ throw new Error(`API returned ${response.status}`)
447
+ }
448
+
449
+ const data = await response.json()
450
+
451
+ // Unisat API response format:
452
+ // { code: 0, msg: 'ok', data: { ticker, overallBalance, transferableBalance, availableBalance } }
453
+ if (data.code === 0 && data.data) {
454
+ // availableBalance is the spendable balance (not in pending transfers)
455
+ const balance = data.data.availableBalance || data.data.overallBalance || '0'
456
+ // BRC-20 balances are strings, convert to bigint
457
+ // Note: BRC-20 uses 18 decimal places by default
458
+ return BigInt(balance.replace('.', '').padEnd(18, '0'))
459
+ }
460
+
461
+ return 0n
462
+ } catch (error) {
463
+ // Fallback: try alternative API or return 0
464
+ return this.queryBrc20BalanceFallback(address, ticker)
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Fallback BRC-20 balance query using Hiro/Ordinals API
470
+ */
471
+ private async queryBrc20BalanceFallback(address: string, ticker: string): Promise<bigint> {
472
+ try {
473
+ // Hiro Ordinals API
474
+ const apiUrl = `https://api.hiro.so/ordinals/v1/brc-20/balances/${address}`
475
+
476
+ const response = await fetch(apiUrl)
477
+ if (!response.ok) return 0n
478
+
479
+ const data = await response.json()
480
+
481
+ // Find the specific ticker in results
482
+ const tokenBalance = data.results?.find(
483
+ (t: { ticker: string }) => t.ticker.toLowerCase() === ticker.toLowerCase()
484
+ )
485
+
486
+ if (tokenBalance?.overall_balance) {
487
+ return BigInt(tokenBalance.overall_balance)
488
+ }
489
+
490
+ return 0n
491
+ } catch {
492
+ return 0n
493
+ }
350
494
  }
351
495
 
352
496
  /**