@sip-protocol/sdk 0.7.3 → 0.8.0

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 (263) hide show
  1. package/README.md +267 -0
  2. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  3. package/dist/browser.d.mts +10 -4
  4. package/dist/browser.d.ts +10 -4
  5. package/dist/browser.js +47556 -19603
  6. package/dist/browser.mjs +628 -48
  7. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  8. package/dist/chunk-5D7A3L3W.mjs +717 -0
  9. package/dist/chunk-64AYA5F5.mjs +7834 -0
  10. package/dist/chunk-GMDGB22A.mjs +379 -0
  11. package/dist/chunk-I534WKN7.mjs +328 -0
  12. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  13. package/dist/chunk-PRRZAWJE.mjs +223 -0
  14. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  15. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  16. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  17. package/dist/constants-LHAAUC2T.mjs +51 -0
  18. package/dist/dist-2OGQ7FED.mjs +3957 -0
  19. package/dist/dist-IFHPYLDX.mjs +254 -0
  20. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  21. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  22. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  23. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  24. package/dist/index.d.mts +9 -3
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.js +48396 -19623
  27. package/dist/index.mjs +537 -19
  28. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  29. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  30. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  31. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  32. package/dist/proofs/halo2.d.mts +151 -0
  33. package/dist/proofs/halo2.d.ts +151 -0
  34. package/dist/proofs/halo2.js +350 -0
  35. package/dist/proofs/halo2.mjs +11 -0
  36. package/dist/proofs/kimchi.d.mts +160 -0
  37. package/dist/proofs/kimchi.d.ts +160 -0
  38. package/dist/proofs/kimchi.js +431 -0
  39. package/dist/proofs/kimchi.mjs +13 -0
  40. package/dist/proofs/noir.d.mts +1 -1
  41. package/dist/proofs/noir.d.ts +1 -1
  42. package/dist/proofs/noir.js +74 -18
  43. package/dist/proofs/noir.mjs +84 -24
  44. package/dist/solana-U3MEGU7W.mjs +280 -0
  45. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  46. package/package.json +44 -11
  47. package/src/adapters/index.ts +41 -0
  48. package/src/adapters/jupiter.ts +571 -0
  49. package/src/adapters/near-intents.ts +135 -0
  50. package/src/advisor/advisor.ts +653 -0
  51. package/src/advisor/index.ts +54 -0
  52. package/src/advisor/tools.ts +303 -0
  53. package/src/advisor/types.ts +164 -0
  54. package/src/chains/ethereum/announcement.ts +536 -0
  55. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  56. package/src/chains/ethereum/commitment.ts +522 -0
  57. package/src/chains/ethereum/constants.ts +462 -0
  58. package/src/chains/ethereum/deployment.ts +596 -0
  59. package/src/chains/ethereum/gas-estimation.ts +538 -0
  60. package/src/chains/ethereum/index.ts +268 -0
  61. package/src/chains/ethereum/optimizations.ts +614 -0
  62. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  63. package/src/chains/ethereum/registry.ts +584 -0
  64. package/src/chains/ethereum/rpc.ts +905 -0
  65. package/src/chains/ethereum/stealth.ts +491 -0
  66. package/src/chains/ethereum/token.ts +790 -0
  67. package/src/chains/ethereum/transfer.ts +637 -0
  68. package/src/chains/ethereum/types.ts +456 -0
  69. package/src/chains/ethereum/viewing-key.ts +455 -0
  70. package/src/chains/near/commitment.ts +608 -0
  71. package/src/chains/near/constants.ts +284 -0
  72. package/src/chains/near/function-call.ts +871 -0
  73. package/src/chains/near/history.ts +654 -0
  74. package/src/chains/near/implicit-account.ts +840 -0
  75. package/src/chains/near/index.ts +393 -0
  76. package/src/chains/near/native-transfer.ts +658 -0
  77. package/src/chains/near/nep141.ts +775 -0
  78. package/src/chains/near/privacy-adapter.ts +889 -0
  79. package/src/chains/near/resolver.ts +971 -0
  80. package/src/chains/near/rpc.ts +1016 -0
  81. package/src/chains/near/stealth.ts +419 -0
  82. package/src/chains/near/types.ts +317 -0
  83. package/src/chains/near/viewing-key.ts +876 -0
  84. package/src/chains/solana/anchor-transfer.ts +386 -0
  85. package/src/chains/solana/commitment.ts +577 -0
  86. package/src/chains/solana/constants.ts +126 -12
  87. package/src/chains/solana/ephemeral-keys.ts +543 -0
  88. package/src/chains/solana/index.ts +252 -1
  89. package/src/chains/solana/key-derivation.ts +418 -0
  90. package/src/chains/solana/kit-compat.ts +334 -0
  91. package/src/chains/solana/optimizations.ts +560 -0
  92. package/src/chains/solana/privacy-adapter.ts +605 -0
  93. package/src/chains/solana/providers/generic.ts +47 -6
  94. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  95. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  96. package/src/chains/solana/providers/helius.ts +186 -33
  97. package/src/chains/solana/providers/index.ts +31 -0
  98. package/src/chains/solana/providers/interface.ts +61 -18
  99. package/src/chains/solana/providers/quicknode.ts +409 -0
  100. package/src/chains/solana/providers/triton.ts +426 -0
  101. package/src/chains/solana/providers/webhook.ts +338 -67
  102. package/src/chains/solana/rpc-client.ts +1150 -0
  103. package/src/chains/solana/scan.ts +83 -66
  104. package/src/chains/solana/sol-transfer.ts +732 -0
  105. package/src/chains/solana/spl-transfer.ts +886 -0
  106. package/src/chains/solana/stealth-scanner.ts +703 -0
  107. package/src/chains/solana/sunspot-verifier.ts +453 -0
  108. package/src/chains/solana/transaction-builder.ts +755 -0
  109. package/src/chains/solana/transfer.ts +74 -5
  110. package/src/chains/solana/types.ts +57 -6
  111. package/src/chains/solana/utils.ts +110 -0
  112. package/src/chains/solana/viewing-key.ts +807 -0
  113. package/src/compliance/fireblocks.ts +921 -0
  114. package/src/compliance/index.ts +23 -0
  115. package/src/compliance/range-sas.ts +398 -33
  116. package/src/config/endpoints.ts +100 -0
  117. package/src/crypto.ts +11 -8
  118. package/src/errors.ts +82 -0
  119. package/src/evm/erc4337-relayer.ts +830 -0
  120. package/src/evm/index.ts +47 -0
  121. package/src/fees/calculator.ts +396 -0
  122. package/src/fees/index.ts +87 -0
  123. package/src/fees/near-contract.ts +429 -0
  124. package/src/fees/types.ts +268 -0
  125. package/src/index.ts +686 -1
  126. package/src/intent.ts +6 -3
  127. package/src/logger.ts +324 -0
  128. package/src/network/index.ts +80 -0
  129. package/src/network/proxy.ts +691 -0
  130. package/src/optimizations/index.ts +541 -0
  131. package/src/oracle/types.ts +1 -0
  132. package/src/privacy-backends/arcium-types.ts +727 -0
  133. package/src/privacy-backends/arcium.ts +719 -0
  134. package/src/privacy-backends/combined-privacy.ts +866 -0
  135. package/src/privacy-backends/cspl-token.ts +595 -0
  136. package/src/privacy-backends/cspl-types.ts +512 -0
  137. package/src/privacy-backends/cspl.ts +907 -0
  138. package/src/privacy-backends/health.ts +488 -0
  139. package/src/privacy-backends/inco-types.ts +323 -0
  140. package/src/privacy-backends/inco.ts +616 -0
  141. package/src/privacy-backends/index.ts +254 -4
  142. package/src/privacy-backends/interface.ts +649 -6
  143. package/src/privacy-backends/lru-cache.ts +343 -0
  144. package/src/privacy-backends/magicblock.ts +458 -0
  145. package/src/privacy-backends/mock.ts +258 -0
  146. package/src/privacy-backends/privacycash.ts +13 -17
  147. package/src/privacy-backends/private-swap.ts +570 -0
  148. package/src/privacy-backends/rate-limiter.ts +683 -0
  149. package/src/privacy-backends/registry.ts +414 -2
  150. package/src/privacy-backends/router.ts +283 -3
  151. package/src/privacy-backends/shadowwire.ts +449 -0
  152. package/src/privacy-backends/sip-native.ts +3 -0
  153. package/src/privacy-logger.ts +191 -0
  154. package/src/production-safety.ts +373 -0
  155. package/src/proofs/aggregator.ts +1029 -0
  156. package/src/proofs/browser-composer.ts +1150 -0
  157. package/src/proofs/browser.ts +113 -25
  158. package/src/proofs/cache/index.ts +127 -0
  159. package/src/proofs/cache/interface.ts +545 -0
  160. package/src/proofs/cache/key-generator.ts +188 -0
  161. package/src/proofs/cache/lru-cache.ts +481 -0
  162. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  163. package/src/proofs/cache/persistent-cache.ts +788 -0
  164. package/src/proofs/compliance-proof.ts +872 -0
  165. package/src/proofs/composer/base.ts +923 -0
  166. package/src/proofs/composer/index.ts +25 -0
  167. package/src/proofs/composer/interface.ts +518 -0
  168. package/src/proofs/composer/types.ts +383 -0
  169. package/src/proofs/converters/halo2.ts +452 -0
  170. package/src/proofs/converters/index.ts +208 -0
  171. package/src/proofs/converters/interface.ts +363 -0
  172. package/src/proofs/converters/kimchi.ts +462 -0
  173. package/src/proofs/converters/noir.ts +451 -0
  174. package/src/proofs/fallback.ts +888 -0
  175. package/src/proofs/halo2.ts +42 -0
  176. package/src/proofs/index.ts +471 -0
  177. package/src/proofs/interface.ts +13 -0
  178. package/src/proofs/kimchi.ts +42 -0
  179. package/src/proofs/lazy.ts +1004 -0
  180. package/src/proofs/mock.ts +25 -1
  181. package/src/proofs/noir.ts +110 -29
  182. package/src/proofs/orchestrator.ts +960 -0
  183. package/src/proofs/parallel/concurrency.ts +297 -0
  184. package/src/proofs/parallel/dependency-graph.ts +602 -0
  185. package/src/proofs/parallel/executor.ts +420 -0
  186. package/src/proofs/parallel/index.ts +131 -0
  187. package/src/proofs/parallel/interface.ts +685 -0
  188. package/src/proofs/parallel/worker-pool.ts +644 -0
  189. package/src/proofs/providers/halo2.ts +560 -0
  190. package/src/proofs/providers/index.ts +34 -0
  191. package/src/proofs/providers/kimchi.ts +641 -0
  192. package/src/proofs/validator.ts +881 -0
  193. package/src/proofs/verifier.ts +867 -0
  194. package/src/quantum/index.ts +112 -0
  195. package/src/quantum/winternitz-vault.ts +639 -0
  196. package/src/quantum/wots.ts +611 -0
  197. package/src/settlement/backends/direct-chain.ts +1 -0
  198. package/src/settlement/index.ts +9 -0
  199. package/src/settlement/router.ts +732 -46
  200. package/src/solana/index.ts +72 -0
  201. package/src/solana/jito-relayer.ts +687 -0
  202. package/src/solana/noir-verifier-types.ts +430 -0
  203. package/src/solana/noir-verifier.ts +816 -0
  204. package/src/stealth/address-derivation.ts +193 -0
  205. package/src/stealth/ed25519.ts +431 -0
  206. package/src/stealth/index.ts +233 -0
  207. package/src/stealth/meta-address.ts +221 -0
  208. package/src/stealth/secp256k1.ts +368 -0
  209. package/src/stealth/utils.ts +194 -0
  210. package/src/stealth.ts +50 -1504
  211. package/src/sync/index.ts +106 -0
  212. package/src/sync/manager.ts +504 -0
  213. package/src/sync/mock-provider.ts +318 -0
  214. package/src/sync/oblivious.ts +625 -0
  215. package/src/tokens/index.ts +15 -0
  216. package/src/tokens/registry.ts +301 -0
  217. package/src/utils/deprecation.ts +94 -0
  218. package/src/utils/index.ts +9 -0
  219. package/src/wallet/ethereum/index.ts +68 -0
  220. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  221. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  222. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  223. package/src/wallet/ethereum/types.ts +3 -1
  224. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  225. package/src/wallet/hardware/index.ts +10 -0
  226. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  227. package/src/wallet/index.ts +71 -0
  228. package/src/wallet/near/adapter.ts +626 -0
  229. package/src/wallet/near/index.ts +86 -0
  230. package/src/wallet/near/meteor-wallet.ts +1153 -0
  231. package/src/wallet/near/my-near-wallet.ts +790 -0
  232. package/src/wallet/near/wallet-selector.ts +702 -0
  233. package/src/wallet/solana/adapter.ts +6 -4
  234. package/src/wallet/solana/index.ts +13 -0
  235. package/src/wallet/solana/privacy-adapter.ts +567 -0
  236. package/src/wallet/sui/types.ts +6 -4
  237. package/src/zcash/rpc-client.ts +13 -6
  238. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  239. package/dist/chunk-3INS3PR5.mjs +0 -884
  240. package/dist/chunk-3OVABDRH.mjs +0 -17096
  241. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  242. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  243. package/dist/chunk-E6SZWREQ.mjs +0 -57
  244. package/dist/chunk-F6F73W35.mjs +0 -16166
  245. package/dist/chunk-G33LB27A.mjs +0 -16166
  246. package/dist/chunk-HGU6HZRC.mjs +0 -231
  247. package/dist/chunk-L2K34JCU.mjs +0 -1496
  248. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  249. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  250. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  251. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  252. package/dist/constants-VOI7BSLK.mjs +0 -27
  253. package/dist/index-B71aXVzk.d.ts +0 -13264
  254. package/dist/index-BYZbDjal.d.ts +0 -11390
  255. package/dist/index-CHB3KuOB.d.mts +0 -11859
  256. package/dist/index-CzWPI6Le.d.ts +0 -11859
  257. package/dist/index-pOIIuwfV.d.mts +0 -13264
  258. package/dist/index-xbWjohNq.d.mts +0 -11390
  259. package/dist/solana-4O4K45VU.mjs +0 -46
  260. package/dist/solana-5EMCTPTS.mjs +0 -46
  261. package/dist/solana-NDABAZ6P.mjs +0 -56
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
  263. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,732 @@
