From 6548adfd9523cc9aec40cdfbfee4f6d20a84efd2 Mon Sep 17 00:00:00 2001
From: Alejo Acosta <alejoacos@gmail.com>
Date: Mon, 28 Oct 2024 14:39:33 -0300
Subject: [PATCH 1/2] export address info interfaces

---
 src/quais.ts              | 2 ++
 src/wallet/index.ts       | 4 ++--
 src/wallet/qi-hdwallet.ts | 2 +-
 3 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/quais.ts b/src/quais.ts
index d5ddcce7..a7d0a14c 100644
--- a/src/quais.ts
+++ b/src/quais.ts
@@ -218,6 +218,8 @@ export {
     encryptKeystoreJsonSync,
     SerializedHDWallet,
     SerializedQiHDWallet,
+    QiAddressInfo,
+    NeuteredAddressInfo,
 } from './wallet/index.js';
 
 // WORDLIST
diff --git a/src/wallet/index.ts b/src/wallet/index.ts
index 85043b2d..dec13e6d 100644
--- a/src/wallet/index.ts
+++ b/src/wallet/index.ts
@@ -13,7 +13,7 @@
 
 export { BaseWallet } from './base-wallet.js';
 
-export type { SerializedHDWallet } from './hdwallet.js';
+export type { SerializedHDWallet, NeuteredAddressInfo } from './hdwallet.js';
 
 export { QuaiHDWallet } from './quai-hdwallet.js';
 
@@ -31,6 +31,6 @@ export { Wallet } from './wallet.js';
 
 export type { KeystoreAccount, EncryptOptions } from './json-keystore.js';
 
-export { QiHDWallet, SerializedQiHDWallet } from './qi-hdwallet.js';
+export { QiHDWallet, SerializedQiHDWallet, QiAddressInfo } from './qi-hdwallet.js';
 
 export { HDNodeVoidWallet, HDNodeWallet } from './hdnodewallet.js';
diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts
index 46f20253..14eb915a 100644
--- a/src/wallet/qi-hdwallet.ts
+++ b/src/wallet/qi-hdwallet.ts
@@ -62,7 +62,7 @@ type DerivationPath = 'BIP44:external' | 'BIP44:change' | string; // string for
  *
  * @extends NeuteredAddressInfo
  */
