@sip-protocol/sdk 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +48874 -18336
  7. package/dist/browser.mjs +674 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/chunk-YWGJ77A2.mjs +33806 -0
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/index-DXh2IGkz.d.ts +24681 -0
  24. package/dist/index-DeE1ZzA4.d.mts +24681 -0
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48676 -17318
  28. package/dist/index.mjs +583 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +276 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +201 -0
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +402 -0
  98. package/src/chains/solana/providers/index.ts +85 -0
  99. package/src/chains/solana/providers/interface.ts +221 -0
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +790 -0
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +170 -73
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +77 -7
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +37 -0
  116. package/src/compliance/range-sas.ts +956 -0
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +785 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +336 -0
  143. package/src/privacy-backends/interface.ts +906 -0
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash-types.ts +278 -0
  148. package/src/privacy-backends/privacycash.ts +456 -0
  149. package/src/privacy-backends/private-swap.ts +570 -0
  150. package/src/privacy-backends/rate-limiter.ts +683 -0
  151. package/src/privacy-backends/registry.ts +690 -0
  152. package/src/privacy-backends/router.ts +626 -0
  153. package/src/privacy-backends/shadowwire.ts +449 -0
  154. package/src/privacy-backends/sip-native.ts +256 -0
  155. package/src/privacy-logger.ts +191 -0
  156. package/src/production-safety.ts +373 -0
  157. package/src/proofs/aggregator.ts +1029 -0
  158. package/src/proofs/browser-composer.ts +1150 -0
  159. package/src/proofs/browser.ts +113 -25
  160. package/src/proofs/cache/index.ts +127 -0
  161. package/src/proofs/cache/interface.ts +545 -0
  162. package/src/proofs/cache/key-generator.ts +188 -0
  163. package/src/proofs/cache/lru-cache.ts +481 -0
  164. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  165. package/src/proofs/cache/persistent-cache.ts +788 -0
  166. package/src/proofs/compliance-proof.ts +872 -0
  167. package/src/proofs/composer/base.ts +923 -0
  168. package/src/proofs/composer/index.ts +25 -0
  169. package/src/proofs/composer/interface.ts +518 -0
  170. package/src/proofs/composer/types.ts +383 -0
  171. package/src/proofs/converters/halo2.ts +452 -0
  172. package/src/proofs/converters/index.ts +208 -0
  173. package/src/proofs/converters/interface.ts +363 -0
  174. package/src/proofs/converters/kimchi.ts +462 -0
  175. package/src/proofs/converters/noir.ts +451 -0
  176. package/src/proofs/fallback.ts +888 -0
  177. package/src/proofs/halo2.ts +42 -0
  178. package/src/proofs/index.ts +471 -0
  179. package/src/proofs/interface.ts +13 -0
  180. package/src/proofs/kimchi.ts +42 -0
  181. package/src/proofs/lazy.ts +1004 -0
  182. package/src/proofs/mock.ts +25 -1
  183. package/src/proofs/noir.ts +111 -30
  184. package/src/proofs/orchestrator.ts +960 -0
  185. package/src/proofs/parallel/concurrency.ts +297 -0
  186. package/src/proofs/parallel/dependency-graph.ts +602 -0
  187. package/src/proofs/parallel/executor.ts +420 -0
  188. package/src/proofs/parallel/index.ts +131 -0
  189. package/src/proofs/parallel/interface.ts +685 -0
  190. package/src/proofs/parallel/worker-pool.ts +644 -0
  191. package/src/proofs/providers/halo2.ts +560 -0
  192. package/src/proofs/providers/index.ts +34 -0
  193. package/src/proofs/providers/kimchi.ts +641 -0
  194. package/src/proofs/validator.ts +881 -0
  195. package/src/proofs/verifier.ts +867 -0
  196. package/src/quantum/index.ts +112 -0
  197. package/src/quantum/winternitz-vault.ts +639 -0
  198. package/src/quantum/wots.ts +611 -0
  199. package/src/settlement/backends/direct-chain.ts +1 -0
  200. package/src/settlement/index.ts +9 -0
  201. package/src/settlement/router.ts +732 -46
  202. package/src/solana/index.ts +72 -0
  203. package/src/solana/jito-relayer.ts +687 -0
  204. package/src/solana/noir-verifier-types.ts +430 -0
  205. package/src/solana/noir-verifier.ts +816 -0
  206. package/src/stealth/address-derivation.ts +193 -0
  207. package/src/stealth/ed25519.ts +431 -0
  208. package/src/stealth/index.ts +233 -0
  209. package/src/stealth/meta-address.ts +221 -0
  210. package/src/stealth/secp256k1.ts +368 -0
  211. package/src/stealth/utils.ts +194 -0
  212. package/src/stealth.ts +50 -1504
  213. package/src/surveillance/algorithms/address-reuse.ts +143 -0
  214. package/src/surveillance/algorithms/cluster.ts +247 -0
  215. package/src/surveillance/algorithms/exchange.ts +295 -0
  216. package/src/surveillance/algorithms/temporal.ts +337 -0
  217. package/src/surveillance/analyzer.ts +442 -0
  218. package/src/surveillance/index.ts +64 -0
  219. package/src/surveillance/scoring.ts +372 -0
  220. package/src/surveillance/types.ts +264 -0
  221. package/src/sync/index.ts +106 -0
  222. package/src/sync/manager.ts +504 -0
  223. package/src/sync/mock-provider.ts +318 -0
  224. package/src/sync/oblivious.ts +625 -0
  225. package/src/tokens/index.ts +15 -0
  226. package/src/tokens/registry.ts +301 -0
  227. package/src/utils/deprecation.ts +94 -0
  228. package/src/utils/index.ts +9 -0
  229. package/src/wallet/ethereum/index.ts +68 -0
  230. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  231. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  232. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  233. package/src/wallet/ethereum/types.ts +3 -1
  234. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  235. package/src/wallet/hardware/index.ts +10 -0
  236. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  237. package/src/wallet/index.ts +71 -0
  238. package/src/wallet/near/adapter.ts +626 -0
  239. package/src/wallet/near/index.ts +86 -0
  240. package/src/wallet/near/meteor-wallet.ts +1153 -0
  241. package/src/wallet/near/my-near-wallet.ts +790 -0
  242. package/src/wallet/near/wallet-selector.ts +702 -0
  243. package/src/wallet/solana/adapter.ts +6 -4
  244. package/src/wallet/solana/index.ts +13 -0
  245. package/src/wallet/solana/privacy-adapter.ts +567 -0
  246. package/src/wallet/sui/types.ts +6 -4
  247. package/src/zcash/rpc-client.ts +13 -6
  248. package/dist/chunk-3INS3PR5.mjs +0 -884
  249. package/dist/chunk-3OVABDRH.mjs +0 -17096
  250. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  251. package/dist/chunk-E6SZWREQ.mjs +0 -57
  252. package/dist/chunk-G33LB27A.mjs +0 -16166
  253. package/dist/chunk-HGU6HZRC.mjs +0 -231
  254. package/dist/chunk-L2K34JCU.mjs +0 -1496
  255. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  256. package/dist/constants-VOI7BSLK.mjs +0 -27
  257. package/dist/index-BYZbDjal.d.ts +0 -11390
  258. package/dist/index-CHB3KuOB.d.mts +0 -11859
  259. package/dist/index-CzWPI6Le.d.ts +0 -11859
  260. package/dist/index-xbWjohNq.d.mts +0 -11390
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Privacy-Aware Logger
3
+ *
4
+ * Provides logging utilities that automatically redact sensitive information
5
+ * like wallet addresses and transaction amounts to prevent privacy leaks
6
+ * through log aggregators or error reporting tools.
7
+ */
8
+
9
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'
10
+
11
+ export interface PrivacyLoggerConfig {
12
+ prefix?: string
13
+ level?: LogLevel
14
+ productionMode?: boolean
15
+ output?: (level: LogLevel, message: string) => void
16
+ silent?: boolean
17
+ }
18
+
19
+ export interface SensitiveData {
20
+ address?: string
21
+ from?: string
22
+ to?: string
23
+ owner?: string
24
+ amount?: bigint | number | string
25
+ signature?: string
26
+ txHash?: string
27
+ [key: string]: unknown
28
+ }
29
+
30
+ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
31
+ debug: 0,
32
+ info: 1,
33
+ warn: 2,
34
+ error: 3,
35
+ silent: 4,
36
+ }
37
+
38
+ const ADDRESS_FIELDS = new Set([
39
+ 'address', 'from', 'to', 'owner', 'sender', 'recipient', 'wallet', 'publicKey', 'pubkey',
40
+ ])
41
+
42
+ const TX_FIELDS = new Set([
43
+ 'signature', 'txHash', 'txSignature', 'transactionHash', 'computationId',
44
+ ])
45
+
46
+ export function redactAddress(address: string, chars: number = 4): string {
47
+ if (!address || typeof address !== 'string') return '[invalid]'
48
+ if (address.length <= chars * 2 + 3) return address
49
+ return `${address.slice(0, chars)}...${address.slice(-chars)}`
50
+ }
51
+
52
+ export function redactSignature(signature: string, chars: number = 6): string {
53
+ if (!signature || typeof signature !== 'string') return '[invalid]'
54
+ if (signature.length <= chars * 2 + 3) return signature
55
+ return `${signature.slice(0, chars)}...${signature.slice(-chars)}`
56
+ }
57
+
58
+ export function maskAmount(
59
+ amount: bigint | number | string,
60
+ decimals: number = 9,
61
+ symbol: string = ''
62
+ ): string {
63
+ let value: number
64
+ if (typeof amount === 'bigint') {
65
+ value = Number(amount) / 10 ** decimals
66
+ } else if (typeof amount === 'string') {
67
+ value = parseFloat(amount)
68
+ if (isNaN(value)) return '[hidden]'
69
+ } else {
70
+ value = amount / 10 ** decimals
71
+ }
72
+
73
+ let range: string
74
+ if (value <= 0) range = '0'
75
+ else if (value < 0.01) range = '<0.01'
76
+ else if (value < 0.1) range = '0.01-0.1'
77
+ else if (value < 1) range = '0.1-1'
78
+ else if (value < 10) range = '1-10'
79
+ else if (value < 100) range = '10-100'
80
+ else if (value < 1000) range = '100-1K'
81
+ else if (value < 10000) range = '1K-10K'
82
+ else if (value < 100000) range = '10K-100K'
83
+ else range = '>100K'
84
+
85
+ return symbol ? `${range} ${symbol}` : range
86
+ }
87
+
88
+ export function redactSensitiveData(
89
+ data: SensitiveData,
90
+ productionMode: boolean = false
91
+ ): Record<string, string | undefined> {
92
+ const result: Record<string, string | undefined> = {}
93
+ for (const [key, value] of Object.entries(data)) {
94
+ if (value === undefined || value === null) {
95
+ result[key] = undefined
96
+ continue
97
+ }
98
+ if (ADDRESS_FIELDS.has(key) && typeof value === 'string') {
99
+ result[key] = redactAddress(value)
100
+ continue
101
+ }
102
+ if (TX_FIELDS.has(key) && typeof value === 'string') {
103
+ result[key] = redactSignature(value)
104
+ continue
105
+ }
106
+ if (key === 'amount' || key.toLowerCase().includes('amount')) {
107
+ if (typeof value === 'bigint' || typeof value === 'number' || typeof value === 'string') {
108
+ result[key] = productionMode ? '[hidden]' : maskAmount(value)
109
+ }
110
+ continue
111
+ }
112
+ if (typeof value === 'string') result[key] = value
113
+ else if (typeof value === 'number' || typeof value === 'bigint') result[key] = value.toString()
114
+ else if (typeof value === 'boolean') result[key] = value.toString()
115
+ else result[key] = '[object]'
116
+ }
117
+ return result
118
+ }
119
+
120
+ export class PrivacyLogger {
121
+ private config: Required<PrivacyLoggerConfig>
122
+
123
+ constructor(config: PrivacyLoggerConfig = {}) {
124
+ this.config = {
125
+ prefix: config.prefix ?? '',
126
+ level: config.level ?? 'info',
127
+ productionMode: config.productionMode ?? false,
128
+ silent: config.silent ?? false,
129
+ output: config.output ?? this.defaultOutput.bind(this),
130
+ }
131
+ }
132
+
133
+ private defaultOutput(level: LogLevel, message: string): void {
134
+ switch (level) {
135
+ case 'debug': console.debug(message); break
136
+ case 'info': console.info(message); break
137
+ case 'warn': console.warn(message); break
138
+ case 'error': console.error(message); break
139
+ }
140
+ }
141
+
142
+ private shouldLog(level: LogLevel): boolean {
143
+ if (this.config.silent) return false
144
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.config.level]
145
+ }
146
+
147
+ private formatMessage(message: string, data?: SensitiveData): string {
148
+ const prefix = this.config.prefix ? `${this.config.prefix} ` : ''
149
+ if (!data || Object.keys(data).length === 0) return `${prefix}${message}`
150
+ const redacted = redactSensitiveData(data, this.config.productionMode)
151
+ const dataStr = Object.entries(redacted)
152
+ .filter(([, v]) => v !== undefined)
153
+ .map(([k, v]) => `${k}=${v}`)
154
+ .join(' ')
155
+ return `${prefix}${message} ${dataStr}`
156
+ }
157
+
158
+ debug(message: string, data?: SensitiveData): void {
159
+ if (this.shouldLog('debug')) this.config.output('debug', this.formatMessage(message, data))
160
+ }
161
+
162
+ info(message: string, data?: SensitiveData): void {
163
+ if (this.shouldLog('info')) this.config.output('info', this.formatMessage(message, data))
164
+ }
165
+
166
+ warn(message: string, data?: SensitiveData): void {
167
+ if (this.shouldLog('warn')) this.config.output('warn', this.formatMessage(message, data))
168
+ }
169
+
170
+ error(message: string, data?: SensitiveData): void {
171
+ if (this.shouldLog('error')) this.config.output('error', this.formatMessage(message, data))
172
+ }
173
+
174
+ configure(config: Partial<PrivacyLoggerConfig>): void {
175
+ this.config = { ...this.config, ...config }
176
+ }
177
+
178
+ child(prefix: string): PrivacyLogger {
179
+ const childPrefix = this.config.prefix ? `${this.config.prefix}${prefix}` : prefix
180
+ return new PrivacyLogger({ ...this.config, prefix: childPrefix })
181
+ }
182
+ }
183
+
184
+ export const privacyLogger = new PrivacyLogger({ prefix: '[SIP]', level: 'warn' })
185
+
186
+ export function createPrivacyLogger(
187
+ moduleName: string,
188
+ config: Partial<PrivacyLoggerConfig> = {}
189
+ ): PrivacyLogger {
190
+ return new PrivacyLogger({ prefix: `[${moduleName}]`, level: 'warn', ...config })
191
+ }
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Production Safety Checks
3
+ *
4
+ * Runtime validation to detect development-only configurations in production.
5
+ * Prevents accidental localhost URLs, default credentials, and other dev-only
6
+ * settings from being used in production deployments.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { validateProductionConfig, isProductionEnvironment } from '@sip-protocol/sdk'
11
+ *
12
+ * // Check if running in production
13
+ * if (isProductionEnvironment()) {
14
+ * // Validate config throws if localhost URLs detected
15
+ * validateProductionConfig({
16
+ * rpcEndpoint: process.env.RPC_ENDPOINT,
17
+ * apiUrl: process.env.API_URL,
18
+ * })
19
+ * }
20
+ *
21
+ * // Or use assertNoLocalhost for individual URLs
22
+ * const endpoint = assertNoLocalhost(
23
+ * process.env.RPC_ENDPOINT || 'http://localhost:8899',
24
+ * 'RPC_ENDPOINT'
25
+ * )
26
+ * ```
27
+ */
28
+
29
+ // ─── Constants ──────────────────────────────────────────────────────────────────
30
+
31
+ /**
32
+ * Patterns that indicate localhost/development URLs
33
+ */
34
+ const LOCALHOST_PATTERNS = [
35
+ /^https?:\/\/localhost(:\d+)?/i,
36
+ /^https?:\/\/127\.0\.0\.1(:\d+)?/i,
37
+ /^https?:\/\/0\.0\.0\.0(:\d+)?/i,
38
+ /^https?:\/\/\[::1\](:\d+)?/i,
39
+ /^https?:\/\/host\.docker\.internal(:\d+)?/i,
40
+ ]
41
+
42
+ /**
43
+ * Environment variable that can override localhost safety checks
44
+ */
45
+ const ALLOW_LOCALHOST_ENV = 'SIP_ALLOW_LOCALHOST_IN_PROD'
46
+
47
+ // ─── Environment Detection ──────────────────────────────────────────────────────
48
+
49
+ /**
50
+ * Check if running in a production environment
51
+ *
52
+ * Production is detected when:
53
+ * - NODE_ENV === 'production'
54
+ * - SIP_ENV === 'production'
55
+ *
56
+ * @returns true if production environment detected
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * if (isProductionEnvironment()) {
61
+ * console.log('Running in production mode')
62
+ * }
63
+ * ```
64
+ */
65
+ export function isProductionEnvironment(): boolean {
66
+ if (typeof process === 'undefined' || !process.env) {
67
+ // Browser without process - check window location
68
+ if (typeof window !== 'undefined' && window.location) {
69
+ const hostname = window.location.hostname
70
+ // Not localhost = likely production
71
+ return !isLocalhostUrl(`https://${hostname}`)
72
+ }
73
+ return false
74
+ }
75
+
76
+ const nodeEnv = process.env.NODE_ENV?.toLowerCase()
77
+ const sipEnv = process.env.SIP_ENV?.toLowerCase()
78
+
79
+ return nodeEnv === 'production' || sipEnv === 'production'
80
+ }
81
+
82
+ /**
83
+ * Check if localhost URLs are explicitly allowed in production
84
+ *
85
+ * @returns true if SIP_ALLOW_LOCALHOST_IN_PROD=true
86
+ */
87
+ export function isLocalhostAllowed(): boolean {
88
+ if (typeof process === 'undefined' || !process.env) {
89
+ return false
90
+ }
91
+ return process.env[ALLOW_LOCALHOST_ENV] === 'true'
92
+ }
93
+
94
+ // ─── URL Validation ─────────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Check if a URL points to localhost
98
+ *
99
+ * @param url - URL to check
100
+ * @returns true if URL is localhost
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * isLocalhostUrl('http://localhost:8899') // true
105
+ * isLocalhostUrl('https://api.mainnet.solana.com') // false
106
+ * ```
107
+ */
108
+ export function isLocalhostUrl(url: string): boolean {
109
+ return LOCALHOST_PATTERNS.some((pattern) => pattern.test(url))
110
+ }
111
+
112
+ /**
113
+ * Result from validateProductionConfig
114
+ */
115
+ export interface ProductionConfigValidationResult {
116
+ valid: boolean
117
+ errors: ProductionConfigError[]
118
+ warnings: ProductionConfigWarning[]
119
+ }
120
+
121
+ /**
122
+ * Error found during production config validation
123
+ */
124
+ export interface ProductionConfigError {
125
+ key: string
126
+ value: string
127
+ message: string
128
+ }
129
+
130
+ /**
131
+ * Warning found during production config validation
132
+ */
133
+ export interface ProductionConfigWarning {
134
+ key: string
135
+ message: string
136
+ }
137
+
138
+ /**
139
+ * Error thrown when production validation fails
140
+ */
141
+ export class ProductionSafetyError extends Error {
142
+ constructor(
143
+ message: string,
144
+ public readonly errors: ProductionConfigError[]
145
+ ) {
146
+ super(message)
147
+ this.name = 'ProductionSafetyError'
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Validate configuration for production safety
153
+ *
154
+ * In production mode:
155
+ * - Throws if any URL value contains localhost
156
+ * - Can be bypassed with SIP_ALLOW_LOCALHOST_IN_PROD=true
157
+ *
158
+ * In non-production mode:
159
+ * - Returns validation result without throwing
160
+ * - Logs warnings for localhost URLs
161
+ *
162
+ * @param config - Configuration object to validate (key-value pairs)
163
+ * @param options - Validation options
164
+ * @returns Validation result with errors and warnings
165
+ * @throws ProductionSafetyError if production mode and localhost URLs found
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * // Validates all URL-like values in config
170
+ * validateProductionConfig({
171
+ * rpcEndpoint: 'http://localhost:8899', // Error in production
172
+ * apiUrl: 'https://api.example.com', // OK
173
+ * name: 'my-app', // Ignored (not a URL)
174
+ * })
175
+ * ```
176
+ */
177
+ export function validateProductionConfig(
178
+ config: Record<string, unknown>,
179
+ options: {
180
+ /** Only validate these keys (defaults to all string values that look like URLs) */
181
+ keys?: string[]
182
+ /** Throw in production even if localhost allowed (for critical configs) */
183
+ strict?: boolean
184
+ } = {}
185
+ ): ProductionConfigValidationResult {
186
+ const isProduction = isProductionEnvironment()
187
+ const localhostAllowed = isLocalhostAllowed()
188
+ const errors: ProductionConfigError[] = []
189
+ const warnings: ProductionConfigWarning[] = []
190
+
191
+ // Determine which keys to validate
192
+ const keysToValidate = options.keys ?? Object.keys(config)
193
+
194
+ for (const key of keysToValidate) {
195
+ const value = config[key]
196
+
197
+ // Skip non-string values
198
+ if (typeof value !== 'string') {
199
+ continue
200
+ }
201
+
202
+ // Skip values that don't look like URLs
203
+ if (!value.startsWith('http://') && !value.startsWith('https://')) {
204
+ continue
205
+ }
206
+
207
+ // Check for localhost
208
+ if (isLocalhostUrl(value)) {
209
+ if (isProduction && !localhostAllowed) {
210
+ errors.push({
211
+ key,
212
+ value: maskSensitiveUrl(value),
213
+ message: `Localhost URL detected in production for '${key}'. Set a production URL or ${ALLOW_LOCALHOST_ENV}=true to override.`,
214
+ })
215
+ } else if (isProduction && localhostAllowed && options.strict) {
216
+ errors.push({
217
+ key,
218
+ value: maskSensitiveUrl(value),
219
+ message: `Localhost URL not allowed for '${key}' even with ${ALLOW_LOCALHOST_ENV}=true (strict mode).`,
220
+ })
221
+ } else if (isProduction) {
222
+ warnings.push({
223
+ key,
224
+ message: `Using localhost URL for '${key}' in production (allowed via ${ALLOW_LOCALHOST_ENV})`,
225
+ })
226
+ }
227
+ }
228
+ }
229
+
230
+ const result: ProductionConfigValidationResult = {
231
+ valid: errors.length === 0,
232
+ errors,
233
+ warnings,
234
+ }
235
+
236
+ // Throw in production if errors found
237
+ if (!result.valid && isProduction) {
238
+ const errorMessages = errors.map((e) => ` - ${e.key}: ${e.message}`).join('\n')
239
+ throw new ProductionSafetyError(
240
+ `Production safety check failed:\n${errorMessages}`,
241
+ errors
242
+ )
243
+ }
244
+
245
+ return result
246
+ }
247
+
248
+ /**
249
+ * Assert that a URL is not localhost in production
250
+ *
251
+ * Convenience function for validating individual URLs.
252
+ *
253
+ * @param url - URL to validate
254
+ * @param name - Name of the config key (for error messages)
255
+ * @returns The URL if valid
256
+ * @throws ProductionSafetyError if localhost in production
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * // Throws in production if localhost
261
+ * const endpoint = assertNoLocalhost(
262
+ * process.env.RPC_ENDPOINT || 'http://localhost:8899',
263
+ * 'RPC_ENDPOINT'
264
+ * )
265
+ * ```
266
+ */
267
+ export function assertNoLocalhost(url: string, name: string): string {
268
+ validateProductionConfig({ [name]: url }, { keys: [name] })
269
+ return url
270
+ }
271
+
272
+ /**
273
+ * Get a URL with production fallback
274
+ *
275
+ * In production:
276
+ * - If primary is localhost, throws error
277
+ * - Returns primary if valid
278
+ *
279
+ * In development:
280
+ * - Returns primary (even if localhost)
281
+ *
282
+ * @param primary - Primary URL (may be localhost in dev)
283
+ * @param name - Name of the config key
284
+ * @returns Valid URL for the current environment
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * const rpcEndpoint = getProductionUrl(
289
+ * process.env.RPC_ENDPOINT || 'http://localhost:8899',
290
+ * 'RPC_ENDPOINT'
291
+ * )
292
+ * ```
293
+ */
294
+ export function getProductionUrl(primary: string, name: string): string {
295
+ if (isProductionEnvironment() && isLocalhostUrl(primary) && !isLocalhostAllowed()) {
296
+ throw new ProductionSafetyError(
297
+ `No production URL configured for '${name}'. Current value: ${maskSensitiveUrl(primary)}`,
298
+ [{ key: name, value: maskSensitiveUrl(primary), message: 'Localhost URL in production' }]
299
+ )
300
+ }
301
+ return primary
302
+ }
303
+
304
+ // ─── Utility Functions ──────────────────────────────────────────────────────────
305
+
306
+ /**
307
+ * Mask sensitive parts of a URL for safe logging
308
+ *
309
+ * @param url - URL to mask
310
+ * @returns URL with credentials and API keys masked
311
+ */
312
+ function maskSensitiveUrl(url: string): string {
313
+ try {
314
+ const parsed = new URL(url)
315
+
316
+ // Mask credentials
317
+ if (parsed.username || parsed.password) {
318
+ parsed.username = '***'
319
+ parsed.password = ''
320
+ }
321
+
322
+ // Mask API keys in query params
323
+ const sensitiveParams = ['api-key', 'apikey', 'key', 'token', 'secret']
324
+ for (const param of sensitiveParams) {
325
+ if (parsed.searchParams.has(param)) {
326
+ parsed.searchParams.set(param, '***')
327
+ }
328
+ }
329
+
330
+ return parsed.toString()
331
+ } catch {
332
+ // If URL parsing fails, return as-is (it's likely just localhost anyway)
333
+ return url
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Create a production-safe configuration helper
339
+ *
340
+ * Returns a function that validates URLs and provides defaults.
341
+ *
342
+ * @param defaults - Default values for non-production environments
343
+ * @returns Configuration getter function
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * const getConfig = createProductionConfig({
348
+ * rpcEndpoint: 'http://localhost:8899',
349
+ * apiUrl: 'http://localhost:3000',
350
+ * })
351
+ *
352
+ * // In dev: returns localhost
353
+ * // In prod: throws if env vars not set
354
+ * const rpc = getConfig('rpcEndpoint', process.env.RPC_ENDPOINT)
355
+ * ```
356
+ */
357
+ export function createProductionConfig<T extends Record<string, string>>(
358
+ defaults: T
359
+ ): <K extends keyof T>(key: K, envValue?: string) => string {
360
+ return <K extends keyof T>(key: K, envValue?: string): string => {
361
+ const value = envValue ?? defaults[key]
362
+
363
+ if (isProductionEnvironment() && (!envValue || isLocalhostUrl(value)) && !isLocalhostAllowed()) {
364
+ throw new ProductionSafetyError(
365
+ `Production configuration required for '${String(key)}'. ` +
366
+ `Set the environment variable or ${ALLOW_LOCALHOST_ENV}=true to use defaults.`,
367
+ [{ key: String(key), value: maskSensitiveUrl(value), message: 'Missing production configuration' }]
368
+ )
369
+ }
370
+
371
+ return value
372
+ }
373
+ }