1
+ /**
2
+ * Native SOL Transfer with Stealth Addresses
3
+ *
4
+ * Provides private SOL transfers using stealth addresses.
5
+ * Simpler than SPL transfers - no ATA required.
6
+ *
7
+ * @module chains/solana/sol-transfer
8
+ */
9
+
10
+ import {
11
+ Connection,
12
+ PublicKey,
13
+ Transaction,
14
+ TransactionInstruction,
15
+ SystemProgram,
16
+ LAMPORTS_PER_SOL,
17
+ type Commitment,
18
+ } from '@solana/web3.js'
19
+ import {
20
+ generateEd25519StealthAddress,
21
+ ed25519PublicKeyToSolanaAddress,
22
+ } from '../../stealth'
23
+ import { ValidationError } from '../../errors'
24
+ import type { StealthMetaAddress } from '@sip-protocol/types'
25
+ import { createAnnouncementMemo } from './types'
26
+ import {
27
+ MEMO_PROGRAM_ID,
28
+ getExplorerUrl,
29
+ ESTIMATED_TX_FEE_LAMPORTS,
30
+ type SolanaCluster,
31
+ } from './constants'
32
+
33
+ // ─── Constants ────────────────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * Minimum rent-exempt balance for a Solana account (in lamports)
37
+ * This is approximately 0.00089 SOL
38
+ */
39
+ export const RENT_EXEMPT_MINIMUM = 890_880n
40
+
41
+ /**
42
+ * Recommended buffer for new stealth accounts
43
+ * Slightly more than minimum to cover edge cases
44
+ */
45
+ export const STEALTH_ACCOUNT_BUFFER = 1_000_000n // ~0.001 SOL
46
+
47
+ // ─── Types ────────────────────────────────────────────────────────────────────
48
+
49
+ /**
50
+ * Parameters for native SOL transfer to stealth address
51
+ */
52
+ export interface SOLTransferParams {
53
+ /** Solana RPC connection */
54
+ connection: Connection
55
+ /** Sender's public key */
56
+ sender: PublicKey
57
+ /** Recipient's stealth meta-address */
58
+ recipientMetaAddress: StealthMetaAddress
59
+ /** Amount to transfer (in lamports) */
60
+ amount: bigint
61
+ /** Function to sign the transaction */
62
+ signTransaction: <T extends Transaction>(tx: T) => Promise<T>
63
+ /** Include rent-exempt buffer (default: true for new accounts) */
64
+ includeRentBuffer?: boolean
65
+ /** Transaction commitment level */
66
+ commitment?: Commitment
67
+ /** Custom memo to append */
68
+ customMemo?: string
69
+ }
70
+
71
+ /**
72
+ * Parameters for max SOL transfer (send all available balance)
73
+ */
74
+ export interface MaxSOLTransferParams extends Omit<SOLTransferParams, 'amount'> {
75
+ /** Minimum to leave in sender account (default: 0) */
76
+ keepMinimum?: bigint
77
+ }
78
+
79
+ /**
80
+ * Result of a native SOL transfer
81
+ */
82
+ export interface SOLTransferResult {
83
+ /** Transaction signature */
84
+ txSignature: string
85
+ /** Stealth address (base58) */
86
+ stealthAddress: string
87
+ /** Ephemeral public key (base58) */
88
+ ephemeralPublicKey: string
89
+ /** View tag for scanning */
90
+ viewTag: string
91
+ /** Explorer URL */
92
+ explorerUrl: string
93
+ /** Cluster */
94
+ cluster: SolanaCluster
95
+ /** Amount transferred (lamports) */
96
+ amount: bigint
97
+ /** Amount in SOL */
98
+ amountSol: number
99
+ /** Whether rent buffer was included */
100
+ rentBufferIncluded: boolean
101
+ /** Estimated fee paid */
102
+ estimatedFee: bigint
103
+ }
104
+
105
+ /**
106
+ * SOL transfer validation result
107
+ */
108
+ export interface SOLTransferValidation {
109
+ /** Whether transfer is valid */
110
+ isValid: boolean
111
+ /** Validation errors */
112
+ errors: string[]
113
+ /** Sender's SOL balance (lamports) */
114
+ senderBalance?: bigint
115
+ /** Sender's SOL balance in SOL */
116
+ senderBalanceSol?: number
117
+ /** Whether stealth account exists */
118
+ stealthAccountExists?: boolean
119
+ /** Recommended rent buffer */
120
+ recommendedRentBuffer: bigint
121
+ /** Estimated total fee */
122
+ estimatedFee: bigint
123
+ /** Maximum transferable amount */
124
+ maxTransferable?: bigint
125
+ }
126
+
127
+ /**
128
+ * Gas estimation for SOL transfer
129
+ */
130
+ export interface SOLTransferEstimate {
131
+ /** Base transaction fee (lamports) */
132
+ baseFee: bigint
133
+ /** Rent buffer for new account (if needed) */
134
+ rentBuffer: bigint
135
+ /** Total estimated cost (fee + rent) */
136
+ totalCost: bigint
137
+ /** Whether stealth account exists */
138
+ stealthAccountExists: boolean
139
+ }
140
+
141
+ // ─── Validation ───────────────────────────────────────────────────────────────
142
+
143
+ /**
144
+ * Validate a SOL transfer before execution
145
+ *
146
+ * Checks:
147
+ * - Sender has sufficient balance
148
+ * - Meta-address is valid for Solana
149
+ * - Amount is valid and meets minimums
150
+ * - Stealth account requirements
151
+ *
152
+ * @param params - Transfer parameters to validate
153
+ * @returns Validation result
154
+ */
155
+ export async function validateSOLTransfer(
156
+ params: Omit<SOLTransferParams, 'signTransaction'>
157
+ ): Promise<SOLTransferValidation> {
158
+ const errors: string[] = []
159
+ let senderBalance: bigint | undefined
160
+ let senderBalanceSol: number | undefined
161
+ let stealthAccountExists = false
162
+ let maxTransferable: bigint | undefined
163
+
164
+ const estimatedFee = ESTIMATED_TX_FEE_LAMPORTS
165
+ let recommendedRentBuffer = 0n
166
+
167
+ // Validate meta-address
168
+ if (!params.recipientMetaAddress) {
169
+ errors.push('Recipient meta-address is required')
170
+ } else if (params.recipientMetaAddress.chain !== 'solana') {
171
+ errors.push(`Invalid chain: expected 'solana', got '${params.recipientMetaAddress.chain}'`)
172
+ }
173
+
174
+ // Validate amount
175
+ if (params.amount <= 0n) {
176
+ errors.push('Amount must be greater than 0')
177
+ }
178
+
179
+ // Get sender balance
180
+ try {
181
+ const balance = await params.connection.getBalance(params.sender)
182
+ senderBalance = BigInt(balance)
183
+ senderBalanceSol = balance / LAMPORTS_PER_SOL
184
+
185
+ // Calculate max transferable (balance - fee)
186
+ maxTransferable = senderBalance > estimatedFee ? senderBalance - estimatedFee : 0n
187
+ } catch (error) {
188
+ errors.push(`Failed to check sender balance: ${error instanceof Error ? error.message : 'Unknown error'}`)
189
+ }
190
+
191
+ // Check if stealth account exists and calculate rent buffer
192
+ if (params.recipientMetaAddress && params.recipientMetaAddress.chain === 'solana') {
193
+ try {
194
+ const { stealthAddress } = generateEd25519StealthAddress(params.recipientMetaAddress)
195
+ const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
196
+ const stealthPubkey = new PublicKey(stealthAddressBase58)
197
+
198
+ const accountInfo = await params.connection.getAccountInfo(stealthPubkey)
199
+ stealthAccountExists = accountInfo !== null
200
+
201
+ if (!stealthAccountExists) {
202
+ // Account doesn't exist, recommend rent buffer
203
+ recommendedRentBuffer = STEALTH_ACCOUNT_BUFFER
204
+
205
+ // Amount must be >= rent-exempt minimum
206
+ if (params.amount < RENT_EXEMPT_MINIMUM) {
207
+ errors.push(
208
+ `Amount ${formatLamports(params.amount)} SOL is below rent-exempt minimum ${formatLamports(RENT_EXEMPT_MINIMUM)} SOL`
209
+ )
210
+ }
211
+ }
212
+ } catch {
213
+ // Cannot generate stealth address - error already captured
214
+ }
215
+ }
216
+
217
+ // Check sufficient balance
218
+ if (senderBalance !== undefined) {
219
+ const totalNeeded = params.amount + estimatedFee
220
+ if (senderBalance < totalNeeded) {
221
+ errors.push(
222
+ `Insufficient balance: have ${formatLamports(senderBalance)} SOL, need ${formatLamports(totalNeeded)} SOL (including fee)`
223
+ )
224
+ }
225
+ }
226
+
227
+ return {
228
+ isValid: errors.length === 0,
229
+ errors,
230
+ senderBalance,
231
+ senderBalanceSol,
232
+ stealthAccountExists,
233
+ recommendedRentBuffer,
234
+ estimatedFee,
235
+ maxTransferable,
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Estimate gas and costs for SOL transfer
241
+ *
242
+ * @param connection - Solana RPC connection
243
+ * @param recipientMetaAddress - Recipient's meta-address
244
+ * @returns Gas estimation
245
+ */
246
+ export async function estimateSOLTransfer(
247
+ connection: Connection,
248
+ recipientMetaAddress: StealthMetaAddress
249
+ ): Promise<SOLTransferEstimate> {
250
+ const baseFee = ESTIMATED_TX_FEE_LAMPORTS
251
+ let rentBuffer = 0n
252
+ let stealthAccountExists = false
253
+
254
+ // Check if stealth account exists
255
+ const { stealthAddress } = generateEd25519StealthAddress(recipientMetaAddress)
256
+ const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
257
+ const stealthPubkey = new PublicKey(stealthAddressBase58)
258
+
259
+ const accountInfo = await connection.getAccountInfo(stealthPubkey)
260
+ stealthAccountExists = accountInfo !== null
261
+
262
+ if (!stealthAccountExists) {
263
+ rentBuffer = STEALTH_ACCOUNT_BUFFER
264
+ }
265
+
266
+ return {
267
+ baseFee,
268
+ rentBuffer,
269
+ totalCost: baseFee + rentBuffer,
270
+ stealthAccountExists,
271
+ }
272
+ }
273
+
274
+ // ─── Transfer Functions ───────────────────────────────────────────────────────
275
+
276
+ /**
277
+ * Send native SOL privately to a stealth address
278
+ *
279
+ * @param params - Transfer parameters
280
+ * @returns Transfer result
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * const result = await sendSOLTransfer({
285
+ * connection,
286
+ * sender: wallet.publicKey,
287
+ * recipientMetaAddress: recipientMeta,
288
+ * amount: 1_000_000_000n, // 1 SOL
289
+ * signTransaction: wallet.signTransaction,
290
+ * })
291
+ *
292
+ * console.log(`Sent ${result.amountSol} SOL to ${result.stealthAddress}`)
293
+ * ```
294
+ */
295
+ export async function sendSOLTransfer(
296
+ params: SOLTransferParams
297
+ ): Promise<SOLTransferResult> {
298
+ const {
299
+ connection,
300
+ sender,
301
+ recipientMetaAddress,
302
+ amount,
303
+ signTransaction,
304
+ includeRentBuffer = true,
305
+ commitment = 'confirmed',
306
+ customMemo,
307
+ } = params
308
+
309
+ // Validate meta-address
310
+ if (!recipientMetaAddress) {
311
+ throw new ValidationError('recipientMetaAddress is required', 'recipientMetaAddress')
312
+ }
313
+ if (recipientMetaAddress.chain !== 'solana') {
314
+ throw new ValidationError(
315
+ `Invalid chain: expected 'solana', got '${recipientMetaAddress.chain}'`,
316
+ 'recipientMetaAddress.chain'
317
+ )
318
+ }
319
+
320
+ // Validate amount
321
+ if (amount <= 0n) {
322
+ throw new ValidationError('amount must be greater than 0', 'amount')
323
+ }
324
+
325
+ // Generate stealth address
326
+ const { stealthAddress } = generateEd25519StealthAddress(recipientMetaAddress)
327
+ const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
328
+ const stealthPubkey = new PublicKey(stealthAddressBase58)
329
+ const ephemeralPubkeyBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.ephemeralPublicKey)
330
+
331
+ // Check if stealth account exists
332
+ const accountInfo = await connection.getAccountInfo(stealthPubkey)
333
+ const stealthAccountExists = accountInfo !== null
334
+
335
+ // Calculate actual transfer amount
336
+ const transferAmount = amount
337
+ let rentBufferIncluded = false
338
+
339
+ if (!stealthAccountExists && includeRentBuffer) {
340
+ // Check if amount is below rent-exempt minimum
341
+ if (amount < RENT_EXEMPT_MINIMUM) {
342
+ throw new ValidationError(
343
+ `Amount ${formatLamports(amount)} SOL is below rent-exempt minimum ${formatLamports(RENT_EXEMPT_MINIMUM)} SOL for new accounts`,
344
+ 'amount'
345
+ )
346
+ }
347
+ rentBufferIncluded = true
348
+ }
349
+
350
+ // Build transaction
351
+ const transaction = new Transaction()
352
+
353
+ // Add SOL transfer instruction
354
+ transaction.add(
355
+ SystemProgram.transfer({
356
+ fromPubkey: sender,
357
+ toPubkey: stealthPubkey,
358
+ lamports: transferAmount,
359
+ })
360
+ )
361
+
362
+ // Add SIP announcement memo
363
+ const viewTagHex = stealthAddress.viewTag.toString(16).padStart(2, '0')
364
+ const memoContent = createAnnouncementMemo(
365
+ ephemeralPubkeyBase58,
366
+ viewTagHex,
367
+ stealthAddressBase58
368
+ )
369
+
370
+ transaction.add(
371
+ new TransactionInstruction({
372
+ keys: [],
373
+ programId: new PublicKey(MEMO_PROGRAM_ID),
374
+ data: Buffer.from(memoContent, 'utf-8'),
375
+ })
376
+ )
377
+
378
+ // Add custom memo if provided
379
+ if (customMemo) {
380
+ transaction.add(
381
+ new TransactionInstruction({
382
+ keys: [],
383
+ programId: new PublicKey(MEMO_PROGRAM_ID),
384
+ data: Buffer.from(customMemo, 'utf-8'),
385
+ })
386
+ )
387
+ }
388
+
389
+ // Get blockhash and sign
390
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment)
391
+ transaction.recentBlockhash = blockhash
392
+ transaction.lastValidBlockHeight = lastValidBlockHeight
393
+ transaction.feePayer = sender
394
+
395
+ const signedTx = await signTransaction(transaction)
396
+
397
+ // Send and confirm
398
+ const txSignature = await connection.sendRawTransaction(signedTx.serialize(), {
399
+ skipPreflight: false,
400
+ preflightCommitment: commitment,
401
+ })
402
+
403
+ await connection.confirmTransaction(
404
+ { signature: txSignature, blockhash, lastValidBlockHeight },
405
+ commitment
406
+ )
407
+
408
+ const cluster = detectCluster(connection.rpcEndpoint)
409
+
410
+ return {
411
+ txSignature,
412
+ stealthAddress: stealthAddressBase58,
413
+ ephemeralPublicKey: ephemeralPubkeyBase58,
414
+ viewTag: viewTagHex,
415
+ explorerUrl: getExplorerUrl(txSignature, cluster),
416
+ cluster,
417
+ amount: transferAmount,
418
+ amountSol: Number(transferAmount) / LAMPORTS_PER_SOL,
419
+ rentBufferIncluded,
420
+ estimatedFee: ESTIMATED_TX_FEE_LAMPORTS,
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Send maximum available SOL privately to a stealth address
426
+ *
427
+ * Calculates maximum transferable amount (balance - fee - keepMinimum)
428
+ * and sends that amount.
429
+ *
430
+ * @param params - Transfer parameters
431
+ * @returns Transfer result
432
+ *
433
+ * @example
434
+ * ```typescript
435
+ * // Send all SOL
436
+ * const result = await sendMaxSOLTransfer({
437
+ * connection,
438
+ * sender: wallet.publicKey,
439
+ * recipientMetaAddress: recipientMeta,
440
+ * signTransaction: wallet.signTransaction,
441
+ * })
442
+ *
443
+ * // Keep 0.1 SOL
444
+ * const result = await sendMaxSOLTransfer({
445
+ * connection,
446
+ * sender: wallet.publicKey,
447
+ * recipientMetaAddress: recipientMeta,
448
+ * keepMinimum: 100_000_000n, // 0.1 SOL
449
+ * signTransaction: wallet.signTransaction,
450
+ * })
451
+ * ```
452
+ */
453
+ export async function sendMaxSOLTransfer(
454
+ params: MaxSOLTransferParams
455
+ ): Promise<SOLTransferResult> {
456
+ const {
457
+ connection,
458
+ sender,
459
+ recipientMetaAddress,
460
+ signTransaction,
461
+ keepMinimum = 0n,
462
+ includeRentBuffer = true,
463
+ commitment = 'confirmed',
464
+ customMemo,
465
+ } = params
466
+
467
+ // Get sender balance
468
+ const balance = await connection.getBalance(sender)
469
+ const balanceLamports = BigInt(balance)
470
+
471
+ // Calculate max transferable
472
+ const estimatedFee = ESTIMATED_TX_FEE_LAMPORTS
473
+ const reserved = estimatedFee + keepMinimum
474
+
475
+ if (balanceLamports <= reserved) {
476
+ throw new ValidationError(
477
+ `Insufficient balance for max transfer: have ${formatLamports(balanceLamports)} SOL, need at least ${formatLamports(reserved + 1n)} SOL`,
478
+ 'amount'
479
+ )
480
+ }
481
+
482
+ const maxAmount = balanceLamports - reserved
483
+
484
+ // Check rent requirement for new accounts
485
+ const { stealthAddress } = generateEd25519StealthAddress(recipientMetaAddress)
486
+ const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
487
+ const stealthPubkey = new PublicKey(stealthAddressBase58)
488
+
489
+ const accountInfo = await connection.getAccountInfo(stealthPubkey)
490
+ const stealthAccountExists = accountInfo !== null
491
+
492
+ if (!stealthAccountExists && maxAmount < RENT_EXEMPT_MINIMUM) {
493
+ throw new ValidationError(
494
+ `Insufficient balance for max transfer to new account: need at least ${formatLamports(RENT_EXEMPT_MINIMUM)} SOL after fees`,
495
+ 'amount'
496
+ )
497
+ }
498
+
499
+ return sendSOLTransfer({
500
+ connection,
501
+ sender,
502
+ recipientMetaAddress,
503
+ amount: maxAmount,
504
+ signTransaction,
505
+ includeRentBuffer,
506
+ commitment,
507
+ customMemo,
508
+ })
509
+ }
510
+
511
+ // ─── Batch Transfer ───────────────────────────────────────────────────────────
512
+
513
+ /**
514
+ * Batch SOL transfer item
515
+ */
516
+ export interface BatchSOLTransferItem {
517
+ /** Recipient's stealth meta-address */
518
+ recipientMetaAddress: StealthMetaAddress
519
+ /** Amount in lamports */
520
+ amount: bigint
521
+ /** Custom memo */
522
+ customMemo?: string
523
+ }
524
+
525
+ /**
526
+ * Batch SOL transfer result
527
+ */
528
+ export interface BatchSOLTransferResult {
529
+ /** Transaction signature */
530
+ txSignature: string
531
+ /** Individual transfer results */
532
+ transfers: Array<{
533
+ stealthAddress: string
534
+ ephemeralPublicKey: string
535
+ viewTag: string
536
+ amount: bigint
537
+ amountSol: number
538
+ }>
539
+ /** Explorer URL */
540
+ explorerUrl: string
541
+ /** Cluster */
542
+ cluster: SolanaCluster
543
+ /** Total amount transferred */
544
+ totalAmount: bigint
545
+ /** Total amount in SOL */
546
+ totalAmountSol: number
547
+ }
548
+
549
+ /**
550
+ * Send SOL to multiple stealth addresses in a single transaction
551
+ *
552
+ * @param connection - Solana RPC connection
553
+ * @param sender - Sender's public key
554
+ * @param transfers - Array of transfer items
555
+ * @param signTransaction - Transaction signing function
556
+ * @returns Batch transfer result
557
+ */
558
+ export async function sendBatchSOLTransfer(
559
+ connection: Connection,
560
+ sender: PublicKey,
561
+ transfers: BatchSOLTransferItem[],
562
+ signTransaction: <T extends Transaction>(tx: T) => Promise<T>
563
+ ): Promise<BatchSOLTransferResult> {
564
+ // Validate batch size
565
+ const MAX_BATCH_SIZE = 8 // SOL transfers are smaller than SPL
566
+ if (transfers.length > MAX_BATCH_SIZE) {
567
+ throw new ValidationError(
568
+ `Batch size ${transfers.length} exceeds maximum ${MAX_BATCH_SIZE}`,
569
+ 'transfers'
570
+ )
571
+ }
572
+
573
+ if (transfers.length === 0) {
574
+ throw new ValidationError('At least one transfer is required', 'transfers')
575
+ }
576
+
577
+ // Calculate total amount
578
+ const totalAmount = transfers.reduce((sum, t) => sum + t.amount, 0n)
579
+
580
+ // Check balance
581
+ const balance = await connection.getBalance(sender)
582
+ const balanceLamports = BigInt(balance)
583
+ const estimatedFee = ESTIMATED_TX_FEE_LAMPORTS
584
+
585
+ if (balanceLamports < totalAmount + estimatedFee) {
586
+ throw new ValidationError(
587
+ `Insufficient balance for batch: have ${formatLamports(balanceLamports)} SOL, need ${formatLamports(totalAmount + estimatedFee)} SOL`,
588
+ 'amount'
589
+ )
590
+ }
591
+
592
+ // Build transaction
593
+ const transaction = new Transaction()
594
+ const transferResults: BatchSOLTransferResult['transfers'] = []
595
+
596
+ for (const transfer of transfers) {
597
+ // Validate meta-address
598
+ if (transfer.recipientMetaAddress.chain !== 'solana') {
599
+ throw new ValidationError(
600
+ `Invalid chain for recipient: expected 'solana', got '${transfer.recipientMetaAddress.chain}'`,
601
+ 'recipientMetaAddress'
602
+ )
603
+ }
604
+
605
+ // Generate stealth address
606
+ const { stealthAddress } = generateEd25519StealthAddress(transfer.recipientMetaAddress)
607
+ const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
608
+ const stealthPubkey = new PublicKey(stealthAddressBase58)
609
+ const ephemeralPubkeyBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.ephemeralPublicKey)
610
+
611
+ // Add transfer
612
+ transaction.add(
613
+ SystemProgram.transfer({
614
+ fromPubkey: sender,
615
+ toPubkey: stealthPubkey,
616
+ lamports: transfer.amount,
617
+ })
618
+ )
619
+
620
+ // Add announcement memo
621
+ const viewTagHex = stealthAddress.viewTag.toString(16).padStart(2, '0')
622
+ const memoContent = createAnnouncementMemo(
623
+ ephemeralPubkeyBase58,
624
+ viewTagHex,
625
+ stealthAddressBase58
626
+ )
627
+
628
+ transaction.add(
629
+ new TransactionInstruction({
630
+ keys: [],
631
+ programId: new PublicKey(MEMO_PROGRAM_ID),
632
+ data: Buffer.from(memoContent, 'utf-8'),
633
+ })
634
+ )
635
+
636
+ // Add custom memo if provided
637
+ if (transfer.customMemo) {
638
+ transaction.add(
639
+ new TransactionInstruction({
640
+ keys: [],
641
+ programId: new PublicKey(MEMO_PROGRAM_ID),
642
+ data: Buffer.from(transfer.customMemo, 'utf-8'),
643
+ })
644
+ )
645
+ }
646
+
647
+ transferResults.push({
648
+ stealthAddress: stealthAddressBase58,
649
+ ephemeralPublicKey: ephemeralPubkeyBase58,
650
+ viewTag: viewTagHex,
651
+ amount: transfer.amount,
652
+ amountSol: Number(transfer.amount) / LAMPORTS_PER_SOL,
653
+ })
654
+ }
655
+
656
+ // Get blockhash and sign
657
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
658
+ transaction.recentBlockhash = blockhash
659
+ transaction.lastValidBlockHeight = lastValidBlockHeight
660
+ transaction.feePayer = sender
661
+
662
+ const signedTx = await signTransaction(transaction)
663
+
664
+ // Send and confirm
665
+ const txSignature = await connection.sendRawTransaction(signedTx.serialize(), {
666
+ skipPreflight: false,
667
+ preflightCommitment: 'confirmed',
668
+ })
669
+
670
+ await connection.confirmTransaction(
671
+ { signature: txSignature, blockhash, lastValidBlockHeight },
672
+ 'confirmed'
673
+ )
674
+
675
+ const cluster = detectCluster(connection.rpcEndpoint)
676
+
677
+ return {
678
+ txSignature,
679
+ transfers: transferResults,
680
+ explorerUrl: getExplorerUrl(txSignature, cluster),
681
+ cluster,
682
+ totalAmount,
683
+ totalAmountSol: Number(totalAmount) / LAMPORTS_PER_SOL,
684
+ }
685
+ }
686
+
687
+ // ─── Utilities ────────────────────────────────────────────────────────────────
688
+
689
+ /**
690
+ * Format lamports as SOL string
691
+ */
692
+ export function formatLamports(lamports: bigint): string {
693
+ const sol = Number(lamports) / LAMPORTS_PER_SOL
694
+ if (sol === 0) return '0'
695
+ if (sol < 0.0001) return sol.toExponential(2)
696
+ return sol.toFixed(sol < 1 ? 4 : 2).replace(/\.?0+$/, '')
697
+ }
698
+
699
+ /**
700
+ * Parse SOL amount to lamports
701
+ */
702
+ export function parseSOLToLamports(sol: string | number): bigint {
703
+ const value = typeof sol === 'string' ? parseFloat(sol.replace(/[,\s]/g, '')) : sol
704
+ if (isNaN(value) || value < 0) {
705
+ throw new ValidationError(`Invalid SOL amount: ${sol}`, 'amount')
706
+ }
707
+ return BigInt(Math.round(value * LAMPORTS_PER_SOL))
708
+ }
709
+
710
+ /**
711
+ * Get sender's SOL balance
712
+ */
713
+ export async function getSOLBalance(
714
+ connection: Connection,
715
+ address: PublicKey
716
+ ): Promise<{ lamports: bigint; sol: number }> {
717
+ const balance = await connection.getBalance(address)
718
+ return {
719
+ lamports: BigInt(balance),
720
+ sol: balance / LAMPORTS_PER_SOL,
721
+ }
722
+ }
723
+
724
+ /**
725
+ * Detect Solana cluster from RPC endpoint
726
+ */
727
+ function detectCluster(endpoint: string): SolanaCluster {
728
+ if (endpoint.includes('devnet')) return 'devnet'
729
+ if (endpoint.includes('testnet')) return 'testnet'
730
+ if (endpoint.includes('localhost') || endpoint.includes('127.0.0.1')) return 'localnet'
731
+ return 'mainnet-beta'
732
+ }