@obelyzk/sdk 1.0.0 → 1.0.1

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/dist/index.mjs CHANGED
@@ -84,7 +84,7 @@ import {
84
84
  powMod,
85
85
  randomScalar,
86
86
  subMod
87
- } from "./chunk-O2PF7VJA.mjs";
87
+ } from "./chunk-CGPFHXUF.mjs";
88
88
 
89
89
  // src/types.ts
90
90
  function getMinStake(tier) {
@@ -924,7 +924,14 @@ declare class VM31VaultClient {
924
924
  private get contractAddress();
925
925
  private get relayerUrl();
926
926
  private get relayerApiKey();
927
+ /** Cached relayer X25519 public key (fetched on first encrypted submit) */
928
+ private _relayerPubkey;
927
929
  private relayerFetch;
930
+ /**
931
+ * Submit a payload to the relayer, encrypted with ECIES.
932
+ * Falls back to plaintext if `encrypt: false` is passed.
933
+ */
934
+ private relayerSubmit;
928
935
  /** Get the current Merkle root of the VM31 commitment tree */
929
936
  getMerkleRoot(): Promise<PackedDigest>;
930
937
  /** Get the number of leaves in the commitment tree */
@@ -969,12 +976,24 @@ declare class VM31VaultClient {
969
976
  version: number;
970
977
  algorithm: string;
971
978
  }>;
972
- /** Submit a deposit transaction to the relayer */
973
- submitDeposit(params: VaultDepositParams): Promise<VaultSubmitResult>;
974
- /** Submit a withdrawal transaction to the relayer */
975
- submitWithdraw(params: VaultWithdrawParams): Promise<VaultSubmitResult>;
976
- /** Submit a private transfer transaction to the relayer */
977
- submitTransfer(params: VaultTransferParams): Promise<VaultSubmitResult>;
979
+ /**
980
+ * Submit a deposit transaction to the relayer.
981
+ * Validates denomination (privacy gap #7) and encrypts with ECIES by default.
982
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
983
+ */
984
+ submitDeposit(params: VaultDepositParams, encrypt?: boolean): Promise<VaultSubmitResult>;
985
+ /**
986
+ * Submit a withdrawal transaction to the relayer.
987
+ * Withdrawals are not denomination-restricted.
988
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
989
+ */
990
+ submitWithdraw(params: VaultWithdrawParams, encrypt?: boolean): Promise<VaultSubmitResult>;
991
+ /**
992
+ * Submit a private transfer transaction to the relayer.
993
+ * Transfers are not denomination-restricted.
994
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
995
+ */
996
+ submitTransfer(params: VaultTransferParams, encrypt?: boolean): Promise<VaultSubmitResult>;
978
997
  /** Query batch info from the relayer */
979
998
  queryBatch(batchId: string): Promise<VaultBatchInfo>;
980
999
  /** Fetch Merkle path for a commitment from the relayer */
@@ -1342,4 +1361,74 @@ declare class ObelyskClient {
1342
1361
  requireAccount(): Account;
1343
1362
  }
1344
1363
 
1345
- export { type AssetPair, type BatchStatus, type BridgeConfig, type ClaimFillParams, type ClaimParams, type CommitOrderParams, ConfidentialTransferClient, DARKPOOL_ASSET_IDS, DarkPoolClient, type DarkPoolDepositParams, type DarkPoolOrderData, type DepositParams, type DepositResult, ECPoint, type EpochInfo, type FundParams, type GpuTier, MAINNET_PRIVACY_POOLS, MAINNET_TOKENS, type OTCOrder, OTCOrderbookClient, type OTCTrade, ObelyskClient, type ObelyskConfig, type ObelyskNetwork, ObelyskPrivacy, OrderSide, OrderStatus, OrderType, type OrderbookConfig, type OrderbookDepth, type OrderbookDepthLevel, type PackedDigest, type PlaceLimitOrderParams, type PlaceMarketOrderParams, type PrivacyNote, PrivacyPoolClient, PrivacyRouterClient, type PrivateAccount, ProverStakingClient, type RelayerInfo, type RevealOrderParams, ShieldedSwapClient, type ShieldedSwapParams, type StakingConfig, type Stats24h, type StealthAnnouncement, StealthClient, type StealthMetaAddress, type StealthSendParams, type StealthSpendingProof, TOKEN_DECIMALS, type TradingPair, type TransferParams, type VM31AssetId, VM31BridgeClient, VM31VaultClient, VM31_ASSET_IDS, type VaultBatchInfo, type VaultDepositParams, type VaultMerklePath, type VaultNote, type VaultSubmitResult, type VaultTransferParams, type VaultWithdrawParams, type WithdrawCTParams, type WithdrawParams, type WithdrawResult, type WorkerStake, commitmentToHash, createAEHint, createEncryptionProof, deriveNullifier, ecAdd, ecMul, elgamalEncrypt, formatAmount, getContracts, getRpcUrl, mod, modInverse, parseAmount, pedersenCommit, randomScalar };
1364
+ /**
1365
+ * ECIES Encryption for VM31 Relayer
1366
+ *
1367
+ * X25519 ECDH + HKDF-SHA256 + AES-256-GCM
1368
+ * Compatible with the Obelysk VM31 relayer (Rust: x25519-dalek + aes-gcm + hkdf)
1369
+ *
1370
+ * Uses Web Crypto API (works in Node 20+ and all modern browsers).
1371
+ */
1372
+ interface ECIESEnvelope {
1373
+ ephemeral_pubkey: string;
1374
+ ciphertext: string;
1375
+ nonce: string;
1376
+ version: number;
1377
+ }
1378
+ /**
1379
+ * Encrypt a JSON payload for the VM31 relayer using ECIES.
1380
+ *
1381
+ * @param payload - The SubmitRequest object to encrypt
1382
+ * @param relayerPubkeyHex - The relayer's X25519 public key (hex, 64 chars)
1383
+ * @returns ECIES envelope ready to POST to /submit
1384
+ */
1385
+ declare function eciesEncrypt(payload: unknown, relayerPubkeyHex: string): Promise<ECIESEnvelope>;
1386
+
1387
+ /**
1388
+ * VM31 Deposit Denomination Whitelists
1389
+ *
1390
+ * Privacy gap #7 mitigation: deposits must use standard denominations
1391
+ * to prevent exact-amount correlation attacks.
1392
+ *
1393
+ * Only deposits are restricted — withdrawals and transfers are unrestricted.
1394
+ */
1395
+ /** wBTC denominations (8 decimals) */
1396
+ declare const WBTC_DENOMINATIONS: readonly bigint[];
1397
+ /** SAGE denominations (18 decimals) */
1398
+ declare const SAGE_DENOMINATIONS: readonly bigint[];
1399
+ /** ETH denominations (18 decimals) */
1400
+ declare const ETH_DENOMINATIONS: readonly bigint[];
1401
+ /** STRK denominations (18 decimals) */
1402
+ declare const STRK_DENOMINATIONS: readonly bigint[];
1403
+ /** USDC denominations (6 decimals) */
1404
+ declare const USDC_DENOMINATIONS: readonly bigint[];
1405
+ /** All denomination lists by VM31 asset ID */
1406
+ declare const VM31_DENOMINATIONS: Record<number, readonly bigint[]>;
1407
+ /** Denomination lists by token symbol */
1408
+ declare const DENOMINATION_BY_SYMBOL: Record<string, readonly bigint[]>;
1409
+ /**
1410
+ * Validate that a deposit amount is a standard denomination.
1411
+ * Throws if the amount is not in the whitelist.
1412
+ * Unknown assets pass through (forward-compatible).
1413
+ *
1414
+ * @param amount - Amount in base units (e.g., wei, satoshis)
1415
+ * @param assetIdOrSymbol - VM31 asset ID (0-4) or token symbol
1416
+ */
1417
+ declare function validateDenomination(amount: bigint, assetIdOrSymbol: number | string): void;
1418
+ /**
1419
+ * Split an amount into the fewest standard denominations (greedy, largest first).
1420
+ * Useful for automatically batching a deposit into multiple denomination-safe transactions.
1421
+ *
1422
+ * @returns denominations array and any remainder that can't be represented
1423
+ */
1424
+ declare function splitIntoDenominations(amount: bigint, assetIdOrSymbol: number | string): {
1425
+ denominations: bigint[];
1426
+ remainder: bigint;
1427
+ };
1428
+ /**
1429
+ * Get valid denominations for an asset.
1430
+ * Returns undefined for unknown assets.
1431
+ */
1432
+ declare function getDenominations(assetIdOrSymbol: number | string): readonly bigint[] | undefined;
1433
+
1434
+ export { type AssetPair, type BatchStatus, type BridgeConfig, type ClaimFillParams, type ClaimParams, type CommitOrderParams, ConfidentialTransferClient, DARKPOOL_ASSET_IDS, DENOMINATION_BY_SYMBOL, DarkPoolClient, type DarkPoolDepositParams, type DarkPoolOrderData, type DepositParams, type DepositResult, type ECIESEnvelope, ECPoint, ETH_DENOMINATIONS, type EpochInfo, type FundParams, type GpuTier, MAINNET_PRIVACY_POOLS, MAINNET_TOKENS, type OTCOrder, OTCOrderbookClient, type OTCTrade, ObelyskClient, type ObelyskConfig, type ObelyskNetwork, ObelyskPrivacy, OrderSide, OrderStatus, OrderType, type OrderbookConfig, type OrderbookDepth, type OrderbookDepthLevel, type PackedDigest, type PlaceLimitOrderParams, type PlaceMarketOrderParams, type PrivacyNote, PrivacyPoolClient, PrivacyRouterClient, type PrivateAccount, ProverStakingClient, type RelayerInfo, type RevealOrderParams, SAGE_DENOMINATIONS, STRK_DENOMINATIONS, ShieldedSwapClient, type ShieldedSwapParams, type StakingConfig, type Stats24h, type StealthAnnouncement, StealthClient, type StealthMetaAddress, type StealthSendParams, type StealthSpendingProof, TOKEN_DECIMALS, type TradingPair, type TransferParams, USDC_DENOMINATIONS, type VM31AssetId, VM31BridgeClient, VM31VaultClient, VM31_ASSET_IDS, VM31_DENOMINATIONS, type VaultBatchInfo, type VaultDepositParams, type VaultMerklePath, type VaultNote, type VaultSubmitResult, type VaultTransferParams, type VaultWithdrawParams, WBTC_DENOMINATIONS, type WithdrawCTParams, type WithdrawParams, type WithdrawResult, type WorkerStake, commitmentToHash, createAEHint, createEncryptionProof, deriveNullifier, ecAdd, ecMul, eciesEncrypt, elgamalEncrypt, formatAmount, getContracts, getDenominations, getRpcUrl, mod, modInverse, parseAmount, pedersenCommit, randomScalar, splitIntoDenominations, validateDenomination };
@@ -924,7 +924,14 @@ declare class VM31VaultClient {
924
924
  private get contractAddress();
925
925
  private get relayerUrl();
926
926
  private get relayerApiKey();
927
+ /** Cached relayer X25519 public key (fetched on first encrypted submit) */
928
+ private _relayerPubkey;
927
929
  private relayerFetch;
930
+ /**
931
+ * Submit a payload to the relayer, encrypted with ECIES.
932
+ * Falls back to plaintext if `encrypt: false` is passed.
933
+ */
934
+ private relayerSubmit;
928
935
  /** Get the current Merkle root of the VM31 commitment tree */
929
936
  getMerkleRoot(): Promise<PackedDigest>;
930
937
  /** Get the number of leaves in the commitment tree */
@@ -969,12 +976,24 @@ declare class VM31VaultClient {
969
976
  version: number;
970
977
  algorithm: string;
971
978
  }>;
972
- /** Submit a deposit transaction to the relayer */
973
- submitDeposit(params: VaultDepositParams): Promise<VaultSubmitResult>;
974
- /** Submit a withdrawal transaction to the relayer */
975
- submitWithdraw(params: VaultWithdrawParams): Promise<VaultSubmitResult>;
976
- /** Submit a private transfer transaction to the relayer */
977
- submitTransfer(params: VaultTransferParams): Promise<VaultSubmitResult>;
979
+ /**
980
+ * Submit a deposit transaction to the relayer.
981
+ * Validates denomination (privacy gap #7) and encrypts with ECIES by default.
982
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
983
+ */
984
+ submitDeposit(params: VaultDepositParams, encrypt?: boolean): Promise<VaultSubmitResult>;
985
+ /**
986
+ * Submit a withdrawal transaction to the relayer.
987
+ * Withdrawals are not denomination-restricted.
988
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
989
+ */
990
+ submitWithdraw(params: VaultWithdrawParams, encrypt?: boolean): Promise<VaultSubmitResult>;
991
+ /**
992
+ * Submit a private transfer transaction to the relayer.
993
+ * Transfers are not denomination-restricted.
994
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
995
+ */
996
+ submitTransfer(params: VaultTransferParams, encrypt?: boolean): Promise<VaultSubmitResult>;
978
997
  /** Query batch info from the relayer */
979
998
  queryBatch(batchId: string): Promise<VaultBatchInfo>;
980
999
  /** Fetch Merkle path for a commitment from the relayer */
@@ -1342,4 +1361,74 @@ declare class ObelyskClient {
1342
1361
  requireAccount(): Account;
1343
1362
  }
1344
1363
 
1345
- export { type AssetPair, type BatchStatus, type BridgeConfig, type ClaimFillParams, type ClaimParams, type CommitOrderParams, ConfidentialTransferClient, DARKPOOL_ASSET_IDS, DarkPoolClient, type DarkPoolDepositParams, type DarkPoolOrderData, type DepositParams, type DepositResult, ECPoint, type EpochInfo, type FundParams, type GpuTier, MAINNET_PRIVACY_POOLS, MAINNET_TOKENS, type OTCOrder, OTCOrderbookClient, type OTCTrade, ObelyskClient, type ObelyskConfig, type ObelyskNetwork, ObelyskPrivacy, OrderSide, OrderStatus, OrderType, type OrderbookConfig, type OrderbookDepth, type OrderbookDepthLevel, type PackedDigest, type PlaceLimitOrderParams, type PlaceMarketOrderParams, type PrivacyNote, PrivacyPoolClient, PrivacyRouterClient, type PrivateAccount, ProverStakingClient, type RelayerInfo, type RevealOrderParams, ShieldedSwapClient, type ShieldedSwapParams, type StakingConfig, type Stats24h, type StealthAnnouncement, StealthClient, type StealthMetaAddress, type StealthSendParams, type StealthSpendingProof, TOKEN_DECIMALS, type TradingPair, type TransferParams, type VM31AssetId, VM31BridgeClient, VM31VaultClient, VM31_ASSET_IDS, type VaultBatchInfo, type VaultDepositParams, type VaultMerklePath, type VaultNote, type VaultSubmitResult, type VaultTransferParams, type VaultWithdrawParams, type WithdrawCTParams, type WithdrawParams, type WithdrawResult, type WorkerStake, commitmentToHash, createAEHint, createEncryptionProof, deriveNullifier, ecAdd, ecMul, elgamalEncrypt, formatAmount, getContracts, getRpcUrl, mod, modInverse, parseAmount, pedersenCommit, randomScalar };
1364
+ /**
1365
+ * ECIES Encryption for VM31 Relayer
1366
+ *
1367
+ * X25519 ECDH + HKDF-SHA256 + AES-256-GCM
1368
+ * Compatible with the Obelysk VM31 relayer (Rust: x25519-dalek + aes-gcm + hkdf)
1369
+ *
1370
+ * Uses Web Crypto API (works in Node 20+ and all modern browsers).
1371
+ */
1372
+ interface ECIESEnvelope {
1373
+ ephemeral_pubkey: string;
1374
+ ciphertext: string;
1375
+ nonce: string;
1376
+ version: number;
1377
+ }
1378
+ /**
1379
+ * Encrypt a JSON payload for the VM31 relayer using ECIES.
1380
+ *
1381
+ * @param payload - The SubmitRequest object to encrypt
1382
+ * @param relayerPubkeyHex - The relayer's X25519 public key (hex, 64 chars)
1383
+ * @returns ECIES envelope ready to POST to /submit
1384
+ */
1385
+ declare function eciesEncrypt(payload: unknown, relayerPubkeyHex: string): Promise<ECIESEnvelope>;
1386
+
1387
+ /**
1388
+ * VM31 Deposit Denomination Whitelists
1389
+ *
1390
+ * Privacy gap #7 mitigation: deposits must use standard denominations
1391
+ * to prevent exact-amount correlation attacks.
1392
+ *
1393
+ * Only deposits are restricted — withdrawals and transfers are unrestricted.
1394
+ */
1395
+ /** wBTC denominations (8 decimals) */
1396
+ declare const WBTC_DENOMINATIONS: readonly bigint[];
1397
+ /** SAGE denominations (18 decimals) */
1398
+ declare const SAGE_DENOMINATIONS: readonly bigint[];
1399
+ /** ETH denominations (18 decimals) */
1400
+ declare const ETH_DENOMINATIONS: readonly bigint[];
1401
+ /** STRK denominations (18 decimals) */
1402
+ declare const STRK_DENOMINATIONS: readonly bigint[];
1403
+ /** USDC denominations (6 decimals) */
1404
+ declare const USDC_DENOMINATIONS: readonly bigint[];
1405
+ /** All denomination lists by VM31 asset ID */
1406
+ declare const VM31_DENOMINATIONS: Record<number, readonly bigint[]>;
1407
+ /** Denomination lists by token symbol */
1408
+ declare const DENOMINATION_BY_SYMBOL: Record<string, readonly bigint[]>;
1409
+ /**
1410
+ * Validate that a deposit amount is a standard denomination.
1411
+ * Throws if the amount is not in the whitelist.
1412
+ * Unknown assets pass through (forward-compatible).
1413
+ *
1414
+ * @param amount - Amount in base units (e.g., wei, satoshis)
1415
+ * @param assetIdOrSymbol - VM31 asset ID (0-4) or token symbol
1416
+ */
1417
+ declare function validateDenomination(amount: bigint, assetIdOrSymbol: number | string): void;
1418
+ /**
1419
+ * Split an amount into the fewest standard denominations (greedy, largest first).
1420
+ * Useful for automatically batching a deposit into multiple denomination-safe transactions.
1421
+ *
1422
+ * @returns denominations array and any remainder that can't be represented
1423
+ */
1424
+ declare function splitIntoDenominations(amount: bigint, assetIdOrSymbol: number | string): {
1425
+ denominations: bigint[];
1426
+ remainder: bigint;
1427
+ };
1428
+ /**
1429
+ * Get valid denominations for an asset.
1430
+ * Returns undefined for unknown assets.
1431
+ */
1432
+ declare function getDenominations(assetIdOrSymbol: number | string): readonly bigint[] | undefined;
1433
+
1434
+ export { type AssetPair, type BatchStatus, type BridgeConfig, type ClaimFillParams, type ClaimParams, type CommitOrderParams, ConfidentialTransferClient, DARKPOOL_ASSET_IDS, DENOMINATION_BY_SYMBOL, DarkPoolClient, type DarkPoolDepositParams, type DarkPoolOrderData, type DepositParams, type DepositResult, type ECIESEnvelope, ECPoint, ETH_DENOMINATIONS, type EpochInfo, type FundParams, type GpuTier, MAINNET_PRIVACY_POOLS, MAINNET_TOKENS, type OTCOrder, OTCOrderbookClient, type OTCTrade, ObelyskClient, type ObelyskConfig, type ObelyskNetwork, ObelyskPrivacy, OrderSide, OrderStatus, OrderType, type OrderbookConfig, type OrderbookDepth, type OrderbookDepthLevel, type PackedDigest, type PlaceLimitOrderParams, type PlaceMarketOrderParams, type PrivacyNote, PrivacyPoolClient, PrivacyRouterClient, type PrivateAccount, ProverStakingClient, type RelayerInfo, type RevealOrderParams, SAGE_DENOMINATIONS, STRK_DENOMINATIONS, ShieldedSwapClient, type ShieldedSwapParams, type StakingConfig, type Stats24h, type StealthAnnouncement, StealthClient, type StealthMetaAddress, type StealthSendParams, type StealthSpendingProof, TOKEN_DECIMALS, type TradingPair, type TransferParams, USDC_DENOMINATIONS, type VM31AssetId, VM31BridgeClient, VM31VaultClient, VM31_ASSET_IDS, VM31_DENOMINATIONS, type VaultBatchInfo, type VaultDepositParams, type VaultMerklePath, type VaultNote, type VaultSubmitResult, type VaultTransferParams, type VaultWithdrawParams, WBTC_DENOMINATIONS, type WithdrawCTParams, type WithdrawParams, type WithdrawResult, type WorkerStake, commitmentToHash, createAEHint, createEncryptionProof, deriveNullifier, ecAdd, ecMul, eciesEncrypt, elgamalEncrypt, formatAmount, getContracts, getDenominations, getRpcUrl, mod, modInverse, parseAmount, pedersenCommit, randomScalar, splitIntoDenominations, validateDenomination };
@@ -23,7 +23,9 @@ __export(obelysk_exports, {
23
23
  CURVE_ORDER: () => CURVE_ORDER,
24
24
  ConfidentialTransferClient: () => ConfidentialTransferClient,
25
25
  DARKPOOL_ASSET_IDS: () => DARKPOOL_ASSET_IDS,
26
+ DENOMINATION_BY_SYMBOL: () => DENOMINATION_BY_SYMBOL,
26
27
  DarkPoolClient: () => DarkPoolClient,
28
+ ETH_DENOMINATIONS: () => ETH_DENOMINATIONS,
27
29
  FIELD_PRIME: () => FIELD_PRIME,
28
30
  GENERATOR_G: () => GENERATOR_G,
29
31
  GENERATOR_H: () => GENERATOR_H,
@@ -38,27 +40,36 @@ __export(obelysk_exports, {
38
40
  PrivacyPoolClient: () => PrivacyPoolClient,
39
41
  PrivacyRouterClient: () => PrivacyRouterClient,
40
42
  ProverStakingClient: () => ProverStakingClient,
43
+ SAGE_DENOMINATIONS: () => SAGE_DENOMINATIONS,
44
+ STRK_DENOMINATIONS: () => STRK_DENOMINATIONS,
41
45
  ShieldedSwapClient: () => ShieldedSwapClient,
42
46
  StealthClient: () => StealthClient,
43
47
  TOKEN_DECIMALS: () => TOKEN_DECIMALS,
48
+ USDC_DENOMINATIONS: () => USDC_DENOMINATIONS,
44
49
  VM31BridgeClient: () => VM31BridgeClient,
45
50
  VM31VaultClient: () => VM31VaultClient,
46
51
  VM31_ASSET_IDS: () => VM31_ASSET_IDS,
52
+ VM31_DENOMINATIONS: () => VM31_DENOMINATIONS,
53
+ WBTC_DENOMINATIONS: () => WBTC_DENOMINATIONS,
47
54
  commitmentToHash: () => commitmentToHash,
48
55
  createAEHint: () => createAEHint,
49
56
  createEncryptionProof: () => createEncryptionProof,
50
57
  deriveNullifier: () => deriveNullifier,
51
58
  ecAdd: () => ecAdd2,
52
59
  ecMul: () => ecMul2,
60
+ eciesEncrypt: () => eciesEncrypt,
53
61
  elgamalEncrypt: () => elgamalEncrypt,
54
62
  formatAmount: () => formatAmount,
55
63
  getContracts: () => getContracts,
64
+ getDenominations: () => getDenominations,
56
65
  getRpcUrl: () => getRpcUrl,
57
66
  mod: () => mod,
58
67
  modInverse: () => modInverse,
59
68
  parseAmount: () => parseAmount,
60
69
  pedersenCommit: () => pedersenCommit,
61
- randomScalar: () => randomScalar2
70
+ randomScalar: () => randomScalar2,
71
+ splitIntoDenominations: () => splitIntoDenominations,
72
+ validateDenomination: () => validateDenomination
62
73
  });
63
74
  module.exports = __toCommonJS(obelysk_exports);
64
75
 
@@ -2104,6 +2115,225 @@ var ShieldedSwapClient = class {
2104
2115
  }
2105
2116
  };
2106
2117
 
2118
+ // src/obelysk/ecies.ts
2119
+ var HKDF_INFO = "obelysk-ecies-v1";
2120
+ var ECIES_VERSION = 1;
2121
+ async function eciesEncrypt(payload, relayerPubkeyHex) {
2122
+ const subtle = getCrypto();
2123
+ const relayerPubkeyBytes = hexToBytes(relayerPubkeyHex);
2124
+ if (relayerPubkeyBytes.length !== 32) {
2125
+ throw new Error(`Invalid relayer public key length: ${relayerPubkeyBytes.length} (expected 32)`);
2126
+ }
2127
+ const relayerPubkey = await subtle.importKey(
2128
+ "raw",
2129
+ relayerPubkeyBytes.buffer,
2130
+ { name: "X25519" },
2131
+ false,
2132
+ []
2133
+ );
2134
+ const ephemeralKeyPair = await subtle.generateKey(
2135
+ { name: "X25519" },
2136
+ true,
2137
+ ["deriveBits"]
2138
+ );
2139
+ const sharedBits = await subtle.deriveBits(
2140
+ { name: "X25519", public: relayerPubkey },
2141
+ ephemeralKeyPair.privateKey,
2142
+ 256
2143
+ );
2144
+ const sharedKey = await subtle.importKey(
2145
+ "raw",
2146
+ sharedBits,
2147
+ { name: "HKDF" },
2148
+ false,
2149
+ ["deriveKey"]
2150
+ );
2151
+ const aesKey = await subtle.deriveKey(
2152
+ {
2153
+ name: "HKDF",
2154
+ hash: "SHA-256",
2155
+ salt: new ArrayBuffer(0),
2156
+ info: new TextEncoder().encode(HKDF_INFO)
2157
+ },
2158
+ sharedKey,
2159
+ { name: "AES-GCM", length: 256 },
2160
+ false,
2161
+ ["encrypt"]
2162
+ );
2163
+ const nonce = getRandomBytes(12);
2164
+ const plaintext = new TextEncoder().encode(JSON.stringify(payload));
2165
+ const ciphertext = await subtle.encrypt(
2166
+ { name: "AES-GCM", iv: nonce },
2167
+ aesKey,
2168
+ plaintext
2169
+ );
2170
+ const ephPubRaw = await subtle.exportKey("raw", ephemeralKeyPair.publicKey);
2171
+ return {
2172
+ ephemeral_pubkey: bytesToHex(new Uint8Array(ephPubRaw)),
2173
+ ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
2174
+ nonce: bytesToHex(nonce),
2175
+ version: ECIES_VERSION
2176
+ };
2177
+ }
2178
+ function getCrypto() {
2179
+ if (typeof globalThis.crypto?.subtle !== "undefined") {
2180
+ return globalThis.crypto.subtle;
2181
+ }
2182
+ try {
2183
+ const { webcrypto } = require("crypto");
2184
+ return webcrypto.subtle;
2185
+ } catch {
2186
+ throw new Error("ECIES requires Web Crypto API (Node 20+ or a modern browser)");
2187
+ }
2188
+ }
2189
+ function getRandomBytes(n) {
2190
+ if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
2191
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
2192
+ }
2193
+ const { randomBytes: randomBytes2 } = require("crypto");
2194
+ return new Uint8Array(randomBytes2(n));
2195
+ }
2196
+ function hexToBytes(hex) {
2197
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2198
+ const bytes = new Uint8Array(clean.length / 2);
2199
+ for (let i = 0; i < bytes.length; i++) {
2200
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
2201
+ }
2202
+ return bytes;
2203
+ }
2204
+ function bytesToHex(bytes) {
2205
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
2206
+ }
2207
+ function bytesToBase64(bytes) {
2208
+ if (typeof btoa === "function") {
2209
+ return btoa(String.fromCharCode(...bytes));
2210
+ }
2211
+ return Buffer.from(bytes).toString("base64");
2212
+ }
2213
+
2214
+ // src/obelysk/denominations.ts
2215
+ var WBTC_DENOMINATIONS = [
2216
+ 50000n,
2217
+ // 0.0005 BTC
2218
+ 100000n,
2219
+ // 0.001 BTC
2220
+ 500000n,
2221
+ // 0.005 BTC
2222
+ 1000000n,
2223
+ // 0.01 BTC
2224
+ 5000000n,
2225
+ // 0.05 BTC
2226
+ 10000000n
2227
+ // 0.1 BTC
2228
+ ];
2229
+ var SAGE_DENOMINATIONS = [
2230
+ 10000000000000000n,
2231
+ // 0.01 SAGE
2232
+ 50000000000000000n,
2233
+ // 0.05 SAGE
2234
+ 100000000000000000n,
2235
+ // 0.1 SAGE
2236
+ 500000000000000000n,
2237
+ // 0.5 SAGE
2238
+ 1000000000000000000n,
2239
+ // 1 SAGE
2240
+ 5000000000000000000n
2241
+ // 5 SAGE
2242
+ ];
2243
+ var ETH_DENOMINATIONS = [
2244
+ 1000000000000000n,
2245
+ // 0.001 ETH
2246
+ 5000000000000000n,
2247
+ // 0.005 ETH
2248
+ 10000000000000000n,
2249
+ // 0.01 ETH
2250
+ 50000000000000000n,
2251
+ // 0.05 ETH
2252
+ 100000000000000000n,
2253
+ // 0.1 ETH
2254
+ 500000000000000000n
2255
+ // 0.5 ETH
2256
+ ];
2257
+ var STRK_DENOMINATIONS = [
2258
+ 50000000000000000n,
2259
+ // 0.05 STRK
2260
+ 100000000000000000n,
2261
+ // 0.1 STRK
2262
+ 500000000000000000n,
2263
+ // 0.5 STRK
2264
+ 1000000000000000000n,
2265
+ // 1 STRK
2266
+ 5000000000000000000n
2267
+ // 5 STRK
2268
+ ];
2269
+ var USDC_DENOMINATIONS = [
2270
+ 1000000n,
2271
+ // 1 USDC
2272
+ 5000000n,
2273
+ // 5 USDC
2274
+ 10000000n,
2275
+ // 10 USDC
2276
+ 50000000n,
2277
+ // 50 USDC
2278
+ 100000000n,
2279
+ // 100 USDC
2280
+ 500000000n
2281
+ // 500 USDC
2282
+ ];
2283
+ var VM31_DENOMINATIONS = {
2284
+ 0: WBTC_DENOMINATIONS,
2285
+ 1: SAGE_DENOMINATIONS,
2286
+ 2: ETH_DENOMINATIONS,
2287
+ 3: STRK_DENOMINATIONS,
2288
+ 4: USDC_DENOMINATIONS
2289
+ };
2290
+ var DENOMINATION_BY_SYMBOL = {
2291
+ wbtc: WBTC_DENOMINATIONS,
2292
+ sage: SAGE_DENOMINATIONS,
2293
+ eth: ETH_DENOMINATIONS,
2294
+ strk: STRK_DENOMINATIONS,
2295
+ usdc: USDC_DENOMINATIONS
2296
+ };
2297
+ function validateDenomination(amount, assetIdOrSymbol) {
2298
+ let denoms;
2299
+ if (typeof assetIdOrSymbol === "number") {
2300
+ denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
2301
+ } else {
2302
+ denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2303
+ }
2304
+ if (!denoms) return;
2305
+ if (!denoms.includes(amount)) {
2306
+ throw new Error(
2307
+ `Deposit must use a standard denomination for asset ${assetIdOrSymbol}. Got ${amount}. Valid: [${denoms.join(", ")}]`
2308
+ );
2309
+ }
2310
+ }
2311
+ function splitIntoDenominations(amount, assetIdOrSymbol) {
2312
+ let denoms;
2313
+ if (typeof assetIdOrSymbol === "number") {
2314
+ denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
2315
+ } else {
2316
+ denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2317
+ }
2318
+ if (!denoms) return { denominations: [amount], remainder: 0n };
2319
+ const sorted = [...denoms].sort((a, b) => b > a ? 1 : b < a ? -1 : 0);
2320
+ const result = [];
2321
+ let remaining = amount;
2322
+ for (const denom of sorted) {
2323
+ while (remaining >= denom) {
2324
+ result.push(denom);
2325
+ remaining -= denom;
2326
+ }
2327
+ }
2328
+ return { denominations: result, remainder: remaining };
2329
+ }
2330
+ function getDenominations(assetIdOrSymbol) {
2331
+ if (typeof assetIdOrSymbol === "number") {
2332
+ return VM31_DENOMINATIONS[assetIdOrSymbol];
2333
+ }
2334
+ return DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2335
+ }
2336
+
2107
2337
  // src/obelysk/vm31Vault.ts
2108
2338
  var VM31VaultClient = class {
2109
2339
  constructor(obelysk) {
@@ -2118,6 +2348,8 @@ var VM31VaultClient = class {
2118
2348
  get relayerApiKey() {
2119
2349
  return this.obelysk.relayerApiKey;
2120
2350
  }
2351
+ /** Cached relayer X25519 public key (fetched on first encrypted submit) */
2352
+ _relayerPubkey = null;
2121
2353
  async relayerFetch(path, init) {
2122
2354
  const url = `${this.relayerUrl}${path}`;
2123
2355
  const headers = { "Content-Type": "application/json" };
@@ -2129,6 +2361,27 @@ var VM31VaultClient = class {
2129
2361
  }
2130
2362
  return res.json();
2131
2363
  }
2364
+ /**
2365
+ * Submit a payload to the relayer, encrypted with ECIES.
2366
+ * Falls back to plaintext if `encrypt: false` is passed.
2367
+ */
2368
+ async relayerSubmit(payload, encrypt = true) {
2369
+ if (!encrypt) {
2370
+ return this.relayerFetch("/submit", {
2371
+ method: "POST",
2372
+ body: JSON.stringify(payload)
2373
+ });
2374
+ }
2375
+ if (!this._relayerPubkey) {
2376
+ const keyInfo = await this.getRelayerPublicKey();
2377
+ this._relayerPubkey = keyInfo.publicKey;
2378
+ }
2379
+ const envelope = await eciesEncrypt(payload, this._relayerPubkey);
2380
+ return this.relayerFetch("/submit", {
2381
+ method: "POST",
2382
+ body: JSON.stringify(envelope)
2383
+ });
2384
+ }
2132
2385
  // --------------------------------------------------------------------------
2133
2386
  // On-chain reads (callContract to vm31_pool)
2134
2387
  // --------------------------------------------------------------------------
@@ -2292,61 +2545,65 @@ var VM31VaultClient = class {
2292
2545
  algorithm: data.algorithm
2293
2546
  };
2294
2547
  }
2295
- /** Submit a deposit transaction to the relayer */
2296
- async submitDeposit(params) {
2297
- return this.relayerFetch("/submit", {
2298
- method: "POST",
2299
- body: JSON.stringify({
2300
- type: "deposit",
2301
- amount: Number(params.amount),
2302
- asset_id: params.assetId,
2303
- recipient_pubkey: params.recipientPubkey,
2304
- recipient_viewing_key: params.recipientViewingKey
2305
- })
2306
- });
2307
- }
2308
- /** Submit a withdrawal transaction to the relayer */
2309
- async submitWithdraw(params) {
2310
- return this.relayerFetch("/submit", {
2311
- method: "POST",
2312
- body: JSON.stringify({
2313
- type: "withdraw",
2314
- amount: Number(params.amount),
2315
- asset_id: params.assetId,
2316
- note: {
2317
- owner_pubkey: params.note.owner_pubkey,
2318
- asset_id: params.note.asset_id,
2319
- amount_lo: params.note.amount_lo,
2320
- amount_hi: params.note.amount_hi,
2321
- blinding: params.note.blinding
2322
- },
2323
- spending_key: params.spendingKey,
2324
- merkle_path: params.merklePath,
2325
- merkle_root: params.merkleRoot,
2326
- withdrawal_binding: params.withdrawalBinding,
2327
- binding_salt: params.bindingSalt
2328
- })
2329
- });
2330
- }
2331
- /** Submit a private transfer transaction to the relayer */
2332
- async submitTransfer(params) {
2333
- return this.relayerFetch("/submit", {
2334
- method: "POST",
2335
- body: JSON.stringify({
2336
- type: "transfer",
2337
- amount: Number(params.amount),
2338
- asset_id: params.assetId,
2339
- recipient_pubkey: params.recipientPubkey,
2340
- recipient_viewing_key: params.recipientViewingKey,
2341
- sender_viewing_key: params.senderViewingKey,
2342
- input_notes: params.inputNotes.map((n) => ({
2343
- note: n.note,
2344
- spending_key: n.spendingKey,
2345
- merkle_path: n.merklePath
2346
- })),
2347
- merkle_root: params.merkleRoot
2348
- })
2349
- });
2548
+ /**
2549
+ * Submit a deposit transaction to the relayer.
2550
+ * Validates denomination (privacy gap #7) and encrypts with ECIES by default.
2551
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2552
+ */
2553
+ async submitDeposit(params, encrypt = true) {
2554
+ validateDenomination(params.amount, params.assetId);
2555
+ return this.relayerSubmit({
2556
+ type: "deposit",
2557
+ amount: Number(params.amount),
2558
+ asset_id: params.assetId,
2559
+ recipient_pubkey: params.recipientPubkey,
2560
+ recipient_viewing_key: params.recipientViewingKey
2561
+ }, encrypt);
2562
+ }
2563
+ /**
2564
+ * Submit a withdrawal transaction to the relayer.
2565
+ * Withdrawals are not denomination-restricted.
2566
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2567
+ */
2568
+ async submitWithdraw(params, encrypt = true) {
2569
+ return this.relayerSubmit({
2570
+ type: "withdraw",
2571
+ amount: Number(params.amount),
2572
+ asset_id: params.assetId,
2573
+ note: {
2574
+ owner_pubkey: params.note.owner_pubkey,
2575
+ asset_id: params.note.asset_id,
2576
+ amount_lo: params.note.amount_lo,
2577
+ amount_hi: params.note.amount_hi,
2578
+ blinding: params.note.blinding
2579
+ },
2580
+ spending_key: params.spendingKey,
2581
+ merkle_path: params.merklePath,
2582
+ merkle_root: params.merkleRoot,
2583
+ withdrawal_binding: params.withdrawalBinding,
2584
+ binding_salt: params.bindingSalt
2585
+ }, encrypt);
2586
+ }
2587
+ /**
2588
+ * Submit a private transfer transaction to the relayer.
2589
+ * Transfers are not denomination-restricted.
2590
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2591
+ */
2592
+ async submitTransfer(params, encrypt = true) {
2593
+ return this.relayerSubmit({
2594
+ type: "transfer",
2595
+ amount: Number(params.amount),
2596
+ asset_id: params.assetId,
2597
+ recipient_pubkey: params.recipientPubkey,
2598
+ recipient_viewing_key: params.recipientViewingKey,
2599
+ sender_viewing_key: params.senderViewingKey,
2600
+ input_notes: params.inputNotes.map((n) => ({
2601
+ note: n.note,
2602
+ spending_key: n.spendingKey,
2603
+ merkle_path: n.merklePath
2604
+ })),
2605
+ merkle_root: params.merkleRoot
2606
+ }, encrypt);
2350
2607
  }
2351
2608
  /** Query batch info from the relayer */
2352
2609
  async queryBatch(batchId) {
@@ -3064,7 +3321,9 @@ var ObelyskClient = class {
3064
3321
  CURVE_ORDER,
3065
3322
  ConfidentialTransferClient,
3066
3323
  DARKPOOL_ASSET_IDS,
3324
+ DENOMINATION_BY_SYMBOL,
3067
3325
  DarkPoolClient,
3326
+ ETH_DENOMINATIONS,
3068
3327
  FIELD_PRIME,
3069
3328
  GENERATOR_G,
3070
3329
  GENERATOR_H,
@@ -3079,25 +3338,34 @@ var ObelyskClient = class {
3079
3338
  PrivacyPoolClient,
3080
3339
  PrivacyRouterClient,
3081
3340
  ProverStakingClient,
3341
+ SAGE_DENOMINATIONS,
3342
+ STRK_DENOMINATIONS,
3082
3343
  ShieldedSwapClient,
3083
3344
  StealthClient,
3084
3345
  TOKEN_DECIMALS,
3346
+ USDC_DENOMINATIONS,
3085
3347
  VM31BridgeClient,
3086
3348
  VM31VaultClient,
3087
3349
  VM31_ASSET_IDS,
3350
+ VM31_DENOMINATIONS,
3351
+ WBTC_DENOMINATIONS,
3088
3352
  commitmentToHash,
3089
3353
  createAEHint,
3090
3354
  createEncryptionProof,
3091
3355
  deriveNullifier,
3092
3356
  ecAdd,
3093
3357
  ecMul,
3358
+ eciesEncrypt,
3094
3359
  elgamalEncrypt,
3095
3360
  formatAmount,
3096
3361
  getContracts,
3362
+ getDenominations,
3097
3363
  getRpcUrl,
3098
3364
  mod,
3099
3365
  modInverse,
3100
3366
  parseAmount,
3101
3367
  pedersenCommit,
3102
- randomScalar
3368
+ randomScalar,
3369
+ splitIntoDenominations,
3370
+ validateDenomination
3103
3371
  });
@@ -4,11 +4,12 @@ import {
4
4
  GENERATOR_G,
5
5
  GENERATOR_H,
6
6
  ObelyskPrivacy,
7
+ __require,
7
8
  ecAdd,
8
9
  ecMul,
9
10
  invMod,
10
11
  randomScalar
11
- } from "../chunk-O2PF7VJA.mjs";
12
+ } from "../chunk-CGPFHXUF.mjs";
12
13
 
13
14
  // src/obelysk/client.ts
14
15
  import { RpcProvider } from "starknet";
@@ -1788,6 +1789,225 @@ var ShieldedSwapClient = class {
1788
1789
  }
1789
1790
  };
1790
1791
 
1792
+ // src/obelysk/ecies.ts
1793
+ var HKDF_INFO = "obelysk-ecies-v1";
1794
+ var ECIES_VERSION = 1;
1795
+ async function eciesEncrypt(payload, relayerPubkeyHex) {
1796
+ const subtle = getCrypto();
1797
+ const relayerPubkeyBytes = hexToBytes(relayerPubkeyHex);
1798
+ if (relayerPubkeyBytes.length !== 32) {
1799
+ throw new Error(`Invalid relayer public key length: ${relayerPubkeyBytes.length} (expected 32)`);
1800
+ }
1801
+ const relayerPubkey = await subtle.importKey(
1802
+ "raw",
1803
+ relayerPubkeyBytes.buffer,
1804
+ { name: "X25519" },
1805
+ false,
1806
+ []
1807
+ );
1808
+ const ephemeralKeyPair = await subtle.generateKey(
1809
+ { name: "X25519" },
1810
+ true,
1811
+ ["deriveBits"]
1812
+ );
1813
+ const sharedBits = await subtle.deriveBits(
1814
+ { name: "X25519", public: relayerPubkey },
1815
+ ephemeralKeyPair.privateKey,
1816
+ 256
1817
+ );
1818
+ const sharedKey = await subtle.importKey(
1819
+ "raw",
1820
+ sharedBits,
1821
+ { name: "HKDF" },
1822
+ false,
1823
+ ["deriveKey"]
1824
+ );
1825
+ const aesKey = await subtle.deriveKey(
1826
+ {
1827
+ name: "HKDF",
1828
+ hash: "SHA-256",
1829
+ salt: new ArrayBuffer(0),
1830
+ info: new TextEncoder().encode(HKDF_INFO)
1831
+ },
1832
+ sharedKey,
1833
+ { name: "AES-GCM", length: 256 },
1834
+ false,
1835
+ ["encrypt"]
1836
+ );
1837
+ const nonce = getRandomBytes(12);
1838
+ const plaintext = new TextEncoder().encode(JSON.stringify(payload));
1839
+ const ciphertext = await subtle.encrypt(
1840
+ { name: "AES-GCM", iv: nonce },
1841
+ aesKey,
1842
+ plaintext
1843
+ );
1844
+ const ephPubRaw = await subtle.exportKey("raw", ephemeralKeyPair.publicKey);
1845
+ return {
1846
+ ephemeral_pubkey: bytesToHex(new Uint8Array(ephPubRaw)),
1847
+ ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
1848
+ nonce: bytesToHex(nonce),
1849
+ version: ECIES_VERSION
1850
+ };
1851
+ }
1852
+ function getCrypto() {
1853
+ if (typeof globalThis.crypto?.subtle !== "undefined") {
1854
+ return globalThis.crypto.subtle;
1855
+ }
1856
+ try {
1857
+ const { webcrypto } = __require("crypto");
1858
+ return webcrypto.subtle;
1859
+ } catch {
1860
+ throw new Error("ECIES requires Web Crypto API (Node 20+ or a modern browser)");
1861
+ }
1862
+ }
1863
+ function getRandomBytes(n) {
1864
+ if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
1865
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
1866
+ }
1867
+ const { randomBytes } = __require("crypto");
1868
+ return new Uint8Array(randomBytes(n));
1869
+ }
1870
+ function hexToBytes(hex) {
1871
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1872
+ const bytes = new Uint8Array(clean.length / 2);
1873
+ for (let i = 0; i < bytes.length; i++) {
1874
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
1875
+ }
1876
+ return bytes;
1877
+ }
1878
+ function bytesToHex(bytes) {
1879
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1880
+ }
1881
+ function bytesToBase64(bytes) {
1882
+ if (typeof btoa === "function") {
1883
+ return btoa(String.fromCharCode(...bytes));
1884
+ }
1885
+ return Buffer.from(bytes).toString("base64");
1886
+ }
1887
+
1888
+ // src/obelysk/denominations.ts
1889
+ var WBTC_DENOMINATIONS = [
1890
+ 50000n,
1891
+ // 0.0005 BTC
1892
+ 100000n,
1893
+ // 0.001 BTC
1894
+ 500000n,
1895
+ // 0.005 BTC
1896
+ 1000000n,
1897
+ // 0.01 BTC
1898
+ 5000000n,
1899
+ // 0.05 BTC
1900
+ 10000000n
1901
+ // 0.1 BTC
1902
+ ];
1903
+ var SAGE_DENOMINATIONS = [
1904
+ 10000000000000000n,
1905
+ // 0.01 SAGE
1906
+ 50000000000000000n,
1907
+ // 0.05 SAGE
1908
+ 100000000000000000n,
1909
+ // 0.1 SAGE
1910
+ 500000000000000000n,
1911
+ // 0.5 SAGE
1912
+ 1000000000000000000n,
1913
+ // 1 SAGE
1914
+ 5000000000000000000n
1915
+ // 5 SAGE
1916
+ ];
1917
+ var ETH_DENOMINATIONS = [
1918
+ 1000000000000000n,
1919
+ // 0.001 ETH
1920
+ 5000000000000000n,
1921
+ // 0.005 ETH
1922
+ 10000000000000000n,
1923
+ // 0.01 ETH
1924
+ 50000000000000000n,
1925
+ // 0.05 ETH
1926
+ 100000000000000000n,
1927
+ // 0.1 ETH
1928
+ 500000000000000000n
1929
+ // 0.5 ETH
1930
+ ];
1931
+ var STRK_DENOMINATIONS = [
1932
+ 50000000000000000n,
1933
+ // 0.05 STRK
1934
+ 100000000000000000n,
1935
+ // 0.1 STRK
1936
+ 500000000000000000n,
1937
+ // 0.5 STRK
1938
+ 1000000000000000000n,
1939
+ // 1 STRK
1940
+ 5000000000000000000n
1941
+ // 5 STRK
1942
+ ];
1943
+ var USDC_DENOMINATIONS = [
1944
+ 1000000n,
1945
+ // 1 USDC
1946
+ 5000000n,
1947
+ // 5 USDC
1948
+ 10000000n,
1949
+ // 10 USDC
1950
+ 50000000n,
1951
+ // 50 USDC
1952
+ 100000000n,
1953
+ // 100 USDC
1954
+ 500000000n
1955
+ // 500 USDC
1956
+ ];
1957
+ var VM31_DENOMINATIONS = {
1958
+ 0: WBTC_DENOMINATIONS,
1959
+ 1: SAGE_DENOMINATIONS,
1960
+ 2: ETH_DENOMINATIONS,
1961
+ 3: STRK_DENOMINATIONS,
1962
+ 4: USDC_DENOMINATIONS
1963
+ };
1964
+ var DENOMINATION_BY_SYMBOL = {
1965
+ wbtc: WBTC_DENOMINATIONS,
1966
+ sage: SAGE_DENOMINATIONS,
1967
+ eth: ETH_DENOMINATIONS,
1968
+ strk: STRK_DENOMINATIONS,
1969
+ usdc: USDC_DENOMINATIONS
1970
+ };
1971
+ function validateDenomination(amount, assetIdOrSymbol) {
1972
+ let denoms;
1973
+ if (typeof assetIdOrSymbol === "number") {
1974
+ denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
1975
+ } else {
1976
+ denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
1977
+ }
1978
+ if (!denoms) return;
1979
+ if (!denoms.includes(amount)) {
1980
+ throw new Error(
1981
+ `Deposit must use a standard denomination for asset ${assetIdOrSymbol}. Got ${amount}. Valid: [${denoms.join(", ")}]`
1982
+ );
1983
+ }
1984
+ }
1985
+ function splitIntoDenominations(amount, assetIdOrSymbol) {
1986
+ let denoms;
1987
+ if (typeof assetIdOrSymbol === "number") {
1988
+ denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
1989
+ } else {
1990
+ denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
1991
+ }
1992
+ if (!denoms) return { denominations: [amount], remainder: 0n };
1993
+ const sorted = [...denoms].sort((a, b) => b > a ? 1 : b < a ? -1 : 0);
1994
+ const result = [];
1995
+ let remaining = amount;
1996
+ for (const denom of sorted) {
1997
+ while (remaining >= denom) {
1998
+ result.push(denom);
1999
+ remaining -= denom;
2000
+ }
2001
+ }
2002
+ return { denominations: result, remainder: remaining };
2003
+ }
2004
+ function getDenominations(assetIdOrSymbol) {
2005
+ if (typeof assetIdOrSymbol === "number") {
2006
+ return VM31_DENOMINATIONS[assetIdOrSymbol];
2007
+ }
2008
+ return DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
2009
+ }
2010
+
1791
2011
  // src/obelysk/vm31Vault.ts
1792
2012
  var VM31VaultClient = class {
1793
2013
  constructor(obelysk) {
@@ -1802,6 +2022,8 @@ var VM31VaultClient = class {
1802
2022
  get relayerApiKey() {
1803
2023
  return this.obelysk.relayerApiKey;
1804
2024
  }
2025
+ /** Cached relayer X25519 public key (fetched on first encrypted submit) */
2026
+ _relayerPubkey = null;
1805
2027
  async relayerFetch(path, init) {
1806
2028
  const url = `${this.relayerUrl}${path}`;
1807
2029
  const headers = { "Content-Type": "application/json" };
@@ -1813,6 +2035,27 @@ var VM31VaultClient = class {
1813
2035
  }
1814
2036
  return res.json();
1815
2037
  }
2038
+ /**
2039
+ * Submit a payload to the relayer, encrypted with ECIES.
2040
+ * Falls back to plaintext if `encrypt: false` is passed.
2041
+ */
2042
+ async relayerSubmit(payload, encrypt = true) {
2043
+ if (!encrypt) {
2044
+ return this.relayerFetch("/submit", {
2045
+ method: "POST",
2046
+ body: JSON.stringify(payload)
2047
+ });
2048
+ }
2049
+ if (!this._relayerPubkey) {
2050
+ const keyInfo = await this.getRelayerPublicKey();
2051
+ this._relayerPubkey = keyInfo.publicKey;
2052
+ }
2053
+ const envelope = await eciesEncrypt(payload, this._relayerPubkey);
2054
+ return this.relayerFetch("/submit", {
2055
+ method: "POST",
2056
+ body: JSON.stringify(envelope)
2057
+ });
2058
+ }
1816
2059
  // --------------------------------------------------------------------------
1817
2060
  // On-chain reads (callContract to vm31_pool)
1818
2061
  // --------------------------------------------------------------------------
@@ -1976,61 +2219,65 @@ var VM31VaultClient = class {
1976
2219
  algorithm: data.algorithm
1977
2220
  };
1978
2221
  }
1979
- /** Submit a deposit transaction to the relayer */
1980
- async submitDeposit(params) {
1981
- return this.relayerFetch("/submit", {
1982
- method: "POST",
1983
- body: JSON.stringify({
1984
- type: "deposit",
1985
- amount: Number(params.amount),
1986
- asset_id: params.assetId,
1987
- recipient_pubkey: params.recipientPubkey,
1988
- recipient_viewing_key: params.recipientViewingKey
1989
- })
1990
- });
2222
+ /**
2223
+ * Submit a deposit transaction to the relayer.
2224
+ * Validates denomination (privacy gap #7) and encrypts with ECIES by default.
2225
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2226
+ */
2227
+ async submitDeposit(params, encrypt = true) {
2228
+ validateDenomination(params.amount, params.assetId);
2229
+ return this.relayerSubmit({
2230
+ type: "deposit",
2231
+ amount: Number(params.amount),
2232
+ asset_id: params.assetId,
2233
+ recipient_pubkey: params.recipientPubkey,
2234
+ recipient_viewing_key: params.recipientViewingKey
2235
+ }, encrypt);
1991
2236
  }
1992
- /** Submit a withdrawal transaction to the relayer */
1993
- async submitWithdraw(params) {
1994
- return this.relayerFetch("/submit", {
1995
- method: "POST",
1996
- body: JSON.stringify({
1997
- type: "withdraw",
1998
- amount: Number(params.amount),
1999
- asset_id: params.assetId,
2000
- note: {
2001
- owner_pubkey: params.note.owner_pubkey,
2002
- asset_id: params.note.asset_id,
2003
- amount_lo: params.note.amount_lo,
2004
- amount_hi: params.note.amount_hi,
2005
- blinding: params.note.blinding
2006
- },
2007
- spending_key: params.spendingKey,
2008
- merkle_path: params.merklePath,
2009
- merkle_root: params.merkleRoot,
2010
- withdrawal_binding: params.withdrawalBinding,
2011
- binding_salt: params.bindingSalt
2012
- })
2013
- });
2237
+ /**
2238
+ * Submit a withdrawal transaction to the relayer.
2239
+ * Withdrawals are not denomination-restricted.
2240
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2241
+ */
2242
+ async submitWithdraw(params, encrypt = true) {
2243
+ return this.relayerSubmit({
2244
+ type: "withdraw",
2245
+ amount: Number(params.amount),
2246
+ asset_id: params.assetId,
2247
+ note: {
2248
+ owner_pubkey: params.note.owner_pubkey,
2249
+ asset_id: params.note.asset_id,
2250
+ amount_lo: params.note.amount_lo,
2251
+ amount_hi: params.note.amount_hi,
2252
+ blinding: params.note.blinding
2253
+ },
2254
+ spending_key: params.spendingKey,
2255
+ merkle_path: params.merklePath,
2256
+ merkle_root: params.merkleRoot,
2257
+ withdrawal_binding: params.withdrawalBinding,
2258
+ binding_salt: params.bindingSalt
2259
+ }, encrypt);
2014
2260
  }
2015
- /** Submit a private transfer transaction to the relayer */
2016
- async submitTransfer(params) {
2017
- return this.relayerFetch("/submit", {
2018
- method: "POST",
2019
- body: JSON.stringify({
2020
- type: "transfer",
2021
- amount: Number(params.amount),
2022
- asset_id: params.assetId,
2023
- recipient_pubkey: params.recipientPubkey,
2024
- recipient_viewing_key: params.recipientViewingKey,
2025
- sender_viewing_key: params.senderViewingKey,
2026
- input_notes: params.inputNotes.map((n) => ({
2027
- note: n.note,
2028
- spending_key: n.spendingKey,
2029
- merkle_path: n.merklePath
2030
- })),
2031
- merkle_root: params.merkleRoot
2032
- })
2033
- });
2261
+ /**
2262
+ * Submit a private transfer transaction to the relayer.
2263
+ * Transfers are not denomination-restricted.
2264
+ * @param encrypt - Set to false for legacy plaintext mode (default: true)
2265
+ */
2266
+ async submitTransfer(params, encrypt = true) {
2267
+ return this.relayerSubmit({
2268
+ type: "transfer",
2269
+ amount: Number(params.amount),
2270
+ asset_id: params.assetId,
2271
+ recipient_pubkey: params.recipientPubkey,
2272
+ recipient_viewing_key: params.recipientViewingKey,
2273
+ sender_viewing_key: params.senderViewingKey,
2274
+ input_notes: params.inputNotes.map((n) => ({
2275
+ note: n.note,
2276
+ spending_key: n.spendingKey,
2277
+ merkle_path: n.merklePath
2278
+ })),
2279
+ merkle_root: params.merkleRoot
2280
+ }, encrypt);
2034
2281
  }
2035
2282
  /** Query batch info from the relayer */
2036
2283
  async queryBatch(batchId) {
@@ -2747,7 +2994,9 @@ export {
2747
2994
  CURVE_ORDER,
2748
2995
  ConfidentialTransferClient,
2749
2996
  DARKPOOL_ASSET_IDS,
2997
+ DENOMINATION_BY_SYMBOL,
2750
2998
  DarkPoolClient,
2999
+ ETH_DENOMINATIONS,
2751
3000
  FIELD_PRIME,
2752
3001
  GENERATOR_G,
2753
3002
  GENERATOR_H,
@@ -2762,25 +3011,34 @@ export {
2762
3011
  PrivacyPoolClient,
2763
3012
  PrivacyRouterClient,
2764
3013
  ProverStakingClient,
3014
+ SAGE_DENOMINATIONS,
3015
+ STRK_DENOMINATIONS,
2765
3016
  ShieldedSwapClient,
2766
3017
  StealthClient,
2767
3018
  TOKEN_DECIMALS,
3019
+ USDC_DENOMINATIONS,
2768
3020
  VM31BridgeClient,
2769
3021
  VM31VaultClient,
2770
3022
  VM31_ASSET_IDS,
3023
+ VM31_DENOMINATIONS,
3024
+ WBTC_DENOMINATIONS,
2771
3025
  commitmentToHash,
2772
3026
  createAEHint,
2773
3027
  createEncryptionProof,
2774
3028
  deriveNullifier,
2775
3029
  ecAdd2 as ecAdd,
2776
3030
  ecMul2 as ecMul,
3031
+ eciesEncrypt,
2777
3032
  elgamalEncrypt,
2778
3033
  formatAmount,
2779
3034
  getContracts,
3035
+ getDenominations,
2780
3036
  getRpcUrl,
2781
3037
  mod,
2782
3038
  modInverse,
2783
3039
  parseAmount,
2784
3040
  pedersenCommit,
2785
- randomScalar2 as randomScalar
3041
+ randomScalar2 as randomScalar,
3042
+ splitIntoDenominations,
3043
+ validateDenomination
2786
3044
  };
@@ -19,7 +19,7 @@ import {
19
19
  randomBytes,
20
20
  randomScalar,
21
21
  subMod
22
- } from "../chunk-O2PF7VJA.mjs";
22
+ } from "../chunk-CGPFHXUF.mjs";
23
23
  export {
24
24
  AssetId,
25
25
  CURVE_ORDER,
@@ -16,7 +16,7 @@ import {
16
16
  WorkersClient,
17
17
  getContractsForNetwork
18
18
  } from "../chunk-LXJT3QK6.mjs";
19
- import "../chunk-O2PF7VJA.mjs";
19
+ import "../chunk-CGPFHXUF.mjs";
20
20
 
21
21
  // src/react/providers/BitSageProvider.tsx
22
22
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obelyzk/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "BitSage Network SDK - Client library for distributed compute, privacy swaps, and Obelysk encryption",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",