@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.
- 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 +48874 -18336
- package/dist/browser.mjs +674 -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-YWGJ77A2.mjs +33806 -0
- 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-DXh2IGkz.d.ts +24681 -0
- package/dist/index-DeE1ZzA4.d.mts +24681 -0
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48676 -17318
- package/dist/index.mjs +583 -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 +276 -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 +201 -0
- 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 +402 -0
- package/src/chains/solana/providers/index.ts +85 -0
- package/src/chains/solana/providers/interface.ts +221 -0
- 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 +790 -0
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +170 -73
- 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 +77 -7
- 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 +37 -0
- package/src/compliance/range-sas.ts +956 -0
- 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 +785 -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 +336 -0
- package/src/privacy-backends/interface.ts +906 -0
- 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-types.ts +278 -0
- package/src/privacy-backends/privacycash.ts +456 -0
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +690 -0
- package/src/privacy-backends/router.ts +626 -0
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +256 -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 +111 -30
- 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/surveillance/algorithms/address-reuse.ts +143 -0
- package/src/surveillance/algorithms/cluster.ts +247 -0
- package/src/surveillance/algorithms/exchange.ts +295 -0
- package/src/surveillance/algorithms/temporal.ts +337 -0
- package/src/surveillance/analyzer.ts +442 -0
- package/src/surveillance/index.ts +64 -0
- package/src/surveillance/scoring.ts +372 -0
- package/src/surveillance/types.ts +264 -0
- 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-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/constants-VOI7BSLK.mjs +0 -27
- 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-xbWjohNq.d.mts +0 -11390
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-Q4NAVBTS.mjs +0 -46
package/dist/chunk-DLDWZFYC.mjs
DELETED
|
@@ -1,1495 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ValidationError
|
|
3
|
-
} from "./chunk-6WGN57S2.mjs";
|
|
4
|
-
import {
|
|
5
|
-
ESTIMATED_TX_FEE_LAMPORTS,
|
|
6
|
-
MEMO_PROGRAM_ID,
|
|
7
|
-
SIP_MEMO_PREFIX,
|
|
8
|
-
SOLANA_TOKEN_MINTS,
|
|
9
|
-
getExplorerUrl
|
|
10
|
-
} from "./chunk-E6SZWREQ.mjs";
|
|
11
|
-
|
|
12
|
-
// src/chains/solana/types.ts
|
|
13
|
-
function parseAnnouncement(memo) {
|
|
14
|
-
if (!memo.startsWith("SIP:1:")) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
const parts = memo.slice(6).split(":");
|
|
18
|
-
if (parts.length < 2) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
ephemeralPublicKey: parts[0],
|
|
23
|
-
viewTag: parts[1],
|
|
24
|
-
stealthAddress: parts[2]
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function createAnnouncementMemo(ephemeralPublicKey, viewTag, stealthAddress) {
|
|
28
|
-
const parts = ["SIP:1", ephemeralPublicKey, viewTag];
|
|
29
|
-
if (stealthAddress) {
|
|
30
|
-
parts.push(stealthAddress);
|
|
31
|
-
}
|
|
32
|
-
return parts.join(":");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/chains/solana/transfer.ts
|
|
36
|
-
import {
|
|
37
|
-
PublicKey,
|
|
38
|
-
Transaction,
|
|
39
|
-
TransactionInstruction
|
|
40
|
-
} from "@solana/web3.js";
|
|
41
|
-
import {
|
|
42
|
-
getAssociatedTokenAddress,
|
|
43
|
-
createAssociatedTokenAccountInstruction,
|
|
44
|
-
createTransferInstruction,
|
|
45
|
-
TOKEN_PROGRAM_ID,
|
|
46
|
-
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
47
|
-
getAccount
|
|
48
|
-
} from "@solana/spl-token";
|
|
49
|
-
|
|
50
|
-
// src/stealth.ts
|
|
51
|
-
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
52
|
-
import { ed25519 } from "@noble/curves/ed25519";
|
|
53
|
-
import { sha256 } from "@noble/hashes/sha256";
|
|
54
|
-
import { sha512 } from "@noble/hashes/sha512";
|
|
55
|
-
import { keccak_256 } from "@noble/hashes/sha3";
|
|
56
|
-
import { bytesToHex, hexToBytes, randomBytes as randomBytes2 } from "@noble/hashes/utils";
|
|
57
|
-
|
|
58
|
-
// src/validation.ts
|
|
59
|
-
var VALID_CHAIN_IDS = [
|
|
60
|
-
"solana",
|
|
61
|
-
"ethereum",
|
|
62
|
-
"near",
|
|
63
|
-
"zcash",
|
|
64
|
-
"polygon",
|
|
65
|
-
"arbitrum",
|
|
66
|
-
"optimism",
|
|
67
|
-
"base",
|
|
68
|
-
"bitcoin",
|
|
69
|
-
"aptos",
|
|
70
|
-
"sui",
|
|
71
|
-
"cosmos",
|
|
72
|
-
"osmosis",
|
|
73
|
-
"injective",
|
|
74
|
-
"celestia",
|
|
75
|
-
"sei",
|
|
76
|
-
"dydx"
|
|
77
|
-
];
|
|
78
|
-
function isValidChainId(chain) {
|
|
79
|
-
return VALID_CHAIN_IDS.includes(chain);
|
|
80
|
-
}
|
|
81
|
-
function isValidPrivacyLevel(level) {
|
|
82
|
-
if (typeof level !== "string") return false;
|
|
83
|
-
return ["transparent", "shielded", "compliant"].includes(level);
|
|
84
|
-
}
|
|
85
|
-
function isValidHex(value) {
|
|
86
|
-
if (typeof value !== "string") return false;
|
|
87
|
-
if (!value.startsWith("0x")) return false;
|
|
88
|
-
const hex = value.slice(2);
|
|
89
|
-
if (hex.length === 0) return false;
|
|
90
|
-
return /^[0-9a-fA-F]+$/.test(hex);
|
|
91
|
-
}
|
|
92
|
-
function isValidHexLength(value, byteLength) {
|
|
93
|
-
if (!isValidHex(value)) return false;
|
|
94
|
-
const hex = value.slice(2);
|
|
95
|
-
return hex.length === byteLength * 2;
|
|
96
|
-
}
|
|
97
|
-
function isValidAmount(value) {
|
|
98
|
-
return typeof value === "bigint" && value > 0n;
|
|
99
|
-
}
|
|
100
|
-
function isNonNegativeAmount(value) {
|
|
101
|
-
return typeof value === "bigint" && value >= 0n;
|
|
102
|
-
}
|
|
103
|
-
function isValidSlippage(value) {
|
|
104
|
-
return typeof value === "number" && !isNaN(value) && value >= 0 && value < 1;
|
|
105
|
-
}
|
|
106
|
-
var STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{64,66}:0x[0-9a-fA-F]{64,66}$/;
|
|
107
|
-
function isValidStealthMetaAddress(addr) {
|
|
108
|
-
if (typeof addr !== "string") return false;
|
|
109
|
-
return STEALTH_META_ADDRESS_REGEX.test(addr);
|
|
110
|
-
}
|
|
111
|
-
function isValidCompressedPublicKey(key) {
|
|
112
|
-
if (!isValidHexLength(key, 33)) return false;
|
|
113
|
-
const prefix = key.slice(2, 4);
|
|
114
|
-
return prefix === "02" || prefix === "03";
|
|
115
|
-
}
|
|
116
|
-
function isValidEd25519PublicKey(key) {
|
|
117
|
-
return isValidHexLength(key, 32);
|
|
118
|
-
}
|
|
119
|
-
function isValidPrivateKey(key) {
|
|
120
|
-
return isValidHexLength(key, 32);
|
|
121
|
-
}
|
|
122
|
-
function validateAsset(asset, field) {
|
|
123
|
-
if (!asset || typeof asset !== "object") {
|
|
124
|
-
throw new ValidationError("must be an object", field);
|
|
125
|
-
}
|
|
126
|
-
const a = asset;
|
|
127
|
-
if (!a.chain || !isValidChainId(a.chain)) {
|
|
128
|
-
throw new ValidationError(
|
|
129
|
-
`invalid chain '${a.chain}', must be one of: ${VALID_CHAIN_IDS.join(", ")}`,
|
|
130
|
-
`${field}.chain`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
if (typeof a.symbol !== "string" || a.symbol.length === 0) {
|
|
134
|
-
throw new ValidationError("symbol must be a non-empty string", `${field}.symbol`);
|
|
135
|
-
}
|
|
136
|
-
if (a.address !== null && !isValidHex(a.address)) {
|
|
137
|
-
throw new ValidationError("address must be null or valid hex string", `${field}.address`);
|
|
138
|
-
}
|
|
139
|
-
if (typeof a.decimals !== "number" || !Number.isInteger(a.decimals) || a.decimals < 0) {
|
|
140
|
-
throw new ValidationError("decimals must be a non-negative integer", `${field}.decimals`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
function validateIntentInput(input, field = "input") {
|
|
144
|
-
if (!input || typeof input !== "object") {
|
|
145
|
-
throw new ValidationError("must be an object", field);
|
|
146
|
-
}
|
|
147
|
-
const i = input;
|
|
148
|
-
validateAsset(i.asset, `${field}.asset`);
|
|
149
|
-
if (!isValidAmount(i.amount)) {
|
|
150
|
-
throw new ValidationError(
|
|
151
|
-
"amount must be a positive bigint",
|
|
152
|
-
`${field}.amount`,
|
|
153
|
-
{ received: typeof i.amount, value: String(i.amount) }
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function validateIntentOutput(output, field = "output") {
|
|
158
|
-
if (!output || typeof output !== "object") {
|
|
159
|
-
throw new ValidationError("must be an object", field);
|
|
160
|
-
}
|
|
161
|
-
const o = output;
|
|
162
|
-
validateAsset(o.asset, `${field}.asset`);
|
|
163
|
-
if (!isNonNegativeAmount(o.minAmount)) {
|
|
164
|
-
throw new ValidationError(
|
|
165
|
-
"minAmount must be a non-negative bigint",
|
|
166
|
-
`${field}.minAmount`,
|
|
167
|
-
{ received: typeof o.minAmount, value: String(o.minAmount) }
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
if (!isValidSlippage(o.maxSlippage)) {
|
|
171
|
-
throw new ValidationError(
|
|
172
|
-
"maxSlippage must be a number between 0 and 1 (exclusive)",
|
|
173
|
-
`${field}.maxSlippage`,
|
|
174
|
-
{ received: o.maxSlippage }
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function validateCreateIntentParams(params) {
|
|
179
|
-
if (!params || typeof params !== "object") {
|
|
180
|
-
throw new ValidationError("params must be an object");
|
|
181
|
-
}
|
|
182
|
-
const p = params;
|
|
183
|
-
if (!p.input) {
|
|
184
|
-
throw new ValidationError("input is required", "input");
|
|
185
|
-
}
|
|
186
|
-
validateIntentInput(p.input, "input");
|
|
187
|
-
if (!p.output) {
|
|
188
|
-
throw new ValidationError("output is required", "output");
|
|
189
|
-
}
|
|
190
|
-
validateIntentOutput(p.output, "output");
|
|
191
|
-
if (!p.privacy) {
|
|
192
|
-
throw new ValidationError("privacy is required", "privacy");
|
|
193
|
-
}
|
|
194
|
-
if (!isValidPrivacyLevel(p.privacy)) {
|
|
195
|
-
throw new ValidationError(
|
|
196
|
-
`invalid privacy level '${p.privacy}', must be one of: transparent, shielded, compliant`,
|
|
197
|
-
"privacy"
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
if ((p.privacy === "shielded" || p.privacy === "compliant") && p.recipientMetaAddress) {
|
|
201
|
-
if (!isValidStealthMetaAddress(p.recipientMetaAddress)) {
|
|
202
|
-
throw new ValidationError(
|
|
203
|
-
"invalid stealth meta-address format, expected: sip:<chain>:<spendingKey>:<viewingKey>",
|
|
204
|
-
"recipientMetaAddress"
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (p.privacy === "compliant" && !p.viewingKey) {
|
|
209
|
-
throw new ValidationError(
|
|
210
|
-
"viewingKey is required for compliant mode",
|
|
211
|
-
"viewingKey"
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
if (p.viewingKey && !isValidHex(p.viewingKey)) {
|
|
215
|
-
throw new ValidationError(
|
|
216
|
-
"viewingKey must be a valid hex string",
|
|
217
|
-
"viewingKey"
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
if (p.ttl !== void 0) {
|
|
221
|
-
if (typeof p.ttl !== "number" || !Number.isInteger(p.ttl) || p.ttl <= 0) {
|
|
222
|
-
throw new ValidationError(
|
|
223
|
-
"ttl must be a positive integer (seconds)",
|
|
224
|
-
"ttl",
|
|
225
|
-
{ received: p.ttl }
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function validateViewingKey(key, field = "viewingKey") {
|
|
231
|
-
if (!key || typeof key !== "string") {
|
|
232
|
-
throw new ValidationError("must be a string", field);
|
|
233
|
-
}
|
|
234
|
-
if (!isValidHex(key)) {
|
|
235
|
-
throw new ValidationError("must be a valid hex string with 0x prefix", field);
|
|
236
|
-
}
|
|
237
|
-
if (!isValidHexLength(key, 32)) {
|
|
238
|
-
throw new ValidationError("must be 32 bytes (64 hex characters)", field);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
var SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
|
|
242
|
-
function isValidScalar(value) {
|
|
243
|
-
return value > 0n && value < SECP256K1_ORDER;
|
|
244
|
-
}
|
|
245
|
-
function validateScalar(value, field) {
|
|
246
|
-
if (typeof value !== "bigint") {
|
|
247
|
-
throw new ValidationError("must be a bigint", field);
|
|
248
|
-
}
|
|
249
|
-
if (!isValidScalar(value)) {
|
|
250
|
-
throw new ValidationError(
|
|
251
|
-
"must be in range (0, curve order)",
|
|
252
|
-
field,
|
|
253
|
-
{ curveOrder: SECP256K1_ORDER.toString(16) }
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function isValidEvmAddress(address) {
|
|
258
|
-
if (typeof address !== "string") return false;
|
|
259
|
-
return /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
260
|
-
}
|
|
261
|
-
function isValidSolanaAddressFormat(address) {
|
|
262
|
-
if (typeof address !== "string") return false;
|
|
263
|
-
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
|
|
264
|
-
}
|
|
265
|
-
function isValidNearAddressFormat(address) {
|
|
266
|
-
if (typeof address !== "string") return false;
|
|
267
|
-
if (/^[0-9a-f]{64}$/.test(address)) return true;
|
|
268
|
-
if (address.length < 2 || address.length > 64) return false;
|
|
269
|
-
if (!/^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/.test(address)) return false;
|
|
270
|
-
if (address.includes("..")) return false;
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
function isValidCosmosAddressFormat(address) {
|
|
274
|
-
if (typeof address !== "string") return false;
|
|
275
|
-
if (address.length < 39 || address.length > 90) return false;
|
|
276
|
-
const bech32Pattern = /^[a-z]+1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,}$/;
|
|
277
|
-
return bech32Pattern.test(address);
|
|
278
|
-
}
|
|
279
|
-
function getChainAddressType(chain) {
|
|
280
|
-
switch (chain) {
|
|
281
|
-
case "ethereum":
|
|
282
|
-
case "polygon":
|
|
283
|
-
case "arbitrum":
|
|
284
|
-
case "optimism":
|
|
285
|
-
case "base":
|
|
286
|
-
return "evm";
|
|
287
|
-
case "solana":
|
|
288
|
-
return "solana";
|
|
289
|
-
case "near":
|
|
290
|
-
return "near";
|
|
291
|
-
case "zcash":
|
|
292
|
-
return "zcash";
|
|
293
|
-
case "cosmos":
|
|
294
|
-
case "osmosis":
|
|
295
|
-
case "injective":
|
|
296
|
-
case "celestia":
|
|
297
|
-
case "sei":
|
|
298
|
-
case "dydx":
|
|
299
|
-
return "cosmos";
|
|
300
|
-
default:
|
|
301
|
-
return "unknown";
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function validateAddressForChain(address, chain, field = "address") {
|
|
305
|
-
const addressType = getChainAddressType(chain);
|
|
306
|
-
switch (addressType) {
|
|
307
|
-
case "evm":
|
|
308
|
-
if (!isValidEvmAddress(address)) {
|
|
309
|
-
throw new ValidationError(
|
|
310
|
-
`Invalid address format for ${chain}. Expected EVM address (0x + 40 hex chars), got: ${address.slice(0, 20)}...`,
|
|
311
|
-
field,
|
|
312
|
-
{ chain, expectedFormat: "0x...", receivedFormat: address.startsWith("0x") ? "hex but wrong length" : "not hex" }
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
break;
|
|
316
|
-
case "solana":
|
|
317
|
-
if (!isValidSolanaAddressFormat(address)) {
|
|
318
|
-
throw new ValidationError(
|
|
319
|
-
`Invalid address format for ${chain}. Expected Solana address (base58, 32-44 chars), got: ${address.slice(0, 20)}...`,
|
|
320
|
-
field,
|
|
321
|
-
{ chain, expectedFormat: "base58", receivedFormat: address.startsWith("0x") ? "looks like EVM" : "unknown" }
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
break;
|
|
325
|
-
case "near":
|
|
326
|
-
if (!isValidNearAddressFormat(address)) {
|
|
327
|
-
throw new ValidationError(
|
|
328
|
-
`Invalid address format for ${chain}. Expected NEAR account ID (named or implicit), got: ${address.slice(0, 20)}...`,
|
|
329
|
-
field,
|
|
330
|
-
{ chain, expectedFormat: "alice.near or 64 hex chars" }
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
break;
|
|
334
|
-
case "zcash":
|
|
335
|
-
if (!address || address.length === 0) {
|
|
336
|
-
throw new ValidationError(
|
|
337
|
-
`Invalid address format for ${chain}. Expected Zcash address.`,
|
|
338
|
-
field,
|
|
339
|
-
{ chain }
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
case "cosmos":
|
|
344
|
-
if (!isValidCosmosAddressFormat(address)) {
|
|
345
|
-
throw new ValidationError(
|
|
346
|
-
`Invalid address format for ${chain}. Expected Cosmos bech32 address, got: ${address.slice(0, 20)}...`,
|
|
347
|
-
field,
|
|
348
|
-
{ chain, expectedFormat: "bech32" }
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
default:
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function isAddressValidForChain(address, chain) {
|
|
357
|
-
try {
|
|
358
|
-
validateAddressForChain(address, chain);
|
|
359
|
-
return true;
|
|
360
|
-
} catch {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// src/secure-memory.ts
|
|
366
|
-
import { randomBytes } from "@noble/hashes/utils";
|
|
367
|
-
function secureWipe(buffer) {
|
|
368
|
-
if (!buffer || buffer.length === 0) {
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const random = randomBytes(buffer.length);
|
|
372
|
-
buffer.set(random);
|
|
373
|
-
buffer.fill(0);
|
|
374
|
-
}
|
|
375
|
-
async function withSecureBuffer(createSecret, useSecret) {
|
|
376
|
-
const secret = createSecret();
|
|
377
|
-
try {
|
|
378
|
-
return await useSecret(secret);
|
|
379
|
-
} finally {
|
|
380
|
-
secureWipe(secret);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
function withSecureBufferSync(createSecret, useSecret) {
|
|
384
|
-
const secret = createSecret();
|
|
385
|
-
try {
|
|
386
|
-
return useSecret(secret);
|
|
387
|
-
} finally {
|
|
388
|
-
secureWipe(secret);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
function secureWipeAll(...buffers) {
|
|
392
|
-
for (const buffer of buffers) {
|
|
393
|
-
if (buffer) {
|
|
394
|
-
secureWipe(buffer);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// src/stealth.ts
|
|
400
|
-
function generateStealthMetaAddress(chain, label) {
|
|
401
|
-
if (!isValidChainId(chain)) {
|
|
402
|
-
throw new ValidationError(
|
|
403
|
-
`invalid chain '${chain}', must be one of: solana, ethereum, near, zcash, polygon, arbitrum, optimism, base, bitcoin, aptos, sui, cosmos, osmosis, injective, celestia, sei, dydx`,
|
|
404
|
-
"chain"
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
if (isEd25519Chain(chain)) {
|
|
408
|
-
return generateEd25519StealthMetaAddress(chain, label);
|
|
409
|
-
}
|
|
410
|
-
const spendingPrivateKey = randomBytes2(32);
|
|
411
|
-
const viewingPrivateKey = randomBytes2(32);
|
|
412
|
-
try {
|
|
413
|
-
const spendingKey = secp256k1.getPublicKey(spendingPrivateKey, true);
|
|
414
|
-
const viewingKey = secp256k1.getPublicKey(viewingPrivateKey, true);
|
|
415
|
-
const result = {
|
|
416
|
-
metaAddress: {
|
|
417
|
-
spendingKey: `0x${bytesToHex(spendingKey)}`,
|
|
418
|
-
viewingKey: `0x${bytesToHex(viewingKey)}`,
|
|
419
|
-
chain,
|
|
420
|
-
label
|
|
421
|
-
},
|
|
422
|
-
spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}`,
|
|
423
|
-
viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}`
|
|
424
|
-
};
|
|
425
|
-
return result;
|
|
426
|
-
} finally {
|
|
427
|
-
secureWipeAll(spendingPrivateKey, viewingPrivateKey);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
function validateStealthMetaAddress(metaAddress, field = "recipientMetaAddress") {
|
|
431
|
-
if (!metaAddress || typeof metaAddress !== "object") {
|
|
432
|
-
throw new ValidationError("must be an object", field);
|
|
433
|
-
}
|
|
434
|
-
if (!isValidChainId(metaAddress.chain)) {
|
|
435
|
-
throw new ValidationError(
|
|
436
|
-
`invalid chain '${metaAddress.chain}'`,
|
|
437
|
-
`${field}.chain`
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
const isEd25519 = isEd25519Chain(metaAddress.chain);
|
|
441
|
-
if (isEd25519) {
|
|
442
|
-
if (!isValidEd25519PublicKey(metaAddress.spendingKey)) {
|
|
443
|
-
throw new ValidationError(
|
|
444
|
-
"spendingKey must be a valid ed25519 public key (32 bytes)",
|
|
445
|
-
`${field}.spendingKey`
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
if (!isValidEd25519PublicKey(metaAddress.viewingKey)) {
|
|
449
|
-
throw new ValidationError(
|
|
450
|
-
"viewingKey must be a valid ed25519 public key (32 bytes)",
|
|
451
|
-
`${field}.viewingKey`
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
|
|
456
|
-
throw new ValidationError(
|
|
457
|
-
"spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)",
|
|
458
|
-
`${field}.spendingKey`
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
|
|
462
|
-
throw new ValidationError(
|
|
463
|
-
"viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)",
|
|
464
|
-
`${field}.viewingKey`
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function generateStealthAddress(recipientMetaAddress) {
|
|
470
|
-
validateStealthMetaAddress(recipientMetaAddress);
|
|
471
|
-
if (isEd25519Chain(recipientMetaAddress.chain)) {
|
|
472
|
-
return generateEd25519StealthAddress(recipientMetaAddress);
|
|
473
|
-
}
|
|
474
|
-
const ephemeralPrivateKey = randomBytes2(32);
|
|
475
|
-
try {
|
|
476
|
-
const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true);
|
|
477
|
-
const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2));
|
|
478
|
-
const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2));
|
|
479
|
-
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
480
|
-
ephemeralPrivateKey,
|
|
481
|
-
spendingKeyBytes
|
|
482
|
-
);
|
|
483
|
-
const sharedSecretHash = sha256(sharedSecretPoint);
|
|
484
|
-
const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true);
|
|
485
|
-
const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes);
|
|
486
|
-
const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG);
|
|
487
|
-
const stealthPoint = viewingKeyPoint.add(hashTimesGPoint);
|
|
488
|
-
const stealthAddressBytes = stealthPoint.toRawBytes(true);
|
|
489
|
-
const viewTag = sharedSecretHash[0];
|
|
490
|
-
return {
|
|
491
|
-
stealthAddress: {
|
|
492
|
-
address: `0x${bytesToHex(stealthAddressBytes)}`,
|
|
493
|
-
ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}`,
|
|
494
|
-
viewTag
|
|
495
|
-
},
|
|
496
|
-
sharedSecret: `0x${bytesToHex(sharedSecretHash)}`
|
|
497
|
-
};
|
|
498
|
-
} finally {
|
|
499
|
-
secureWipe(ephemeralPrivateKey);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
function validateStealthAddress(stealthAddress, field = "stealthAddress") {
|
|
503
|
-
if (!stealthAddress || typeof stealthAddress !== "object") {
|
|
504
|
-
throw new ValidationError("must be an object", field);
|
|
505
|
-
}
|
|
506
|
-
if (!isValidCompressedPublicKey(stealthAddress.address)) {
|
|
507
|
-
throw new ValidationError(
|
|
508
|
-
"address must be a valid compressed secp256k1 public key",
|
|
509
|
-
`${field}.address`
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
if (!isValidCompressedPublicKey(stealthAddress.ephemeralPublicKey)) {
|
|
513
|
-
throw new ValidationError(
|
|
514
|
-
"ephemeralPublicKey must be a valid compressed secp256k1 public key",
|
|
515
|
-
`${field}.ephemeralPublicKey`
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
if (typeof stealthAddress.viewTag !== "number" || !Number.isInteger(stealthAddress.viewTag) || stealthAddress.viewTag < 0 || stealthAddress.viewTag > 255) {
|
|
519
|
-
throw new ValidationError(
|
|
520
|
-
"viewTag must be an integer between 0 and 255",
|
|
521
|
-
`${field}.viewTag`
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
function deriveStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
|
|
526
|
-
validateStealthAddress(stealthAddress);
|
|
527
|
-
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
528
|
-
throw new ValidationError(
|
|
529
|
-
"must be a valid 32-byte hex string",
|
|
530
|
-
"spendingPrivateKey"
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
534
|
-
throw new ValidationError(
|
|
535
|
-
"must be a valid 32-byte hex string",
|
|
536
|
-
"viewingPrivateKey"
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2));
|
|
540
|
-
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2));
|
|
541
|
-
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2));
|
|
542
|
-
try {
|
|
543
|
-
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
544
|
-
spendingPrivBytes,
|
|
545
|
-
ephemeralPubBytes
|
|
546
|
-
);
|
|
547
|
-
const sharedSecretHash = sha256(sharedSecretPoint);
|
|
548
|
-
const viewingScalar = bytesToBigInt(viewingPrivBytes);
|
|
549
|
-
const hashScalar = bytesToBigInt(sharedSecretHash);
|
|
550
|
-
const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n;
|
|
551
|
-
const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32);
|
|
552
|
-
const result = {
|
|
553
|
-
stealthAddress: stealthAddress.address,
|
|
554
|
-
ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
|
|
555
|
-
privateKey: `0x${bytesToHex(stealthPrivateKey)}`
|
|
556
|
-
};
|
|
557
|
-
secureWipe(stealthPrivateKey);
|
|
558
|
-
return result;
|
|
559
|
-
} finally {
|
|
560
|
-
secureWipeAll(spendingPrivBytes, viewingPrivBytes);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
function checkStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
|
|
564
|
-
validateStealthAddress(stealthAddress);
|
|
565
|
-
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
566
|
-
throw new ValidationError(
|
|
567
|
-
"must be a valid 32-byte hex string",
|
|
568
|
-
"spendingPrivateKey"
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
572
|
-
throw new ValidationError(
|
|
573
|
-
"must be a valid 32-byte hex string",
|
|
574
|
-
"viewingPrivateKey"
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2));
|
|
578
|
-
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2));
|
|
579
|
-
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2));
|
|
580
|
-
try {
|
|
581
|
-
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
582
|
-
spendingPrivBytes,
|
|
583
|
-
ephemeralPubBytes
|
|
584
|
-
);
|
|
585
|
-
const sharedSecretHash = sha256(sharedSecretPoint);
|
|
586
|
-
if (sharedSecretHash[0] !== stealthAddress.viewTag) {
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
const viewingScalar = bytesToBigInt(viewingPrivBytes);
|
|
590
|
-
const hashScalar = bytesToBigInt(sharedSecretHash);
|
|
591
|
-
const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n;
|
|
592
|
-
const derivedKeyBytes = bigIntToBytes(stealthPrivateScalar, 32);
|
|
593
|
-
const expectedPubKey = secp256k1.getPublicKey(derivedKeyBytes, true);
|
|
594
|
-
secureWipe(derivedKeyBytes);
|
|
595
|
-
const providedAddress = hexToBytes(stealthAddress.address.slice(2));
|
|
596
|
-
return bytesToHex(expectedPubKey) === bytesToHex(providedAddress);
|
|
597
|
-
} finally {
|
|
598
|
-
secureWipeAll(spendingPrivBytes, viewingPrivBytes);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
function encodeStealthMetaAddress(metaAddress) {
|
|
602
|
-
return `sip:${metaAddress.chain}:${metaAddress.spendingKey}:${metaAddress.viewingKey}`;
|
|
603
|
-
}
|
|
604
|
-
function decodeStealthMetaAddress(encoded) {
|
|
605
|
-
if (typeof encoded !== "string") {
|
|
606
|
-
throw new ValidationError("must be a string", "encoded");
|
|
607
|
-
}
|
|
608
|
-
const parts = encoded.split(":");
|
|
609
|
-
if (parts.length < 4 || parts[0] !== "sip") {
|
|
610
|
-
throw new ValidationError(
|
|
611
|
-
"invalid format, expected: sip:<chain>:<spendingKey>:<viewingKey>",
|
|
612
|
-
"encoded"
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
const [, chain, spendingKey, viewingKey] = parts;
|
|
616
|
-
if (!isValidChainId(chain)) {
|
|
617
|
-
throw new ValidationError(
|
|
618
|
-
`invalid chain '${chain}'`,
|
|
619
|
-
"encoded.chain"
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
const chainId = chain;
|
|
623
|
-
if (isEd25519Chain(chainId)) {
|
|
624
|
-
if (!isValidEd25519PublicKey(spendingKey)) {
|
|
625
|
-
throw new ValidationError(
|
|
626
|
-
"spendingKey must be a valid 32-byte ed25519 public key",
|
|
627
|
-
"encoded.spendingKey"
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
if (!isValidEd25519PublicKey(viewingKey)) {
|
|
631
|
-
throw new ValidationError(
|
|
632
|
-
"viewingKey must be a valid 32-byte ed25519 public key",
|
|
633
|
-
"encoded.viewingKey"
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
} else {
|
|
637
|
-
if (!isValidCompressedPublicKey(spendingKey)) {
|
|
638
|
-
throw new ValidationError(
|
|
639
|
-
"spendingKey must be a valid compressed secp256k1 public key",
|
|
640
|
-
"encoded.spendingKey"
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
if (!isValidCompressedPublicKey(viewingKey)) {
|
|
644
|
-
throw new ValidationError(
|
|
645
|
-
"viewingKey must be a valid compressed secp256k1 public key",
|
|
646
|
-
"encoded.viewingKey"
|
|
647
|
-
);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
return {
|
|
651
|
-
chain,
|
|
652
|
-
spendingKey,
|
|
653
|
-
viewingKey
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
function bytesToBigInt(bytes) {
|
|
657
|
-
let result = 0n;
|
|
658
|
-
for (const byte of bytes) {
|
|
659
|
-
result = (result << 8n) + BigInt(byte);
|
|
660
|
-
}
|
|
661
|
-
return result;
|
|
662
|
-
}
|
|
663
|
-
function bigIntToBytes(value, length) {
|
|
664
|
-
const bytes = new Uint8Array(length);
|
|
665
|
-
for (let i = length - 1; i >= 0; i--) {
|
|
666
|
-
bytes[i] = Number(value & 0xffn);
|
|
667
|
-
value >>= 8n;
|
|
668
|
-
}
|
|
669
|
-
return bytes;
|
|
670
|
-
}
|
|
671
|
-
function publicKeyToEthAddress(publicKey) {
|
|
672
|
-
const keyHex = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey;
|
|
673
|
-
const keyBytes = hexToBytes(keyHex);
|
|
674
|
-
let uncompressedBytes;
|
|
675
|
-
if (keyBytes.length === 33) {
|
|
676
|
-
const point = secp256k1.ProjectivePoint.fromHex(keyBytes);
|
|
677
|
-
uncompressedBytes = point.toRawBytes(false);
|
|
678
|
-
} else if (keyBytes.length === 65) {
|
|
679
|
-
uncompressedBytes = keyBytes;
|
|
680
|
-
} else {
|
|
681
|
-
throw new ValidationError(
|
|
682
|
-
`invalid public key length: ${keyBytes.length}, expected 33 (compressed) or 65 (uncompressed)`,
|
|
683
|
-
"publicKey"
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
const pubKeyWithoutPrefix = uncompressedBytes.slice(1);
|
|
687
|
-
const hash = keccak_256(pubKeyWithoutPrefix);
|
|
688
|
-
const addressBytes = hash.slice(-20);
|
|
689
|
-
return toChecksumAddress(`0x${bytesToHex(addressBytes)}`);
|
|
690
|
-
}
|
|
691
|
-
function toChecksumAddress(address) {
|
|
692
|
-
const addr = address.toLowerCase().replace("0x", "");
|
|
693
|
-
const hash = bytesToHex(keccak_256(new TextEncoder().encode(addr)));
|
|
694
|
-
let checksummed = "0x";
|
|
695
|
-
for (let i = 0; i < addr.length; i++) {
|
|
696
|
-
if (parseInt(hash[i], 16) >= 8) {
|
|
697
|
-
checksummed += addr[i].toUpperCase();
|
|
698
|
-
} else {
|
|
699
|
-
checksummed += addr[i];
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
return checksummed;
|
|
703
|
-
}
|
|
704
|
-
var ED25519_ORDER = 2n ** 252n + 27742317777372353535851937790883648493n;
|
|
705
|
-
var ED25519_CHAINS = ["solana", "near", "aptos", "sui"];
|
|
706
|
-
function isEd25519Chain(chain) {
|
|
707
|
-
return ED25519_CHAINS.includes(chain);
|
|
708
|
-
}
|
|
709
|
-
function getCurveForChain(chain) {
|
|
710
|
-
return isEd25519Chain(chain) ? "ed25519" : "secp256k1";
|
|
711
|
-
}
|
|
712
|
-
function validateEd25519StealthMetaAddress(metaAddress, field = "recipientMetaAddress") {
|
|
713
|
-
if (!metaAddress || typeof metaAddress !== "object") {
|
|
714
|
-
throw new ValidationError("must be an object", field);
|
|
715
|
-
}
|
|
716
|
-
if (!isValidChainId(metaAddress.chain)) {
|
|
717
|
-
throw new ValidationError(
|
|
718
|
-
`invalid chain '${metaAddress.chain}'`,
|
|
719
|
-
`${field}.chain`
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
if (!isEd25519Chain(metaAddress.chain)) {
|
|
723
|
-
throw new ValidationError(
|
|
724
|
-
`chain '${metaAddress.chain}' does not use ed25519, use secp256k1 functions instead`,
|
|
725
|
-
`${field}.chain`
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
if (!isValidEd25519PublicKey(metaAddress.spendingKey)) {
|
|
729
|
-
throw new ValidationError(
|
|
730
|
-
"spendingKey must be a valid ed25519 public key (32 bytes)",
|
|
731
|
-
`${field}.spendingKey`
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
if (!isValidEd25519PublicKey(metaAddress.viewingKey)) {
|
|
735
|
-
throw new ValidationError(
|
|
736
|
-
"viewingKey must be a valid ed25519 public key (32 bytes)",
|
|
737
|
-
`${field}.viewingKey`
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
function validateEd25519StealthAddress(stealthAddress, field = "stealthAddress") {
|
|
742
|
-
if (!stealthAddress || typeof stealthAddress !== "object") {
|
|
743
|
-
throw new ValidationError("must be an object", field);
|
|
744
|
-
}
|
|
745
|
-
if (!isValidEd25519PublicKey(stealthAddress.address)) {
|
|
746
|
-
throw new ValidationError(
|
|
747
|
-
"address must be a valid ed25519 public key (32 bytes)",
|
|
748
|
-
`${field}.address`
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (!isValidEd25519PublicKey(stealthAddress.ephemeralPublicKey)) {
|
|
752
|
-
throw new ValidationError(
|
|
753
|
-
"ephemeralPublicKey must be a valid ed25519 public key (32 bytes)",
|
|
754
|
-
`${field}.ephemeralPublicKey`
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
if (typeof stealthAddress.viewTag !== "number" || !Number.isInteger(stealthAddress.viewTag) || stealthAddress.viewTag < 0 || stealthAddress.viewTag > 255) {
|
|
758
|
-
throw new ValidationError(
|
|
759
|
-
"viewTag must be an integer between 0 and 255",
|
|
760
|
-
`${field}.viewTag`
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
function getEd25519Scalar(privateKey) {
|
|
765
|
-
const hash = sha512(privateKey);
|
|
766
|
-
const scalar = hash.slice(0, 32);
|
|
767
|
-
scalar[0] &= 248;
|
|
768
|
-
scalar[31] &= 127;
|
|
769
|
-
scalar[31] |= 64;
|
|
770
|
-
return bytesToBigIntLE(scalar);
|
|
771
|
-
}
|
|
772
|
-
function bytesToBigIntLE(bytes) {
|
|
773
|
-
let result = 0n;
|
|
774
|
-
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
775
|
-
result = (result << 8n) + BigInt(bytes[i]);
|
|
776
|
-
}
|
|
777
|
-
return result;
|
|
778
|
-
}
|
|
779
|
-
function bigIntToBytesLE(value, length) {
|
|
780
|
-
const bytes = new Uint8Array(length);
|
|
781
|
-
for (let i = 0; i < length; i++) {
|
|
782
|
-
bytes[i] = Number(value & 0xffn);
|
|
783
|
-
value >>= 8n;
|
|
784
|
-
}
|
|
785
|
-
return bytes;
|
|
786
|
-
}
|
|
787
|
-
function generateEd25519StealthMetaAddress(chain, label) {
|
|
788
|
-
if (!isValidChainId(chain)) {
|
|
789
|
-
throw new ValidationError(
|
|
790
|
-
`invalid chain '${chain}', must be one of: solana, ethereum, near, zcash, polygon, arbitrum, optimism, base, bitcoin, aptos, sui, cosmos, osmosis, injective, celestia, sei, dydx`,
|
|
791
|
-
"chain"
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
if (!isEd25519Chain(chain)) {
|
|
795
|
-
throw new ValidationError(
|
|
796
|
-
`chain '${chain}' does not use ed25519, use generateStealthMetaAddress() for secp256k1 chains`,
|
|
797
|
-
"chain"
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
const spendingPrivateKey = randomBytes2(32);
|
|
801
|
-
const viewingPrivateKey = randomBytes2(32);
|
|
802
|
-
try {
|
|
803
|
-
const spendingKey = ed25519.getPublicKey(spendingPrivateKey);
|
|
804
|
-
const viewingKey = ed25519.getPublicKey(viewingPrivateKey);
|
|
805
|
-
const result = {
|
|
806
|
-
metaAddress: {
|
|
807
|
-
spendingKey: `0x${bytesToHex(spendingKey)}`,
|
|
808
|
-
viewingKey: `0x${bytesToHex(viewingKey)}`,
|
|
809
|
-
chain,
|
|
810
|
-
label
|
|
811
|
-
},
|
|
812
|
-
spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}`,
|
|
813
|
-
viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}`
|
|
814
|
-
};
|
|
815
|
-
return result;
|
|
816
|
-
} finally {
|
|
817
|
-
secureWipeAll(spendingPrivateKey, viewingPrivateKey);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
function generateEd25519StealthAddress(recipientMetaAddress) {
|
|
821
|
-
validateEd25519StealthMetaAddress(recipientMetaAddress);
|
|
822
|
-
const ephemeralPrivateKey = randomBytes2(32);
|
|
823
|
-
try {
|
|
824
|
-
const ephemeralPublicKey = ed25519.getPublicKey(ephemeralPrivateKey);
|
|
825
|
-
const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2));
|
|
826
|
-
const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2));
|
|
827
|
-
const rawEphemeralScalar = getEd25519Scalar(ephemeralPrivateKey);
|
|
828
|
-
const ephemeralScalar = rawEphemeralScalar % ED25519_ORDER;
|
|
829
|
-
if (ephemeralScalar === 0n) {
|
|
830
|
-
throw new Error("CRITICAL: Zero ephemeral scalar after reduction - investigate RNG");
|
|
831
|
-
}
|
|
832
|
-
const spendingPoint = ed25519.ExtendedPoint.fromHex(spendingKeyBytes);
|
|
833
|
-
const sharedSecretPoint = spendingPoint.multiply(ephemeralScalar);
|
|
834
|
-
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes());
|
|
835
|
-
const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER;
|
|
836
|
-
if (hashScalar === 0n) {
|
|
837
|
-
throw new Error("CRITICAL: Zero hash scalar after reduction - investigate hash computation");
|
|
838
|
-
}
|
|
839
|
-
const hashTimesG = ed25519.ExtendedPoint.BASE.multiply(hashScalar);
|
|
840
|
-
const viewingPoint = ed25519.ExtendedPoint.fromHex(viewingKeyBytes);
|
|
841
|
-
const stealthPoint = viewingPoint.add(hashTimesG);
|
|
842
|
-
const stealthAddressBytes = stealthPoint.toRawBytes();
|
|
843
|
-
const viewTag = sharedSecretHash[0];
|
|
844
|
-
return {
|
|
845
|
-
stealthAddress: {
|
|
846
|
-
address: `0x${bytesToHex(stealthAddressBytes)}`,
|
|
847
|
-
ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}`,
|
|
848
|
-
viewTag
|
|
849
|
-
},
|
|
850
|
-
sharedSecret: `0x${bytesToHex(sharedSecretHash)}`
|
|
851
|
-
};
|
|
852
|
-
} finally {
|
|
853
|
-
secureWipe(ephemeralPrivateKey);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
function deriveEd25519StealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
|
|
857
|
-
validateEd25519StealthAddress(stealthAddress);
|
|
858
|
-
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
859
|
-
throw new ValidationError(
|
|
860
|
-
"must be a valid 32-byte hex string",
|
|
861
|
-
"spendingPrivateKey"
|
|
862
|
-
);
|
|
863
|
-
}
|
|
864
|
-
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
865
|
-
throw new ValidationError(
|
|
866
|
-
"must be a valid 32-byte hex string",
|
|
867
|
-
"viewingPrivateKey"
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2));
|
|
871
|
-
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2));
|
|
872
|
-
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2));
|
|
873
|
-
try {
|
|
874
|
-
const rawSpendingScalar = getEd25519Scalar(spendingPrivBytes);
|
|
875
|
-
const spendingScalar = rawSpendingScalar % ED25519_ORDER;
|
|
876
|
-
if (spendingScalar === 0n) {
|
|
877
|
-
throw new Error("CRITICAL: Zero spending scalar after reduction - investigate key derivation");
|
|
878
|
-
}
|
|
879
|
-
const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes);
|
|
880
|
-
const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar);
|
|
881
|
-
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes());
|
|
882
|
-
const rawViewingScalar = getEd25519Scalar(viewingPrivBytes);
|
|
883
|
-
const viewingScalar = rawViewingScalar % ED25519_ORDER;
|
|
884
|
-
if (viewingScalar === 0n) {
|
|
885
|
-
throw new Error("CRITICAL: Zero viewing scalar after reduction - investigate key derivation");
|
|
886
|
-
}
|
|
887
|
-
const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER;
|
|
888
|
-
if (hashScalar === 0n) {
|
|
889
|
-
throw new Error("CRITICAL: Zero hash scalar after reduction - investigate hash computation");
|
|
890
|
-
}
|
|
891
|
-
const stealthPrivateScalar = (viewingScalar + hashScalar) % ED25519_ORDER;
|
|
892
|
-
if (stealthPrivateScalar === 0n) {
|
|
893
|
-
throw new Error("CRITICAL: Zero stealth scalar after reduction - investigate key derivation");
|
|
894
|
-
}
|
|
895
|
-
const stealthPrivateKey = bigIntToBytesLE(stealthPrivateScalar, 32);
|
|
896
|
-
const result = {
|
|
897
|
-
stealthAddress: stealthAddress.address,
|
|
898
|
-
ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
|
|
899
|
-
privateKey: `0x${bytesToHex(stealthPrivateKey)}`
|
|
900
|
-
};
|
|
901
|
-
secureWipe(stealthPrivateKey);
|
|
902
|
-
return result;
|
|
903
|
-
} finally {
|
|
904
|
-
secureWipeAll(spendingPrivBytes, viewingPrivBytes);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
function checkEd25519StealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
|
|
908
|
-
validateEd25519StealthAddress(stealthAddress);
|
|
909
|
-
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
910
|
-
throw new ValidationError(
|
|
911
|
-
"must be a valid 32-byte hex string",
|
|
912
|
-
"spendingPrivateKey"
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
916
|
-
throw new ValidationError(
|
|
917
|
-
"must be a valid 32-byte hex string",
|
|
918
|
-
"viewingPrivateKey"
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2));
|
|
922
|
-
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2));
|
|
923
|
-
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2));
|
|
924
|
-
try {
|
|
925
|
-
const rawSpendingScalar = getEd25519Scalar(spendingPrivBytes);
|
|
926
|
-
const spendingScalar = rawSpendingScalar % ED25519_ORDER;
|
|
927
|
-
if (spendingScalar === 0n) {
|
|
928
|
-
throw new Error("CRITICAL: Zero spending scalar after reduction - investigate key derivation");
|
|
929
|
-
}
|
|
930
|
-
const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes);
|
|
931
|
-
const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar);
|
|
932
|
-
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes());
|
|
933
|
-
if (sharedSecretHash[0] !== stealthAddress.viewTag) {
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
|
-
const rawViewingScalar = getEd25519Scalar(viewingPrivBytes);
|
|
937
|
-
const viewingScalar = rawViewingScalar % ED25519_ORDER;
|
|
938
|
-
if (viewingScalar === 0n) {
|
|
939
|
-
throw new Error("CRITICAL: Zero viewing scalar after reduction - investigate key derivation");
|
|
940
|
-
}
|
|
941
|
-
const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER;
|
|
942
|
-
if (hashScalar === 0n) {
|
|
943
|
-
throw new Error("CRITICAL: Zero hash scalar after reduction - investigate hash computation");
|
|
944
|
-
}
|
|
945
|
-
const stealthPrivateScalar = (viewingScalar + hashScalar) % ED25519_ORDER;
|
|
946
|
-
if (stealthPrivateScalar === 0n) {
|
|
947
|
-
throw new Error("CRITICAL: Zero stealth scalar after reduction - investigate key derivation");
|
|
948
|
-
}
|
|
949
|
-
const expectedPubKey = ed25519.ExtendedPoint.BASE.multiply(stealthPrivateScalar);
|
|
950
|
-
const expectedPubKeyBytes = expectedPubKey.toRawBytes();
|
|
951
|
-
const providedAddress = hexToBytes(stealthAddress.address.slice(2));
|
|
952
|
-
return bytesToHex(expectedPubKeyBytes) === bytesToHex(providedAddress);
|
|
953
|
-
} finally {
|
|
954
|
-
secureWipeAll(spendingPrivBytes, viewingPrivBytes);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
958
|
-
function bytesToBase58(bytes) {
|
|
959
|
-
let leadingZeros = 0;
|
|
960
|
-
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
|
|
961
|
-
leadingZeros++;
|
|
962
|
-
}
|
|
963
|
-
let value = 0n;
|
|
964
|
-
for (const byte of bytes) {
|
|
965
|
-
value = value * 256n + BigInt(byte);
|
|
966
|
-
}
|
|
967
|
-
let result = "";
|
|
968
|
-
while (value > 0n) {
|
|
969
|
-
const remainder = value % 58n;
|
|
970
|
-
value = value / 58n;
|
|
971
|
-
result = BASE58_ALPHABET[Number(remainder)] + result;
|
|
972
|
-
}
|
|
973
|
-
return "1".repeat(leadingZeros) + result;
|
|
974
|
-
}
|
|
975
|
-
function base58ToBytes(str) {
|
|
976
|
-
let leadingOnes = 0;
|
|
977
|
-
for (let i = 0; i < str.length && str[i] === "1"; i++) {
|
|
978
|
-
leadingOnes++;
|
|
979
|
-
}
|
|
980
|
-
let value = 0n;
|
|
981
|
-
for (const char of str) {
|
|
982
|
-
const index = BASE58_ALPHABET.indexOf(char);
|
|
983
|
-
if (index === -1) {
|
|
984
|
-
throw new ValidationError(`Invalid base58 character: ${char}`, "address");
|
|
985
|
-
}
|
|
986
|
-
value = value * 58n + BigInt(index);
|
|
987
|
-
}
|
|
988
|
-
const bytes = [];
|
|
989
|
-
while (value > 0n) {
|
|
990
|
-
bytes.unshift(Number(value % 256n));
|
|
991
|
-
value = value / 256n;
|
|
992
|
-
}
|
|
993
|
-
const result = new Uint8Array(leadingOnes + bytes.length);
|
|
994
|
-
for (let i = 0; i < leadingOnes; i++) {
|
|
995
|
-
result[i] = 0;
|
|
996
|
-
}
|
|
997
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
998
|
-
result[leadingOnes + i] = bytes[i];
|
|
999
|
-
}
|
|
1000
|
-
return result;
|
|
1001
|
-
}
|
|
1002
|
-
function ed25519PublicKeyToSolanaAddress(publicKey) {
|
|
1003
|
-
if (!isValidHex(publicKey)) {
|
|
1004
|
-
throw new ValidationError(
|
|
1005
|
-
"publicKey must be a valid hex string with 0x prefix",
|
|
1006
|
-
"publicKey"
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
|
-
if (!isValidEd25519PublicKey(publicKey)) {
|
|
1010
|
-
throw new ValidationError(
|
|
1011
|
-
"publicKey must be 32 bytes (64 hex characters)",
|
|
1012
|
-
"publicKey"
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
const publicKeyBytes = hexToBytes(publicKey.slice(2));
|
|
1016
|
-
return bytesToBase58(publicKeyBytes);
|
|
1017
|
-
}
|
|
1018
|
-
function isValidSolanaAddress(address) {
|
|
1019
|
-
if (typeof address !== "string" || address.length === 0) {
|
|
1020
|
-
return false;
|
|
1021
|
-
}
|
|
1022
|
-
if (address.length < 32 || address.length > 44) {
|
|
1023
|
-
return false;
|
|
1024
|
-
}
|
|
1025
|
-
try {
|
|
1026
|
-
const decoded = base58ToBytes(address);
|
|
1027
|
-
return decoded.length === 32;
|
|
1028
|
-
} catch {
|
|
1029
|
-
return false;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
function solanaAddressToEd25519PublicKey(address) {
|
|
1033
|
-
if (!isValidSolanaAddress(address)) {
|
|
1034
|
-
throw new ValidationError(
|
|
1035
|
-
"Invalid Solana address format",
|
|
1036
|
-
"address"
|
|
1037
|
-
);
|
|
1038
|
-
}
|
|
1039
|
-
const decoded = base58ToBytes(address);
|
|
1040
|
-
return `0x${bytesToHex(decoded)}`;
|
|
1041
|
-
}
|
|
1042
|
-
function ed25519PublicKeyToNearAddress(publicKey) {
|
|
1043
|
-
if (!isValidHex(publicKey)) {
|
|
1044
|
-
throw new ValidationError(
|
|
1045
|
-
"publicKey must be a valid hex string with 0x prefix",
|
|
1046
|
-
"publicKey"
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1049
|
-
if (!isValidEd25519PublicKey(publicKey)) {
|
|
1050
|
-
throw new ValidationError(
|
|
1051
|
-
"publicKey must be 32 bytes (64 hex characters)",
|
|
1052
|
-
"publicKey"
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
return publicKey.slice(2).toLowerCase();
|
|
1056
|
-
}
|
|
1057
|
-
function nearAddressToEd25519PublicKey(address) {
|
|
1058
|
-
if (!isValidNearImplicitAddress(address)) {
|
|
1059
|
-
throw new ValidationError(
|
|
1060
|
-
"Invalid NEAR implicit address format",
|
|
1061
|
-
"address"
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
return `0x${address.toLowerCase()}`;
|
|
1065
|
-
}
|
|
1066
|
-
function isValidNearImplicitAddress(address) {
|
|
1067
|
-
if (typeof address !== "string" || address.length === 0) {
|
|
1068
|
-
return false;
|
|
1069
|
-
}
|
|
1070
|
-
if (address.length !== 64) {
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
return /^[0-9a-f]{64}$/.test(address);
|
|
1074
|
-
}
|
|
1075
|
-
function isValidNearAccountId(accountId) {
|
|
1076
|
-
if (typeof accountId !== "string" || accountId.length === 0) {
|
|
1077
|
-
return false;
|
|
1078
|
-
}
|
|
1079
|
-
if (isValidNearImplicitAddress(accountId)) {
|
|
1080
|
-
return true;
|
|
1081
|
-
}
|
|
1082
|
-
if (accountId.length < 2 || accountId.length > 64) {
|
|
1083
|
-
return false;
|
|
1084
|
-
}
|
|
1085
|
-
const nearAccountPattern = /^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/;
|
|
1086
|
-
if (!nearAccountPattern.test(accountId)) {
|
|
1087
|
-
return false;
|
|
1088
|
-
}
|
|
1089
|
-
if (accountId.includes("..")) {
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
return true;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// src/chains/solana/transfer.ts
|
|
1096
|
-
async function sendPrivateSPLTransfer(params) {
|
|
1097
|
-
const {
|
|
1098
|
-
connection,
|
|
1099
|
-
sender,
|
|
1100
|
-
senderTokenAccount,
|
|
1101
|
-
recipientMetaAddress,
|
|
1102
|
-
mint,
|
|
1103
|
-
amount,
|
|
1104
|
-
signTransaction
|
|
1105
|
-
} = params;
|
|
1106
|
-
if (recipientMetaAddress.chain !== "solana") {
|
|
1107
|
-
throw new Error(
|
|
1108
|
-
`Invalid chain: expected 'solana', got '${recipientMetaAddress.chain}'`
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
const { stealthAddress } = generateEd25519StealthAddress(recipientMetaAddress);
|
|
1112
|
-
const stealthAddressBase58 = ed25519PublicKeyToSolanaAddress(stealthAddress.address);
|
|
1113
|
-
const stealthPubkey = new PublicKey(stealthAddressBase58);
|
|
1114
|
-
const ephemeralPubkeyBase58 = ed25519PublicKeyToSolanaAddress(
|
|
1115
|
-
stealthAddress.ephemeralPublicKey
|
|
1116
|
-
);
|
|
1117
|
-
const stealthATA = await getAssociatedTokenAddress(
|
|
1118
|
-
mint,
|
|
1119
|
-
stealthPubkey,
|
|
1120
|
-
true
|
|
1121
|
-
// allowOwnerOffCurve - stealth addresses may be off-curve
|
|
1122
|
-
);
|
|
1123
|
-
const transaction = new Transaction();
|
|
1124
|
-
let stealthATAExists = false;
|
|
1125
|
-
try {
|
|
1126
|
-
await getAccount(connection, stealthATA);
|
|
1127
|
-
stealthATAExists = true;
|
|
1128
|
-
} catch {
|
|
1129
|
-
stealthATAExists = false;
|
|
1130
|
-
}
|
|
1131
|
-
if (!stealthATAExists) {
|
|
1132
|
-
transaction.add(
|
|
1133
|
-
createAssociatedTokenAccountInstruction(
|
|
1134
|
-
sender,
|
|
1135
|
-
// payer
|
|
1136
|
-
stealthATA,
|
|
1137
|
-
// associatedToken
|
|
1138
|
-
stealthPubkey,
|
|
1139
|
-
// owner
|
|
1140
|
-
mint,
|
|
1141
|
-
// mint
|
|
1142
|
-
TOKEN_PROGRAM_ID,
|
|
1143
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1144
|
-
)
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
transaction.add(
|
|
1148
|
-
createTransferInstruction(
|
|
1149
|
-
senderTokenAccount,
|
|
1150
|
-
// source
|
|
1151
|
-
stealthATA,
|
|
1152
|
-
// destination
|
|
1153
|
-
sender,
|
|
1154
|
-
// owner
|
|
1155
|
-
amount
|
|
1156
|
-
// amount
|
|
1157
|
-
)
|
|
1158
|
-
);
|
|
1159
|
-
const memoContent = createAnnouncementMemo(
|
|
1160
|
-
ephemeralPubkeyBase58,
|
|
1161
|
-
stealthAddress.viewTag.slice(2),
|
|
1162
|
-
// Remove 0x prefix
|
|
1163
|
-
stealthAddressBase58
|
|
1164
|
-
);
|
|
1165
|
-
const memoInstruction = new TransactionInstruction({
|
|
1166
|
-
keys: [],
|
|
1167
|
-
programId: new PublicKey(MEMO_PROGRAM_ID),
|
|
1168
|
-
data: Buffer.from(memoContent, "utf-8")
|
|
1169
|
-
});
|
|
1170
|
-
transaction.add(memoInstruction);
|
|
1171
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
1172
|
-
transaction.recentBlockhash = blockhash;
|
|
1173
|
-
transaction.lastValidBlockHeight = lastValidBlockHeight;
|
|
1174
|
-
transaction.feePayer = sender;
|
|
1175
|
-
const signedTx = await signTransaction(transaction);
|
|
1176
|
-
const txSignature = await connection.sendRawTransaction(signedTx.serialize(), {
|
|
1177
|
-
skipPreflight: false,
|
|
1178
|
-
preflightCommitment: "confirmed"
|
|
1179
|
-
});
|
|
1180
|
-
await connection.confirmTransaction(
|
|
1181
|
-
{
|
|
1182
|
-
signature: txSignature,
|
|
1183
|
-
blockhash,
|
|
1184
|
-
lastValidBlockHeight
|
|
1185
|
-
},
|
|
1186
|
-
"confirmed"
|
|
1187
|
-
);
|
|
1188
|
-
const cluster = detectCluster(connection.rpcEndpoint);
|
|
1189
|
-
return {
|
|
1190
|
-
txSignature,
|
|
1191
|
-
stealthAddress: stealthAddressBase58,
|
|
1192
|
-
ephemeralPublicKey: ephemeralPubkeyBase58,
|
|
1193
|
-
viewTag: stealthAddress.viewTag,
|
|
1194
|
-
explorerUrl: getExplorerUrl(txSignature, cluster),
|
|
1195
|
-
cluster
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
async function estimatePrivateTransferFee(connection, needsATACreation = true) {
|
|
1199
|
-
let fee = ESTIMATED_TX_FEE_LAMPORTS;
|
|
1200
|
-
if (needsATACreation) {
|
|
1201
|
-
const rentExemption = await connection.getMinimumBalanceForRentExemption(165);
|
|
1202
|
-
fee += BigInt(rentExemption);
|
|
1203
|
-
}
|
|
1204
|
-
return fee;
|
|
1205
|
-
}
|
|
1206
|
-
async function hasTokenAccount(connection, stealthAddress, mint) {
|
|
1207
|
-
try {
|
|
1208
|
-
const stealthPubkey = new PublicKey(stealthAddress);
|
|
1209
|
-
const ata = await getAssociatedTokenAddress(mint, stealthPubkey, true);
|
|
1210
|
-
await getAccount(connection, ata);
|
|
1211
|
-
return true;
|
|
1212
|
-
} catch {
|
|
1213
|
-
return false;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
function detectCluster(endpoint) {
|
|
1217
|
-
if (endpoint.includes("devnet")) {
|
|
1218
|
-
return "devnet";
|
|
1219
|
-
}
|
|
1220
|
-
if (endpoint.includes("testnet")) {
|
|
1221
|
-
return "testnet";
|
|
1222
|
-
}
|
|
1223
|
-
if (endpoint.includes("localhost") || endpoint.includes("127.0.0.1")) {
|
|
1224
|
-
return "localnet";
|
|
1225
|
-
}
|
|
1226
|
-
return "mainnet-beta";
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// src/chains/solana/scan.ts
|
|
1230
|
-
import {
|
|
1231
|
-
PublicKey as PublicKey2,
|
|
1232
|
-
Transaction as Transaction2,
|
|
1233
|
-
Keypair
|
|
1234
|
-
} from "@solana/web3.js";
|
|
1235
|
-
import {
|
|
1236
|
-
getAssociatedTokenAddress as getAssociatedTokenAddress2,
|
|
1237
|
-
createTransferInstruction as createTransferInstruction2,
|
|
1238
|
-
getAccount as getAccount2
|
|
1239
|
-
} from "@solana/spl-token";
|
|
1240
|
-
import { hexToBytes as hexToBytes2 } from "@noble/hashes/utils";
|
|
1241
|
-
async function scanForPayments(params) {
|
|
1242
|
-
const {
|
|
1243
|
-
connection,
|
|
1244
|
-
viewingPrivateKey,
|
|
1245
|
-
spendingPublicKey,
|
|
1246
|
-
fromSlot,
|
|
1247
|
-
toSlot,
|
|
1248
|
-
limit = 100
|
|
1249
|
-
} = params;
|
|
1250
|
-
const results = [];
|
|
1251
|
-
const memoProgram = new PublicKey2(MEMO_PROGRAM_ID);
|
|
1252
|
-
try {
|
|
1253
|
-
const signatures = await connection.getSignaturesForAddress(
|
|
1254
|
-
memoProgram,
|
|
1255
|
-
{
|
|
1256
|
-
limit,
|
|
1257
|
-
minContextSlot: fromSlot
|
|
1258
|
-
}
|
|
1259
|
-
);
|
|
1260
|
-
const filteredSignatures = toSlot ? signatures.filter((s) => s.slot <= toSlot) : signatures;
|
|
1261
|
-
for (const sigInfo of filteredSignatures) {
|
|
1262
|
-
try {
|
|
1263
|
-
const tx = await connection.getTransaction(sigInfo.signature, {
|
|
1264
|
-
maxSupportedTransactionVersion: 0
|
|
1265
|
-
});
|
|
1266
|
-
if (!tx?.meta?.logMessages) continue;
|
|
1267
|
-
for (const log of tx.meta.logMessages) {
|
|
1268
|
-
if (!log.includes(SIP_MEMO_PREFIX)) continue;
|
|
1269
|
-
const memoMatch = log.match(/Program log: (.+)/);
|
|
1270
|
-
if (!memoMatch) continue;
|
|
1271
|
-
const memoContent = memoMatch[1];
|
|
1272
|
-
const announcement = parseAnnouncement(memoContent);
|
|
1273
|
-
if (!announcement) continue;
|
|
1274
|
-
const ephemeralPubKeyHex = solanaAddressToEd25519PublicKey(
|
|
1275
|
-
announcement.ephemeralPublicKey
|
|
1276
|
-
);
|
|
1277
|
-
const stealthAddressToCheck = {
|
|
1278
|
-
address: announcement.stealthAddress ? solanaAddressToEd25519PublicKey(announcement.stealthAddress) : "0x" + "00".repeat(32),
|
|
1279
|
-
// Will be computed
|
|
1280
|
-
ephemeralPublicKey: ephemeralPubKeyHex,
|
|
1281
|
-
viewTag: "0x" + announcement.viewTag
|
|
1282
|
-
};
|
|
1283
|
-
const isOurs = checkEd25519StealthAddress(
|
|
1284
|
-
stealthAddressToCheck,
|
|
1285
|
-
viewingPrivateKey,
|
|
1286
|
-
spendingPublicKey
|
|
1287
|
-
);
|
|
1288
|
-
if (isOurs) {
|
|
1289
|
-
const transferInfo = parseTokenTransfer(tx);
|
|
1290
|
-
if (transferInfo) {
|
|
1291
|
-
results.push({
|
|
1292
|
-
stealthAddress: announcement.stealthAddress || "",
|
|
1293
|
-
ephemeralPublicKey: announcement.ephemeralPublicKey,
|
|
1294
|
-
amount: transferInfo.amount,
|
|
1295
|
-
mint: transferInfo.mint,
|
|
1296
|
-
tokenSymbol: getTokenSymbol(transferInfo.mint),
|
|
1297
|
-
txSignature: sigInfo.signature,
|
|
1298
|
-
slot: sigInfo.slot,
|
|
1299
|
-
timestamp: sigInfo.blockTime || 0
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
} catch (err) {
|
|
1305
|
-
console.warn(`Failed to parse tx ${sigInfo.signature}:`, err);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
} catch (err) {
|
|
1309
|
-
console.error("Scan failed:", err);
|
|
1310
|
-
throw new Error(`Failed to scan for payments: ${err}`);
|
|
1311
|
-
}
|
|
1312
|
-
return results;
|
|
1313
|
-
}
|
|
1314
|
-
async function claimStealthPayment(params) {
|
|
1315
|
-
const {
|
|
1316
|
-
connection,
|
|
1317
|
-
stealthAddress,
|
|
1318
|
-
ephemeralPublicKey,
|
|
1319
|
-
viewingPrivateKey,
|
|
1320
|
-
spendingPrivateKey,
|
|
1321
|
-
destinationAddress,
|
|
1322
|
-
mint
|
|
1323
|
-
} = params;
|
|
1324
|
-
const stealthAddressHex = solanaAddressToEd25519PublicKey(stealthAddress);
|
|
1325
|
-
const ephemeralPubKeyHex = solanaAddressToEd25519PublicKey(ephemeralPublicKey);
|
|
1326
|
-
const stealthAddressObj = {
|
|
1327
|
-
address: stealthAddressHex,
|
|
1328
|
-
ephemeralPublicKey: ephemeralPubKeyHex,
|
|
1329
|
-
viewTag: "0x00"
|
|
1330
|
-
// Not needed for derivation
|
|
1331
|
-
};
|
|
1332
|
-
const recovery = deriveEd25519StealthPrivateKey(
|
|
1333
|
-
stealthAddressObj,
|
|
1334
|
-
spendingPrivateKey,
|
|
1335
|
-
viewingPrivateKey
|
|
1336
|
-
);
|
|
1337
|
-
const stealthPrivKeyBytes = hexToBytes2(recovery.privateKey.slice(2));
|
|
1338
|
-
const stealthPubkey = new PublicKey2(stealthAddress);
|
|
1339
|
-
const stealthKeypair = Keypair.fromSecretKey(
|
|
1340
|
-
new Uint8Array([...stealthPrivKeyBytes, ...stealthPubkey.toBytes()])
|
|
1341
|
-
);
|
|
1342
|
-
const stealthATA = await getAssociatedTokenAddress2(
|
|
1343
|
-
mint,
|
|
1344
|
-
stealthPubkey,
|
|
1345
|
-
true
|
|
1346
|
-
);
|
|
1347
|
-
const destinationPubkey = new PublicKey2(destinationAddress);
|
|
1348
|
-
const destinationATA = await getAssociatedTokenAddress2(
|
|
1349
|
-
mint,
|
|
1350
|
-
destinationPubkey
|
|
1351
|
-
);
|
|
1352
|
-
const stealthAccount = await getAccount2(connection, stealthATA);
|
|
1353
|
-
const amount = stealthAccount.amount;
|
|
1354
|
-
const transaction = new Transaction2();
|
|
1355
|
-
transaction.add(
|
|
1356
|
-
createTransferInstruction2(
|
|
1357
|
-
stealthATA,
|
|
1358
|
-
destinationATA,
|
|
1359
|
-
stealthPubkey,
|
|
1360
|
-
amount
|
|
1361
|
-
)
|
|
1362
|
-
);
|
|
1363
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
1364
|
-
transaction.recentBlockhash = blockhash;
|
|
1365
|
-
transaction.lastValidBlockHeight = lastValidBlockHeight;
|
|
1366
|
-
transaction.feePayer = stealthPubkey;
|
|
1367
|
-
transaction.sign(stealthKeypair);
|
|
1368
|
-
const txSignature = await connection.sendRawTransaction(
|
|
1369
|
-
transaction.serialize(),
|
|
1370
|
-
{
|
|
1371
|
-
skipPreflight: false,
|
|
1372
|
-
preflightCommitment: "confirmed"
|
|
1373
|
-
}
|
|
1374
|
-
);
|
|
1375
|
-
await connection.confirmTransaction(
|
|
1376
|
-
{
|
|
1377
|
-
signature: txSignature,
|
|
1378
|
-
blockhash,
|
|
1379
|
-
lastValidBlockHeight
|
|
1380
|
-
},
|
|
1381
|
-
"confirmed"
|
|
1382
|
-
);
|
|
1383
|
-
const cluster = detectCluster2(connection.rpcEndpoint);
|
|
1384
|
-
return {
|
|
1385
|
-
txSignature,
|
|
1386
|
-
destinationAddress,
|
|
1387
|
-
amount,
|
|
1388
|
-
explorerUrl: getExplorerUrl(txSignature, cluster)
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
async function getStealthBalance(connection, stealthAddress, mint) {
|
|
1392
|
-
try {
|
|
1393
|
-
const stealthPubkey = new PublicKey2(stealthAddress);
|
|
1394
|
-
const ata = await getAssociatedTokenAddress2(mint, stealthPubkey, true);
|
|
1395
|
-
const account = await getAccount2(connection, ata);
|
|
1396
|
-
return account.amount;
|
|
1397
|
-
} catch {
|
|
1398
|
-
return 0n;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
function parseTokenTransfer(tx) {
|
|
1402
|
-
if (!tx?.meta?.postTokenBalances || !tx.meta.preTokenBalances) {
|
|
1403
|
-
return null;
|
|
1404
|
-
}
|
|
1405
|
-
for (let i = 0; i < tx.meta.postTokenBalances.length; i++) {
|
|
1406
|
-
const post = tx.meta.postTokenBalances[i];
|
|
1407
|
-
const pre = tx.meta.preTokenBalances.find(
|
|
1408
|
-
(p) => p.accountIndex === post.accountIndex
|
|
1409
|
-
);
|
|
1410
|
-
const postAmount = BigInt(post.uiTokenAmount.amount);
|
|
1411
|
-
const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
|
|
1412
|
-
if (postAmount > preAmount) {
|
|
1413
|
-
return {
|
|
1414
|
-
mint: post.mint,
|
|
1415
|
-
amount: postAmount - preAmount
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
return null;
|
|
1420
|
-
}
|
|
1421
|
-
function getTokenSymbol(mint) {
|
|
1422
|
-
for (const [symbol, address] of Object.entries(SOLANA_TOKEN_MINTS)) {
|
|
1423
|
-
if (address === mint) {
|
|
1424
|
-
return symbol;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
return void 0;
|
|
1428
|
-
}
|
|
1429
|
-
function detectCluster2(endpoint) {
|
|
1430
|
-
if (endpoint.includes("devnet")) {
|
|
1431
|
-
return "devnet";
|
|
1432
|
-
}
|
|
1433
|
-
if (endpoint.includes("testnet")) {
|
|
1434
|
-
return "testnet";
|
|
1435
|
-
}
|
|
1436
|
-
if (endpoint.includes("localhost") || endpoint.includes("127.0.0.1")) {
|
|
1437
|
-
return "localnet";
|
|
1438
|
-
}
|
|
1439
|
-
return "mainnet-beta";
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
export {
|
|
1443
|
-
isValidChainId,
|
|
1444
|
-
isValidPrivacyLevel,
|
|
1445
|
-
isValidHex,
|
|
1446
|
-
isValidHexLength,
|
|
1447
|
-
isValidAmount,
|
|
1448
|
-
isNonNegativeAmount,
|
|
1449
|
-
isValidSlippage,
|
|
1450
|
-
isValidStealthMetaAddress,
|
|
1451
|
-
isValidCompressedPublicKey,
|
|
1452
|
-
isValidEd25519PublicKey,
|
|
1453
|
-
isValidPrivateKey,
|
|
1454
|
-
validateAsset,
|
|
1455
|
-
validateIntentInput,
|
|
1456
|
-
validateIntentOutput,
|
|
1457
|
-
validateCreateIntentParams,
|
|
1458
|
-
validateViewingKey,
|
|
1459
|
-
isValidScalar,
|
|
1460
|
-
validateScalar,
|
|
1461
|
-
getChainAddressType,
|
|
1462
|
-
isAddressValidForChain,
|
|
1463
|
-
secureWipe,
|
|
1464
|
-
withSecureBuffer,
|
|
1465
|
-
withSecureBufferSync,
|
|
1466
|
-
secureWipeAll,
|
|
1467
|
-
generateStealthMetaAddress,
|
|
1468
|
-
generateStealthAddress,
|
|
1469
|
-
deriveStealthPrivateKey,
|
|
1470
|
-
checkStealthAddress,
|
|
1471
|
-
encodeStealthMetaAddress,
|
|
1472
|
-
decodeStealthMetaAddress,
|
|
1473
|
-
publicKeyToEthAddress,
|
|
1474
|
-
isEd25519Chain,
|
|
1475
|
-
getCurveForChain,
|
|
1476
|
-
generateEd25519StealthMetaAddress,
|
|
1477
|
-
generateEd25519StealthAddress,
|
|
1478
|
-
deriveEd25519StealthPrivateKey,
|
|
1479
|
-
checkEd25519StealthAddress,
|
|
1480
|
-
ed25519PublicKeyToSolanaAddress,
|
|
1481
|
-
isValidSolanaAddress,
|
|
1482
|
-
solanaAddressToEd25519PublicKey,
|
|
1483
|
-
ed25519PublicKeyToNearAddress,
|
|
1484
|
-
nearAddressToEd25519PublicKey,
|
|
1485
|
-
isValidNearImplicitAddress,
|
|
1486
|
-
isValidNearAccountId,
|
|
1487
|
-
parseAnnouncement,
|
|
1488
|
-
createAnnouncementMemo,
|
|
1489
|
-
sendPrivateSPLTransfer,
|
|
1490
|
-
estimatePrivateTransferFee,
|
|
1491
|
-
hasTokenAccount,
|
|
1492
|
-
scanForPayments,
|
|
1493
|
-
claimStealthPayment,
|
|
1494
|
-
getStealthBalance
|
|
1495
|
-
};
|