-interface QiAddressInfo extends NeuteredAddressInfo {
+export interface QiAddressInfo extends NeuteredAddressInfo {
     status: AddressStatus;
     derivationPath: DerivationPath;
 }

From 8934d8b52b7655ca64adfbf991118e4d3fef7bf1 Mon Sep 17 00:00:00 2001
From: Alejo Acosta <alejoacos@gmail.com>
Date: Mon, 28 Oct 2024 17:39:03 -0300
Subject: [PATCH 2/2] add unit test for Qi address derivation

---
 ...qihdwallet-address-derivation.unit.test.ts | 113 ++++++++++++++++++
 testcases/qi-address-derivation.json.gz       | Bin 0 -> 2941 bytes
 2 files changed, 113 insertions(+)
 create mode 100644 src/_tests/unit/qihdwallet-address-derivation.unit.test.ts
 create mode 100644 testcases/qi-address-derivation.json.gz

diff --git a/src/_tests/unit/qihdwallet-address-derivation.unit.test.ts b/src/_tests/unit/qihdwallet-address-derivation.unit.test.ts
new file mode 100644
index 00000000..5060bad9
--- /dev/null
+++ b/src/_tests/unit/qihdwallet-address-derivation.unit.test.ts
@@ -0,0 +1,113 @@
+import assert from 'assert';
+import { loadTests } from '../utils.js';
+import { Mnemonic, QiHDWallet, Zone, QiAddressInfo } from '../../index.js';
+
+interface TestCaseQiAddressDerivation {
+    mnemonic: string;
+    externalAddresses: Array<{
+        zone: string;
+        addresses: Array<QiAddressInfo>;
+    }>;
+    changeAddresses: Array<{
+        zone: string;
+        addresses: Array<QiAddressInfo>;
+    }>;
+    paymentCodeAddresses: {
+        bobMnemonic: string;
+        sendAddresses: Array<{
+            zone: string;
+            addresses: Array<QiAddressInfo>;
+        }>;
+        receiveAddresses: Array<{
+            zone: string;
+            addresses: Array<QiAddressInfo>;
+        }>;
+    };
+}
+
+describe('QiHDWallet Address Derivation', function () {
+    this.timeout(2 * 60 * 1000);
+    const tests = loadTests<TestCaseQiAddressDerivation>('qi-address-derivation');
+
+    for (const test of tests) {
+        it('derives external addresses correctly', function () {
+            const mnemonic = Mnemonic.fromPhrase(test.mnemonic);
+            const qiWallet = QiHDWallet.fromMnemonic(mnemonic);
+
+            for (const externalAddressesInfo of test.externalAddresses) {
+                const zone = externalAddressesInfo.zone as Zone;
+                for (const expectedAddressInfo of externalAddressesInfo.addresses) {
+                    const derivedAddressInfo = qiWallet.getNextAddressSync(0, zone);
+                    assert.deepEqual(
+                        derivedAddressInfo,
+                        expectedAddressInfo,
+                        `External address mismatch for zone ${zone}, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`,
+                    );
+                }
+            }
+        });
+
+        it('derives change addresses correctly', function () {
+            const mnemonic = Mnemonic.fromPhrase(test.mnemonic);
+            const qiWallet = QiHDWallet.fromMnemonic(mnemonic);
+
+            for (const changeAddressesInfo of test.changeAddresses) {
+                const zone = changeAddressesInfo.zone as Zone;
+                for (const expectedAddressInfo of changeAddressesInfo.addresses) {
+                    const derivedAddressInfo = qiWallet.getNextChangeAddressSync(0, zone);
+                    assert.deepEqual(
+                        derivedAddressInfo,
+                        expectedAddressInfo,
+                        `Change address mismatch for zone ${zone}, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`,
+                    );
+                }
+            }
+        });
+
+        it('derives payment code send addresses correctly', function () {
+            const mnemonic = Mnemonic.fromPhrase(test.mnemonic);
+            const qiWallet = QiHDWallet.fromMnemonic(mnemonic);
+
+            const bobMnemonic = Mnemonic.fromPhrase(test.paymentCodeAddresses.bobMnemonic);
+            const bobQiWallet = QiHDWallet.fromMnemonic(bobMnemonic);
+            const bobPaymentCode = bobQiWallet.getPaymentCode(0);
+
+            qiWallet.openChannel(bobPaymentCode);
+
+            for (const sendAddressesInfo of test.paymentCodeAddresses.sendAddresses) {
+                const zone = sendAddressesInfo.zone as Zone;
+                for (const expectedAddressInfo of sendAddressesInfo.addresses) {
+                    const derivedAddressInfo = qiWallet.getNextSendAddress(bobPaymentCode, zone);
+                    assert.deepEqual(
+                        derivedAddressInfo,
+                        expectedAddressInfo,
+                        `Payment code send address mismatch, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`,
+                    );
+                }
+            }
+        });
+
+        it('derives payment code receive addresses correctly', function () {
+            const mnemonic = Mnemonic.fromPhrase(test.mnemonic);
+            const qiWallet = QiHDWallet.fromMnemonic(mnemonic);
+
+            const bobMnemonic = Mnemonic.fromPhrase(test.paymentCodeAddresses.bobMnemonic);
+            const bobQiWallet = QiHDWallet.fromMnemonic(bobMnemonic);
+            const bobPaymentCode = bobQiWallet.getPaymentCode(0);
+
+            qiWallet.openChannel(bobPaymentCode);
+
+            for (const receiveAddressesInfo of test.paymentCodeAddresses.receiveAddresses) {
+                const zone = receiveAddressesInfo.zone as Zone;
+                for (const expectedAddressInfo of receiveAddressesInfo.addresses) {
+                    const derivedAddressInfo = qiWallet.getNextReceiveAddress(bobPaymentCode, zone);
+                    assert.deepEqual(
+                        derivedAddressInfo,
+                        expectedAddressInfo,
+                        `Payment code receive address mismatch, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`,
+                    );
+                }
+            }
+        });
+    }
+});
diff --git a/testcases/qi-address-derivation.json.gz b/testcases/qi-address-derivation.json.gz
new file mode 100644
index 0000000000000000000000000000000000000000..023247746e817e8a6189d2b6501e43bf614c46a9
GIT binary patch
literal 2941
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

literal 0
HcmV?d00001