@orbinum/sdk 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1098,7 +1098,7 @@ var IndexerClient = class _IndexerClient {
1098
1098
  /**
1099
1099
  * Returns a paginated list of stealth scan hints ordered ascending by leafIndex.
1100
1100
  * Each hint contains only the fields required for ECDH triage and decryption:
1101
- * leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo.
1101
+ * leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo, circuitVersion.
1102
1102
  *
1103
1103
  * Use `sinceLeafIndex` for incremental scans (cursor = last seen leafIndex + 1).
1104
1104
  */
@@ -1518,8 +1518,8 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
1518
1518
  // src/shielded-pool/protocol/memo.ts
1519
1519
  import { sha256 } from "@noble/hashes/sha2.js";
1520
1520
  var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
1521
- var MEMO_PLAINTEXT_SIZE = 116;
1522
- function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
1521
+ var MEMO_PLAINTEXT_SIZE = 120;
1522
+ function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
1523
1523
  const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
1524
1524
  const view = new DataView(buf.buffer);
1525
1525
  view.setBigUint64(0, value & 0xffffffffffffffffn, true);
@@ -1528,6 +1528,7 @@ function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
1528
1528
  buf.set(blinding.slice(0, 32), 48);
1529
1529
  view.setUint32(80, assetId >>> 0, true);
1530
1530
  buf.set(counterpartyPk.slice(0, 32), 84);
1531
+ view.setUint32(116, circuitVersion >>> 0, true);
1531
1532
  return buf;
1532
1533
  }
1533
1534
  function deriveEncryptionKey(sharedSecret, commitment) {
@@ -1540,7 +1541,7 @@ function deriveEncryptionKey(sharedSecret, commitment) {
1540
1541
 
1541
1542
  // src/shielded-pool/protocol/EncryptedMemo.ts
1542
1543
  var NONCE_SIZE = 12;
1543
- var CIPHERTEXT_SIZE = 132;
1544
+ var CIPHERTEXT_SIZE = 136;
1544
1545
  var EPH_PK_SIZE = 32;
1545
1546
  var ENCRYPTED_MEMO_SIZE = NONCE_SIZE + CIPHERTEXT_SIZE + EPH_PK_SIZE;
1546
1547
  function bytesToBjjScalar(bytes) {
@@ -1559,14 +1560,15 @@ function parsePlaintext(nonce, ciphertextWithMac, encKey) {
1559
1560
  const blinding = bytesToBigintLE(plaintext.slice(48, 80));
1560
1561
  const assetId = BigInt(view.getUint32(80, true));
1561
1562
  const counterpartyPk = bytesToBigintLE(plaintext.slice(84, 116));
1562
- return { value, ownerPk, blinding, assetId, counterpartyPk };
1563
+ const circuitVersion = view.getUint32(116, true);
1564
+ return { value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion };
1563
1565
  } catch {
1564
1566
  return null;
1565
1567
  }
1566
1568
  }
1567
1569
  var EncryptedMemo = {
1568
1570
  /**
1569
- * Build and encrypt a memo for a note using ECDH (v2, 168 bytes).
1571
+ * Build and encrypt a memo for a note using ECDH (v2, 180 bytes).
1570
1572
  *
1571
1573
  * @param value Note value in planck.
1572
1574
  * @param ownerPk 32-byte owner public key (LE).
@@ -1578,11 +1580,20 @@ var EncryptedMemo = {
1578
1580
  * decoded from a privacy address).
1579
1581
  * Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
1580
1582
  * @param counterpartyPk 32-byte counterparty BJJ Ax. Default: all zeros.
1581
- * @returns 168-byte encrypted memo: nonce(12) || ciphertext+MAC(124) || ephPk(32).
1583
+ * @param circuitVersion ZK circuit version the note is spent under. Default: 0.
1584
+ * @param ephSkOverride 32-byte ephemeral secret key (stealth coordination). Optional.
1585
+ * @returns 180-byte encrypted memo: nonce(12) || ciphertext+MAC(136) || ephPk(32).
1582
1586
  */
1583
- encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), ephSkOverride) {
1587
+ encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), circuitVersion = 0, ephSkOverride) {
1584
1588
  const nonce = randomBytes(NONCE_SIZE);
1585
- const plaintext = serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk);
1589
+ const plaintext = serializeMemo(
1590
+ value,
1591
+ ownerPk,
1592
+ blinding,
1593
+ assetId,
1594
+ counterpartyPk,
1595
+ circuitVersion
1596
+ );
1586
1597
  const isZeroKey = recipientIvkPacked.every((b) => b === 0);
1587
1598
  let sharedSecret;
