@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,1153 @@
1
+ /**
2
+ * Meteor Wallet Privacy Integration
3
+ *
4
+ * Specific integration for Meteor wallet with privacy transaction support.
5
+ * Handles both browser extension and mobile deep link signing flows.
6
+ *
7
+ * @example Browser extension usage
8
+ * ```typescript
9
+ * import { MeteorWalletPrivacy } from '@sip-protocol/sdk'
10
+ *
11
+ * const wallet = new MeteorWalletPrivacy({
12
+ * network: 'mainnet',
13
+ * })
14
+ *
15
+ * // Connect via extension
16
+ * await wallet.connect()
17
+ *
18
+ * // Send private transfer
19
+ * await wallet.sendPrivateTransfer({
20
+ * recipientMetaAddress: 'sip:near:0x...',
21
+ * amount: '1000000000000000000000000',
22
+ * })
23
+ * ```
24
+ *
25
+ * @example Mobile deep link usage
26
+ * ```typescript
27
+ * const wallet = new MeteorWalletPrivacy({
28
+ * network: 'mainnet',
29
+ * callbackUrl: 'myapp://callback',
30
+ * })
31
+ *
32
+ * // Get deep link for mobile signing
33
+ * const deepLink = wallet.getPrivateTransferDeepLink({
34
+ * recipientMetaAddress: 'sip:near:0x...',
35
+ * amount: '1000000000000000000000000',
36
+ * })
37
+ *
38
+ * // Open in mobile app
39
+ * window.location.href = deepLink
40
+ * ```
41
+ *
42
+ * @packageDocumentation
43
+ */
44
+
45
+ import { sha256 } from '@noble/hashes/sha2'
46
+ import { bytesToHex } from '@noble/hashes/utils'
47
+ import { ed25519 } from '@noble/curves/ed25519'
48
+ import type { HexString, StealthMetaAddress, StealthAddress } from '@sip-protocol/types'
49
+ import type { NEARNetwork } from '../../chains/near/constants'
50
+ import {
51
+ generateNEARStealthAddress,
52
+ deriveNEARStealthPrivateKey,
53
+ checkNEARStealthAddress,
54
+ encodeNEARStealthMetaAddress,
55
+ parseNEARStealthMetaAddress,
56
+ } from '../../chains/near/stealth'
57
+ import { buildPrivateTransfer } from '../../chains/near/implicit-account'
58
+
59
+ // ─── Constants ────────────────────────────────────────────────────────────────
60
+
61
+ /** Meteor wallet deep link scheme */
62
+ export const METEOR_DEEP_LINK_SCHEME = 'meteorwallet://'
63
+
64
+ /** Meteor wallet mainnet app link */
65
+ export const METEOR_APP_LINK_MAINNET = 'https://app.meteorwallet.app'
66
+
67
+ /** Meteor wallet testnet app link */
68
+ export const METEOR_APP_LINK_TESTNET = 'https://testnet.meteorwallet.app'
69
+
70
+ /** Meteor wallet provider key in window object */
71
+ export const METEOR_PROVIDER_KEY = 'meteorWallet'
72
+
73
+ // ─── Types ────────────────────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Meteor wallet provider interface (injected in browser extension)
77
+ */
78
+ export interface MeteorWalletProvider {
79
+ /** Check if connected */
80
+ isConnected(): Promise<boolean>
81
+ /** Request connection */
82
+ requestSignIn(options?: {
83
+ contractId?: string
84
+ methodNames?: string[]
85
+ }): Promise<{
86
+ accountId: string
87
+ publicKey: string
88
+ }>
89
+ /** Sign out */
90
+ signOut(): Promise<void>
91
+ /** Get account ID */
92
+ getAccountId(): Promise<string | null>
93
+ /** Sign and send transaction */
94
+ signAndSendTransaction(params: {
95
+ receiverId: string
96
+ actions: Array<{
97
+ type: string
98
+ params: Record<string, unknown>
99
+ }>
100
+ }): Promise<{
101
+ transactionHash: string
102
+ }>
103
+ /** Sign and send multiple transactions */
104
+ signAndSendTransactions(params: {
105
+ transactions: Array<{
106
+ receiverId: string
107
+ actions: Array<{
108
+ type: string
109
+ params: Record<string, unknown>
110
+ }>
111
+ }>
112
+ }): Promise<Array<{ transactionHash: string }>>
113
+ /** Sign message */
114
+ signMessage(params: {
115
+ message: string
116
+ recipient?: string
117
+ nonce?: Uint8Array
118
+ }): Promise<{
119
+ signature: string
120
+ publicKey: string
121
+ accountId: string
122
+ }>
123
+ /** Simulate transaction */
124
+ simulateTransaction?(params: {
125
+ receiverId: string
126
+ actions: Array<{
127
+ type: string
128
+ params: Record<string, unknown>
129
+ }>
130
+ }): Promise<{
131
+ success: boolean
132
+ gasUsed: string
133
+ result?: unknown
134
+ error?: string
135
+ }>
136
+ }
137
+
138
+ /**
139
+ * Meteor wallet configuration
140
+ */
141
+ export interface MeteorWalletConfig {
142
+ /** NEAR network */
143
+ network: NEARNetwork
144
+ /** Callback URL for mobile deep links (optional) */
145
+ callbackUrl?: string
146
+ /** Contract ID for function calls (optional) */
147
+ contractId?: string
148
+ /** Prefer extension over mobile if both available */
149
+ preferExtension?: boolean
150
+ }
151
+
152
+ /**
153
+ * Connection state
154
+ */
155
+ export type MeteorConnectionState =
156
+ | 'disconnected'
157
+ | 'connecting'
158
+ | 'connected'
159
+ | 'signing'
160
+ | 'error'
161
+
162
+ /**
163
+ * Signing mode
164
+ */
165
+ export type MeteorSigningMode = 'extension' | 'deeplink' | 'unknown'
166
+
167
+ /**
168
+ * Privacy key pair
169
+ */
170
+ export interface MeteorPrivacyKeys {
171
+ spendingPrivateKey: HexString
172
+ spendingPublicKey: HexString
173
+ viewingPrivateKey: HexString
174
+ viewingPublicKey: HexString
175
+ derivedFrom: 'signature' | 'secret'
176
+ }
177
+
178
+ /**
179
+ * Private transfer parameters
180
+ */
181
+ export interface MeteorPrivateTransferParams {
182
+ /** Recipient's stealth meta-address */
183
+ recipientMetaAddress: string | StealthMetaAddress
184
+ /** Amount in yoctoNEAR */
185
+ amount: string | bigint
186
+ /** Optional memo */
187
+ memo?: string
188
+ /** Optional label for tracking */
189
+ label?: string
190
+ }
191
+
192
+ /**
193
+ * Transaction simulation result
194
+ */
195
+ export interface TransactionSimulation {
196
+ /** Would succeed */
197
+ success: boolean
198
+ /** Estimated gas used */
199
+ gasUsed: string
200
+ /** Formatted gas cost in NEAR */
201
+ gasCostNEAR: string
202
+ /** Result data (if successful) */
203
+ result?: unknown
204
+ /** Error message (if failed) */
205
+ error?: string
206
+ }
207
+
208
+ /**
209
+ * Transaction result
210
+ */
211
+ export interface MeteorTransactionResult {
212
+ /** Transaction hash */
213
+ transactionHash: string
214
+ /** Stealth account ID */
215
+ stealthAccountId: string
216
+ /** Announcement memo */
217
+ announcementMemo: string
218
+ /** Success status */
219
+ success: boolean
220
+ }
221
+
222
+ /**
223
+ * Multi-account info
224
+ */
225
+ export interface MeteorAccountInfo {
226
+ accountId: string
227
+ publicKey: string
228
+ hasPrivacyKeys: boolean
229
+ }
230
+
231
+ /**
232
+ * Meteor-specific error codes
233
+ */
234
+ export enum MeteorErrorCode {
235
+ USER_REJECTED = 'USER_REJECTED',
236
+ NOT_CONNECTED = 'NOT_CONNECTED',
237
+ EXTENSION_NOT_FOUND = 'EXTENSION_NOT_FOUND',
238
+ INVALID_PARAMS = 'INVALID_PARAMS',
239
+ SIMULATION_FAILED = 'SIMULATION_FAILED',
240
+ TRANSACTION_FAILED = 'TRANSACTION_FAILED',
241
+ SIGNING_FAILED = 'SIGNING_FAILED',
242
+ NETWORK_ERROR = 'NETWORK_ERROR',
243
+ UNKNOWN = 'UNKNOWN',
244
+ }
245
+
246
+ /**
247
+ * Meteor wallet error
248
+ */
249
+ export class MeteorWalletError extends Error {
250
+ constructor(
251
+ message: string,
252
+ public readonly code: MeteorErrorCode,
253
+ public readonly originalError?: unknown
254
+ ) {
255
+ super(message)
256
+ this.name = 'MeteorWalletError'
257
+ }
258
+ }
259
+
260
+ // ─── Meteor Wallet Privacy Class ──────────────────────────────────────────────
261
+
262
+ /**
263
+ * Meteor Wallet Privacy Integration
264
+ *
265
+ * Provides privacy transaction support for Meteor wallet.
266
+ */
267
+ export class MeteorWalletPrivacy {
268
+ private config: MeteorWalletConfig
269
+ private connectionState: MeteorConnectionState = 'disconnected'
270
+ private signingMode: MeteorSigningMode = 'unknown'
271
+ private accountId: string | null = null
272
+ private publicKey: string | null = null
273
+ private privacyKeys: MeteorPrivacyKeys | null = null
274
+ private accounts: Map<string, MeteorAccountInfo> = new Map()
275
+ private provider: MeteorWalletProvider | null = null
276
+
277
+ constructor(config: MeteorWalletConfig) {
278
+ this.config = {
279
+ preferExtension: true,
280
+ ...config,
281
+ }
282
+
283
+ // Try to get extension provider if in browser
284
+ if (typeof window !== 'undefined') {
285
+ this.provider = (window as unknown as Record<string, unknown>)[METEOR_PROVIDER_KEY] as MeteorWalletProvider | null ?? null
286
+ }
287
+ }
288
+
289
+ // ─── Connection ─────────────────────────────────────────────────────────────
290
+
291
+ /**
292
+ * Check if extension is available
293
+ */
294
+ isExtensionAvailable(): boolean {
295
+ return this.provider !== null
296
+ }
297
+
298
+ /**
299
+ * Get signing mode
300
+ */
301
+ getSigningMode(): MeteorSigningMode {
302
+ return this.signingMode
303
+ }
304
+
305
+ /**
306
+ * Connect via extension
307
+ */
308
+ async connectExtension(): Promise<void> {
309
+ if (!this.provider) {
310
+ throw new MeteorWalletError(
311
+ 'Meteor wallet extension not found',
312
+ MeteorErrorCode.EXTENSION_NOT_FOUND
313
+ )
314
+ }
315
+
316
+ this.connectionState = 'connecting'
317
+ this.signingMode = 'extension'
318
+
319
+ try {
320
+ const result = await this.provider.requestSignIn({
321
+ contractId: this.config.contractId,
322
+ })
323
+
324
+ this.accountId = result.accountId
325
+ this.publicKey = result.publicKey
326
+ this.connectionState = 'connected'
327
+
328
+ // Track account
329
+ this.accounts.set(result.accountId, {
330
+ accountId: result.accountId,
331
+ publicKey: result.publicKey,
332
+ hasPrivacyKeys: false,
333
+ })
334
+ } catch (error) {
335
+ this.connectionState = 'error'
336
+ throw this.wrapError(error)
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Connect (auto-detect mode)
342
+ */
343
+ async connect(): Promise<void> {
344
+ // Prefer extension if available and configured
345
+ if (this.config.preferExtension && this.provider) {
346
+ return this.connectExtension()
347
+ }
348
+
349
+ // Fall back to extension if available
350
+ if (this.provider) {
351
+ return this.connectExtension()
352
+ }
353
+
354
+ // No extension, must use deep links
355
+ if (!this.config.callbackUrl) {
356
+ throw new MeteorWalletError(
357
+ 'No extension available and no callback URL configured for deep links',
358
+ MeteorErrorCode.EXTENSION_NOT_FOUND
359
+ )
360
+ }
361
+
362
+ this.signingMode = 'deeplink'
363
+ throw new MeteorWalletError(
364
+ 'Deep link connection requires redirect. Use getConnectDeepLink() instead.',
365
+ MeteorErrorCode.INVALID_PARAMS
366
+ )
367
+ }
368
+
369
+ /**
370
+ * Get deep link for connection (mobile)
371
+ */
372
+ getConnectDeepLink(): string {
373
+ if (!this.config.callbackUrl) {
374
+ throw new MeteorWalletError(
375
+ 'Callback URL required for deep links',
376
+ MeteorErrorCode.INVALID_PARAMS
377
+ )
378
+ }
379
+
380
+ const params = new URLSearchParams({
381
+ network: this.config.network,
382
+ callback: this.config.callbackUrl,
383
+ })
384
+
385
+ if (this.config.contractId) {
386
+ params.set('contractId', this.config.contractId)
387
+ }
388
+
389
+ return `${METEOR_DEEP_LINK_SCHEME}connect?${params.toString()}`
390
+ }
391
+
392
+ /**
393
+ * Handle deep link callback
394
+ */
395
+ handleDeepLinkCallback(params: URLSearchParams): boolean {
396
+ const accountId = params.get('accountId')
397
+ const publicKey = params.get('publicKey')
398
+ const error = params.get('error')
399
+
400
+ if (error) {
401
+ this.connectionState = 'error'
402
+ return false
403
+ }
404
+
405
+ if (accountId) {
406
+ this.accountId = accountId
407
+ this.publicKey = publicKey
408
+ this.connectionState = 'connected'
409
+ this.signingMode = 'deeplink'
410
+
411
+ this.accounts.set(accountId, {
412
+ accountId,
413
+ publicKey: publicKey ?? '',
414
+ hasPrivacyKeys: false,
415
+ })
416
+
417
+ return true
418
+ }
419
+
420
+ this.connectionState = 'error'
421
+ return false
422
+ }
423
+
424
+ /**
425
+ * Set connection manually (for restoring from storage)
426
+ */
427
+ setConnection(
428
+ accountId: string,
429
+ publicKey?: string,
430
+ mode: MeteorSigningMode = 'unknown'
431
+ ): void {
432
+ this.accountId = accountId
433
+ this.publicKey = publicKey ?? null
434
+ this.connectionState = 'connected'
435
+ this.signingMode = mode
436
+
437
+ this.accounts.set(accountId, {
438
+ accountId,
439
+ publicKey: publicKey ?? '',
440
+ hasPrivacyKeys: this.privacyKeys !== null,
441
+ })
442
+ }
443
+
444
+ /**
445
+ * Disconnect
446
+ */
447
+ async disconnect(): Promise<void> {
448
+ if (this.provider && this.signingMode === 'extension') {
449
+ try {
450
+ await this.provider.signOut()
451
+ } catch {
452
+ // Ignore sign out errors
453
+ }
454
+ }
455
+
456
+ this.accountId = null
457
+ this.publicKey = null
458
+ this.privacyKeys = null
459
+ this.connectionState = 'disconnected'
460
+ this.signingMode = 'unknown'
461
+ this.accounts.clear()
462
+ }
463
+
464
+ /**
465
+ * Get connection state
466
+ */
467
+ getConnectionState(): MeteorConnectionState {
468
+ return this.connectionState
469
+ }
470
+
471
+ /**
472
+ * Check if connected
473
+ */
474
+ isConnected(): boolean {
475
+ return this.connectionState === 'connected' && this.accountId !== null
476
+ }
477
+
478
+ /**
479
+ * Get account ID
480
+ */
481
+ getAccountId(): string | null {
482
+ return this.accountId
483
+ }
484
+
485
+ /**
486
+ * Get public key
487
+ */
488
+ getPublicKey(): string | null {
489
+ return this.publicKey
490
+ }
491
+
492
+ // ─── Multi-Account Support ──────────────────────────────────────────────────
493
+
494
+ /**
495
+ * Get all connected accounts
496
+ */
497
+ getAccounts(): MeteorAccountInfo[] {
498
+ return Array.from(this.accounts.values())
499
+ }
500
+
501
+ /**
502
+ * Switch active account
503
+ */
504
+ switchAccount(accountId: string): void {
505
+ const account = this.accounts.get(accountId)
506
+ if (!account) {
507
+ throw new MeteorWalletError(
508
+ `Account ${accountId} not found`,
509
+ MeteorErrorCode.INVALID_PARAMS
510
+ )
511
+ }
512
+
513
+ this.accountId = account.accountId
514
+ this.publicKey = account.publicKey
515
+ this.privacyKeys = null // Reset privacy keys for new account
516
+ }
517
+
518
+ /**
519
+ * Add account
520
+ */
521
+ addAccount(accountId: string, publicKey: string): void {
522
+ this.accounts.set(accountId, {
523
+ accountId,
524
+ publicKey,
525
+ hasPrivacyKeys: false,
526
+ })
527
+ }
528
+
529
+ // ─── Privacy Key Management ─────────────────────────────────────────────────
530
+
531
+ /**
532
+ * Derive privacy keys from signature (more secure)
533
+ */
534
+ async derivePrivacyKeysFromSignature(
535
+ message: string = 'SIP Privacy Key Derivation'
536
+ ): Promise<MeteorPrivacyKeys> {
537
+ if (!this.accountId) {
538
+ throw new MeteorWalletError(
539
+ 'Not connected to Meteor wallet',
540
+ MeteorErrorCode.NOT_CONNECTED
541
+ )
542
+ }
543
+
544
+ if (!this.provider) {
545
+ throw new MeteorWalletError(
546
+ 'Extension required for signature-based key derivation',
547
+ MeteorErrorCode.EXTENSION_NOT_FOUND
548
+ )
549
+ }
550
+
551
+ try {
552
+ const nonce = new Uint8Array(32)
553
+ if (typeof crypto !== 'undefined') {
554
+ crypto.getRandomValues(nonce)
555
+ }
556
+
557
+ const result = await this.provider.signMessage({
558
+ message: `${message}:${this.accountId}:${this.config.network}`,
559
+ nonce,
560
+ })
561
+
562
+ // Use signature as entropy for key derivation
563
+ const signatureBytes = hexToBytes(result.signature)
564
+ const entropy = sha256(signatureBytes)
565
+
566
+ return this.deriveKeysFromEntropy(entropy, 'signature')
567
+ } catch (error) {
568
+ throw this.wrapError(error)
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Derive privacy keys from secret (works for both modes)
574
+ */
575
+ derivePrivacyKeysFromSecret(secret: string): MeteorPrivacyKeys {
576
+ if (!this.accountId) {
577
+ throw new MeteorWalletError(
578
+ 'Not connected to Meteor wallet',
579
+ MeteorErrorCode.NOT_CONNECTED
580
+ )
581
+ }
582
+
583
+ // Create deterministic entropy from account + secret
584
+ const derivationPath = `sip/meteor/${this.config.network}/${this.accountId}`
585
+ const entropy = sha256(
586
+ new TextEncoder().encode(`${derivationPath}:${secret}`)
587
+ )
588
+
589
+ return this.deriveKeysFromEntropy(entropy, 'secret')
590
+ }
591
+
592
+ /**
593
+ * Internal: derive keys from entropy
594
+ */
595
+ private deriveKeysFromEntropy(
596
+ entropy: Uint8Array,
597
+ derivedFrom: 'signature' | 'secret'
598
+ ): MeteorPrivacyKeys {
599
+ // Derive spending key
600
+ const spendingEntropy = sha256(new Uint8Array([...entropy, 0x01]))
601
+ const spendingPrivateKey = clampScalar(spendingEntropy)
602
+ const spendingPublicKey = ed25519.getPublicKey(spendingPrivateKey)
603
+
604
+ // Derive viewing key
605
+ const viewingEntropy = sha256(new Uint8Array([...entropy, 0x02]))
606
+ const viewingPrivateKey = clampScalar(viewingEntropy)
607
+ const viewingPublicKey = ed25519.getPublicKey(viewingPrivateKey)
608
+
609
+ const keys: MeteorPrivacyKeys = {
610
+ spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
611
+ spendingPublicKey: `0x${bytesToHex(spendingPublicKey)}` as HexString,
612
+ viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
613
+ viewingPublicKey: `0x${bytesToHex(viewingPublicKey)}` as HexString,
614
+ derivedFrom,
615
+ }
616
+
617
+ this.privacyKeys = keys
618
+
619
+ // Update account info
620
+ if (this.accountId) {
621
+ const account = this.accounts.get(this.accountId)
622
+ if (account) {
623
+ account.hasPrivacyKeys = true
624
+ }
625
+ }
626
+
627
+ return keys
628
+ }
629
+
630
+ /**
631
+ * Check if privacy keys are derived
632
+ */
633
+ hasPrivacyKeys(): boolean {
634
+ return this.privacyKeys !== null
635
+ }
636
+
637
+ /**
638
+ * Get privacy keys
639
+ */
640
+ getPrivacyKeys(): MeteorPrivacyKeys | null {
641
+ return this.privacyKeys
642
+ }
643
+
644
+ // ─── Stealth Address Operations ─────────────────────────────────────────────
645
+
646
+ /**
647
+ * Generate stealth meta-address
648
+ */
649
+ generateStealthMetaAddress(label?: string): {
650
+ metaAddress: StealthMetaAddress
651
+ encoded: string
652
+ viewingPrivateKey: HexString
653
+ spendingPrivateKey: HexString
654
+ } {
655
+ if (!this.privacyKeys) {
656
+ throw new MeteorWalletError(
657
+ 'Privacy keys not derived',
658
+ MeteorErrorCode.NOT_CONNECTED
659
+ )
660
+ }
661
+
662
+ const metaAddress: StealthMetaAddress = {
663
+ chain: 'near',
664
+ spendingKey: this.privacyKeys.spendingPublicKey,
665
+ viewingKey: this.privacyKeys.viewingPublicKey,
666
+ label,
667
+ }
668
+
669
+ const encoded = encodeNEARStealthMetaAddress(metaAddress)
670
+
671
+ return {
672
+ metaAddress,
673
+ encoded,
674
+ viewingPrivateKey: this.privacyKeys.viewingPrivateKey,
675
+ spendingPrivateKey: this.privacyKeys.spendingPrivateKey,
676
+ }
677
+ }
678
+
679
+ /**
680
+ * Generate stealth address for receiving
681
+ */
682
+ generateStealthAddress(metaAddress: string | StealthMetaAddress): {
683
+ stealthAddress: StealthAddress
684
+ stealthAccountId: string
685
+ ephemeralPublicKey: HexString
686
+ } {
687
+ const meta = typeof metaAddress === 'string'
688
+ ? parseNEARStealthMetaAddress(metaAddress)
689
+ : metaAddress
690
+
691
+ const { stealthAddress, implicitAccountId } = generateNEARStealthAddress(meta)
692
+
693
+ return {
694
+ stealthAddress,
695
+ stealthAccountId: implicitAccountId,
696
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
697
+ }
698
+ }
699
+
700
+ /**
701
+ * Check stealth address ownership
702
+ */
703
+ checkStealthAddress(stealthAddress: StealthAddress): boolean {
704
+ if (!this.privacyKeys) {
705
+ throw new MeteorWalletError(
706
+ 'Privacy keys not derived',
707
+ MeteorErrorCode.NOT_CONNECTED
708
+ )
709
+ }
710
+
711
+ return checkNEARStealthAddress(
712
+ stealthAddress,
713
+ this.privacyKeys.spendingPrivateKey,
714
+ this.privacyKeys.viewingPrivateKey
715
+ )
716
+ }
717
+
718
+ /**
719
+ * Derive stealth private key
720
+ */
721
+ deriveStealthPrivateKey(stealthAddress: StealthAddress): HexString {
722
+ if (!this.privacyKeys) {
723
+ throw new MeteorWalletError(
724
+ 'Privacy keys not derived',
725
+ MeteorErrorCode.NOT_CONNECTED
726
+ )
727
+ }
728
+
729
+ const isOwner = this.checkStealthAddress(stealthAddress)
730
+ if (!isOwner) {
731
+ throw new MeteorWalletError(
732
+ 'Stealth address does not belong to this wallet',
733
+ MeteorErrorCode.INVALID_PARAMS
734
+ )
735
+ }
736
+
737
+ const recovery = deriveNEARStealthPrivateKey(
738
+ stealthAddress,
739
+ this.privacyKeys.spendingPrivateKey,
740
+ this.privacyKeys.viewingPrivateKey
741
+ )
742
+
743
+ return recovery.privateKey
744
+ }
745
+
746
+ // ─── Transaction Simulation ─────────────────────────────────────────────────
747
+
748
+ /**
749
+ * Simulate a private transfer (if supported by extension)
750
+ */
751
+ async simulatePrivateTransfer(
752
+ params: MeteorPrivateTransferParams
753
+ ): Promise<TransactionSimulation> {
754
+ if (!this.accountId) {
755
+ throw new MeteorWalletError(
756
+ 'Not connected to Meteor wallet',
757
+ MeteorErrorCode.NOT_CONNECTED
758
+ )
759
+ }
760
+
761
+ if (!this.provider?.simulateTransaction) {
762
+ // Return estimated simulation if not supported
763
+ return {
764
+ success: true,
765
+ gasUsed: '30000000000000', // ~30 TGas estimate
766
+ gasCostNEAR: '0.003',
767
+ result: undefined,
768
+ error: undefined,
769
+ }
770
+ }
771
+
772
+ const recipientMeta = typeof params.recipientMetaAddress === 'string'
773
+ ? parseNEARStealthMetaAddress(params.recipientMetaAddress)
774
+ : params.recipientMetaAddress
775
+
776
+ const amount = typeof params.amount === 'string' ? BigInt(params.amount) : params.amount
777
+ const transfer = buildPrivateTransfer(recipientMeta, amount)
778
+
779
+ try {
780
+ const result = await this.provider.simulateTransaction({
781
+ receiverId: transfer.stealthAccountId,
782
+ actions: transfer.actions.map((action) => ({
783
+ type: action.type,
784
+ params: serializeParams(action.params as unknown as Record<string, unknown>),
785
+ })),
786
+ })
787
+
788
+ return {
789
+ success: result.success,
790
+ gasUsed: result.gasUsed,
791
+ gasCostNEAR: formatGasCost(BigInt(result.gasUsed)),
792
+ result: result.result,
793
+ error: result.error,
794
+ }
795
+ } catch (error) {
796
+ throw new MeteorWalletError(
797
+ 'Transaction simulation failed',
798
+ MeteorErrorCode.SIMULATION_FAILED,
799
+ error
800
+ )
801
+ }
802
+ }
803
+
804
+ // ─── Private Transfers (Extension) ──────────────────────────────────────────
805
+
806
+ /**
807
+ * Send private transfer via extension
808
+ */
809
+ async sendPrivateTransfer(
810
+ params: MeteorPrivateTransferParams
811
+ ): Promise<MeteorTransactionResult> {
812
+ if (!this.accountId) {
813
+ throw new MeteorWalletError(
814
+ 'Not connected to Meteor wallet',
815
+ MeteorErrorCode.NOT_CONNECTED
816
+ )
817
+ }
818
+
819
+ if (!this.provider) {
820
+ throw new MeteorWalletError(
821
+ 'Extension required for sendPrivateTransfer. Use getPrivateTransferDeepLink() for mobile.',
822
+ MeteorErrorCode.EXTENSION_NOT_FOUND
823
+ )
824
+ }
825
+
826
+ const recipientMeta = typeof params.recipientMetaAddress === 'string'
827
+ ? parseNEARStealthMetaAddress(params.recipientMetaAddress)
828
+ : params.recipientMetaAddress
829
+
830
+ const amount = typeof params.amount === 'string' ? BigInt(params.amount) : params.amount
831
+ const transfer = buildPrivateTransfer(recipientMeta, amount)
832
+
833
+ this.connectionState = 'signing'
834
+
835
+ try {
836
+ const result = await this.provider.signAndSendTransaction({
837
+ receiverId: transfer.stealthAccountId,
838
+ actions: transfer.actions.map((action) => ({
839
+ type: action.type,
840
+ params: serializeParams(action.params as unknown as Record<string, unknown>),
841
+ })),
842
+ })
843
+
844
+ this.connectionState = 'connected'
845
+
846
+ return {
847
+ transactionHash: result.transactionHash,
848
+ stealthAccountId: transfer.stealthAccountId,
849
+ announcementMemo: transfer.announcementMemo,
850
+ success: true,
851
+ }
852
+ } catch (error) {
853
+ this.connectionState = 'connected'
854
+ throw this.wrapError(error)
855
+ }
856
+ }
857
+
858
+ /**
859
+ * Send batch private transfers via extension
860
+ */
861
+ async sendBatchPrivateTransfers(
862
+ transfers: MeteorPrivateTransferParams[]
863
+ ): Promise<MeteorTransactionResult[]> {
864
+ if (!this.accountId) {
865
+ throw new MeteorWalletError(
866
+ 'Not connected to Meteor wallet',
867
+ MeteorErrorCode.NOT_CONNECTED
868
+ )
869
+ }
870
+
871
+ if (!this.provider) {
872
+ throw new MeteorWalletError(
873
+ 'Extension required for sendBatchPrivateTransfers',
874
+ MeteorErrorCode.EXTENSION_NOT_FOUND
875
+ )
876
+ }
877
+
878
+ const transactions = transfers.map((params) => {
879
+ const recipientMeta = typeof params.recipientMetaAddress === 'string'
880
+ ? parseNEARStealthMetaAddress(params.recipientMetaAddress)
881
+ : params.recipientMetaAddress
882
+
883
+ const amount = typeof params.amount === 'string' ? BigInt(params.amount) : params.amount
884
+ return buildPrivateTransfer(recipientMeta, amount)
885
+ })
886
+
887
+ this.connectionState = 'signing'
888
+
889
+ try {
890
+ const results = await this.provider.signAndSendTransactions({
891
+ transactions: transactions.map((transfer) => ({
892
+ receiverId: transfer.stealthAccountId,
893
+ actions: transfer.actions.map((action) => ({
894
+ type: action.type,
895
+ params: serializeParams(action.params as unknown as Record<string, unknown>),
896
+ })),
897
+ })),
898
+ })
899
+
900
+ this.connectionState = 'connected'
901
+
902
+ return results.map((result, index) => ({
903
+ transactionHash: result.transactionHash,
904
+ stealthAccountId: transactions[index].stealthAccountId,
905
+ announcementMemo: transactions[index].announcementMemo,
906
+ success: true,
907
+ }))
908
+ } catch (error) {
909
+ this.connectionState = 'connected'
910
+ throw this.wrapError(error)
911
+ }
912
+ }
913
+
914
+ // ─── Private Transfers (Deep Link) ──────────────────────────────────────────
915
+
916
+ /**
917
+ * Get deep link for private transfer (mobile)
918
+ */
919
+ getPrivateTransferDeepLink(params: MeteorPrivateTransferParams): string {
920
+ if (!this.accountId) {
921
+ throw new MeteorWalletError(
922
+ 'Not connected to Meteor wallet',
923
+ MeteorErrorCode.NOT_CONNECTED
924
+ )
925
+ }
926
+
927
+ if (!this.config.callbackUrl) {
928
+ throw new MeteorWalletError(
929
+ 'Callback URL required for deep links',
930
+ MeteorErrorCode.INVALID_PARAMS
931
+ )
932
+ }
933
+
934
+ const recipientMeta = typeof params.recipientMetaAddress === 'string'
935
+ ? parseNEARStealthMetaAddress(params.recipientMetaAddress)
936
+ : params.recipientMetaAddress
937
+
938
+ const amount = typeof params.amount === 'string' ? BigInt(params.amount) : params.amount
939
+ const transfer = buildPrivateTransfer(recipientMeta, amount)
940
+
941
+ const urlParams = new URLSearchParams({
942
+ signerId: this.accountId,
943
+ receiverId: transfer.stealthAccountId,
944
+ callback: this.config.callbackUrl,
945
+ network: this.config.network,
946
+ actions: JSON.stringify(transfer.actions.map((action) => ({
947
+ type: action.type,
948
+ ...serializeParams(action.params as unknown as Record<string, unknown>),
949
+ }))),
950
+ meta: JSON.stringify({
951
+ isPrivate: true,
952
+ stealthAccountId: transfer.stealthAccountId,
953
+ announcementMemo: transfer.announcementMemo,
954
+ label: params.label,
955
+ }),
956
+ })
957
+
958
+ return `${METEOR_DEEP_LINK_SCHEME}sign?${urlParams.toString()}`
959
+ }
960
+
961
+ /**
962
+ * Get app link for private transfer (universal link)
963
+ */
964
+ getPrivateTransferAppLink(params: MeteorPrivateTransferParams): string {
965
+ const appBase = this.config.network === 'mainnet'
966
+ ? METEOR_APP_LINK_MAINNET
967
+ : METEOR_APP_LINK_TESTNET
968
+
969
+ const deepLink = this.getPrivateTransferDeepLink(params)
970
+ return `${appBase}/sign?link=${encodeURIComponent(deepLink)}`
971
+ }
972
+
973
+ /**
974
+ * Handle deep link transaction callback
975
+ */
976
+ handleTransactionCallback(params: URLSearchParams): MeteorTransactionResult {
977
+ this.connectionState = 'connected'
978
+
979
+ const transactionHash = params.get('transactionHash')
980
+ const stealthAccountId = params.get('stealthAccountId')
981
+ const announcementMemo = params.get('announcementMemo')
982
+ const error = params.get('error')
983
+
984
+ if (error) {
985
+ throw new MeteorWalletError(
986
+ error,
987
+ MeteorErrorCode.TRANSACTION_FAILED
988
+ )
989
+ }
990
+
991
+ return {
992
+ transactionHash: transactionHash ?? '',
993
+ stealthAccountId: stealthAccountId ?? '',
994
+ announcementMemo: announcementMemo ?? '',
995
+ success: !!transactionHash,
996
+ }
997
+ }
998
+
999
+ // ─── Stealth Address Display ────────────────────────────────────────────────
1000
+
1001
+ /**
1002
+ * Format stealth account ID for display
1003
+ */
1004
+ formatStealthAccountId(accountId: string): string {
1005
+ if (accountId.length !== 64) {
1006
+ return accountId
1007
+ }
1008
+ return `${accountId.slice(0, 8)}...${accountId.slice(-8)}`
1009
+ }
1010
+
1011
+ /**
1012
+ * Get explorer URL for stealth account
1013
+ */
1014
+ getStealthAccountExplorerUrl(accountId: string): string {
1015
+ const baseUrl = this.config.network === 'mainnet'
1016
+ ? 'https://nearblocks.io/address'
1017
+ : 'https://testnet.nearblocks.io/address'
1018
+ return `${baseUrl}/${accountId}`
1019
+ }
1020
+
1021
+ // ─── Error Handling ─────────────────────────────────────────────────────────
1022
+
1023
+ /**
1024
+ * Wrap errors in MeteorWalletError
1025
+ */
1026
+ private wrapError(error: unknown): MeteorWalletError {
1027
+ if (error instanceof MeteorWalletError) {
1028
+ return error
1029
+ }
1030
+
1031
+ const errorMessage = error instanceof Error ? error.message : String(error)
1032
+
1033
+ // Map common error patterns to codes
1034
+ if (errorMessage.includes('User rejected') || errorMessage.includes('user rejected')) {
1035
+ return new MeteorWalletError(errorMessage, MeteorErrorCode.USER_REJECTED, error)
1036
+ }
1037
+ if (errorMessage.includes('not connected') || errorMessage.includes('Not connected')) {
1038
+ return new MeteorWalletError(errorMessage, MeteorErrorCode.NOT_CONNECTED, error)
1039
+ }
1040
+ if (errorMessage.includes('network') || errorMessage.includes('Network')) {
1041
+ return new MeteorWalletError(errorMessage, MeteorErrorCode.NETWORK_ERROR, error)
1042
+ }
1043
+
1044
+ return new MeteorWalletError(errorMessage, MeteorErrorCode.UNKNOWN, error)
1045
+ }
1046
+
1047
+ // ─── Utility Methods ────────────────────────────────────────────────────────
1048
+
1049
+ /**
1050
+ * Get config
1051
+ */
1052
+ getConfig(): MeteorWalletConfig {
1053
+ return { ...this.config }
1054
+ }
1055
+ }
1056
+
1057
+ // ─── Factory Functions ────────────────────────────────────────────────────────
1058
+
1059
+ /**
1060
+ * Create Meteor wallet privacy integration
1061
+ */
1062
+ export function createMeteorWalletPrivacy(
1063
+ config: MeteorWalletConfig
1064
+ ): MeteorWalletPrivacy {
1065
+ return new MeteorWalletPrivacy(config)
1066
+ }
1067
+
1068
+ /**
1069
+ * Create mainnet Meteor wallet integration
1070
+ */
1071
+ export function createMainnetMeteorWallet(
1072
+ callbackUrl?: string
1073
+ ): MeteorWalletPrivacy {
1074
+ return new MeteorWalletPrivacy({
1075
+ network: 'mainnet',
1076
+ callbackUrl,
1077
+ })
1078
+ }
1079
+
1080
+ /**
1081
+ * Create testnet Meteor wallet integration
1082
+ */
1083
+ export function createTestnetMeteorWallet(
1084
+ callbackUrl?: string
1085
+ ): MeteorWalletPrivacy {
1086
+ return new MeteorWalletPrivacy({
1087
+ network: 'testnet',
1088
+ callbackUrl,
1089
+ })
1090
+ }
1091
+
1092
+ /**
1093
+ * Detect if Meteor wallet is available
1094
+ */
1095
+ export function isMeteorWalletAvailable(): boolean {
1096
+ if (typeof window === 'undefined') {
1097
+ return false
1098
+ }
1099
+ return (window as unknown as Record<string, unknown>)[METEOR_PROVIDER_KEY] !== undefined
1100
+ }
1101
+
1102
+ // ─── Utility Functions ────────────────────────────────────────────────────────
1103
+
1104
+ /**
1105
+ * Clamp scalar for ed25519
1106
+ */
1107
+ function clampScalar(bytes: Uint8Array): Uint8Array {
1108
+ const clamped = new Uint8Array(bytes)
1109
+ clamped[0] &= 248
1110
+ clamped[31] &= 127
1111
+ clamped[31] |= 64
1112
+ return clamped
1113
+ }
1114
+
1115
+ /**
1116
+ * Convert hex string to bytes (without 0x prefix handling)
1117
+ */
1118
+ function hexToBytes(hex: string): Uint8Array {
1119
+ const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex
1120
+ const bytes = new Uint8Array(cleanHex.length / 2)
1121
+ for (let i = 0; i < bytes.length; i++) {
1122
+ bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16)
1123
+ }
1124
+ return bytes
1125
+ }
1126
+
1127
+ /**
1128
+ * Serialize action params for URL encoding / JSON
1129
+ */
1130
+ function serializeParams(params: Record<string, unknown>): Record<string, unknown> {
1131
+ const serialized: Record<string, unknown> = {}
1132
+
1133
+ for (const [key, value] of Object.entries(params)) {
1134
+ if (typeof value === 'bigint') {
1135
+ serialized[key] = value.toString()
1136
+ } else if (value instanceof Uint8Array) {
1137
+ serialized[key] = bytesToHex(value)
1138
+ } else {
1139
+ serialized[key] = value
1140
+ }
1141
+ }
1142
+
1143
+ return serialized
1144
+ }
1145
+
1146
+ /**
1147
+ * Format gas cost in NEAR
1148
+ */
1149
+ function formatGasCost(gas: bigint): string {
1150
+ // 1 TGas = 0.0001 NEAR at 100 Tgas/NEAR price
1151
+ const nearAmount = Number(gas) / 1e12 * 0.0001
1152
+ return nearAmount.toFixed(6)
1153
+ }