diff --git a/etc/sdk-stellar.api.md b/etc/sdk-stellar.api.md index 52e8129..764cb06 100644 --- a/etc/sdk-stellar.api.md +++ b/etc/sdk-stellar.api.md @@ -1,689 +1,689 @@ -## API Report File for "@wraith-protocol/sdk" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { Asset } from '@stellar/stellar-sdk'; -import { ExtPointType } from '@noble/curves/abstract/edwards'; -import { Horizon } from '@stellar/stellar-sdk'; -import { Keypair } from '@stellar/stellar-sdk'; -import { Memo } from '@stellar/stellar-sdk'; -import { Operation } from '@stellar/stellar-sdk'; -import * as _stellar_stellar_sdk from '@stellar/stellar-sdk'; -import { Transaction } from '@stellar/stellar-sdk'; -import { TransactionBuilder } from '@stellar/stellar-sdk'; -import { xdr } from '@stellar/stellar-sdk'; - -// @public -export function addStealthMultisigSigner(tx: Transaction, signerKey: Keypair | string): Transaction; - -// @public -export const ANNOUNCE_EVENT_SYMBOL = "announce"; - -// @public -export interface Announcement { - caller: string; - ephemeralPubKey: string; - ledger?: number; - // Warning: (ae-forgotten-export) The symbol "StellarMemo" needs to be exported by the entry point index.d.ts - memo?: StellarMemo; - metadata: string; - schemeId: number; - stealthAddress: string; - viewTagBucket?: number; -} - -// @public (undocumented) -export interface AnnouncementCache { - clear(network: Network): Promise; - get(network: Network, fromLedger: number, toLedger: number): Promise; - getLastSeen(network: Network): Promise<{ - ledger: number; - cursor: string; - } | null>; - put(network: Network, announcements: Announcement[]): Promise; - setLastSeen(network: Network, ledger: number, cursor: string): Promise; -} - -// @public -export function assertViewTagBucket(bucket: number): void; - -// @public -export interface AssetMetadata { - decimals: number; - name: string; - symbol: string; -} - -// @public -export interface AssetReceivabilityResult { - hasTrustline: boolean; - issuerAuthRequired: boolean; - ops: ReturnType[]; -} - -// @public -export function autoSelectCache(): AnnouncementCache; - -// @public (undocumented) -export interface BatchConfig { - feePerOp?: number; - networkPassphrase: string; - sequence: string; - source: string; - timeout?: number; -} - -// @public -export function buildAnnouncementData(stealthAddresses: GeneratedStealthAddress[], caller: string): Array<{ - schemeId: number; - stealthAddress: string; - caller: string; - ephemeralPubKey: string; - metadata: string; -}>; - -// @public -export interface BuildAnnouncementOptions { - announcerContract: string; - fee?: string; - networkPassphrase: string; - sender: string; - sequence: string; - stealthResult: GeneratedStealthAddress; -} - -// @public -export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSendTxResult; - -// @public -export interface BuildBatchSendTxParams { - baseFee?: number; - batchSenderContract?: string; - batchSenderThreshold?: number; - maxOperations?: number; - memo?: string; - networkPassphrase: string; - payments: StealthPayment[]; - sourceAccount: any; -} - -// @public -export interface BuildBatchSendTxResult { - stealthAddresses: GeneratedStealthAddress[]; - totalFee: number; - transaction: any; - usedBatchSender: boolean; -} - -// @public -export function buildMultisigStealthWithdraw(options: BuildMultisigStealthWithdrawOptions): Promise; - -// @public (undocumented) -export interface BuildMultisigStealthWithdrawOptions { - account?: Pick; - destination: string; - fee?: string; - horizonUrl?: string; - networkPassphrase: string; - requiredWeight?: number; - sequence?: string; - // Warning: (ae-forgotten-export) The symbol "WeightedSigner" needs to be exported by the entry point index.d.ts - signers: Array; - stealthAddress: string; - timeout?: number; -} - -// @public -export function buildPathStealthPayment(options: BuildPathStealthPaymentOptions): PathStealthPaymentResult; - -// @public -export interface BuildPathStealthPaymentOptions { - announcerContract: string; - destAmount: string; - _ephemeralSeed?: Uint8Array; - fee?: string; - networkPassphrase: string; - path?: Asset[]; - receiveAsset: Asset; - recipientMeta: string; - sendAsset: Asset; - sender: string; - sendMax: string; - sequence: string; -} - -// @public (undocumented) -export interface BuildResult { - paymentCount: number; - totalFee: number; - transactions: string[]; - txCount: number; -} - -// @public -export function buildStealthAnnouncement(options: BuildAnnouncementOptions): _stellar_stellar_sdk.Transaction<_stellar_stellar_sdk.Memo<_stellar_stellar_sdk.MemoType>, Operation[]>; - -// @public -export function buildStealthPayment(options: BuildStealthPaymentOptions): _stellar_stellar_sdk.Transaction<_stellar_stellar_sdk.Memo<_stellar_stellar_sdk.MemoType>, Operation[]>; - -// @public -export interface BuildStealthPaymentOptions { - amount: string; - asset?: Asset; - fee?: string; - networkPassphrase: string; - sender: string; - sequence: string; - stealthExists?: boolean; - stealthResult: GeneratedStealthAddress; -} - -// @public -export function buildStellarSwapAndStealth(options: BuildStellarSwapAndStealthOptions): SwapAndStealthResult; - -// @public -export interface BuildStellarSwapAndStealthOptions { - announcerContract: string; - destAmount: string; - _ephemeralSeed?: Uint8Array; - fee?: string; - fromAsset: Asset; - networkPassphrase: string; - path?: Asset[]; - recipientMeta: string; - sender: string; - sendMax: string; - sequence: string; - toAsset: Asset; -} - -// @public -export function buildV1AnnouncerEventFilter(contractId: string): SorobanEventFilter; - -// @public -export function buildV2AllBucketsEventFilter(contractId: string): SorobanEventFilter; - -// @public -export function buildV2BucketEventFilter(contractId: string, viewTagBucket: number): SorobanEventFilter; - -// @public -export function buildV2BucketEventFilterBatches(contractId: string, viewTagBuckets: number[]): SorobanEventFilter[][]; - -// @public -export function buildWithdrawCustomAsset(options: BuildWithdrawCustomAssetOptions): _stellar_stellar_sdk.Transaction<_stellar_stellar_sdk.Memo<_stellar_stellar_sdk.MemoType>, Operation[]>; - -// @public -export interface BuildWithdrawCustomAssetOptions { - asset: Asset; - balanceId: string; - fee?: string; - needsTrustline?: boolean; - networkPassphrase: string; - sequence: string; - stealthAddress: string; -} - -// @public -export function bytesToHex(bytes: Uint8Array): string; - -// @public -export function checkStealthAddress(ephemeralPubKey: Uint8Array, viewingKey: Uint8Array, spendingPubKey: Uint8Array, viewTag: number): { - isMatch: boolean; - stealthAddress: string | null; - hashScalar: bigint | null; - stealthPubKeyBytes: Uint8Array | null; -}; - -// @public -export function clearAssetMetadataCache(): void; - -// @public -export function computeAnnouncementViewTag(ephemeralPubKey: Uint8Array, viewingPubKey: Uint8Array): number; - -// @public -export function computeSharedSecret(privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array; - -// @public @deprecated -export function computeViewTag(sharedSecret: Uint8Array): number; - -// @public (undocumented) -export function createHorizonClient(config: HorizonClientConfig): HorizonClient; - -// @public -export function decodeAnnouncementData(data: Uint8Array): { - schemeId: number; - ephemeralPubKey: Uint8Array; - viewTag: number; -}; - -// @public -export function decodeMemo(memo: Memo | xdr.Memo): TypedMemo; - -// @public -export function decodeStealthMetaAddress(metaAddress: string): StealthMetaAddress; - -// @public -export const DEFAULT_BASE_FEE = 100; - -// @public -export const DEFAULT_BATCH_SENDER_THRESHOLD = 10; - -// @public -export const DEPLOYMENTS: Record; - -// @public -export function deriveStealthKeys(signature: Uint8Array): StealthKeys; - -// @public -export function deriveStealthKeysFromSigner(signer: StellarStealthSigner): Promise; - -// @public -export function deriveStealthPrivateScalar(spendingScalar: bigint, viewingKey: Uint8Array, ephemeralPubKey: Uint8Array): bigint; - -// @public -export function deriveStealthPubKey(spendingPubKey: Uint8Array, hashScalar: bigint, spendingPoint?: ExtPointType): Uint8Array; - -// @public -export function encodeAnnouncementData(ephemeralPubKey: Uint8Array, viewTag: number): Uint8Array; - -// @public -export function encodeMemo(memo: TypedMemo): Memo; - -// @public -export function encodeStealthMetaAddress(spendingPubKey: Uint8Array, viewingPubKey: Uint8Array): string; - -// @public -export function encodeSymbolTopic(symbol: string): string; - -// @public -export function encodeU32Topic(value: number): string; - -// @public -export function extractMemoFromTransaction(tx: { - memo: Memo | xdr.Memo; -}): TypedMemo; - -// @public (undocumented) -export interface FetchAnnouncementsOptions { - cursor?: string; - fromLedger?: number; - fromTimestamp?: Date; - includeV1?: boolean; - includeV2?: boolean; - sorobanUrl?: string; - toLedger?: number; - toTimestamp?: Date; - viewTagBuckets?: number[]; -} - -// @public -export function fetchAnnouncementsStream(chain?: string, sorobanUrlOrOpts?: string | FetchAnnouncementsOptions, maybeOpts?: FetchAnnouncementsOptions): AsyncGenerator; - -// @public -export function findStrictReceivePath(options: FindStrictReceivePathOptions): Promise; - -// @public -export interface FindStrictReceivePathOptions { - chain?: string; - destAmount: string; - horizonUrl?: string; - receiveAsset: Asset; - sendAsset: Asset; -} - -// @public -export interface FreighterLikeWallet { - // (undocumented) - signMessage(message: string): Promise<{ - signedMessage: Uint8Array | string; - }>; -} - -// @public -export class FreighterStealthSigner implements StellarStealthSigner { - constructor(wallet: FreighterLikeWallet); - // (undocumented) - signMessage(message: Uint8Array): Promise; -} - -// @public -export interface GeneratedStealthAddress { - ephemeralPubKey: Uint8Array; - stealthAddress: string; - viewTag: number; -} - -// @public -export function generateStealthAddress(spendingPubKey: Uint8Array, viewingPubKey: Uint8Array, ephemeralSeed?: Uint8Array): GeneratedStealthAddress; - -// @public -export function getAssetBalance(contractId: string, address: string, network?: Network, opts?: GetAssetBalanceOptions): Promise; - -// @public -export interface GetAssetBalanceOptions { - rpcUrl?: string; -} - -// @public -export function getAssetMetadata(contractId: string, network?: Network, opts?: GetAssetMetadataOptions): Promise; - -// @public -export interface GetAssetMetadataOptions { - bypassCache?: boolean; - rpcUrl?: string; -} - -// @public -export function getDeployment(chain: string): StellarChainDeployment; - -// @public -export const HASH_MEMO_BYTES = 32; - -// @public -export function hashToScalar(sharedSecret: Uint8Array): bigint; - -// @public -export type HexString = `0x${string}`; - -// @public -export function hexToBytes(hex: string): Uint8Array; - -// @public (undocumented) -export interface HorizonClient { - get(path: string, overrides?: { - retry?: Partial; - }): Promise; - post(path: string, body: URLSearchParams | string, overrides?: { - retry?: Partial; - }): Promise; -} - -// @public (undocumented) -export interface HorizonClientConfig { - fetchImpl?: typeof fetch; - horizonUrl: string; - retry?: Partial; -} - -// @public -export const ID_MEMO_MAX: bigint; - -// @public -export class IndexedDBCache implements AnnouncementCache { - constructor(maxBytes?: number); - // (undocumented) - clear(network: Network): Promise; - // (undocumented) - get(network: Network, fromLedger: number, toLedger: number): Promise; - // (undocumented) - getLastSeen(network: Network): Promise<{ - ledger: number; - cursor: string; - } | null>; - // (undocumented) - put(network: Network, announcements: Announcement[]): Promise; - // (undocumented) - setLastSeen(network: Network, ledger: number, cursor: string): Promise; -} - -// @public -export function isStealthMultisigReady(tx: Transaction): boolean; - -// @public -export const L: bigint; - -// @public -export interface MatchedAnnouncement extends Announcement { - stealthPrivateScalar: bigint; - stealthPubKeyBytes: Uint8Array; -} - -// @public -export const MAX_RPC_EVENT_FILTERS = 5; - -// @public -export class MemoryCache implements AnnouncementCache { - constructor(maxBytes?: number); - // (undocumented) - clear(network: Network): Promise; - // (undocumented) - get(network: Network, fromLedger: number, toLedger: number): Promise; - // (undocumented) - getLastSeen(network: Network): Promise<{ - ledger: number; - cursor: string; - } | null>; - // (undocumented) - put(network: Network, announcements: Announcement[]): Promise; - // (undocumented) - setLastSeen(network: Network, ledger: number, cursor: string): Promise; -} - -// @public -export type MemoType = 'none' | 'id' | 'text' | 'hash' | 'return'; - -// @public -export class MemoValidationError extends Error { - constructor(message: string); -} - -// @public -export type MemoValue = string | Uint8Array | null; - -// @public -export const META_ADDRESS_PREFIX = "st:xlm:"; - -// @public -export type Network = 'testnet' | 'mainnet'; - -// Warning: (ae-internal-missing-underscore) The name "parseAnnouncementEvent" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal -export function parseAnnouncementEvent(event: Record): Announcement | null; - -// @public -export interface PathStealthPaymentResult { - stealthResult: GeneratedStealthAddress; - transaction: ReturnType; -} - -// @public -export function prepareStealthAccountForAsset(accountBalances: Array<{ - asset_code?: string; - asset_issuer?: string; - asset_type: string; -}>, issuerFlags: { - auth_required?: boolean; -}, asset: Asset): AssetReceivabilityResult; - -// @public -export function pubKeyToStellarAddress(pubKeyBytes: Uint8Array): string; - -// @public (undocumented) -export class RetentionExceededError extends Error { - constructor(requestedLedger: number, oldestAvailableLedger: number); - // (undocumented) - readonly oldestAvailableLedger: number; - // (undocumented) - readonly requestedLedger: number; -} - -// @public (undocumented) -export interface RetryPolicy { - baseDelayMs: number; - maxDelayMs: number; - maxRetries: number; - retryableStatuses: number[]; -} - -// @public @deprecated -export function scanAnnouncements(announcements: Announcement[], viewingKey: Uint8Array, spendingPubKey: Uint8Array, spendingScalar: bigint): MatchedAnnouncement[]; - -// @public -export function scanAnnouncementsLegacySharedSecretTag(announcements: Announcement[], viewingKey: Uint8Array, spendingPubKey: Uint8Array, spendingScalar: bigint): MatchedAnnouncement[]; - -// @public -export const SCHEME_ID = 1; - -// @public -export const SCHEME_ID_V1 = 1; - -// @public -export const SCHEME_ID_V2 = 2; - -// @public -export function seedToScalar(seed: Uint8Array): bigint; - -// @public -export function signStellarTransaction(transactionHash: Uint8Array, stealthScalar: bigint, stealthPubKey: Uint8Array): Uint8Array; - -// @public -export function signWithScalar(message: Uint8Array, scalar: bigint, publicKey: Uint8Array): Uint8Array; - -// @public -export interface SorobanEventFilter { - // (undocumented) - contractIds: string[]; - // (undocumented) - topics?: SorobanTopicMatcher[]; - // (undocumented) - type: 'contract'; -} - -// @public -export type SorobanTopicMatcher = string[]; - -// @public -export const STEALTH_SIGNING_MESSAGE = "Sign this message to generate your Wraith stealth keys.\n\nChain: Stellar\nNote: This signature is used for key derivation only and does not authorize any transaction."; - -// @public -export interface StealthKeys { - spendingKey: Uint8Array; - spendingPubKey: Uint8Array; - spendingScalar: bigint; - viewingKey: Uint8Array; - viewingPubKey: Uint8Array; - viewingScalar: bigint; -} - -// @public -export interface StealthMetaAddress { - prefix: string; - spendingPubKey: Uint8Array; - viewingPubKey: Uint8Array; -} - -// @public -export interface StealthPayment { - amount: string; - metaAddress: string; -} - -// @public (undocumented) -export interface StealthPaymentConfig { - amount: string; - asset?: string; - assetIssuer?: string; - caller?: string; - destination: string; - ephemeralPubKey: string; - viewTag: number; -} - -// @public -export const STELLAR_MAX_OPERATIONS = 100; - -// @public -export class StellarBatchBuilder { - constructor(config: BatchConfig); - addPayment(payment: StealthPaymentConfig): this; - build(): BuildResult; - get expectedTransactionCount(): number; - get operationCount(): number; - get paymentCount(): number; - validateBalance(balanceXlm: string): void; -} - -// @public (undocumented) -export interface StellarChainDeployment { - contracts: { - announcer: string; - announcerV2?: string; - names: string; - }; - horizonUrl: string; - network: string; - networkPassphrase: string; - sorobanUrl: string; -} - -// @public -export interface StellarStealthSigner { - signMessage(message: Uint8Array): Promise; -} - -// @public -export interface StrictReceivePathResult { - path: Asset[]; - sourceAmount: string; -} - -// @public -export interface SwapAndStealthResult { - stealthResult: GeneratedStealthAddress; - transaction: ReturnType; -} - -// @public -export const TEXT_MEMO_MAX_BYTES = 28; - -// @public -export interface TypedMemo { - type: MemoType; - value: MemoValue; -} - -// @public -export const VIEW_TAG_BUCKET_COUNT = 256; - -// @public -export function viewTagToBucket(viewTag: number): number; - -// @public -export interface WebAuthnCredentialsContainer { - // (undocumented) - get(options: Record): Promise; -} - -// @public -export class WebAuthnPasskeyStealthSigner implements StellarStealthSigner { - constructor(options: WebAuthnPasskeyStealthSignerOptions); - // (undocumented) - signMessage(message: Uint8Array): Promise; -} - -// @public -export interface WebAuthnPasskeyStealthSignerOptions { - credentialId: Uint8Array; - credentials?: WebAuthnCredentialsContainer; - rpId?: string; -} - -// @public -export interface WebAuthnPRFAssertion { - // (undocumented) - getClientExtensionResults(): { - prf?: { - results?: { - first?: ArrayBuffer; - second?: ArrayBuffer; - }; - }; - }; -} - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@wraith-protocol/sdk" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Asset } from '@stellar/stellar-sdk'; +import { ExtPointType } from '@noble/curves/abstract/edwards'; +import { Horizon } from '@stellar/stellar-sdk'; +import { Keypair } from '@stellar/stellar-sdk'; +import { Memo } from '@stellar/stellar-sdk'; +import { Operation } from '@stellar/stellar-sdk'; +import * as _stellar_stellar_sdk from '@stellar/stellar-sdk'; +import { Transaction } from '@stellar/stellar-sdk'; +import { TransactionBuilder } from '@stellar/stellar-sdk'; +import { xdr } from '@stellar/stellar-sdk'; + +// @public +export function addStealthMultisigSigner(tx: Transaction, signerKey: Keypair | string): Transaction; + +// @public +export const ANNOUNCE_EVENT_SYMBOL = "announce"; + +// @public +export interface Announcement { + caller: string; + ephemeralPubKey: string; + ledger?: number; + // Warning: (ae-forgotten-export) The symbol "StellarMemo" needs to be exported by the entry point index.d.ts + memo?: StellarMemo; + metadata: string; + schemeId: number; + stealthAddress: string; + viewTagBucket?: number; +} + +// @public (undocumented) +export interface AnnouncementCache { + clear(network: Network): Promise; + get(network: Network, fromLedger: number, toLedger: number): Promise; + getLastSeen(network: Network): Promise<{ + ledger: number; + cursor: string; + } | null>; + put(network: Network, announcements: Announcement[]): Promise; + setLastSeen(network: Network, ledger: number, cursor: string): Promise; +} + +// @public +export function assertViewTagBucket(bucket: number): void; + +// @public +export interface AssetMetadata { + decimals: number; + name: string; + symbol: string; +} + +// @public +export interface AssetReceivabilityResult { + hasTrustline: boolean; + issuerAuthRequired: boolean; + ops: ReturnType[]; +} + +// @public +export function autoSelectCache(): AnnouncementCache; + +// @public (undocumented) +export interface BatchConfig { + feePerOp?: number; + networkPassphrase: string; + sequence: string; + source: string; + timeout?: number; +} + +// @public +export function buildAnnouncementData(stealthAddresses: GeneratedStealthAddress[], caller: string): Array<{ + schemeId: number; + stealthAddress: string; + caller: string; + ephemeralPubKey: string; + metadata: string; +}>; + +// @public +export interface BuildAnnouncementOptions { + announcerContract: string; + fee?: string; + networkPassphrase: string; + sender: string; + sequence: string; + stealthResult: GeneratedStealthAddress; +} + +// @public +export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSendTxResult; + +// @public +export interface BuildBatchSendTxParams { + baseFee?: number; + batchSenderContract?: string; + batchSenderThreshold?: number; + maxOperations?: number; + memo?: string; + networkPassphrase: string; + payments: StealthPayment[]; + sourceAccount: any; +} + +// @public +export interface BuildBatchSendTxResult { + stealthAddresses: GeneratedStealthAddress[]; + totalFee: number; + transaction: any; + usedBatchSender: boolean; +} + +// @public +export function buildMultisigStealthWithdraw(options: BuildMultisigStealthWithdrawOptions): Promise; + +// @public (undocumented) +export interface BuildMultisigStealthWithdrawOptions { + account?: Pick; + destination: string; + fee?: string; + horizonUrl?: string; + networkPassphrase: string; + requiredWeight?: number; + sequence?: string; + // Warning: (ae-forgotten-export) The symbol "WeightedSigner" needs to be exported by the entry point index.d.ts + signers: Array; + stealthAddress: string; + timeout?: number; +} + +// @public +export function buildPathStealthPayment(options: BuildPathStealthPaymentOptions): PathStealthPaymentResult; + +// @public +export interface BuildPathStealthPaymentOptions { + announcerContract: string; + destAmount: string; + _ephemeralSeed?: Uint8Array; + fee?: string; + networkPassphrase: string; + path?: Asset[]; + receiveAsset: Asset; + recipientMeta: string; + sendAsset: Asset; + sender: string; + sendMax: string; + sequence: string; +} + +// @public (undocumented) +export interface BuildResult { + paymentCount: number; + totalFee: number; + transactions: string[]; + txCount: number; +} + +// @public +export function buildStealthAnnouncement(options: BuildAnnouncementOptions): _stellar_stellar_sdk.Transaction<_stellar_stellar_sdk.Memo<_stellar_stellar_sdk.MemoType>, Operation[]>; + +// @public +export function buildStealthPayment(options: BuildStealthPaymentOptions): _stellar_stellar_sdk.Transaction<_stellar_stellar_sdk.Memo<_stellar_stellar_sdk.MemoType>, Operation[]>; + +// @public +export interface BuildStealthPaymentOptions { + amount: string; + asset?: Asset; + fee?: string; + networkPassphrase: string; + sender: string; + sequence: string; + stealthExists?: boolean; + stealthResult: GeneratedStealthAddress; +} + +// @public +export function buildStellarSwapAndStealth(options: BuildStellarSwapAndStealthOptions): SwapAndStealthResult; + +// @public +export interface BuildStellarSwapAndStealthOptions { + announcerContract: string; + destAmount: string; + _ephemeralSeed?: Uint8Array; + fee?: string; + fromAsset: Asset; + networkPassphrase: string; + path?: Asset[]; + recipientMeta: string; + sender: string; + sendMax: string; + sequence: string; + toAsset: Asset; +} + +// @public +export function buildV1AnnouncerEventFilter(contractId: string): SorobanEventFilter; + +// @public +export function buildV2AllBucketsEventFilter(contractId: string): SorobanEventFilter; + +// @public +export function buildV2BucketEventFilter(contractId: string, viewTagBucket: number): SorobanEventFilter; + +// @public +export function buildV2BucketEventFilterBatches(contractId: string, viewTagBuckets: number[]): SorobanEventFilter[][]; + +// @public +export function buildWithdrawCustomAsset(options: BuildWithdrawCustomAssetOptions): _stellar_stellar_sdk.Transaction<_stellar_stellar_sdk.Memo<_stellar_stellar_sdk.MemoType>, Operation[]>; + +// @public +export interface BuildWithdrawCustomAssetOptions { + asset: Asset; + balanceId: string; + fee?: string; + needsTrustline?: boolean; + networkPassphrase: string; + sequence: string; + stealthAddress: string; +} + +// @public +export function bytesToHex(bytes: Uint8Array): string; + +// @public +export function checkStealthAddress(ephemeralPubKey: Uint8Array, viewingKey: Uint8Array, spendingPubKey: Uint8Array, viewTag: number): { + isMatch: boolean; + stealthAddress: string | null; + hashScalar: bigint | null; + stealthPubKeyBytes: Uint8Array | null; +}; + +// @public +export function clearAssetMetadataCache(): void; + +// @public +export function computeAnnouncementViewTag(ephemeralPubKey: Uint8Array, viewingPubKey: Uint8Array): number; + +// @public +export function computeSharedSecret(privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array; + +// @public @deprecated +export function computeViewTag(sharedSecret: Uint8Array): number; + +// @public (undocumented) +export function createHorizonClient(config: HorizonClientConfig): HorizonClient; + +// @public +export function decodeAnnouncementData(data: Uint8Array): { + schemeId: number; + ephemeralPubKey: Uint8Array; + viewTag: number; +}; + +// @public +export function decodeMemo(memo: Memo | xdr.Memo): TypedMemo; + +// @public +export function decodeStealthMetaAddress(metaAddress: string): StealthMetaAddress; + +// @public +export const DEFAULT_BASE_FEE = 100; + +// @public +export const DEFAULT_BATCH_SENDER_THRESHOLD = 10; + +// @public +export const DEPLOYMENTS: Record; + +// @public +export function deriveStealthKeys(signature: Uint8Array): StealthKeys; + +// @public +export function deriveStealthKeysFromSigner(signer: StellarStealthSigner): Promise; + +// @public +export function deriveStealthPrivateScalar(spendingScalar: bigint, viewingKey: Uint8Array, ephemeralPubKey: Uint8Array): bigint; + +// @public +export function deriveStealthPubKey(spendingPubKey: Uint8Array, hashScalar: bigint, spendingPoint?: ExtPointType): Uint8Array; + +// @public +export function encodeAnnouncementData(ephemeralPubKey: Uint8Array, viewTag: number): Uint8Array; + +// @public +export function encodeMemo(memo: TypedMemo): Memo; + +// @public +export function encodeStealthMetaAddress(spendingPubKey: Uint8Array, viewingPubKey: Uint8Array): string; + +// @public +export function encodeSymbolTopic(symbol: string): string; + +// @public +export function encodeU32Topic(value: number): string; + +// @public +export function extractMemoFromTransaction(tx: { + memo: Memo | xdr.Memo; +}): TypedMemo; + +// @public (undocumented) +export interface FetchAnnouncementsOptions { + cursor?: string; + fromLedger?: number; + fromTimestamp?: Date; + includeV1?: boolean; + includeV2?: boolean; + sorobanUrl?: string; + toLedger?: number; + toTimestamp?: Date; + viewTagBuckets?: number[]; +} + +// @public +export function fetchAnnouncementsStream(chain?: string, sorobanUrlOrOpts?: string | FetchAnnouncementsOptions, maybeOpts?: FetchAnnouncementsOptions): AsyncGenerator; + +// @public +export function findStrictReceivePath(options: FindStrictReceivePathOptions): Promise; + +// @public +export interface FindStrictReceivePathOptions { + chain?: string; + destAmount: string; + horizonUrl?: string; + receiveAsset: Asset; + sendAsset: Asset; +} + +// @public +export interface FreighterLikeWallet { + // (undocumented) + signMessage(message: string): Promise<{ + signedMessage: Uint8Array | string; + }>; +} + +// @public +export class FreighterStealthSigner implements StellarStealthSigner { + constructor(wallet: FreighterLikeWallet); + // (undocumented) + signMessage(message: Uint8Array): Promise; +} + +// @public +export interface GeneratedStealthAddress { + ephemeralPubKey: Uint8Array; + stealthAddress: string; + viewTag: number; +} + +// @public +export function generateStealthAddress(spendingPubKey: Uint8Array, viewingPubKey: Uint8Array, ephemeralSeed?: Uint8Array): GeneratedStealthAddress; + +// @public +export function getAssetBalance(contractId: string, address: string, network?: Network, opts?: GetAssetBalanceOptions): Promise; + +// @public +export interface GetAssetBalanceOptions { + rpcUrl?: string; +} + +// @public +export function getAssetMetadata(contractId: string, network?: Network, opts?: GetAssetMetadataOptions): Promise; + +// @public +export interface GetAssetMetadataOptions { + bypassCache?: boolean; + rpcUrl?: string; +} + +// @public +export function getDeployment(chain: string): StellarChainDeployment; + +// @public +export const HASH_MEMO_BYTES = 32; + +// @public +export function hashToScalar(sharedSecret: Uint8Array): bigint; + +// @public +export type HexString = `0x${string}`; + +// @public +export function hexToBytes(hex: string): Uint8Array; + +// @public (undocumented) +export interface HorizonClient { + get(path: string, overrides?: { + retry?: Partial; + }): Promise; + post(path: string, body: URLSearchParams | string, overrides?: { + retry?: Partial; + }): Promise; +} + +// @public (undocumented) +export interface HorizonClientConfig { + fetchImpl?: typeof fetch; + horizonUrl: string; + retry?: Partial; +} + +// @public +export const ID_MEMO_MAX: bigint; + +// @public +export class IndexedDBCache implements AnnouncementCache { + constructor(maxBytes?: number); + // (undocumented) + clear(network: Network): Promise; + // (undocumented) + get(network: Network, fromLedger: number, toLedger: number): Promise; + // (undocumented) + getLastSeen(network: Network): Promise<{ + ledger: number; + cursor: string; + } | null>; + // (undocumented) + put(network: Network, announcements: Announcement[]): Promise; + // (undocumented) + setLastSeen(network: Network, ledger: number, cursor: string): Promise; +} + +// @public +export function isStealthMultisigReady(tx: Transaction): boolean; + +// @public +export const L: bigint; + +// @public +export interface MatchedAnnouncement extends Announcement { + stealthPrivateScalar: bigint; + stealthPubKeyBytes: Uint8Array; +} + +// @public +export const MAX_RPC_EVENT_FILTERS = 5; + +// @public +export class MemoryCache implements AnnouncementCache { + constructor(maxBytes?: number); + // (undocumented) + clear(network: Network): Promise; + // (undocumented) + get(network: Network, fromLedger: number, toLedger: number): Promise; + // (undocumented) + getLastSeen(network: Network): Promise<{ + ledger: number; + cursor: string; + } | null>; + // (undocumented) + put(network: Network, announcements: Announcement[]): Promise; + // (undocumented) + setLastSeen(network: Network, ledger: number, cursor: string): Promise; +} + +// @public +export type MemoType = 'none' | 'id' | 'text' | 'hash' | 'return'; + +// @public +export class MemoValidationError extends Error { + constructor(message: string); +} + +// @public +export type MemoValue = string | Uint8Array | null; + +// @public +export const META_ADDRESS_PREFIX = "st:xlm:"; + +// @public +export type Network = 'testnet' | 'mainnet'; + +// Warning: (ae-internal-missing-underscore) The name "parseAnnouncementEvent" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export function parseAnnouncementEvent(event: Record): Announcement | null; + +// @public +export interface PathStealthPaymentResult { + stealthResult: GeneratedStealthAddress; + transaction: ReturnType; +} + +// @public +export function prepareStealthAccountForAsset(accountBalances: Array<{ + asset_code?: string; + asset_issuer?: string; + asset_type: string; +}>, issuerFlags: { + auth_required?: boolean; +}, asset: Asset): AssetReceivabilityResult; + +// @public +export function pubKeyToStellarAddress(pubKeyBytes: Uint8Array): string; + +// @public (undocumented) +export class RetentionExceededError extends Error { + constructor(requestedLedger: number, oldestAvailableLedger: number); + // (undocumented) + readonly oldestAvailableLedger: number; + // (undocumented) + readonly requestedLedger: number; +} + +// @public (undocumented) +export interface RetryPolicy { + baseDelayMs: number; + maxDelayMs: number; + maxRetries: number; + retryableStatuses: number[]; +} + +// @public @deprecated +export function scanAnnouncements(announcements: Announcement[], viewingKey: Uint8Array, spendingPubKey: Uint8Array, spendingScalar: bigint): MatchedAnnouncement[]; + +// @public +export function scanAnnouncementsLegacySharedSecretTag(announcements: Announcement[], viewingKey: Uint8Array, spendingPubKey: Uint8Array, spendingScalar: bigint): MatchedAnnouncement[]; + +// @public +export const SCHEME_ID = 1; + +// @public +export const SCHEME_ID_V1 = 1; + +// @public +export const SCHEME_ID_V2 = 2; + +// @public +export function seedToScalar(seed: Uint8Array): bigint; + +// @public +export function signStellarTransaction(transactionHash: Uint8Array, stealthScalar: bigint, stealthPubKey: Uint8Array): Uint8Array; + +// @public +export function signWithScalar(message: Uint8Array, scalar: bigint, publicKey: Uint8Array): Uint8Array; + +// @public +export interface SorobanEventFilter { + // (undocumented) + contractIds: string[]; + // (undocumented) + topics?: SorobanTopicMatcher[]; + // (undocumented) + type: 'contract'; +} + +// @public +export type SorobanTopicMatcher = string[]; + +// @public +export const STEALTH_SIGNING_MESSAGE = "Sign this message to generate your Wraith stealth keys.\n\nChain: Stellar\nNote: This signature is used for key derivation only and does not authorize any transaction."; + +// @public +export interface StealthKeys { + spendingKey: Uint8Array; + spendingPubKey: Uint8Array; + spendingScalar: bigint; + viewingKey: Uint8Array; + viewingPubKey: Uint8Array; + viewingScalar: bigint; +} + +// @public +export interface StealthMetaAddress { + prefix: string; + spendingPubKey: Uint8Array; + viewingPubKey: Uint8Array; +} + +// @public +export interface StealthPayment { + amount: string; + metaAddress: string; +} + +// @public (undocumented) +export interface StealthPaymentConfig { + amount: string; + asset?: string; + assetIssuer?: string; + caller?: string; + destination: string; + ephemeralPubKey: string; + viewTag: number; +} + +// @public +export const STELLAR_MAX_OPERATIONS = 100; + +// @public +export class StellarBatchBuilder { + constructor(config: BatchConfig); + addPayment(payment: StealthPaymentConfig): this; + build(): BuildResult; + get expectedTransactionCount(): number; + get operationCount(): number; + get paymentCount(): number; + validateBalance(balanceXlm: string): void; +} + +// @public (undocumented) +export interface StellarChainDeployment { + contracts: { + announcer: string; + announcerV2?: string; + names: string; + }; + horizonUrl: string; + network: string; + networkPassphrase: string; + sorobanUrl: string; +} + +// @public +export interface StellarStealthSigner { + signMessage(message: Uint8Array): Promise; +} + +// @public +export interface StrictReceivePathResult { + path: Asset[]; + sourceAmount: string; +} + +// @public +export interface SwapAndStealthResult { + stealthResult: GeneratedStealthAddress; + transaction: ReturnType; +} + +// @public +export const TEXT_MEMO_MAX_BYTES = 28; + +// @public +export interface TypedMemo { + type: MemoType; + value: MemoValue; +} + +// @public +export const VIEW_TAG_BUCKET_COUNT = 256; + +// @public +export function viewTagToBucket(viewTag: number): number; + +// @public +export interface WebAuthnCredentialsContainer { + // (undocumented) + get(options: Record): Promise; +} + +// @public +export class WebAuthnPasskeyStealthSigner implements StellarStealthSigner { + constructor(options: WebAuthnPasskeyStealthSignerOptions); + // (undocumented) + signMessage(message: Uint8Array): Promise; +} + +// @public +export interface WebAuthnPasskeyStealthSignerOptions { + credentialId: Uint8Array; + credentials?: WebAuthnCredentialsContainer; + rpId?: string; +} + +// @public +export interface WebAuthnPRFAssertion { + // (undocumented) + getClientExtensionResults(): { + prf?: { + results?: { + first?: ArrayBuffer; + second?: ArrayBuffer; + }; + }; + }; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/src/chains/stellar/index.ts b/src/chains/stellar/index.ts index fa45f65..26e5ed5 100644 --- a/src/chains/stellar/index.ts +++ b/src/chains/stellar/index.ts @@ -131,6 +131,16 @@ export type { MemoType, MemoValue, TypedMemo } from './memo'; export { getAssetMetadata, getAssetBalance, clearAssetMetadataCache } from './asset'; export type { AssetMetadata, GetAssetMetadataOptions, GetAssetBalanceOptions } from './asset'; export { MemoValidationError, TEXT_MEMO_MAX_BYTES, HASH_MEMO_BYTES, ID_MEMO_MAX } from './memo'; +export { + encodeMemoSchema, + decodeMemoSchema, + MemoKind, + MEMO_SCHEMA_VERSION, + SCHEMA_MEMO_BYTES, + SCHEMA_HEADER_BYTES, + SCHEMA_MAX_DATA_BYTES, +} from './memo/schema'; +export type { MemoSchemaV1, DecodedMemoSchema } from './memo/schema'; export { createHorizonClient } from './horizon'; export type { RetryPolicy, HorizonClient, HorizonClientConfig } from './horizon'; diff --git a/src/chains/stellar/memo/schema.ts b/src/chains/stellar/memo/schema.ts new file mode 100644 index 0000000..e56ab31 --- /dev/null +++ b/src/chains/stellar/memo/schema.ts @@ -0,0 +1,172 @@ +/** + * Structured memo schema for Wraith Stellar memos. + * + * The schema encodes a { version, kind, data } triplet into exactly 32 bytes, + * fitting Stellar MEMO_HASH and MEMO_RETURN constraints. + * + * # Binary Layout (32 bytes) + * ``` + * [0] version uint8 – must be 0x01 for v1 + * [1] kind uint8 – MemoKind enum value + * [2] data_length uint8 – number of meaningful bytes in data (0–29) + * [3..31] data bytes – payload, zero-padded + * ``` + * + * # Backwards Compatibility + * If the first byte is NOT `0x01`, the buffer is treated as raw bytes and + * returned as a `raw` result. This ensures existing hash/return memos that + * happen not to carry the version marker decode gracefully instead of failing. + * + * # Extension Mechanism + * New kinds are added by extending the {@link MemoKind} enum with a new + * numeric value and updating the encoder/decoder if special handling is needed. + * The `decodeMemoSchema` function already forwards any kind value in the + * parsed result, so consumers can interpret custom kinds without changes + * to this module. + * + * Kind value ranges: + * - `0x01–0xEF` – Reserved for standardised kinds (add via PR). + * - `0xF0–0xFF` – Reserved for private / experimental use (no coordination needed). + * + * @module + */ + +/** Current schema version identifier. */ +export const MEMO_SCHEMA_VERSION = 0x01; + +/** Total byte length of a schema-encoded memo (matches MEMO_HASH / MEMO_RETURN). */ +export const SCHEMA_MEMO_BYTES = 32; + +/** Number of header bytes (version + kind + data_length). */ +export const SCHEMA_HEADER_BYTES = 3; + +/** Maximum number of payload bytes that fit in one schema memo. */ +export const SCHEMA_MAX_DATA_BYTES = SCHEMA_MEMO_BYTES - SCHEMA_HEADER_BYTES; // 29 + +/** + * Well-known memo kinds. + * + * Each variant carries a short description of the intended semantics and the + * expected data encoding so consumers know how to interpret the payload. + * + * | Value | Name | Data encoding | Description | + * |-------|-------------|-----------------------|-------------------------------------| + * | 0x01 | `Reason` | UTF-8 string | Human-readable reason / note | + * | 0x02 | `InvoiceId` | UTF-8 string | Invoice or reference identifier | + * | 0x03 | `Reference` | Arbitrary bytes | Opaque reference payload | + */ +export enum MemoKind { + Reason = 0x01, + InvoiceId = 0x02, + Reference = 0x03, +} + +/** + * Parsed v1 memo schema. + */ +export interface MemoSchemaV1 { + version: typeof MEMO_SCHEMA_VERSION; + kind: MemoKind; + data: Uint8Array; +} + +/** + * Decode result for a 32-byte memo buffer. + * + * If the buffer is schema-shaped (first byte equals {@link MEMO_SCHEMA_VERSION}), + * the `schema` field is populated. Otherwise the buffer is treated as raw bytes + * for backwards compatibility. + */ +export interface DecodedMemoSchema { + /** The original 32-byte buffer. */ + bytes: Uint8Array; + /** Parsed schema when the buffer is schema-shaped; `undefined` for raw memos. */ + schema?: MemoSchemaV1; +} + +/** + * Returns `true` when the 32-byte buffer carries a recognised schema version marker. + */ +function isSchemaShaped(bytes: Uint8Array): boolean { + return bytes.length === SCHEMA_MEMO_BYTES && bytes[0] === MEMO_SCHEMA_VERSION; +} + +/** + * Encodes a structured memo schema into a 32-byte buffer suitable for + * MEMO_HASH or MEMO_RETURN. + * + * @param kind - The {@link MemoKind} to encode. + * @param data - Payload data. Strings are encoded as UTF-8. + * @returns A 32-byte Uint8Array. + * + * @throws {Error} If the encoded data exceeds {@link SCHEMA_MAX_DATA_BYTES} (29 bytes). + * + * @example + * ```ts + * // Encode a reason string + * const bytes = encodeMemoSchema(MemoKind.Reason, 'Payment for order #123'); + * + * // Encode arbitrary reference bytes + * const ref = encodeMemoSchema(MemoKind.Reference, new Uint8Array([0x01, 0x02, 0x03])); + * ``` + */ +export function encodeMemoSchema(kind: MemoKind, data: Uint8Array | string): Uint8Array { + const dataBytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; + + if (dataBytes.length > SCHEMA_MAX_DATA_BYTES) { + throw new Error( + `Memo schema data exceeds maximum of ${SCHEMA_MAX_DATA_BYTES} bytes (got ${dataBytes.length})`, + ); + } + + const buffer = new Uint8Array(SCHEMA_MEMO_BYTES); + buffer[0] = MEMO_SCHEMA_VERSION; + buffer[1] = kind; + buffer[2] = dataBytes.length; + buffer.set(dataBytes, SCHEMA_HEADER_BYTES); + // Remaining bytes are already zero-filled by the Uint8Array constructor + + return buffer; +} + +/** + * Decodes a 32-byte memo buffer into a {@link DecodedMemoSchema}. + * + * **Backwards compatibility**: buffers whose first byte is not the schema + * version marker are returned as `{ bytes, schema: undefined }` so callers + * can fall back to treating the content as opaque raw bytes. + * + * @param bytes - A 32-byte buffer (typically from MEMO_HASH or MEMO_RETURN). + * @returns The decoded result with optional schema. + * + * @example + * ```ts + * const memo = decodeMemoSchema(memoBytes); + * + * if (memo.schema) { + * // Structured — inspect memo.schema.kind / memo.schema.data + * console.log('Kind:', memo.schema.kind); + * console.log('Data hex:', bytesToHex(memo.schema.data)); + * } else { + * // Raw bytes — handle as before + * console.log('Raw hex:', bytesToHex(memo.bytes)); + * } + * ``` + */ +export function decodeMemoSchema(bytes: Uint8Array): DecodedMemoSchema { + const result: DecodedMemoSchema = { bytes }; + + if (isSchemaShaped(bytes)) { + const kind = bytes[1] as MemoKind; + const dataLength = Math.min(bytes[2], SCHEMA_MAX_DATA_BYTES); + const data = bytes.slice(SCHEMA_HEADER_BYTES, SCHEMA_HEADER_BYTES + dataLength); + + result.schema = { + version: MEMO_SCHEMA_VERSION, + kind, + data, + }; + } + + return result; +} diff --git a/test/chains/stellar/memo/schema.test.ts b/test/chains/stellar/memo/schema.test.ts new file mode 100644 index 0000000..d675bf0 --- /dev/null +++ b/test/chains/stellar/memo/schema.test.ts @@ -0,0 +1,296 @@ +import { describe, it, expect } from 'vitest'; +import { + encodeMemoSchema, + decodeMemoSchema, + MemoKind, + MEMO_SCHEMA_VERSION, + SCHEMA_MEMO_BYTES, + SCHEMA_HEADER_BYTES, + SCHEMA_MAX_DATA_BYTES, +} from '../../../../src/chains/stellar/memo/schema'; +import { bytesToHex, hexToBytes } from '../../../../src/chains/stellar/utils'; + +describe('MemoSchema', () => { + describe('encodeMemoSchema', () => { + it('encodes a reason kind with string data', () => { + const bytes = encodeMemoSchema(MemoKind.Reason, 'Invoice #123'); + expect(bytes).toBeInstanceOf(Uint8Array); + expect(bytes.length).toBe(SCHEMA_MEMO_BYTES); + expect(bytes[0]).toBe(MEMO_SCHEMA_VERSION); + expect(bytes[1]).toBe(MemoKind.Reason); + }); + + it('encodes an invoice_id kind', () => { + const bytes = encodeMemoSchema(MemoKind.InvoiceId, 'INV-2024-001'); + expect(bytes[1]).toBe(MemoKind.InvoiceId); + }); + + it('encodes a reference kind with binary data', () => { + const refData = new Uint8Array([0xab, 0xcd, 0xef]); + const bytes = encodeMemoSchema(MemoKind.Reference, refData); + expect(bytes[1]).toBe(MemoKind.Reference); + expect(bytes[2]).toBe(3); + expect(bytes.slice(3, 6)).toEqual(refData); + }); + + it('encodes with correct data_length header', () => { + const data = 'hi'; + const bytes = encodeMemoSchema(MemoKind.Reason, data); + expect(bytes[2]).toBe(2); + expect(bytes[3]).toBe(0x68); // 'h' + expect(bytes[4]).toBe(0x69); // 'i' + }); + + it('zero-pads remaining bytes', () => { + const bytes = encodeMemoSchema(MemoKind.Reason, 'a'); + for (let i = SCHEMA_HEADER_BYTES + 1; i < SCHEMA_MEMO_BYTES; i++) { + expect(bytes[i]).toBe(0); + } + }); + + it('throws when data exceeds maximum length', () => { + const longData = 'x'.repeat(SCHEMA_MAX_DATA_BYTES + 1); + expect(() => encodeMemoSchema(MemoKind.Reason, longData)).toThrow('exceeds maximum'); + }); + + it('accepts data at exactly maximum length', () => { + const maxData = 'x'.repeat(SCHEMA_MAX_DATA_BYTES); + expect(() => encodeMemoSchema(MemoKind.Reason, maxData)).not.toThrow(); + const bytes = encodeMemoSchema(MemoKind.Reason, maxData); + expect(bytes[2]).toBe(SCHEMA_MAX_DATA_BYTES); + }); + + it('accepts zero-length data', () => { + const bytes = encodeMemoSchema(MemoKind.Reason, ''); + expect(bytes[2]).toBe(0); + const decoded = decodeMemoSchema(bytes); + expect(decoded.schema?.data.length).toBe(0); + }); + + it('encodes UTF-8 multi-byte characters', () => { + const emoji = '😀'; + const bytes = encodeMemoSchema(MemoKind.Reason, emoji); + // 😀 is 4 bytes in UTF-8: F0 9F 98 80 + expect(bytes[2]).toBe(4); + expect(bytes.slice(3, 7)).toEqual(new Uint8Array([0xf0, 0x9f, 0x98, 0x80])); + }); + }); + + describe('decodeMemoSchema', () => { + it('decodes a schema-shaped buffer with reason kind', () => { + const original = 'Payment for order'; + const encoded = encodeMemoSchema(MemoKind.Reason, original); + const decoded = decodeMemoSchema(encoded); + + expect(decoded.schema).toBeDefined(); + expect(decoded.schema!.version).toBe(MEMO_SCHEMA_VERSION); + expect(decoded.schema!.kind).toBe(MemoKind.Reason); + expect(new TextDecoder().decode(decoded.schema!.data)).toBe(original); + }); + + it('decodes a schema-shaped buffer with invoice_id kind', () => { + const original = 'INV-2025-999'; + const encoded = encodeMemoSchema(MemoKind.InvoiceId, original); + const decoded = decodeMemoSchema(encoded); + + expect(decoded.schema).toBeDefined(); + expect(decoded.schema!.kind).toBe(MemoKind.InvoiceId); + expect(new TextDecoder().decode(decoded.schema!.data)).toBe(original); + }); + + it('decodes a schema-shaped buffer with reference kind', () => { + const refData = new Uint8Array([0x01, 0x02, 0x03, 0xff]); + const encoded = encodeMemoSchema(MemoKind.Reference, refData); + const decoded = decodeMemoSchema(encoded); + + expect(decoded.schema).toBeDefined(); + expect(decoded.schema!.kind).toBe(MemoKind.Reference); + expect(decoded.schema!.data).toEqual(refData); + }); + + it('returns raw result for buffers that are not schema-shaped', () => { + const rawBytes = new Uint8Array(SCHEMA_MEMO_BYTES).fill(0xab); + const decoded = decodeMemoSchema(rawBytes); + + expect(decoded.schema).toBeUndefined(); + expect(decoded.bytes).toEqual(rawBytes); + }); + + it('returns raw result for buffer starting with 0x02 (non-version)', () => { + const bytes = new Uint8Array(SCHEMA_MEMO_BYTES); + bytes[0] = 0x02; + bytes[1] = 0x01; + const decoded = decodeMemoSchema(bytes); + expect(decoded.schema).toBeUndefined(); + }); + + it('preserves the original bytes in the result', () => { + const encoded = encodeMemoSchema(MemoKind.Reason, 'test'); + const decoded = decodeMemoSchema(encoded); + expect(decoded.bytes).toEqual(encoded); + expect(decoded.bytes.length).toBe(SCHEMA_MEMO_BYTES); + }); + }); + + describe('round-trip', () => { + it('round-trips a reason string', () => { + const data = 'Payment for order #12345'; + const encoded = encodeMemoSchema(MemoKind.Reason, data); + const decoded = decodeMemoSchema(encoded); + expect(new TextDecoder().decode(decoded.schema!.data)).toBe(data); + }); + + it('round-trips an invoice id string', () => { + const data = 'INV-12345-ABC'; + const encoded = encodeMemoSchema(MemoKind.InvoiceId, data); + const decoded = decodeMemoSchema(encoded); + expect(new TextDecoder().decode(decoded.schema!.data)).toBe(data); + }); + + it('round-trips binary reference data', () => { + const data = new Uint8Array([0xde, 0xad, 0xbe, 0xef]); + const encoded = encodeMemoSchema(MemoKind.Reference, data); + const decoded = decodeMemoSchema(encoded); + expect(decoded.schema!.data).toEqual(data); + }); + + it('round-trips all kinds deterministically', () => { + const testCases = [ + { kind: MemoKind.Reason, data: 'test' }, + { kind: MemoKind.InvoiceId, data: 'INV-001' }, + { kind: MemoKind.Reference, data: new Uint8Array([0x01, 0x02]) }, + ]; + + for (const tc of testCases) { + const encoded1 = encodeMemoSchema(tc.kind, tc.data); + const encoded2 = encodeMemoSchema(tc.kind, tc.data); + expect(encoded1).toEqual(encoded2); + + const decoded = decodeMemoSchema(encoded1); + expect(decoded.schema!.kind).toBe(tc.kind); + } + }); + + it('round-trips empty string data', () => { + const encoded = encodeMemoSchema(MemoKind.Reason, ''); + const decoded = decodeMemoSchema(encoded); + expect(decoded.schema!.data.length).toBe(0); + }); + + it('round-trips max-length string data', () => { + const data = 'x'.repeat(SCHEMA_MAX_DATA_BYTES); + const encoded = encodeMemoSchema(MemoKind.Reason, data); + const decoded = decodeMemoSchema(encoded); + expect(new TextDecoder().decode(decoded.schema!.data)).toBe(data); + }); + }); + + describe('backwards compatibility', () => { + it('treats arbitrary 32-byte hash as raw bytes', () => { + const hashBytes = hexToBytes('deadbeef' + '00'.repeat(28)); + const decoded = decodeMemoSchema(hashBytes); + expect(decoded.schema).toBeUndefined(); + }); + + it('treats all-zero buffer as raw bytes', () => { + const zeros = new Uint8Array(SCHEMA_MEMO_BYTES); + const decoded = decodeMemoSchema(zeros); + expect(decoded.schema).toBeUndefined(); + }); + + it('treats buffer starting with version byte but wrong length as raw', () => { + // A buffer that starts with 0x01 but is not 32 bytes long + const short = new Uint8Array([MEMO_SCHEMA_VERSION, 0x01, 0x00]); + const decoded = decodeMemoSchema(short); + expect(decoded.schema).toBeUndefined(); + }); + }); + + describe('extension mechanism', () => { + it('decodes unknown kind values without throwing', () => { + const bytes = new Uint8Array(SCHEMA_MEMO_BYTES); + bytes[0] = MEMO_SCHEMA_VERSION; + bytes[1] = 0x42; // unknown kind + bytes[2] = 2; + bytes[3] = 0xaa; + bytes[4] = 0xbb; + + const decoded = decodeMemoSchema(bytes); + expect(decoded.schema).toBeDefined(); + expect(decoded.schema!.kind).toBe(0x42); + expect(decoded.schema!.data).toEqual(new Uint8Array([0xaa, 0xbb])); + }); + + it('decodes private/experimental range kinds (0xF0-0xFF)', () => { + for (const kind of [0xf0, 0xff]) { + const bytes = new Uint8Array(SCHEMA_MEMO_BYTES); + bytes[0] = MEMO_SCHEMA_VERSION; + bytes[1] = kind; + bytes[2] = 1; + bytes[3] = 0x99; + + const decoded = decodeMemoSchema(bytes); + expect(decoded.schema).toBeDefined(); + expect(decoded.schema!.kind).toBe(kind); + expect(decoded.schema!.data).toEqual(new Uint8Array([0x99])); + } + }); + + it('clamps data_length that exceeds max data bytes', () => { + const bytes = new Uint8Array(SCHEMA_MEMO_BYTES); + bytes[0] = MEMO_SCHEMA_VERSION; + bytes[1] = 0x01; + bytes[2] = 255; // larger than max + bytes[3] = 0xaa; + + const decoded = decodeMemoSchema(bytes); + expect(decoded.schema!.data.length).toBe(SCHEMA_MAX_DATA_BYTES); + }); + }); + + describe('edge cases', () => { + it('handles single-byte data', () => { + const data = new Uint8Array([0x42]); + const encoded = encodeMemoSchema(MemoKind.Reference, data); + const decoded = decodeMemoSchema(encoded); + expect(decoded.schema!.data).toEqual(data); + }); + + it('handles all-zeros data', () => { + const data = new Uint8Array(10); + const encoded = encodeMemoSchema(MemoKind.Reference, data); + const decoded = decodeMemoSchema(encoded); + expect(decoded.schema!.data).toEqual(data); + }); + + it('produces exactly 32 bytes', () => { + const bytes = encodeMemoSchema(MemoKind.Reason, 'hello'); + expect(bytes.length).toBe(32); + }); + + it('header bytes are in correct positions', () => { + const bytes = encodeMemoSchema(MemoKind.InvoiceId, 'test'); + // [0] = version + expect(bytes[0]).toBe(0x01); + // [1] = kind + expect(bytes[1]).toBe(0x02); + // [2] = data_length + expect(bytes[2]).toBe(4); + }); + }); + + describe('exports', () => { + it('exports constants with expected values', () => { + expect(MEMO_SCHEMA_VERSION).toBe(0x01); + expect(SCHEMA_MEMO_BYTES).toBe(32); + expect(SCHEMA_HEADER_BYTES).toBe(3); + expect(SCHEMA_MAX_DATA_BYTES).toBe(29); + }); + + it('exports MemoKind enum values', () => { + expect(MemoKind.Reason).toBe(0x01); + expect(MemoKind.InvoiceId).toBe(0x02); + expect(MemoKind.Reference).toBe(0x03); + }); + }); +});