1588
1599
  let ephPkPackedBytes;
@@ -1613,29 +1624,31 @@ var EncryptedMemo = {
1613
1624
  return result;
1614
1625
  },
1615
1626
  /**
1616
- * Returns a 168-byte public memo encrypted with a zero viewing key.
1627
+ * Returns a 180-byte public memo encrypted with a zero viewing key.
1617
1628
  * Decryptable by anyone with `decrypt(memo, commitment, new Uint8Array(32))`.
1618
1629
  * Convenience alias for `encrypt(..., new Uint8Array(32))`.
1619
1630
  */
1620
- encryptPublic(value, ownerPk, blinding, assetId, commitment) {
1631
+ encryptPublic(value, ownerPk, blinding, assetId, commitment, circuitVersion = 0) {
1621
1632
  return EncryptedMemo.encrypt(
1622
1633
  value,
1623
1634
  ownerPk,
1624
1635
  blinding,
1625
1636
  assetId,
1626
1637
  commitment,
1627
- new Uint8Array(32)
1638
+ new Uint8Array(32),
1639
+ new Uint8Array(32),
1640
+ circuitVersion
1628
1641
  );
1629
1642
  },
1630
1643
  /**
1631
- * Returns a 168-byte zeroed dummy memo (no information, always valid on-chain).
1644
+ * Returns a 180-byte zeroed dummy memo (no information, always valid on-chain).
1632
1645
  */
1633
1646
  dummy() {
1634
1647
  return new Uint8Array(ENCRYPTED_MEMO_SIZE);
1635
1648
  },
1636
1649
  /**
1637
1650
  * Validates that `bytes` is a properly-sized encrypted memo.
1638
- * Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (168 bytes).
1651
+ * Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (180 bytes).
1639
1652
  *
1640
1653
  * Call this at system boundaries (extrinsic builders, precompile encoders)
1641
1654
  * to catch malformed memos before they reach the chain and fail on-chain.
@@ -1656,7 +1669,7 @@ var EncryptedMemo = {
1656
1669
  * Returns null if decryption fails — wrong key, bad MAC, or malformed memo.
1657
1670
  * Never throws; safe for scan loops.
1658
1671
  *
1659
- * @param memoBytes 168-byte encrypted memo.
1672
+ * @param memoBytes 180-byte encrypted memo.
1660
1673
  * @param commitment 32-byte note commitment (LE).
1661
1674
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
1662
1675
  */
@@ -1668,13 +1681,13 @@ var EncryptedMemo = {
1668
1681
  * Extract the ECDH shared secret from an encrypted memo using the recipient's viewing secret key.
1669
1682
  *
1670
1683
  * Used by NoteDecryptor to obtain the shared secret needed for stealth address derivation
1671
- * without re-running the full decrypt path. Safe to call on any 168-byte memo.
1684
+ * without re-running the full decrypt path. Safe to call on any 180-byte memo.
1672
1685
  *
1673
1686
  * Returns `new Uint8Array(32)` (all zeros) for public/dummy memos (zero ephPk).
1674
1687
  * Returns `null` if the memo is malformed or the ephPk is not a valid BJJ point.
1675
1688
  * Never throws; safe for scan loops.
1676
1689
  *
1677
- * @param memoBytes 168-byte encrypted memo.
1690
+ * @param memoBytes 180-byte encrypted memo.
1678
1691
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
1679
1692
  */
1680
1693
  extractSharedSecret(memoBytes, viewingSecretKey) {
@@ -1878,7 +1891,7 @@ var ShieldedPoolModule = class {
1878
1891
  * Withdraws tokens from the shielded pool to a public address.
1879
1892
  * Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
1880
1893
  * Pass a `signer` to fall back to signed submission (e.g. for testing).
1881
- * Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee)
1894
+ * Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee, changeCommitment, changeEncryptedMemo, relayer, circuitVersion)
1882
1895
  */
1883
1896
  async unshield(params, signer, txOptions) {
1884
1897
  const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
@@ -1902,8 +1915,9 @@ var ShieldedPoolModule = class {
1902
1915
  fee: params.fee ?? 0n,
1903
1916
  change_commitment: changeCommitment,
1904
1917
  change_encrypted_memo: changeEncryptedMemo,
1905
- relayer: void 0
1918
+ relayer: void 0,
1906
1919
  // Option<H160> — None for direct Substrate submissions
1920
+ circuit_version: params.circuitVersion
1907
1921
  });
1908
1922
  if (signer) {
1909
1923
  return toTxResult(
@@ -1916,7 +1930,7 @@ var ShieldedPoolModule = class {
1916
1930
  * Performs a private (shielded) transfer between two notes.
1917
1931
  * Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
1918
1932
  * Pass a `signer` to fall back to signed submission (e.g. for testing).
1919
- * Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee)
1933
+ * Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee, relayer, circuitVersion)
1920
1934
  */
1921
1935
  async privateTransfer(params, signer, txOptions) {
1922
1936
  const nullifiers = params.inputs.map((inp) => inp.nullifier);
@@ -1937,8 +1951,9 @@ var ShieldedPoolModule = class {
1937
1951
  encrypted_memos: memos,
1938
1952
  asset_id: params.assetId,
1939
1953
  fee: params.fee ?? 0n,
1940
- relayer: void 0
1954
+ relayer: void 0,
1941
1955
  // Option<H160> — None for direct Substrate submissions
1956
+ circuit_version: params.circuitVersion
1942
1957
  });
1943
1958
  if (signer) {
1944
1959
  return toTxResult(
@@ -1972,7 +1987,7 @@ var ShieldedPoolModule = class {
1972
1987
  * This is a SIGNED transaction — the relayer must sign it with their wallet.
1973
1988
  * Before calling this, generate a ZK value proof with generateFeeClaimProof() (not yet implemented).
1974
1989
  *
1975
- * Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals)
1990
+ * Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals, circuit_version)
1976
1991
  */
1977
1992
  async claimShieldedFees(params, signer, txOptions) {
1978
1993
  EncryptedMemo.validate(params.encryptedMemo, "claimShieldedFees.encryptedMemo");
@@ -1983,7 +1998,8 @@ var ShieldedPoolModule = class {
1983
1998
  asset_id: params.assetId,
1984
1999
  encrypted_memo: params.encryptedMemo,
1985
2000
  proof: params.proof,
1986
- public_signals: params.publicSignals
2001
+ public_signals: params.publicSignals,
2002
+ circuit_version: params.circuitVersion
1987
2003
  });
1988
2004
  return toTxResult(
1989
2005
  await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
@@ -2501,6 +2517,77 @@ var ZkVerifierModule = class {
2501
2517
  }
2502
2518
  };
2503
2519
 
2520
+ // src/shielded-pool/CircuitVersionResolver.ts
2521
+ import {
2522
+ WebArtifactProvider,
2523
+ circuitTypeToId
2524
+ } from "@orbinum/proof-generator";
2525
+ var CircuitVersionResolver = class {
2526
+ constructor(zkVerifier, baseUrl, providerFactory) {
2527
+ this.zkVerifier = zkVerifier;
2528
+ this.makeProvider = providerFactory ?? ((circuit, noteVersion) => new WebArtifactProvider({
2529
+ ...baseUrl ? { baseUrl } : {},
2530
+ circuitVersions: { [circuit]: noteVersion }
2531
+ }));
2532
+ }
2533
+ zkVerifier;
2534
+ makeProvider;
2535
+ /**
2536
+ * Resolves the prover + on-chain version for spending a note of `circuit`
2537
+ * created under `noteVersion`. Fail-closed: throws on unsupported version or
2538
+ * VK-hash mismatch (CDN vs chain), before generating any proof.
2539
+ */
2540
+ async resolve(circuit, noteVersion) {
2541
+ if (!Number.isInteger(noteVersion) || noteVersion <= 0) {
2542
+ throw new Error(
2543
+ `CircuitVersionResolver: invalid note circuitVersion ${noteVersion} for ${circuit}`
2544
+ );
2545
+ }
2546
+ const provider = this.makeProvider(circuit, noteVersion);
2547
+ const resolved = await provider.getResolvedVersion(circuit);
2548
+ if (resolved.version !== noteVersion) {
2549
+ throw new Error(
2550
+ `CircuitVersionResolver: prover resolved ${circuit} to v${resolved.version}, expected v${noteVersion}`
2551
+ );
2552
+ }
2553
+ const circuitId = circuitTypeToId(circuit);
2554
+ const info = await this.zkVerifier.getCircuitVersionInfo(circuitId);
2555
+ if (!info) {
2556
+ throw new Error(
2557
+ `CircuitVersionResolver: chain reports no version info for ${circuit} (id ${circuitId})`
2558
+ );
2559
+ }
2560
+ if (!info.supportedVersions.includes(noteVersion)) {
2561
+ throw new Error(
2562
+ `CircuitVersionResolver: chain does not support ${circuit} v${noteVersion} (supported: ${info.supportedVersions.join(", ")})`
2563
+ );
2564
+ }
2565
+ const chainVk = info.vkHashes.find((h) => h.version === noteVersion);
2566
+ if (!chainVk) {
2567
+ throw new Error(
2568
+ `CircuitVersionResolver: chain has no VK hash for ${circuit} v${noteVersion}`
2569
+ );
2570
+ }
2571
+ if (!vkHashEquals(chainVk.vkHash, resolved.vkHash)) {
2572
+ throw new Error(
2573
+ `CircuitVersionResolver: VK hash mismatch for ${circuit} v${noteVersion} \u2014 prover ${resolved.vkHash}, chain ${chainVk.vkHash}`
2574
+ );
2575
+ }
2576
+ return { provider, version: noteVersion };
2577
+ }
2578
+ };
2579
+ function vkHashEquals(a, b) {
2580
+ const na = normalizeHash(a);
2581
+ const nb = normalizeHash(b);
2582
+ if (na === null || nb === null) return false;
2583
+ return na === nb;
2584
+ }
2585
+ function normalizeHash(h) {
2586
+ if (typeof h !== "string") return null;
2587
+ const s = h.toLowerCase().startsWith("0x") ? h.toLowerCase().slice(2) : h.toLowerCase();
2588
+ return /^[0-9a-f]{64}$/.test(s) ? s : null;
2589
+ }
2590
+
2504
2591
  // src/relayer/RelayerStatusModule.ts
2505
2592
  var RelayerStatusModule = class {
2506
2593
  constructor(substrate) {
@@ -2746,12 +2833,12 @@ var AM_SEL = {
2746
2833
  var SP_SEL = {
2747
2834
  // shield(uint32,bytes32,bytes) → 0x9feb22ea (payable, amount = msg.value)
2748
2835
  SHIELD: new Uint8Array([159, 235, 34, 234]),
2749
- // privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256) 0x8c0f5d24
2750
- PRIVATE_TRANSFER: new Uint8Array([140, 15, 93, 36]),
2751
- // unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32) 0xd21d9a79
2752
- UNSHIELD: new Uint8Array([210, 29, 154, 121]),
2753
- // claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes) 0x42e1e74c
2754
- CLAIM_SHIELDED_FEES: new Uint8Array([66, 225, 231, 76])
2836
+ // privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32) 0x66ed2cd4
2837
+ PRIVATE_TRANSFER: new Uint8Array([102, 237, 44, 212]),
2838
+ // unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32) 0x4e505348
2839
+ UNSHIELD: new Uint8Array([78, 80, 83, 72]),
2840
+ // claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32) 0x88d9deba
2841
+ CLAIM_SHIELDED_FEES: new Uint8Array([136, 217, 222, 186])
2755
2842
  };
2756
2843
  var KNOWN_PRECOMPILES = {
2757
2844
  // ── Ethereum standard (EIP) ─────────────────────────────────────────────
@@ -2795,9 +2882,9 @@ var KNOWN_PRECOMPILES = {
2795
2882
  name: "ShieldedPool",
2796
2883
  functions: {
2797
2884
  "9feb22ea": "shield(uint32,bytes32,bytes)",
2798
- "8c0f5d24": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)",
2799
- d21d9a79: "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)",
2800
- "42e1e74c": "claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)"
2885
+ "66ed2cd4": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)",
2886
+ "4e505348": "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)",
2887
+ "88d9deba": "claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)"
2801
2888
  }
2802
2889
  }
2803
2890
  };
@@ -2851,7 +2938,8 @@ var ShieldedPoolPrecompile = class {
2851
2938
  // ─── privateTransfer ───────────────────────────────────────────────────────
2852
2939
  /**
2853
2940
  * Returns the ABI-encoded calldata for
2854
- * `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256)`.
2941
+ * `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256, uint32)`.
2942
+ * The trailing `uint32` is the circuit version the input notes were created under.
2855
2943
  */
2856
2944
  buildPrivateTransferCalldata(params) {
2857
2945
  const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
@@ -2872,7 +2960,8 @@ var ShieldedPoolPrecompile = class {
2872
2960
  { type: "bytes32[]", value: commitments },
2873
2961
  { type: "bytes[]", value: memos },
2874
2962
  { type: "uint", value: BigInt(params.assetId) },
2875
- { type: "uint", value: params.fee ?? 0n }
2963
+ { type: "uint", value: params.fee ?? 0n },
2964
+ { type: "uint", value: BigInt(params.circuitVersion) }
2876
2965
  );
2877
2966
  }
2878
2967
  /**
@@ -2913,7 +3002,8 @@ var ShieldedPoolPrecompile = class {
2913
3002
  { type: "bytes32", value: recipientBytes },
2914
3003
  { type: "uint", value: params.fee ?? 0n },
2915
3004
  { type: "bytes32", value: changeCommitment },
2916
- { type: "bytes", value: changeEncryptedMemo }
3005
+ { type: "bytes", value: changeEncryptedMemo },
3006
+ { type: "uint", value: BigInt(params.circuitVersion) }
2917
3007
  );
2918
3008
  }
2919
3009
  /**
@@ -2963,7 +3053,7 @@ var ShieldedPoolPrecompile = class {
2963
3053
  // ─── claimShieldedFees ───────────────────────────────────────────────────────────────────
2964
3054
  /**
2965
3055
  * Returns the ABI-encoded calldata for
2966
- * `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)`.
3056
+ * `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)`.
2967
3057
  *
2968
3058
  * ABI layout (params after selector):
2969
3059
  * - `commitment` — bytes32 (fixed)
@@ -2972,6 +3062,7 @@ var ShieldedPoolPrecompile = class {
2972
3062
  * - `memo` — bytes (dynamic)
2973
3063
  * - `proof` — bytes (dynamic, 128 bytes Groth16)
2974
3064
  * - `publicSignals` — bytes (dynamic, 76 bytes)
3065
+ * - `circuitVersion` — uint32 (fixed, right-aligned)
2975
3066
  *
2976
3067
  * The validator identity is derived from `msg.sender` in the precompile —
2977
3068
  * do NOT include it in the calldata.
@@ -2997,7 +3088,8 @@ var ShieldedPoolPrecompile = class {
2997
3088
  { type: "uint", value: BigInt(params.assetId) },
2998
3089
  { type: "bytes", value: params.encryptedMemo },
2999
3090
  { type: "bytes", value: params.proof },
3000
- { type: "bytes", value: params.publicSignals }
3091
+ { type: "bytes", value: params.publicSignals },
3092
+ { type: "uint", value: BigInt(params.circuitVersion) }
3001
3093
  );
3002
3094
  }
3003
3095
  /**
@@ -3435,6 +3527,11 @@ var OrbinumClient = class _OrbinumClient {
3435
3527
  chain;
3436
3528
  /** Typed access to `zkVerifier_*` custom RPC endpoints. */
3437
3529
  zkVerifier;
3530
+ /**
3531
+ * Resolves a note's circuit version to a pinned prover + on-chain version
3532
+ * before spending it (fail-closed: throws on unsupported version / VK mismatch).
3533
+ */
3534
+ circuitVersionResolver;
3438
3535
  /** Typed access to `relayer_*` RPC endpoints (registry lookup and pending fee queries). */
3439
3536
  relayerStatus;
3440
3537
  /**
@@ -3443,7 +3540,7 @@ var OrbinumClient = class _OrbinumClient {
3443
3540
  */
3444
3541
  precompiles;
3445
3542
  /** @internal Use `OrbinumClient.connect()` to obtain an instance. */
3446
- constructor(substrate, evm, indexer) {
3543
+ constructor(substrate, evm, indexer, circuitsBaseUrl) {
3447
3544
  this.substrate = substrate;
3448
3545
  this.evm = evm;
3449
3546
  this.evmExplorer = evm ? new EvmExplorer(evm) : null;
@@ -3453,6 +3550,7 @@ var OrbinumClient = class _OrbinumClient {
3453
3550
  this.privacy = new PrivacyModule(substrate);
3454
3551
  this.chain = new ChainModule(substrate);
3455
3552
  this.zkVerifier = new ZkVerifierModule(substrate);
3553
+ this.circuitVersionResolver = new CircuitVersionResolver(this.zkVerifier, circuitsBaseUrl);
3456
3554
  this.relayerStatus = new RelayerStatusModule(substrate);
3457
3555
  this.precompiles = evm ? {
3458
3556
  shieldedPool: new ShieldedPoolPrecompile(evm),
@@ -3473,7 +3571,7 @@ var OrbinumClient = class _OrbinumClient {
3473
3571
  );
3474
3572
  const evm = config.evmRpc ? new EvmClient(config.evmRpc) : null;
3475
3573
  const indexer = config.indexerUrl ? new IndexerClient({ baseUrl: config.indexerUrl }) : null;
3476
- return new _OrbinumClient(substrate, evm, indexer);
3574
+ return new _OrbinumClient(substrate, evm, indexer, config.circuitsBaseUrl);
3477
3575
  }
3478
3576
  /** Closes the underlying Substrate WebSocket connection and releases all resources. */
3479
3577
  destroy() {
@@ -3595,6 +3693,8 @@ var OrbinumClientProvider = class {
3595
3693
  };
3596
3694
  if (this.config.evmRpc) connectConfig.evmRpc = this.config.evmRpc;
3597
3695
  if (this.config.indexerUrl) connectConfig.indexerUrl = this.config.indexerUrl;
3696
+ if (this.config.circuitsBaseUrl)
3697
+ connectConfig.circuitsBaseUrl = this.config.circuitsBaseUrl;
3598
3698
  const clientPromise = OrbinumClient.connect(connectConfig);
3599
3699
  const client = await Promise.race([clientPromise, timeoutPromise]);
3600
3700
  orphanClient = client;
@@ -3781,6 +3881,9 @@ var OrbinumClientProvider = class {
3781
3881
  }
3782
3882
  };
3783
3883
 
3884
+ // src/shielded-pool/protocol/types.ts
3885
+ var CURRENT_CIRCUIT_VERSION = 1;
3886
+
3784
3887
  // src/utils/stealth.ts
3785
3888
  import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
3786
3889
  import { hkdf } from "@noble/hashes/hkdf.js";
@@ -3889,6 +3992,7 @@ var NoteBuilder = class {
3889
3992
  const blinding = input.blinding ?? BigInt(Date.now());
3890
3993
  const spendingKey = input.spendingKey ?? 0n;
3891
3994
  const counterpartyPk = input.counterpartyPk ?? 0n;
3995
+ const circuitVersion = input.circuitVersion ?? CURRENT_CIRCUIT_VERSION;
3892
3996
  const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
3893
3997
  let memo;
3894
3998
  if (useStealth) {
@@ -3923,6 +4027,7 @@ var NoteBuilder = class {
3923
4027
  stealthCommitmentBytes,
3924
4028
  recipientIvkPacked,
3925
4029
  bigintTo32Le(counterpartyPk),
4030
+ circuitVersion,
3926
4031
  ephSk
3927
4032
  )
3928
4033
  );
@@ -3940,6 +4045,7 @@ var NoteBuilder = class {
3940
4045
  ownerPk: effectiveOwnerPk,
3941
4046
  blinding,
3942
4047
  spendingKey,
4048
+ circuitVersion,
3943
4049
  spent: false,
3944
4050
  spentAt: null,
3945
4051
  commitment: commitment2,
@@ -3962,7 +4068,8 @@ var NoteBuilder = class {
3962
4068
  Number(assetId),
3963
4069
  commitmentBytes,
3964
4070
  input.viewingPublicKey,
3965
- bigintTo32Le(counterpartyPk)
4071
+ bigintTo32Le(counterpartyPk),
4072
+ circuitVersion
3966
4073
  )
3967
4074
  ) : Array.from(EncryptedMemo.dummy());
3968
4075
  if (memo.length !== ENCRYPTED_MEMO_SIZE)
@@ -3975,6 +4082,7 @@ var NoteBuilder = class {
3975
4082
  ownerPk,
3976
4083
  blinding,
3977
4084
  spendingKey,
4085
+ circuitVersion,
3978
4086
  spent: false,
3979
4087
  spentAt: null,
3980
4088
  commitment,
@@ -3986,7 +4094,7 @@ var NoteBuilder = class {
3986
4094
  };
3987
4095
  }
3988
4096
  /**
3989
- * Build the 168-byte ECDH-encrypted memo for a note.
4097
+ * Build the 180-byte ECDH-encrypted memo for a note.
3990
4098
  *
3991
4099
  * Pure TypeScript implementation — no WASM dependency.
3992
4100
  * Uses ChaCha20-Poly1305 with ECDH key agreement (BabyJubJub ephemeral keypair).
@@ -4005,7 +4113,8 @@ var NoteBuilder = class {
4005
4113
  Number(note.assetId),
4006
4114
  bigintTo32Le(note.commitment),
4007
4115
  recipientIvkPacked ?? new Uint8Array(32),
4008
- counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n)
4116
+ counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n),
4117
+ note.circuitVersion
4009
4118
  );
4010
4119
  }
4011
4120
  };
@@ -4067,6 +4176,7 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
4067
4176
  ownerPk: effectiveOwnerPk,
4068
4177
  blinding: plaintext.blinding,
4069
4178
  spendingKey: effectiveSpendingKey,
4179
+ circuitVersion: plaintext.circuitVersion,
4070
4180
  spent: false,
4071
4181
  spentAt: null,
4072
4182
  commitment: recomputed,
@@ -4140,7 +4250,7 @@ function selectNotes(notes, needed) {
4140
4250
  for (let j = i + 1; j < sorted.length; j++) {
4141
4251
  const a = sorted[i];
4142
4252
  const b = sorted[j];
4143
- if (a !== void 0 && b !== void 0 && a.value + b.value >= needed) {
4253
+ if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
4144
4254
  return [a, b];
4145
4255
  }
4146
4256
  }
@@ -4497,6 +4607,11 @@ async function encryptNote(key, blindKey, note) {
4497
4607
  }
4498
4608
  async function decryptNoteRecord(key, rec) {
4499
4609
  const note = await decryptJson(key, rec.iv, rec.ciphertext);
4610
+ if (typeof note.circuitVersion !== "number") {
4611
+ throw new Error(
4612
+ "decryptNoteRecord: note is missing circuitVersion (invalid or corrupt record)"
4613
+ );
4614
+ }
4500
4615
  return applyNoteStatus(note, {
4501
4616
  ...rec.spent !== void 0 && { spent: rec.spent },
4502
4617
  spentAt: rec.spentAt ?? null
@@ -4508,9 +4623,9 @@ async function noteBlindTag(blindKey, hex) {
4508
4623
 
4509
4624
  // src/proof-generator/unshield.ts
4510
4625
  import {
4511
- CircuitType,
4626
+ CircuitType as CircuitType2,
4512
4627
  generateProof,
4513
- WebArtifactProvider
4628
+ WebArtifactProvider as WebArtifactProvider2
4514
4629
  } from "@orbinum/proof-generator";
4515
4630
  import { randomBytes as randomBytes3 } from "@noble/ciphers/utils.js";
4516
4631
  import { mulPointEscalar as mulPointEscalar6, Base8 as Base84 } from "@zk-kit/baby-jubjub";
@@ -4557,18 +4672,18 @@ async function generateUnshieldProof(inputs, options = {}) {
4557
4672
  change_blinding: changeBlinding.toString(),
4558
4673
  change_owner_pubkey: changeOwnerPubkey.toString()
4559
4674
  };
4560
- const provider = options.provider ?? new WebArtifactProvider();
4675
+ const provider = options.provider ?? new WebArtifactProvider2();
4561
4676
  const opts = { provider };
4562
4677
  if (options.verbose !== void 0) opts.verbose = options.verbose;
4563
- const proofResult = await generateProof(CircuitType.Unshield, circuitInputs, opts);
4678
+ const proofResult = await generateProof(CircuitType2.Unshield, circuitInputs, opts);
4564
4679
  return { ...proofResult, changeCommitment, changeValue, changeBlinding, changeOwnerPubkey };
4565
4680
  }
4566
4681
 
4567
4682
  // src/proof-generator/transfer.ts
4568
4683
  import {
4569
- CircuitType as CircuitType2,
4684
+ CircuitType as CircuitType3,
4570
4685
  generateProof as generateProof2,
4571
- WebArtifactProvider as WebArtifactProvider2
4686
+ WebArtifactProvider as WebArtifactProvider3
4572
4687
  } from "@orbinum/proof-generator";
4573
4688
  async function generateTransferProof(params, options = {}) {
4574
4689
  const root = leHexToBigint(params.merkleRoot).toString();
@@ -4594,17 +4709,17 @@ async function generateTransferProof(params, options = {}) {
4594
4709
  output_owner_pubkeys: [o0.ownerPk.toString(), o1.ownerPk.toString()],
4595
4710
  output_blindings: [o0.blinding.toString(), o1.blinding.toString()]
4596
4711
  };
4597
- const provider = options.provider ?? new WebArtifactProvider2();
4712
+ const provider = options.provider ?? new WebArtifactProvider3();
4598
4713
  const opts = { provider };
4599
4714
  if (options.verbose !== void 0) opts.verbose = options.verbose;
4600
- return generateProof2(CircuitType2.Transfer, circuitInputs, opts);
4715
+ return generateProof2(CircuitType3.Transfer, circuitInputs, opts);
4601
4716
  }
4602
4717
 
4603
4718
  // src/proof-generator/fee-claim.ts
4604
4719
  import {
4605
- CircuitType as CircuitType3,
4720
+ CircuitType as CircuitType4,
4606
4721
  generateProof as generateProof3,
4607
- WebArtifactProvider as WebArtifactProvider3
4722
+ WebArtifactProvider as WebArtifactProvider4
4608
4723
  } from "@orbinum/proof-generator";
4609
4724
  async function generateFeeClaimProof(inputs, options = {}) {
4610
4725
  if (inputs.amount <= 0n) {
@@ -4617,10 +4732,10 @@ async function generateFeeClaimProof(inputs, options = {}) {
4617
4732
  owner_pubkey: inputs.ownerPubkey.toString(),
4618
4733
  blinding: inputs.blinding.toString()
4619
4734
  };
4620
- const provider = options.provider ?? new WebArtifactProvider3();
4735
+ const provider = options.provider ?? new WebArtifactProvider4();
4621
4736
  const opts = { provider };
4622
4737
  if (options.verbose !== void 0) opts.verbose = options.verbose;
4623
- const proofResult = await generateProof3(CircuitType3.ValueProof, circuitInputs, opts);
4738
+ const proofResult = await generateProof3(CircuitType4.ValueProof, circuitInputs, opts);
4624
4739
  const [sigCommitment, sigValue, sigAssetId, sigOwnerHash] = proofResult.publicSignals.map(BigInt);
4625
4740
  const buf = new Uint8Array(76);
4626
4741
  buf.set(bigintTo32Le(sigCommitment), 0);
@@ -4676,9 +4791,19 @@ function decodePrecompileCalldata(address, input) {
4676
4791
  const recipient = toHex(data.slice(160, 192));
4677
4792
  const fee = decodeUint(data, 192);
4678
4793
  const changeCommitment = toHex(data.slice(224, 256));
4794
+ const circuitVersion = decodeUint(data, 288);
4679
4795
  return {
4680
4796
  fnSig,
4681
- args: { root, nullifier, assetId, amount, recipient, fee, changeCommitment }
4797
+ args: {
4798
+ root,
4799
+ nullifier,
4800
+ assetId,
4801
+ amount,
4802
+ recipient,
4803
+ fee,
4804
+ changeCommitment,
4805
+ circuitVersion
4806
+ }
4682
4807
  };
4683
4808
  } catch {
4684
4809
  return { fnSig, args: {} };
@@ -4694,7 +4819,8 @@ function decodePrecompileCalldata(address, input) {
4694
4819
  const commitments = Number(decodeUint(data, commOffset));
4695
4820
  const assetId = decodeUint(data, 160);
4696
4821
  const fee = decodeUint(data, 192);
4697
- return { fnSig, args: { root, nullifiers, commitments, assetId, fee } };
4822
+ const circuitVersion = decodeUint(data, 224);
4823
+ return { fnSig, args: { root, nullifiers, commitments, assetId, fee, circuitVersion } };
4698
4824
  } catch {
4699
4825
  return { fnSig, args: {} };
4700
4826
  }
@@ -4705,7 +4831,8 @@ function decodePrecompileCalldata(address, input) {
4705
4831
  const commitment = toHex(data.slice(0, 32));
4706
4832
  const amount = decodeUint(data, 32);
4707
4833
  const assetId = decodeUint(data, 64);
4708
- return { fnSig, args: { commitment, amount, assetId } };
4834
+ const circuitVersion = decodeUint(data, 192);
4835
+ return { fnSig, args: { commitment, amount, assetId, circuitVersion } };
4709
4836
  } catch {
4710
4837
  return { fnSig, args: {} };
4711
4838
  }
@@ -4717,8 +4844,8 @@ function decodePrecompileCalldata(address, input) {
4717
4844
  var CircuitId = {
4718
4845
  Transfer: 1,
4719
4846
  Unshield: 2,
4720
- ValueProof: 4,
4721
- PrivateLink: 5
4847
+ PrivateLink: 5,
4848
+ ValueProof: 6
4722
4849
  };
4723
4850
 
4724
4851
  // src/utils/string.ts
@@ -5400,8 +5527,10 @@ export {
5400
5527
  BABYJUB_SUBORDER,
5401
5528
  BN254_R,
5402
5529
  Blake2256,
5530
+ CURRENT_CIRCUIT_VERSION,
5403
5531
  CircuitId,
5404
- CircuitType,
5532
+ CircuitType2 as CircuitType,
5533
+ CircuitVersionResolver,
5405
5534
  CryptoPrecompiles,
5406
5535
  ENCRYPTED_MEMO_SIZE,
5407
5536
  EncryptedMemo,
@@ -5424,7 +5553,7 @@ export {
5424
5553
  Storage,
5425
5554
  SubstrateClient,
5426
5555
  VaultLockedError,
5427
- WebArtifactProvider,
5556
+ WebArtifactProvider2 as WebArtifactProvider,
5428
5557
  ZkVerifierModule,
5429
5558
  accountIdHexToSs58,
5430
5559
  addressToAccountIdHex,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbinum/sdk",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Official TypeScript SDK for Orbinum.",
5
5
  "author": "Orbinum",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "dependencies": {
48
48
  "@noble/ciphers": "2.2.0",
49
49
  "@noble/hashes": "2.2.0",
50
- "@orbinum/proof-generator": "3.7.0",
50
+ "@orbinum/proof-generator": "4.0.0",
51
51
  "@polkadot-api/metadata-builders": "0.14.2",
52
52
  "@polkadot-api/substrate-bindings": "0.20.2",
53
53
  "@polkadot-api/tx-utils": "0.3.2",