diff --git a/examples/transactions/send-qi-paymentcode.js b/examples/transactions/send-qi-paymentcode.js deleted file mode 100644 index ae7b93fb..00000000 --- a/examples/transactions/send-qi-paymentcode.js +++ /dev/null @@ -1,112 +0,0 @@ -const quais = require('../../lib/commonjs/quais'); -require('dotenv').config(); - -async function main() { - // Create provider - const options = {usePathing: false}; - const provider = new quais.JsonRpcProvider(process.env.RPC_URL, undefined, options); - - // Create wallet and connect to provider - console.log(process.env.RPC_URL) - const aliceMnemonic = quais.Mnemonic.fromPhrase(process.env.MNEMONIC); - const aliceWallet = quais.QiHDWallet.fromMnemonic(aliceMnemonic); - aliceWallet.connect(provider); - - // Get Alice payment code - const alicePaymentCode = aliceWallet.getPaymentCode(0); - console.log("Alice payment code: ", alicePaymentCode); - - // Create Bob wallet - const BOB_MNEMONIC = "innocent perfect bus miss prevent night oval position aspect nut angle usage expose grace juice"; - const bobMnemonic = quais.Mnemonic.fromPhrase(BOB_MNEMONIC); - const bobWallet = quais.QiHDWallet.fromMnemonic(bobMnemonic); - bobWallet.connect(provider); - - // Get Bob payment code - const bobPaymentCode = bobWallet.getPaymentCode(0); - console.log("Bob payment code: ", bobPaymentCode); - - // Open channel - aliceWallet.openChannel(bobPaymentCode); - bobWallet.openChannel(alicePaymentCode); - - // Scan Alice wallet - console.log("...scanning alice wallet"); - await aliceWallet.scan(quais.Zone.Cyprus1); - - // log alice change wallet addresses - console.log("Alice change wallet addresses: ", aliceWallet.getChangeAddressesForZone(quais.Zone.Cyprus1).map(a => a.address)); - // log alice external wallet addresses - console.log("Alice external wallet addresses: ", aliceWallet.getAddressesForZone(quais.Zone.Cyprus1).map(a => a.address)); - - // Scan Bob wallet - console.log("...scanning bob wallet"); - await bobWallet.scan(quais.Zone.Cyprus1); - - // Get Alice initial balance - console.log("...getting alice initial balance"); - const aliceInitialBalance = await aliceWallet.getBalanceForZone(quais.Zone.Cyprus1); - console.log("Alice initial balance: ", aliceInitialBalance); - - // Get Bob initial balance - console.log("...getting bob initial balance"); - const bobInitialBalance = await bobWallet.getBalanceForZone(quais.Zone.Cyprus1); - console.log("Bob initial balance: ", bobInitialBalance); - - // Send Qi - console.log("...sending qi to Bob"); - const amountToSendToBob = 25000; - const tx = await aliceWallet.sendTransaction(bobPaymentCode, amountToSendToBob, quais.Zone.Cyprus1, quais.Zone.Cyprus1); - console.log("... Alice transaction sent. Waiting for receipt..."); - - // Wait for tx to be mined - const txReceipt = await tx.wait(); - console.log("Alice's transaction receipt: ", txReceipt); - - // Sync wallets - console.log("...syncing wallets"); - await aliceWallet.sync(quais.Zone.Cyprus1); - await bobWallet.sync(quais.Zone.Cyprus1); - - // Get Alice updated balance - console.log("...getting alice updated balance"); - const aliceUpdatedBalance = await aliceWallet.getBalanceForZone(quais.Zone.Cyprus1); - console.log("Alice updated balance: ", aliceUpdatedBalance); - - // Get Bob updated balance - console.log("...getting bob updated balance"); - const bobUpdatedBalance = await bobWallet.getBalanceForZone(quais.Zone.Cyprus1); - console.log("Bob updated balance: ", bobUpdatedBalance); - - // Bob sends Qi back to Alice - console.log("...sending qi back to Alice"); - const amountToSendToAlice = 10000; - const tx2 = await bobWallet.sendTransaction(alicePaymentCode, amountToSendToAlice, quais.Zone.Cyprus1, quais.Zone.Cyprus1); - console.log("... Bob sent transaction. Waiting for receipt..."); - - // Wait for tx2 to be mined - const tx2Receipt = await tx2.wait(); - console.log("Bob's transaction receipt: ", tx2Receipt); - - // Sync wallets - await aliceWallet.sync(quais.Zone.Cyprus1); - await bobWallet.sync(quais.Zone.Cyprus1); - - // Get Alice updated balance - console.log("...getting alice updated balance"); - const aliceUpdatedBalance2 = await aliceWallet.getBalanceForZone(quais.Zone.Cyprus1); - console.log("Alice updated balance: ", aliceUpdatedBalance2); - - // Get Bob updated balance - console.log("...getting bob updated balance"); - const bobUpdatedBalance2 = await bobWallet.getBalanceForZone(quais.Zone.Cyprus1); - console.log("Bob updated balance: ", bobUpdatedBalance2); - -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index fa6a01a1..762847cc 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -694,7 +694,7 @@ export class QiHDWallet extends AbstractHDWallet { const fee = BigInt(1000); // temporary hardcode fee to 1 Qi const selection = aggregateCoinSelector.performSelection({ fee, maxDenomination: 6, includeLocked: false }); - const sendAddressesInfo = this._getUnusedBIP44Addresses(1, 0, 'BIP44:external', zone); + const sendAddressesInfo = this.getUnusedBIP44Addresses(1, 0, 'BIP44:external', zone); const sendAddresses = sendAddressesInfo.map((addressInfo) => addressInfo.address); const changeAddresses: string[] = []; const inputPubKeys = selection.inputs.map((input) => { @@ -773,50 +773,8 @@ export class QiHDWallet extends AbstractHDWallet { // 3. Generate as many unused addresses as required to populate the spend outputs const sendAddresses = await getDestinationAddresses(selection.spendOutputs.length); - const getChangeAddressesForOutputs = async (count: number): Promise => { - const currentChangeAddresses = this._addressesMap.get('BIP44:change') || []; - const outputChangeAddresses: QiAddressInfo[] = []; - - for (let i = 0; i < currentChangeAddresses.length; i++) { - if (currentChangeAddresses[i].status === AddressStatus.UNUSED) { - outputChangeAddresses.push(currentChangeAddresses[i]); - } - - if (outputChangeAddresses.length === count) break; - } - - // Generate the remaining number of change addresses if needed - const remainingAddressesNeeded = count - outputChangeAddresses.length; - if (remainingAddressesNeeded > 0) { - outputChangeAddresses.push( - ...Array(remainingAddressesNeeded) - .fill(0) - .map(() => this.getNextChangeAddressSync(0, originZone)), - ); - } - - // Combine the existing change addresses with the newly generated addresses and ensure they are unique and sorted by index - const mergedChangeAddresses = [ - // Not updated last synced block because we are not certain of the success of the transaction - // so we will want to get deltas from last **checked** block - ...outputChangeAddresses.map((address) => ({ - ...address, - status: AddressStatus.ATTEMPTED_USE, - })), - ...currentChangeAddresses, - ]; - const sortedAndFilteredChangeAddresses = mergedChangeAddresses - .filter((address, index, self) => self.findIndex((t) => t.address === address.address) === index) - .sort((a, b) => a.index - b.index); - - // Update the _addressesMap with the modified change addresses and statuses - this._addressesMap.set('BIP44:change', sortedAndFilteredChangeAddresses); - - return outputChangeAddresses.map((address) => address.address); - }; - // 4. Get change addresses - const changeAddresses = await getChangeAddressesForOutputs(selection.changeOutputs.length); + const changeAddresses = await this.getChangeAddressesForOutputs(selection.changeOutputs.length, originZone); // 5. Create the transaction and sign it using the signTransaction method let inputPubKeys = selection.inputs.map((input) => this.locateAddressInfo(input.address)?.pubKey); @@ -843,7 +801,7 @@ export class QiHDWallet extends AbstractHDWallet { const changeAddressesNeeded = selection.changeOutputs.length - changeAddresses.length; if (changeAddressesNeeded > 0) { // Need more change addresses - const newChangeAddresses = await getChangeAddressesForOutputs(changeAddressesNeeded); + const newChangeAddresses = await this.getChangeAddressesForOutputs(changeAddressesNeeded, originZone); changeAddresses.push(...newChangeAddresses); } else if (changeAddressesNeeded < 0) { // Have extra change addresses, remove the excess @@ -990,6 +948,37 @@ export class QiHDWallet extends AbstractHDWallet { }; } + /** + * Gets a set of unused change addresses for transaction outputs and updates their status in the wallet. This method + * retrieves unused BIP44 change addresses, marks them as attempted use, and maintains the wallet's address mapping + * state. + * + * @private + * @param {number} count - The number of change addresses needed + * @param {Zone} zone - The zone to get change addresses from + * @param {number} [account=0] - The account index to use (defaults to 0). Default is `0` + * @returns {Promise} A promise that resolves to an array of change addresses + */ + private async getChangeAddressesForOutputs(count: number, zone: Zone, account: number = 0): Promise { + // Get unused change addresses using existing helper + const unusedAddresses = this.getUnusedBIP44Addresses(count, account, 'BIP44:change', zone); + + // Update address statuses in wallet + const currentAddresses = this._addressesMap.get('BIP44:change') || []; + const updatedAddresses = [ + // Mark selected addresses as attempted use + ...unusedAddresses.map((addr) => ({ ...addr, status: AddressStatus.ATTEMPTED_USE })), + // Keep other existing addresses unchanged + ...currentAddresses.filter((addr) => !unusedAddresses.some((unused) => unused.address === addr.address)), + ].sort((a, b) => a.index - b.index); + + // Update wallet's address map + this._addressesMap.set('BIP44:change', updatedAddresses); + + // Return just the addresses + return unusedAddresses.map((addr) => addr.address); + } + /** * Gets a set of unused BIP44 addresses from the specified derivation path. It first checks if there are any unused * addresses available in the _addressesMap and uses those if possible. If there are not enough unused addresses, it @@ -1000,7 +989,7 @@ export class QiHDWallet extends AbstractHDWallet { * @param zone - The zone to get addresses from. * @returns An array of addresses. */ - private _getUnusedBIP44Addresses( + private getUnusedBIP44Addresses( amount: number, account: number, path: DerivationPath,