@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,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
+ }