From 113905323f07ce67b648e81e5e1d72c6db3a60a2 Mon Sep 17 00:00:00 2001 From: Seyitan <166076743+fwseyi@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:29:42 +0100 Subject: [PATCH 1/6] feat(stellar): implement batch sender contract path --- src/chains/stellar/tx-builder.ts | 92 +++++++++++++++++++++++--- src/chains/stellar/types.ts | 4 ++ test/chains/stellar/tx-builder.test.ts | 81 ++++++++++++++++++++++- 3 files changed, 166 insertions(+), 11 deletions(-) diff --git a/src/chains/stellar/tx-builder.ts b/src/chains/stellar/tx-builder.ts index d8f953e..5198a2d 100644 --- a/src/chains/stellar/tx-builder.ts +++ b/src/chains/stellar/tx-builder.ts @@ -73,7 +73,17 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend batchSenderContract !== undefined && payments.length >= batchSenderThreshold; // Load Stellar SDK dynamically (peer dependency) - const { TransactionBuilder, Operation, Memo, Asset } = require('@stellar/stellar-sdk'); + const { + TransactionBuilder, + Operation, + Memo, + Asset, + Contract, + Address, + StrKey, + nativeToScVal, + xdr, + } = require('@stellar/stellar-sdk'); // Calculate total fee with scaling // Fee scales with operation count to ensure timely inclusion @@ -98,13 +108,56 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend } if (useBatchSender && batchSenderContract) { - // Use stealth-batch-sender contract for large batches - // This would call a Soroban contract that handles the batch efficiently - // For now, this is a placeholder - the actual contract implementation - // would be added when the contract is deployed - throw new Error( - 'stealth-batch-sender contract integration not yet implemented. ' + - 'Please provide batchSenderContract only when the contract is deployed.', + if (!StrKey.isValidContract(batchSenderContract)) { + throw new Error(`Invalid batchSenderContract: expected a Soroban contract address`); + } + + const accountId = sourceAccount.accountId(); + const assets = payments.map((payment) => resolvePaymentAsset({ Asset, StrKey }, payment)); + const firstAsset = assets[0]; + if ( + assets.some( + (asset: any) => + asset.contractId(networkPassphrase) !== firstAsset.contractId(networkPassphrase), + ) + ) { + throw new Error('Batch sender payments must all use the same asset'); + } + + const tokenContract = firstAsset.contractId(networkPassphrase); + const contract = new Contract(batchSenderContract); + const addresses = xdr.ScVal.scvVec( + stealthAddresses.map((stealth: GeneratedStealthAddress) => + new Address(stealth.stealthAddress).toScVal(), + ), + ); + const ephemeralKeys = xdr.ScVal.scvVec( + stealthAddresses.map((stealth: GeneratedStealthAddress) => + xdr.ScVal.scvBytes(Buffer.from(stealth.ephemeralPubKey)), + ), + ); + const metadatas = xdr.ScVal.scvVec( + stealthAddresses.map((stealth: GeneratedStealthAddress) => + xdr.ScVal.scvBytes(Buffer.from([stealth.viewTag])), + ), + ); + const amounts = xdr.ScVal.scvVec( + payments.map((payment: { amount: string }) => + nativeToScVal(toStroops(payment.amount), { type: 'i128' }), + ), + ); + + builder = builder.addOperation( + contract.call( + 'batch_send', + new Address(accountId).toScVal(), + new Address(tokenContract).toScVal(), + nativeToScVal(SCHEME_ID, { type: 'u32' }), + addresses, + ephemeralKeys, + metadatas, + amounts, + ), ); } else { // Build individual payment operations @@ -115,7 +168,7 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend builder = builder.addOperation( Operation.payment({ destination: stealth.stealthAddress, - asset: Asset.native(), + asset: resolvePaymentAsset({ Asset, StrKey }, payment), amount: payment.amount, }), ); @@ -136,6 +189,27 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend }; } +function resolvePaymentAsset( + sdk: { Asset: any; StrKey: any }, + payment: { asset?: string; assetIssuer?: string }, +): any { + const { Asset, StrKey } = sdk; + const asset = payment.asset; + if (!asset || asset === 'native' || asset === 'XLM') return Asset.native(); + if (!payment.assetIssuer || !StrKey.isValidEd25519PublicKey(payment.assetIssuer)) { + throw new Error(`assetIssuer is required and must be a valid account for asset "${asset}"`); + } + return new Asset(asset, payment.assetIssuer); +} + +function toStroops(amount: string): bigint { + if (!/^\d+(?:\.\d{1,7})?$/.test(amount) || amount === '0') { + throw new Error(`Invalid payment amount: ${amount}`); + } + const [whole, fraction = ''] = amount.split('.'); + return BigInt(whole) * 10_000_000n + BigInt(fraction.padEnd(7, '0')); +} + /** * Builds announcement data for stealth payments. * This can be used to publish announcements after sending payments. diff --git a/src/chains/stellar/types.ts b/src/chains/stellar/types.ts index b0121f8..bd132e1 100644 --- a/src/chains/stellar/types.ts +++ b/src/chains/stellar/types.ts @@ -274,6 +274,10 @@ export interface StealthPayment { metaAddress: string; /** Amount in XLM (string to preserve precision) */ amount: string; + /** Asset code ("native"/"XLM" or an issued asset code). Defaults to native XLM. */ + asset?: string; + /** Issuer account for an issued asset. Required when asset is not native/XLM. */ + assetIssuer?: string; } /** Parameters for building a batch send transaction. */ diff --git a/test/chains/stellar/tx-builder.test.ts b/test/chains/stellar/tx-builder.test.ts index a7e7c13..f215a7d 100644 --- a/test/chains/stellar/tx-builder.test.ts +++ b/test/chains/stellar/tx-builder.test.ts @@ -1,4 +1,5 @@ import { describe, test, expect } from 'vitest'; +import { Asset, Operation, scValToNative } from '@stellar/stellar-sdk'; import { deriveStealthKeys } from '../../../src/chains/stellar/keys'; import { encodeStealthMetaAddress } from '../../../src/chains/stellar/meta-address'; import { @@ -234,7 +235,7 @@ describe('tx-builder: buildBatchSendTx', () => { ).toThrow('Memo too long'); }); - test('throws error when batchSenderContract is provided but not implemented', () => { + test('builds a native XLM batch-sender contract invocation', () => { const payments: StealthPayment[] = Array.from( { length: DEFAULT_BATCH_SENDER_THRESHOLD }, () => ({ @@ -243,6 +244,61 @@ describe('tx-builder: buildBatchSendTx', () => { }), ); + const result = buildBatchSendTx({ + payments, + sourceAccount, + networkPassphrase, + batchSenderContract: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4', + }); + const operation = result.transaction.operations[0] as Operation.InvokeHostFunction; + const invocation = operation.func.invokeContract(); + expect(operation.type).toBe('invokeHostFunction'); + expect(invocation.functionName().toString()).toBe('batch_send'); + expect(invocation.args()).toHaveLength(7); + expect(scValToNative(invocation.args()[0])).toBe(sourceAccount.accountId()); + expect(scValToNative(invocation.args()[1])).toMatch(/^C[A-Z2-7]{55}$/); + expect(scValToNative(invocation.args()[2])).toBe(1); + expect(scValToNative(invocation.args()[3])).toHaveLength(payments.length); + expect(scValToNative(invocation.args()[4])[0]).toHaveLength(32); + expect(scValToNative(invocation.args()[5])[0]).toHaveLength(1); + expect(scValToNative(invocation.args()[6])).toEqual(payments.map(() => 10_000_000n)); + expect(result.totalFee).toBe(DEFAULT_BASE_FEE); + expect(result.usedBatchSender).toBe(true); + expect(result.stealthAddresses).toHaveLength(payments.length); + }); + + test('supports the documented issued-asset batch path', () => { + const issuer = 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF'; + const payments: StealthPayment[] = Array.from( + { length: DEFAULT_BATCH_SENDER_THRESHOLD }, + () => ({ + metaAddress, + amount: '1.25', + asset: 'USDC', + assetIssuer: issuer, + }), + ); + + const result = buildBatchSendTx({ + payments, + sourceAccount, + networkPassphrase, + batchSenderContract: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4', + }); + const operation = result.transaction.operations[0] as Operation.InvokeHostFunction; + const invocation = operation.func.invokeContract(); + expect(scValToNative(invocation.args()[1])).toBe( + new Asset('USDC', issuer).contractId(networkPassphrase), + ); + expect(scValToNative(invocation.args()[6])).toEqual(payments.map(() => 12_500_000n)); + }); + + test('rejects an invalid batch-sender contract configuration', () => { + const payments: StealthPayment[] = Array.from( + { length: DEFAULT_BATCH_SENDER_THRESHOLD }, + () => ({ metaAddress, amount: '1' }), + ); + expect(() => buildBatchSendTx({ payments, @@ -250,7 +306,28 @@ describe('tx-builder: buildBatchSendTx', () => { networkPassphrase, batchSenderContract: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF', }), - ).toThrow('stealth-batch-sender contract integration not yet implemented'); + ).toThrow('Invalid batchSenderContract'); + }); + + test('rejects mixed assets in one contract batch', () => { + const issuer = 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF'; + const payments: StealthPayment[] = Array.from( + { length: DEFAULT_BATCH_SENDER_THRESHOLD }, + (_, i) => ({ + metaAddress, + amount: '1', + ...(i === 0 ? { asset: 'USDC', assetIssuer: issuer } : {}), + }), + ); + + expect(() => + buildBatchSendTx({ + payments, + sourceAccount, + networkPassphrase, + batchSenderContract: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4', + }), + ).toThrow('same asset'); }); test('does not use batch sender below threshold', () => { From 4a50ab1c5ccd79bc8471e6eca3b01d89e92df8d5 Mon Sep 17 00:00:00 2001 From: Oluwaseyitan Animasaun Date: Sat, 26 Sep 2026 09:11:33 +0100 Subject: [PATCH 2/6] fix(stellar): match batch sender transfer ABI --- etc/sdk-stellar.api.md | 10 +++--- src/chains/stellar/tx-builder.ts | 48 ++++++++++++++------------ test/chains/stellar/tx-builder.test.ts | 23 +++++++----- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/etc/sdk-stellar.api.md b/etc/sdk-stellar.api.md index dddc646..ada0e58 100644 --- a/etc/sdk-stellar.api.md +++ b/etc/sdk-stellar.api.md @@ -712,10 +712,12 @@ export interface StealthMetaAddress { } // @public -export interface StealthPayment { - amount: string; - metaAddress: string; -} +export interface StealthPayment { + amount: string; + asset?: string; + assetIssuer?: string; + metaAddress: string; +} // @public (undocumented) export interface StealthPaymentConfig { diff --git a/src/chains/stellar/tx-builder.ts b/src/chains/stellar/tx-builder.ts index 5198a2d..e9c9fc1 100644 --- a/src/chains/stellar/tx-builder.ts +++ b/src/chains/stellar/tx-builder.ts @@ -126,24 +126,30 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend const tokenContract = firstAsset.contractId(networkPassphrase); const contract = new Contract(batchSenderContract); - const addresses = xdr.ScVal.scvVec( - stealthAddresses.map((stealth: GeneratedStealthAddress) => - new Address(stealth.stealthAddress).toScVal(), - ), - ); - const ephemeralKeys = xdr.ScVal.scvVec( - stealthAddresses.map((stealth: GeneratedStealthAddress) => - xdr.ScVal.scvBytes(Buffer.from(stealth.ephemeralPubKey)), - ), - ); - const metadatas = xdr.ScVal.scvVec( - stealthAddresses.map((stealth: GeneratedStealthAddress) => - xdr.ScVal.scvBytes(Buffer.from([stealth.viewTag])), - ), - ); - const amounts = xdr.ScVal.scvVec( - payments.map((payment: { amount: string }) => - nativeToScVal(toStroops(payment.amount), { type: 'i128' }), + const transfers = xdr.ScVal.scvVec( + stealthAddresses.map((stealth: GeneratedStealthAddress, index: number) => + xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('stealth_address'), + val: new Address(stealth.stealthAddress).toScVal(), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('amount'), + val: nativeToScVal(toStroops(payments[index].amount), { type: 'i128' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('scheme_id'), + val: nativeToScVal(SCHEME_ID, { type: 'u32' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('ephemeral_pub_key'), + val: xdr.ScVal.scvBytes(Buffer.from(stealth.ephemeralPubKey)), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('metadata'), + val: xdr.ScVal.scvBytes(Buffer.from([stealth.viewTag])), + }), + ]), ), ); @@ -151,12 +157,8 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend contract.call( 'batch_send', new Address(accountId).toScVal(), + transfers, new Address(tokenContract).toScVal(), - nativeToScVal(SCHEME_ID, { type: 'u32' }), - addresses, - ephemeralKeys, - metadatas, - amounts, ), ); } else { diff --git a/test/chains/stellar/tx-builder.test.ts b/test/chains/stellar/tx-builder.test.ts index f215a7d..d3e687c 100644 --- a/test/chains/stellar/tx-builder.test.ts +++ b/test/chains/stellar/tx-builder.test.ts @@ -254,14 +254,17 @@ describe('tx-builder: buildBatchSendTx', () => { const invocation = operation.func.invokeContract(); expect(operation.type).toBe('invokeHostFunction'); expect(invocation.functionName().toString()).toBe('batch_send'); - expect(invocation.args()).toHaveLength(7); + expect(invocation.args()).toHaveLength(3); expect(scValToNative(invocation.args()[0])).toBe(sourceAccount.accountId()); - expect(scValToNative(invocation.args()[1])).toMatch(/^C[A-Z2-7]{55}$/); - expect(scValToNative(invocation.args()[2])).toBe(1); - expect(scValToNative(invocation.args()[3])).toHaveLength(payments.length); - expect(scValToNative(invocation.args()[4])[0]).toHaveLength(32); - expect(scValToNative(invocation.args()[5])[0]).toHaveLength(1); - expect(scValToNative(invocation.args()[6])).toEqual(payments.map(() => 10_000_000n)); + expect(scValToNative(invocation.args()[1])).toHaveLength(payments.length); + expect(scValToNative(invocation.args()[1])[0]).toMatchObject({ + amount: 10_000_000n, + scheme_id: 1, + }); + expect(scValToNative(invocation.args()[1])[0].stealth_address).toMatch(/^G[A-Z2-7]{55}$/); + expect(scValToNative(invocation.args()[1])[0].ephemeral_pub_key).toHaveLength(32); + expect(scValToNative(invocation.args()[1])[0].metadata).toHaveLength(1); + expect(scValToNative(invocation.args()[2])).toMatch(/^C[A-Z2-7]{55}$/); expect(result.totalFee).toBe(DEFAULT_BASE_FEE); expect(result.usedBatchSender).toBe(true); expect(result.stealthAddresses).toHaveLength(payments.length); @@ -287,10 +290,12 @@ describe('tx-builder: buildBatchSendTx', () => { }); const operation = result.transaction.operations[0] as Operation.InvokeHostFunction; const invocation = operation.func.invokeContract(); - expect(scValToNative(invocation.args()[1])).toBe( + expect(scValToNative(invocation.args()[2])).toBe( new Asset('USDC', issuer).contractId(networkPassphrase), ); - expect(scValToNative(invocation.args()[6])).toEqual(payments.map(() => 12_500_000n)); + expect( + scValToNative(invocation.args()[1]).map((transfer: { amount: bigint }) => transfer.amount), + ).toEqual(payments.map(() => 12_500_000n)); }); test('rejects an invalid batch-sender contract configuration', () => { From e87973868fc69facc22e37f9c2d4d30086296bd9 Mon Sep 17 00:00:00 2001 From: Oluwaseyitan Animasaun Date: Sat, 26 Sep 2026 09:42:20 +0100 Subject: [PATCH 3/6] docs(api): regenerate reports for batch sender --- etc/sdk-stellar.api.md | 12 ++++++------ etc/sdk.api.md | 23 +++++++++++------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/etc/sdk-stellar.api.md b/etc/sdk-stellar.api.md index ada0e58..447e42c 100644 --- a/etc/sdk-stellar.api.md +++ b/etc/sdk-stellar.api.md @@ -712,12 +712,12 @@ export interface StealthMetaAddress { } // @public -export interface StealthPayment { - amount: string; - asset?: string; - assetIssuer?: string; - metaAddress: string; -} +export interface StealthPayment { + amount: string; + asset?: string; + assetIssuer?: string; + metaAddress: string; +} // @public (undocumented) export interface StealthPaymentConfig { diff --git a/etc/sdk.api.md b/etc/sdk.api.md index 39bd528..ba7c96c 100644 --- a/etc/sdk.api.md +++ b/etc/sdk.api.md @@ -19,9 +19,9 @@ export interface BaseWalletAdapter { +export interface ChainScannerAdapter { decodeMetaAddress(metaAddress: string): TMetaAddress; - encodeMetaAddress(spendingPubKey: unknown, viewingPubKey: unknown): string; + encodeMetaAddress(spendingPubKey: any, viewingPubKey: any): string; id: string; scan(source: AsyncIterable, keys: TKeys): AsyncGenerator; timestampOf?(matched: TMatched): number | undefined; @@ -64,8 +64,8 @@ export function createSolanaWalletAdapter(wallet: SolanaWalletAdapterLike): Sola export function createViemWalletAdapter(client: ViemWalletClient): ViemWalletAdapter; // @public -export interface CustomChainInput { - adapter: ChainScannerAdapter; +export interface CustomChainInput { + adapter: ChainScannerAdapter; keys: TKeys; source: AsyncIterable; } @@ -266,11 +266,10 @@ export type MatchedAnnouncement = { seq: number; announcement: MatchedStealthCell; } | { - chain: 'custom'; - customChainId: string; + chain: string; timestamp: number; seq: number; - announcement: unknown; + announcement: any; }; // @public (undocumented) @@ -365,7 +364,7 @@ export function scanAll(input: ScanAllInput): AsyncGenerator>; + adapters?: Array | any>; // (undocumented) ckb?: CkbChainInput; // (undocumented) @@ -670,10 +669,10 @@ export abstract class WraithWalletError extends WraithError { // Warnings were encountered during analysis: // -// dist/unified-DZ7PcCQN.d.ts:166:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_2" needs to be exported by the entry point index.d.ts -// dist/unified-DZ7PcCQN.d.ts:171:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement$1" needs to be exported by the entry point index.d.ts -// dist/unified-DZ7PcCQN.d.ts:176:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_3" needs to be exported by the entry point index.d.ts -// dist/unified-DZ7PcCQN.d.ts:181:5 - (ae-forgotten-export) The symbol "MatchedStealthCell" needs to be exported by the entry point index.d.ts +// dist/unified-B4T6tz3t.d.ts:166:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_2" needs to be exported by the entry point index.d.ts +// dist/unified-B4T6tz3t.d.ts:171:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement$1" needs to be exported by the entry point index.d.ts +// dist/unified-B4T6tz3t.d.ts:176:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_3" needs to be exported by the entry point index.d.ts +// dist/unified-B4T6tz3t.d.ts:181:5 - (ae-forgotten-export) The symbol "MatchedStealthCell" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) From d6c7f39d0271fae14262d30e6159555d9d989f9c Mon Sep 17 00:00:00 2001 From: Oluwaseyitan Animasaun Date: Mon, 28 Sep 2026 09:22:52 +0100 Subject: [PATCH 4/6] fix(stellar): match batch sender transfer fields --- src/chains/stellar/tx-builder.ts | 4 ---- test/chains/stellar/tx-builder.test.ts | 16 +++++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/chains/stellar/tx-builder.ts b/src/chains/stellar/tx-builder.ts index e9c9fc1..6622020 100644 --- a/src/chains/stellar/tx-builder.ts +++ b/src/chains/stellar/tx-builder.ts @@ -137,10 +137,6 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend key: xdr.ScVal.scvSymbol('amount'), val: nativeToScVal(toStroops(payments[index].amount), { type: 'i128' }), }), - new xdr.ScMapEntry({ - key: xdr.ScVal.scvSymbol('scheme_id'), - val: nativeToScVal(SCHEME_ID, { type: 'u32' }), - }), new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol('ephemeral_pub_key'), val: xdr.ScVal.scvBytes(Buffer.from(stealth.ephemeralPubKey)), diff --git a/test/chains/stellar/tx-builder.test.ts b/test/chains/stellar/tx-builder.test.ts index d3e687c..270863a 100644 --- a/test/chains/stellar/tx-builder.test.ts +++ b/test/chains/stellar/tx-builder.test.ts @@ -257,13 +257,19 @@ describe('tx-builder: buildBatchSendTx', () => { expect(invocation.args()).toHaveLength(3); expect(scValToNative(invocation.args()[0])).toBe(sourceAccount.accountId()); expect(scValToNative(invocation.args()[1])).toHaveLength(payments.length); - expect(scValToNative(invocation.args()[1])[0]).toMatchObject({ + const decodedTransfer = scValToNative(invocation.args()[1])[0]; + expect(Object.keys(decodedTransfer).sort()).toEqual([ + 'amount', + 'ephemeral_pub_key', + 'metadata', + 'stealth_address', + ]); + expect(decodedTransfer).toMatchObject({ amount: 10_000_000n, - scheme_id: 1, }); - expect(scValToNative(invocation.args()[1])[0].stealth_address).toMatch(/^G[A-Z2-7]{55}$/); - expect(scValToNative(invocation.args()[1])[0].ephemeral_pub_key).toHaveLength(32); - expect(scValToNative(invocation.args()[1])[0].metadata).toHaveLength(1); + expect(decodedTransfer.stealth_address).toMatch(/^G[A-Z2-7]{55}$/); + expect(decodedTransfer.ephemeral_pub_key).toHaveLength(32); + expect(decodedTransfer.metadata).toHaveLength(1); expect(scValToNative(invocation.args()[2])).toMatch(/^C[A-Z2-7]{55}$/); expect(result.totalFee).toBe(DEFAULT_BASE_FEE); expect(result.usedBatchSender).toBe(true); From 72fd0fd440f40347e309f014e2a919b665bf2c69 Mon Sep 17 00:00:00 2001 From: Oluwaseyitan Animasaun Date: Mon, 28 Sep 2026 18:02:27 +0100 Subject: [PATCH 5/6] fix(stellar): sort batch transfer map keys --- src/chains/stellar/tx-builder.ts | 9 +++++---- test/chains/stellar/tx-builder.test.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/chains/stellar/tx-builder.ts b/src/chains/stellar/tx-builder.ts index 6622020..364a499 100644 --- a/src/chains/stellar/tx-builder.ts +++ b/src/chains/stellar/tx-builder.ts @@ -127,8 +127,8 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend const tokenContract = firstAsset.contractId(networkPassphrase); const contract = new Contract(batchSenderContract); const transfers = xdr.ScVal.scvVec( - stealthAddresses.map((stealth: GeneratedStealthAddress, index: number) => - xdr.ScVal.scvMap([ + stealthAddresses.map((stealth: GeneratedStealthAddress, index: number) => { + const transferEntries = [ new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol('stealth_address'), val: new Address(stealth.stealthAddress).toScVal(), @@ -145,8 +145,9 @@ export function buildBatchSendTx(params: BuildBatchSendTxParams): BuildBatchSend key: xdr.ScVal.scvSymbol('metadata'), val: xdr.ScVal.scvBytes(Buffer.from([stealth.viewTag])), }), - ]), - ), + ]; + return xdr.scvSortedMap(transferEntries); + }), ); builder = builder.addOperation( diff --git a/test/chains/stellar/tx-builder.test.ts b/test/chains/stellar/tx-builder.test.ts index 270863a..f305716 100644 --- a/test/chains/stellar/tx-builder.test.ts +++ b/test/chains/stellar/tx-builder.test.ts @@ -258,7 +258,7 @@ describe('tx-builder: buildBatchSendTx', () => { expect(scValToNative(invocation.args()[0])).toBe(sourceAccount.accountId()); expect(scValToNative(invocation.args()[1])).toHaveLength(payments.length); const decodedTransfer = scValToNative(invocation.args()[1])[0]; - expect(Object.keys(decodedTransfer).sort()).toEqual([ + expect(Object.keys(decodedTransfer)).toEqual([ 'amount', 'ephemeral_pub_key', 'metadata', From c9a8731cfe963dbe275640c10f07e29808369335 Mon Sep 17 00:00:00 2001 From: Oluwaseyitan Animasaun Date: Mon, 28 Sep 2026 18:43:29 +0100 Subject: [PATCH 6/6] docs(api): regenerate shared report after rebase --- etc/sdk.api.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/etc/sdk.api.md b/etc/sdk.api.md index ba7c96c..04a05d1 100644 --- a/etc/sdk.api.md +++ b/etc/sdk.api.md @@ -19,9 +19,9 @@ export interface BaseWalletAdapter { +export interface ChainScannerAdapter { decodeMetaAddress(metaAddress: string): TMetaAddress; - encodeMetaAddress(spendingPubKey: any, viewingPubKey: any): string; + encodeMetaAddress(spendingPubKey: unknown, viewingPubKey: unknown): string; id: string; scan(source: AsyncIterable, keys: TKeys): AsyncGenerator; timestampOf?(matched: TMatched): number | undefined; @@ -64,8 +64,8 @@ export function createSolanaWalletAdapter(wallet: SolanaWalletAdapterLike): Sola export function createViemWalletAdapter(client: ViemWalletClient): ViemWalletAdapter; // @public -export interface CustomChainInput { - adapter: ChainScannerAdapter; +export interface CustomChainInput { + adapter: ChainScannerAdapter; keys: TKeys; source: AsyncIterable; } @@ -266,10 +266,11 @@ export type MatchedAnnouncement = { seq: number; announcement: MatchedStealthCell; } | { - chain: string; + chain: 'custom'; + customChainId: string; timestamp: number; seq: number; - announcement: any; + announcement: unknown; }; // @public (undocumented) @@ -364,7 +365,7 @@ export function scanAll(input: ScanAllInput): AsyncGenerator | any>; + adapters?: Array>; // (undocumented) ckb?: CkbChainInput; // (undocumented) @@ -669,10 +670,10 @@ export abstract class WraithWalletError extends WraithError { // Warnings were encountered during analysis: // -// dist/unified-B4T6tz3t.d.ts:166:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_2" needs to be exported by the entry point index.d.ts -// dist/unified-B4T6tz3t.d.ts:171:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement$1" needs to be exported by the entry point index.d.ts -// dist/unified-B4T6tz3t.d.ts:176:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_3" needs to be exported by the entry point index.d.ts -// dist/unified-B4T6tz3t.d.ts:181:5 - (ae-forgotten-export) The symbol "MatchedStealthCell" needs to be exported by the entry point index.d.ts +// dist/unified-BQ_-bZ0t.d.ts:166:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_2" needs to be exported by the entry point index.d.ts +// dist/unified-BQ_-bZ0t.d.ts:171:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement$1" needs to be exported by the entry point index.d.ts +// dist/unified-BQ_-bZ0t.d.ts:176:5 - (ae-forgotten-export) The symbol "MatchedAnnouncement_3" needs to be exported by the entry point index.d.ts +// dist/unified-BQ_-bZ0t.d.ts:181:5 - (ae-forgotten-export) The symbol "MatchedStealthCell" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package)