@orbinum/sdk 0.20.0 → 0.21.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.d.mts CHANGED
@@ -721,6 +721,13 @@ type ZkNote = {
721
721
  spendingKey: bigint;
722
722
  /** Circuit version this note was created under (see `CURRENT_CIRCUIT_VERSION`). Required. */
723
723
  circuitVersion: number;
724
+ /**
725
+ * Global Merkle leaf index, when known. Optional so pre-forest vaults
726
+ * need no migration: notes without it predate the first tree seal and
727
+ * belong to tree 0. Populated on shield and on scan; used only to derive
728
+ * the forest tree for same-tree coin selection (`treeIdOf`).
729
+ */
730
+ leafIndex?: number;
724
731
  /** Whether the note has been spent/nullified on-chain. */
725
732
  spent: boolean;
726
733
  /** Local timestamp when this note was marked spent, or null if still active/unknown. */
@@ -1095,6 +1102,7 @@ type RpcV2MerkleProof = {
1095
1102
  path: string[];
1096
1103
  leafIndex: number;
1097
1104
  treeDepth: number;
1105
+ treeId?: number | undefined;
1098
1106
  };
1099
1107
  type PrivacyMerkleProof = RpcV2MerkleProof & {
1100
1108
  root: string;
@@ -2301,22 +2309,52 @@ declare function generateTransferProof(params: PrivateTransferProofInputs, optio
2301
2309
  verbose?: boolean;
2302
2310
  }): Promise<ProofResult>;
2303
2311
 
2312
+ /**
2313
+ * Forest tree a note belongs to.
2314
+ *
2315
+ * Falls back to tree 0 for a missing or malformed `leafIndex`. Both cases are
2316
+ * expected rather than defensive noise:
2317
+ *
2318
+ * - Notes persisted before the forest upgrade carry no index, and they all
2319
+ * predate the first seal, so tree 0 is the correct answer.
2320
+ * - The index originates in an indexer scan hint, which is untrusted. A
2321
+ * `NaN` reaching this function would make every same-tree comparison
2322
+ * false — no pair would ever be selectable and the whole balance would
2323
+ * look unspendable.
2324
+ */
2325
+ declare function treeIdOf(note: Pick<ZkNote, 'leafIndex'>): number;
2326
+ /**
2327
+ * Outcome of {@link selectNotes}.
2328
+ *
2329
+ * - `[noteA, noteB | null]` — a spendable selection; a `null` second slot
2330
+ * means the transfer runs with a dummy input.
2331
+ * - `{ needsConsolidation: true }` — the balance covers the amount, but only
2332
+ * by pairing notes from different forest trees, which no single proof can
2333
+ * do. The caller should offer a consolidation, not an insufficient-funds
2334
+ * error.
2335
+ * - `null` — no combination covers the amount.
2336
+ */
2337
+ type CoinSelection = [ZkNote, ZkNote | null] | {
2338
+ needsConsolidation: true;
2339
+ } | null;
2304
2340
  /**
2305
2341
  * Selects up to 2 unspent notes that together cover `needed` planck.
2306
2342
  *
2307
- * Priority:
2308
- * 1. A single note whose value >= needed → [note, null] (second input will be a dummy)
2309
- * 2. The smallest pair whose sum >= needed → [noteA, noteB]
2310
- * 3. No combination covers needed → null (consolidation via merge required)
2343
+ * Both inputs of a transfer are proven together against ONE circuit VK and ONE
2344
+ * public `merkle_root`, so a pair must agree on two things: mixing circuit
2345
+ * versions produces an invalid proof, and notes in different forest trees
2346
+ * anchor to different roots that can never converge. A single note needs
2347
+ * neither check.
2311
2348
  *
2312
- * Both inputs of a transfer are proven together against ONE circuit VK, so a
2313
- * pair MUST share a circuitVersion mixing v1 and v2 would produce an invalid
2314
- * proof. Priority 2 only pairs notes of the same version; a single note (P1) is
2315
- * always one version so it needs no check.
2349
+ * Resolution order:
2350
+ * 1. One note that alone covers `needed` `[note, null]`.
2351
+ * 2. Smallest same-version, same-tree pair whose sum covers it `[a, b]`.
2352
+ * 3. A cross-tree pair would cover it `{ needsConsolidation: true }`.
2353
+ * 4. Nothing covers it → `null`.
2316
2354
  *
2317
2355
  * Only unspent notes with value > 0 are considered.
2318
2356
  */
2319
- declare function selectNotes(notes: ZkNote[], needed: bigint): [ZkNote, ZkNote | null] | null;
2357
+ declare function selectNotes(notes: ZkNote[], needed: bigint): CoinSelection;
2320
2358
  /**
2321
2359
  * Builds a dummy `TransferInputNote` for use as the second input in a single-note transfer.
2322
2360
  *
@@ -2450,6 +2488,42 @@ declare function deriveSpendingKeyTypedData(chainId: number, address: string): S
2450
2488
  */
2451
2489
  declare function deriveSpendingKeyMessageV2(chainId: number, address: string): string;
2452
2490
 
2491
+ /**
2492
+ * Canonical account identifier for spending-key derivation.
2493
+ *
2494
+ * The address string is load-bearing twice: it goes into the signed payload AND
2495
+ * into the HKDF `info`. So whatever identifies the account must be stable for
2496
+ * the lifetime of that account — anything else silently rotates the spending key
2497
+ * and orphans every note already shielded.
2498
+ *
2499
+ * SS58 IS NOT STABLE. The same public key encodes to a different string per
2500
+ * network prefix:
2501
+ *
2502
+ * prefix 42 → 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
2503
+ * prefix 2700 → kcuMUgT1VAJR8MtE22C5cmoAR96qsTMLYzmEuyCKUj48jYHqR
2504
+ *
2505
+ * A wallet that lists an account under the generic prefix today and under the
2506
+ * chain's own prefix tomorrow (a wallet setting, a chain-metadata update) would
2507
+ * hand us a different string for the same key. Deriving from it directly means
2508
+ * the user opens the app to an empty vault with no error and no explanation.
2509
+ *
2510
+ * So Substrate accounts are identified by their decoded 32-byte public key,
2511
+ * which no prefix can change. EVM addresses are already canonical and pass
2512
+ * through unchanged.
2513
+ */
2514
+ /**
2515
+ * Reduces an address to the form used for derivation.
2516
+ *
2517
+ * SS58 → `0x`-prefixed hex of the underlying public key, so every prefix of the
2518
+ * same account maps to one identifier. Anything else (EVM `0x…`) is returned
2519
+ * lowercased and unchanged.
2520
+ *
2521
+ * Not a validator: an unrecognised string passes through, because rejecting it
2522
+ * here would break EVM callers. The signature guard in `PrivacyKeys` is what
2523
+ * fails closed on unusable input.
2524
+ */
2525
+ declare function canonicalAccountId(address: string): string;
2526
+
2453
2527
  /**
2454
2528
  * PrivacyKeys
2455
2529
  *
@@ -4417,4 +4491,4 @@ interface ExtrinsicFailedData {
4417
4491
  dispatch_info: DispatchInfo;
4418
4492
  }
4419
4493
 
4420
- export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, MIN_SIGNATURE_BYTES, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, SPENDING_KEY_VERIFYING_CONTRACT, SPENDING_KEY_WARNING, type ScanCommitment, type SelfEphWindowEntry, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type SpendingKeyTypedData, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TryDecryptOptions, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, computeNoteCommitment, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSelfEphSk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessageV2, deriveSpendingKeyTypedData, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewTag, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, selfEphWindow, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
4494
+ export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, MIN_SIGNATURE_BYTES, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, SPENDING_KEY_VERIFYING_CONTRACT, SPENDING_KEY_WARNING, type ScanCommitment, type SelfEphWindowEntry, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type SpendingKeyTypedData, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TryDecryptOptions, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, canonicalAccountId, computeNoteCommitment, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSelfEphSk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessageV2, deriveSpendingKeyTypedData, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewTag, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, selfEphWindow, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, treeIdOf, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
package/dist/index.d.ts CHANGED
@@ -721,6 +721,13 @@ type ZkNote = {
721
721
  spendingKey: bigint;
722
722
  /** Circuit version this note was created under (see `CURRENT_CIRCUIT_VERSION`). Required. */
723
723
  circuitVersion: number;
724
+ /**
725
+ * Global Merkle leaf index, when known. Optional so pre-forest vaults
726
+ * need no migration: notes without it predate the first tree seal and
727
+ * belong to tree 0. Populated on shield and on scan; used only to derive
728
+ * the forest tree for same-tree coin selection (`treeIdOf`).
729
+ */
730
+ leafIndex?: number;
724
731
  /** Whether the note has been spent/nullified on-chain. */
725
732
  spent: boolean;
726
733
  /** Local timestamp when this note was marked spent, or null if still active/unknown. */
@@ -1095,6 +1102,7 @@ type RpcV2MerkleProof = {
1095
1102
  path: string[];
1096
1103
  leafIndex: number;
1097
1104
  treeDepth: number;
1105
+ treeId?: number | undefined;
1098
1106
  };
1099
1107
  type PrivacyMerkleProof = RpcV2MerkleProof & {
1100
1108
  root: string;
@@ -2301,22 +2309,52 @@ declare function generateTransferProof(params: PrivateTransferProofInputs, optio
2301
2309
  verbose?: boolean;
2302
2310
  }): Promise<ProofResult>;
2303
2311
 
2312
+ /**
2313
+ * Forest tree a note belongs to.
2314
+ *
2315
+ * Falls back to tree 0 for a missing or malformed `leafIndex`. Both cases are
2316
+ * expected rather than defensive noise:
2317
+ *
2318
+ * - Notes persisted before the forest upgrade carry no index, and they all
2319
+ * predate the first seal, so tree 0 is the correct answer.
2320
+ * - The index originates in an indexer scan hint, which is untrusted. A
2321
+ * `NaN` reaching this function would make every same-tree comparison
2322
+ * false — no pair would ever be selectable and the whole balance would
2323
+ * look unspendable.
2324
+ */
2325
+ declare function treeIdOf(note: Pick<ZkNote, 'leafIndex'>): number;
2326
+ /**
2327
+ * Outcome of {@link selectNotes}.
2328
+ *
2329
+ * - `[noteA, noteB | null]` — a spendable selection; a `null` second slot
2330
+ * means the transfer runs with a dummy input.
2331
+ * - `{ needsConsolidation: true }` — the balance covers the amount, but only
2332
+ * by pairing notes from different forest trees, which no single proof can
2333
+ * do. The caller should offer a consolidation, not an insufficient-funds
2334
+ * error.
2335
+ * - `null` — no combination covers the amount.
2336
+ */
2337
+ type CoinSelection = [ZkNote, ZkNote | null] | {
2338
+ needsConsolidation: true;
2339
+ } | null;
2304
2340
  /**
2305
2341
  * Selects up to 2 unspent notes that together cover `needed` planck.
2306
2342
  *
2307
- * Priority:
2308
- * 1. A single note whose value >= needed → [note, null] (second input will be a dummy)
2309
- * 2. The smallest pair whose sum >= needed → [noteA, noteB]
2310
- * 3. No combination covers needed → null (consolidation via merge required)
2343
+ * Both inputs of a transfer are proven together against ONE circuit VK and ONE
2344
+ * public `merkle_root`, so a pair must agree on two things: mixing circuit
2345
+ * versions produces an invalid proof, and notes in different forest trees
2346
+ * anchor to different roots that can never converge. A single note needs
2347
+ * neither check.
2311
2348
  *
2312
- * Both inputs of a transfer are proven together against ONE circuit VK, so a
2313
- * pair MUST share a circuitVersion mixing v1 and v2 would produce an invalid
2314
- * proof. Priority 2 only pairs notes of the same version; a single note (P1) is
2315
- * always one version so it needs no check.
2349
+ * Resolution order:
2350
+ * 1. One note that alone covers `needed` `[note, null]`.
2351
+ * 2. Smallest same-version, same-tree pair whose sum covers it `[a, b]`.
2352
+ * 3. A cross-tree pair would cover it `{ needsConsolidation: true }`.
2353
+ * 4. Nothing covers it → `null`.
2316
2354
  *
2317
2355
  * Only unspent notes with value > 0 are considered.
2318
2356
  */
2319
- declare function selectNotes(notes: ZkNote[], needed: bigint): [ZkNote, ZkNote | null] | null;
2357
+ declare function selectNotes(notes: ZkNote[], needed: bigint): CoinSelection;
2320
2358
  /**
2321
2359
  * Builds a dummy `TransferInputNote` for use as the second input in a single-note transfer.
2322
2360
  *
@@ -2450,6 +2488,42 @@ declare function deriveSpendingKeyTypedData(chainId: number, address: string): S
2450
2488
  */
2451
2489
  declare function deriveSpendingKeyMessageV2(chainId: number, address: string): string;
2452
2490
 
2491
+ /**
2492
+ * Canonical account identifier for spending-key derivation.
2493
+ *
2494
+ * The address string is load-bearing twice: it goes into the signed payload AND
2495
+ * into the HKDF `info`. So whatever identifies the account must be stable for
2496
+ * the lifetime of that account — anything else silently rotates the spending key
2497
+ * and orphans every note already shielded.
2498
+ *
2499
+ * SS58 IS NOT STABLE. The same public key encodes to a different string per
2500
+ * network prefix:
2501
+ *
2502
+ * prefix 42 → 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
2503
+ * prefix 2700 → kcuMUgT1VAJR8MtE22C5cmoAR96qsTMLYzmEuyCKUj48jYHqR
2504
+ *
2505
+ * A wallet that lists an account under the generic prefix today and under the
2506
+ * chain's own prefix tomorrow (a wallet setting, a chain-metadata update) would
2507
+ * hand us a different string for the same key. Deriving from it directly means
2508
+ * the user opens the app to an empty vault with no error and no explanation.
2509
+ *
2510
+ * So Substrate accounts are identified by their decoded 32-byte public key,
2511
+ * which no prefix can change. EVM addresses are already canonical and pass
2512
+ * through unchanged.
2513
+ */
2514
+ /**
2515
+ * Reduces an address to the form used for derivation.
2516
+ *
2517
+ * SS58 → `0x`-prefixed hex of the underlying public key, so every prefix of the
2518
+ * same account maps to one identifier. Anything else (EVM `0x…`) is returned
2519
+ * lowercased and unchanged.
2520
+ *
2521
+ * Not a validator: an unrecognised string passes through, because rejecting it
2522
+ * here would break EVM callers. The signature guard in `PrivacyKeys` is what
2523
+ * fails closed on unusable input.
2524
+ */
2525
+ declare function canonicalAccountId(address: string): string;
2526
+
2453
2527
  /**
2454
2528
  * PrivacyKeys
2455
2529
  *
@@ -4417,4 +4491,4 @@ interface ExtrinsicFailedData {
4417
4491
  dispatch_info: DispatchInfo;
4418
4492
  }
4419
4493
 
4420
- export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, MIN_SIGNATURE_BYTES, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, SPENDING_KEY_VERIFYING_CONTRACT, SPENDING_KEY_WARNING, type ScanCommitment, type SelfEphWindowEntry, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type SpendingKeyTypedData, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TryDecryptOptions, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, computeNoteCommitment, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSelfEphSk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessageV2, deriveSpendingKeyTypedData, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewTag, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, selfEphWindow, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
4494
+ export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, MIN_SIGNATURE_BYTES, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, SPENDING_KEY_VERIFYING_CONTRACT, SPENDING_KEY_WARNING, type ScanCommitment, type SelfEphWindowEntry, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type SpendingKeyTypedData, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TryDecryptOptions, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, canonicalAccountId, computeNoteCommitment, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSelfEphSk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessageV2, deriveSpendingKeyTypedData, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewTag, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, selfEphWindow, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, treeIdOf, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ __export(index_exports, {
66
66
  blindTag: () => blindTag,
67
67
  buildDummyTransferInput: () => buildDummyTransferInput,
68
68
  bytesToBigintLE: () => bytesToBigintLE,
69
+ canonicalAccountId: () => canonicalAccountId,
69
70
  computeNoteCommitment: () => computeNoteCommitment,
70
71
  computeNullifier: () => computeNullifier,
71
72
  computePathIndices: () => computePathIndices,
@@ -106,7 +107,7 @@ __export(index_exports, {
106
107
  getPolkadotSigner: () => import_signer.getPolkadotSigner,
107
108
  getPolkadotSignerFromPjs: () => import_pjs_signer.getPolkadotSignerFromPjs,
108
109
  getPrecompileLabel: () => getPrecompileLabel,
109
- getSs58AddressInfo: () => import_polkadot_api4.getSs58AddressInfo,
110
+ getSs58AddressInfo: () => import_polkadot_api5.getSs58AddressInfo,
110
111
  hexToBigint: () => hexToBigint,
111
112
  hexToNumber: () => hexToNumber,
112
113
  implicitSubstrateToEvm: () => implicitSubstrateToEvm,
@@ -130,6 +131,7 @@ __export(index_exports, {
130
131
  toBase64: () => toBase64,
131
132
  toHex: () => toHex,
132
133
  toTxResult: () => toTxResult,
134
+ treeIdOf: () => treeIdOf,
133
135
  truncateMiddle: () => truncateMiddle,
134
136
  tryDecryptNote: () => tryDecryptNote,
135
137
  tryDecryptNoteVerbose: () => tryDecryptNoteVerbose,
@@ -4099,6 +4101,7 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
4099
4101
  blinding: plaintext.blinding,
4100
4102
  spendingKey: effectiveSpendingKey,
4101
4103
  circuitVersion: plaintext.circuitVersion,
4104
+ ...Number.isSafeInteger(commitment.leafIndex) && commitment.leafIndex >= 0 && commitment.leafIndex < 2 ** 32 ? { leafIndex: commitment.leafIndex } : {},
4102
4105
  spent: false,
4103
4106
  spentAt: null,
4104
4107
  commitment: recomputed,
@@ -4163,6 +4166,12 @@ function decodeNoteDisclosureKey(key) {
4163
4166
 
4164
4167
  // src/shielded-pool/protocol/coinSelection.ts
4165
4168
  var TRANSFER_TREE_DEPTH = 20;
4169
+ var LEAVES_PER_TREE = 1 << TRANSFER_TREE_DEPTH;
4170
+ function treeIdOf(note) {
4171
+ const idx = note.leafIndex;
4172
+ if (idx === void 0 || !Number.isSafeInteger(idx) || idx < 0 || idx >= 2 ** 32) return 0;
4173
+ return Math.floor(idx / LEAVES_PER_TREE);
4174
+ }
4166
4175
  function selectNotes(notes, needed) {
4167
4176
  const unspent = notes.filter((n) => !n.spent && n.value > 0n);
4168
4177
  const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
@@ -4172,11 +4181,20 @@ function selectNotes(notes, needed) {
4172
4181
  for (let j = i + 1; j < sorted.length; j++) {
4173
4182
  const a = sorted[i];
4174
4183
  const b = sorted[j];
4175
- if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
4184
+ if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && treeIdOf(a) === treeIdOf(b) && a.value + b.value >= needed) {
4176
4185
  return [a, b];
4177
4186
  }
4178
4187
  }
4179
4188
  }
4189
+ for (let i = 0; i < sorted.length; i++) {
4190
+ for (let j = i + 1; j < sorted.length; j++) {
4191
+ const a = sorted[i];
4192
+ const b = sorted[j];
4193
+ if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
4194
+ return { needsConsolidation: true };
4195
+ }
4196
+ }
4197
+ }
4180
4198
  return null;
4181
4199
  }
4182
4200
  function buildDummyTransferInput(assetId) {
@@ -4205,6 +4223,13 @@ function randomBlinding() {
4205
4223
  return n === 0n ? 1n : n % BN254_R;
4206
4224
  }
4207
4225
 
4226
+ // src/privacy-keys/accountIdentity.ts
4227
+ var import_polkadot_api4 = require("polkadot-api");
4228
+ function canonicalAccountId(address) {
4229
+ const info = (0, import_polkadot_api4.getSs58AddressInfo)(address);
4230
+ return info.isValid ? toHex(info.publicKey) : address.toLowerCase();
4231
+ }
4232
+
4208
4233
  // src/privacy-keys/SpendingKeyRequest.ts
4209
4234
  var SPENDING_KEY_VERIFYING_CONTRACT = PRECOMPILE_ADDR.SHIELDED_POOL;
4210
4235
  var SPENDING_KEY_WARNING = "Signing this grants full control of your Orbinum private funds. Only sign on the official Orbinum app.";
@@ -4234,7 +4259,7 @@ function deriveSpendingKeyMessageV2(chainId, address) {
4234
4259
 
4235
4260
  orbinum-spending-key-v2
4236
4261
  ${chainId}
4237
- ${address.toLowerCase()}`;
4262
+ ${canonicalAccountId(address)}`;
4238
4263
  }
4239
4264
 
4240
4265
  // src/privacy-keys/PrivacyKeys.ts
@@ -4255,7 +4280,7 @@ async function deriveMasterKeyBytes(signatureHex, chainId, address) {
4255
4280
  const sigBytes = fromHex(signatureHex);
4256
4281
  assertUsableSignature(sigBytes);
4257
4282
  const info = new TextEncoder().encode(
4258
- `orbinum-sk-${KEY_VERSION}:${chainId}:${address.toLowerCase()}`
4283
+ `orbinum-sk-${KEY_VERSION}:${chainId}:${canonicalAccountId(address)}`
4259
4284
  );
4260
4285
  return (0, import_hkdf2.hkdf)(import_sha24.sha256, sigBytes, new Uint8Array(0), info, 32);
4261
4286
  }
@@ -5457,7 +5482,7 @@ var import_substrate_bindings3 = require("@polkadot-api/substrate-bindings");
5457
5482
  var import_base = require("@scure/base");
5458
5483
  var import_signer = require("polkadot-api/signer");
5459
5484
  var import_pjs_signer = require("polkadot-api/pjs-signer");
5460
- var import_polkadot_api4 = require("polkadot-api");
5485
+ var import_polkadot_api5 = require("polkadot-api");
5461
5486
  // Annotate the CommonJS export names for ESM import in node:
5462
5487
  0 && (module.exports = {
5463
5488
  AccountId,
@@ -5506,6 +5531,7 @@ var import_polkadot_api4 = require("polkadot-api");
5506
5531
  blindTag,
5507
5532
  buildDummyTransferInput,
5508
5533
  bytesToBigintLE,
5534
+ canonicalAccountId,
5509
5535
  computeNoteCommitment,
5510
5536
  computeNullifier,
5511
5537
  computePathIndices,
@@ -5570,6 +5596,7 @@ var import_polkadot_api4 = require("polkadot-api");
5570
5596
  toBase64,
5571
5597
  toHex,
5572
5598
  toTxResult,
5599
+ treeIdOf,
5573
5600
  truncateMiddle,
5574
5601
  tryDecryptNote,
5575
5602
  tryDecryptNoteVerbose,
package/dist/index.mjs CHANGED
@@ -3963,6 +3963,7 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
3963
3963
  blinding: plaintext.blinding,
3964
3964
  spendingKey: effectiveSpendingKey,
3965
3965
  circuitVersion: plaintext.circuitVersion,
3966
+ ...Number.isSafeInteger(commitment.leafIndex) && commitment.leafIndex >= 0 && commitment.leafIndex < 2 ** 32 ? { leafIndex: commitment.leafIndex } : {},
3966
3967
  spent: false,
3967
3968
  spentAt: null,
3968
3969
  commitment: recomputed,
@@ -4027,6 +4028,12 @@ function decodeNoteDisclosureKey(key) {
4027
4028
 
4028
4029
  // src/shielded-pool/protocol/coinSelection.ts
4029
4030
  var TRANSFER_TREE_DEPTH = 20;
4031
+ var LEAVES_PER_TREE = 1 << TRANSFER_TREE_DEPTH;
4032
+ function treeIdOf(note) {
4033
+ const idx = note.leafIndex;
4034
+ if (idx === void 0 || !Number.isSafeInteger(idx) || idx < 0 || idx >= 2 ** 32) return 0;
4035
+ return Math.floor(idx / LEAVES_PER_TREE);
4036
+ }
4030
4037
  function selectNotes(notes, needed) {
4031
4038
  const unspent = notes.filter((n) => !n.spent && n.value > 0n);
4032
4039
  const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
@@ -4036,11 +4043,20 @@ function selectNotes(notes, needed) {
4036
4043
  for (let j = i + 1; j < sorted.length; j++) {
4037
4044
  const a = sorted[i];
4038
4045
  const b = sorted[j];
4039
- if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
4046
+ if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && treeIdOf(a) === treeIdOf(b) && a.value + b.value >= needed) {
4040
4047
  return [a, b];
4041
4048
  }
4042
4049
  }
4043
4050
  }
4051
+ for (let i = 0; i < sorted.length; i++) {
4052
+ for (let j = i + 1; j < sorted.length; j++) {
4053
+ const a = sorted[i];
4054
+ const b = sorted[j];
4055
+ if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
4056
+ return { needsConsolidation: true };
4057
+ }
4058
+ }
4059
+ }
4044
4060
  return null;
4045
4061
  }
4046
4062
  function buildDummyTransferInput(assetId) {
@@ -4069,6 +4085,13 @@ function randomBlinding() {
4069
4085
  return n === 0n ? 1n : n % BN254_R;
4070
4086
  }
4071
4087
 
4088
+ // src/privacy-keys/accountIdentity.ts
4089
+ import { getSs58AddressInfo } from "polkadot-api";
4090
+ function canonicalAccountId(address) {
4091
+ const info = getSs58AddressInfo(address);
4092
+ return info.isValid ? toHex(info.publicKey) : address.toLowerCase();
4093
+ }
4094
+
4072
4095
  // src/privacy-keys/SpendingKeyRequest.ts
4073
4096
  var SPENDING_KEY_VERIFYING_CONTRACT = PRECOMPILE_ADDR.SHIELDED_POOL;
4074
4097
  var SPENDING_KEY_WARNING = "Signing this grants full control of your Orbinum private funds. Only sign on the official Orbinum app.";
@@ -4098,7 +4121,7 @@ function deriveSpendingKeyMessageV2(chainId, address) {
4098
4121
 
4099
4122
  orbinum-spending-key-v2
4100
4123
  ${chainId}
4101
- ${address.toLowerCase()}`;
4124
+ ${canonicalAccountId(address)}`;
4102
4125
  }
4103
4126
 
4104
4127
  // src/privacy-keys/PrivacyKeys.ts
@@ -4119,7 +4142,7 @@ async function deriveMasterKeyBytes(signatureHex, chainId, address) {
4119
4142
  const sigBytes = fromHex(signatureHex);
4120
4143
  assertUsableSignature(sigBytes);
4121
4144
  const info = new TextEncoder().encode(
4122
- `orbinum-sk-${KEY_VERSION}:${chainId}:${address.toLowerCase()}`
4145
+ `orbinum-sk-${KEY_VERSION}:${chainId}:${canonicalAccountId(address)}`
4123
4146
  );
4124
4147
  return hkdf2(sha2564, sigBytes, new Uint8Array(0), info, 32);
4125
4148
  }
@@ -5344,7 +5367,7 @@ import {
5344
5367
  connectInjectedExtension,
5345
5368
  getInjectedExtensions
5346
5369
  } from "polkadot-api/pjs-signer";
5347
- import { getSs58AddressInfo } from "polkadot-api";
5370
+ import { getSs58AddressInfo as getSs58AddressInfo2 } from "polkadot-api";
5348
5371
  export {
5349
5372
  AccountId2 as AccountId,
5350
5373
  AccountMappingModule,
@@ -5392,6 +5415,7 @@ export {
5392
5415
  blindTag,
5393
5416
  buildDummyTransferInput,
5394
5417
  bytesToBigintLE,
5418
+ canonicalAccountId,
5395
5419
  computeNoteCommitment,
5396
5420
  computeNullifier,
5397
5421
  computePathIndices,
@@ -5432,7 +5456,7 @@ export {
5432
5456
  getPolkadotSigner,
5433
5457
  getPolkadotSignerFromPjs,
5434
5458
  getPrecompileLabel,
5435
- getSs58AddressInfo,
5459
+ getSs58AddressInfo2 as getSs58AddressInfo,
5436
5460
  hexToBigint,
5437
5461
  hexToNumber,
5438
5462
  implicitSubstrateToEvm,
@@ -5456,6 +5480,7 @@ export {
5456
5480
  toBase64,
5457
5481
  toHex,
5458
5482
  toTxResult,
5483
+ treeIdOf,
5459
5484
  truncateMiddle,
5460
5485
  tryDecryptNote,
5461
5486
  tryDecryptNoteVerbose,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbinum/sdk",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Official TypeScript SDK for Orbinum.",
5
5
  "author": "Orbinum",
6
6
  "license": "MIT",