@sip-protocol/sdk 0.7.3 → 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.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +54 -21
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- package/dist/solana-ZYO63LY5.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
|
+
}
|