lightweight-wallet.test.cjs (6524B)
1 const { test } = require('node:test'); 2 const assert = require('node:assert/strict'); 3 const { readFileSync } = require('node:fs'); 4 const { pathToFileURL } = require('node:url'); 5 6 let core; 7 8 test.before(async () => { 9 const source = readFileSync(require.resolve('../wallet/wallet-core.js'), 'utf8'); 10 core = await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`); 11 }); 12 13 test('migrates the single-wallet record into a named wallet collection', () => { 14 const legacy = { address: 'ab'.repeat(32), ciphertext: 'encrypted' }; 15 const store = core.normalizeWalletStore(null, legacy); 16 17 assert.equal(store.version, 2); 18 assert.equal(store.wallets.length, 1); 19 assert.equal(store.wallets[0].type, 'signing'); 20 assert.equal(store.wallets[0].record, legacy); 21 assert.equal(store.activeId, store.wallets[0].id); 22 }); 23 24 test('adds, selects, and removes multiple wallet types', () => { 25 const signing = { id: 'signing', name: 'Daily', type: 'signing', publicKeyHex: '11'.repeat(32), record: { ciphertext: 'secret' } }; 26 const readonly = { id: 'readonly', name: 'Savings', type: 'readonly', publicKeyHex: '22'.repeat(32) }; 27 let store = core.upsertWallet(core.emptyWalletStore(), signing); 28 store = core.upsertWallet(store, readonly); 29 30 assert.deepEqual(store.wallets.map((wallet) => wallet.name), ['Daily', 'Savings']); 31 assert.equal(store.activeId, 'readonly'); 32 assert.equal('record' in store.wallets[1], false); 33 34 store = core.removeWallet(store, 'readonly'); 35 assert.equal(store.activeId, 'signing'); 36 assert.deepEqual(store.wallets.map((wallet) => wallet.id), ['signing']); 37 }); 38 39 test('refuses to sign with a watch-only wallet', async () => { 40 await assert.rejects( 41 core.buildSignedTransfer({ wallet: { publicKeyHex: '22'.repeat(32) } }), 42 /watch-only wallet cannot sign/, 43 ); 44 }); 45 46 test('explains oversized transactions before submission', () => { 47 const signature = 'aa'.repeat(64); 48 const transaction = { 49 kind: 'transfer', 50 inputs: Array.from({ length: 205 }, (_, index) => ({ 51 outpoint: { txid: 'bb'.repeat(32), index }, 52 owner: 'cc'.repeat(32), 53 signature, 54 })), 55 outputs: [ 56 { address: 'dd'.repeat(32), amount: 1 }, 57 { address: 'cc'.repeat(32), amount: 1 }, 58 ], 59 fee: 1, 60 signature, 61 }; 62 63 assert.throws( 64 () => core.ensureTransactionBodySize(transaction), 65 /205 inputs, 65 KiB; maximum 64 KiB.*smaller amount.*consolidate/s, 66 ); 67 }); 68 69 test('validates a configurable fee rate with a default minimum of one', () => { 70 assert.equal(core.parseFeeRate('1'), 1n); 71 assert.equal(core.parseFeeRate('25'), 25n); 72 assert.throws(() => core.parseFeeRate('0'), /at least 1/); 73 assert.throws(() => core.parseFeeRate('1.5'), /whole number/); 74 }); 75 76 test('uses the selected fee rate when calculating a transfer', () => { 77 const utxos = [{ 78 outpoint: { txid: 'aa'.repeat(32), index: 0 }, 79 output: { address: 'bb'.repeat(32), amount: 1_000_000 }, 80 }]; 81 const owner = 'bb'.repeat(32); 82 const recipient = 'cc'.repeat(32); 83 const standard = core.selectInputs(utxos, 100_000n, 1n, owner, recipient); 84 const priority = core.selectInputs(utxos, 100_000n, 5n, owner, recipient); 85 86 assert.equal(priority.fee, standard.fee * 5n); 87 }); 88 89 test('recognizes hybrid Bech32m addresses without treating them as legacy keys', () => { 90 const address = 'iuna1py9qlrnw6cm3mpz26hwwkww9spaa96zrw2g34gu0p4y3ea5cqhg0qa82x9s'; 91 const decoded = core.decodeVersionedAddress(address, 'iuna'); 92 assert.equal(decoded.version, 1); 93 assert.equal(decoded.payloadHex.length, 64); 94 assert.throws(() => core.decodeAddress(address, 'iuna'), /requires a legacy address/); 95 }); 96 97 test('browser crypto derives the same rotating hybrid address vectors as the node', async () => { 98 const modulePath = require.resolve('../wallet/crypto/iuna_wallet_crypto.js'); 99 const wasmPath = require.resolve('../wallet/crypto/iuna_wallet_crypto_bg.wasm'); 100 const quantum = await import(pathToFileURL(modulePath)); 101 await quantum.default({ module_or_path: readFileSync(wasmPath) }); 102 const addresses = JSON.parse(quantum.derive_external_addresses('hybrid-wallet-seed', 2, 'iuna-mainnet-v1')); 103 assert.deepEqual(addresses, [ 104 { index: 0, address: 'iuna1py9qlrnw6cm3mpz26hwwkww9spaa96zrw2g34gu0p4y3ea5cqhg0qa82x9s' }, 105 { index: 1, address: 'iuna1pkxdcktlzf5tc2p59xns2nccq7rg5zjhwg2zn5r9u73c5j9jxgk8s0e5l4e' }, 106 ]); 107 const rewardAddresses = JSON.parse(quantum.derive_reward_addresses('hybrid-wallet-seed', 2, 'iuna-mainnet-v1')); 108 assert.deepEqual(rewardAddresses, [ 109 { index: 0, address: 'iuna1pvxpc35gavamxw0tvqj3ms82gwtvchh7mdyzqdttx7ktx7pfkgz4qkaq24k' }, 110 { index: 1, address: 'iuna1p6cj9c3ths75p7vnsdx9tgkvu4jlzs8cxpnh2lc4klsu5fvd0cpxq8ypw45' }, 111 ]); 112 113 const built = JSON.parse(quantum.build_transfer(JSON.stringify({ 114 seed: 'hybrid-wallet-seed', 115 chainId: 'iuna-mainnet-candidate', 116 genesisHash: '11'.repeat(32), 117 recipientAddress: addresses[1].address, 118 amount: '1000000', 119 feeRate: '1', 120 changeIndex: 2, 121 utxos: [{ 122 addressIndex: 0, 123 outpoint: { txid: '22'.repeat(32), index: 7 }, 124 output: { amount: 2000000 }, 125 }], 126 }))); 127 assert.equal(built.fee, '4079'); 128 assert.equal(built.envelope.length / 2, 4079); 129 assert.equal(built.transactionId, 'bda748b6ba9fd6550ca47d580f980a1c00d0050a20992750265a62d23b0b590d'); 130 131 const rewardBuilt = JSON.parse(quantum.build_transfer(JSON.stringify({ 132 seed: 'hybrid-wallet-seed', 133 chainId: 'iuna-mainnet-candidate', 134 genesisHash: '11'.repeat(32), 135 recipientAddress: addresses[1].address, 136 amount: '1000000', 137 feeRate: '1', 138 changeIndex: 2, 139 utxos: [{ 140 addressBranch: 'reward', 141 addressIndex: 0, 142 outpoint: { txid: '44'.repeat(32), index: 8 }, 143 output: { amount: 2000000 }, 144 }], 145 }))); 146 assert.equal(rewardBuilt.fee, '4079'); 147 assert.equal(rewardBuilt.inputCount, 1); 148 assert.equal(rewardBuilt.transactionId, '558cee1dfa1425e0b214681445e3d0641a898e15245f80d4524d2b98d762df88'); 149 150 const migration = JSON.parse(quantum.build_migration(JSON.stringify({ 151 seed: 'hybrid-wallet-seed', 152 chainId: 'iuna-mainnet-candidate', 153 genesisHash: '11'.repeat(32), 154 destinationIndex: 2, 155 feeRate: '1', 156 utxos: [{ 157 outpoint: { txid: '33'.repeat(32), index: 4 }, 158 output: { amount: 2000000 }, 159 }], 160 }))); 161 assert.equal(migration.fee, '307'); 162 assert.equal(migration.envelope.length / 2, 307); 163 assert.equal(migration.transactionId, '37539c9d89a391c599b838c3f415e7adc93a36c22cd4fb5fe44a79433f336075'); 164 });