aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-11 21:53:28 -0700
committerAdam Soltys <[email protected]>2026-09-11 21:53:28 -0700
commit8beb1c17df8d1c96a43803c0d87a5c5f1ac2494e (patch)
tree9204b5550c8e914e642ca5533db45202fc5c579b /tools
parent207cb4451a07417baf63b2586e6759cf8b471482 (diff)
downloadcoinosv3-8beb1c17df8d1c96a43803c0d87a5c5f1ac2494e.tar.gz
coinosv3-8beb1c17df8d1c96a43803c0d87a5c5f1ac2494e.zip
Include mining fees in the Spending deposit preview
Diffstat (limited to 'tools')
-rw-r--r--tools/ark/board-fee-preview-test.js55
1 files changed, 55 insertions, 0 deletions
diff --git a/tools/ark/board-fee-preview-test.js b/tools/ark/board-fee-preview-test.js
new file mode 100644
index 0000000..0ff96cd
--- /dev/null
+++ b/tools/ark/board-fee-preview-test.js
@@ -0,0 +1,55 @@
+// Fee previews use the real wallet transaction builder, without signing or
+// broadcasting anything. Run: bun tools/ark/board-fee-preview-test.js
+import assert from 'node:assert/strict';
+import { generateMnemonic } from '@scure/bip39';
+import { wordlist } from '@scure/bip39/wordlists/english';
+import { Wallet } from '../../src/wallet.js';
+import { previewBoardFunding } from '../../src/features/ark.js';
+import { p2trAddress } from '../../src/ark/board.js';
+
+const wallet = new Wallet();
+wallet.load({ mnemonic: generateMnemonic(wordlist), netName: 'mainnet', offline: true });
+wallet.utxos = [{ txid: 'a'.repeat(64), vout: 0, value: 300000, chain: 0, index: 0, confirmed: true }];
+wallet.feeRates = { halfHourFee: 1 };
+const ark = { info: { network: 'bitcoin', boardFees: {} }, _key: () => ({ pubkey: wallet.derive(0, 0).pubkey }) };
+// A different P2TR program stands in for the final funding address. Its
+// output size matches the preview while its key is unrelated to the probe.
+const fundingAddress = p2trAddress(wallet.derive(0, 1).pubkey.slice(1), 'bc');
+const actualDraft = sats => wallet.buildTx({ recipients: [{ address: fundingAddress, amount: sats }],
+ feeRate: wallet.feeRates.halfHourFee, noSort: true });
+
+const max = previewBoardFunding(wallet, ark, 0, true);
+assert.deepEqual(max, { fundingSat: 299878, chainFeeSat: 122, serviceFeeSat: 0, feeSat: 122, netSat: 299878 });
+const maxPreview = previewBoardFunding(wallet, ark, max.fundingSat);
+assert.deepEqual(maxPreview, max);
+assert.equal(maxPreview.chainFeeSat, actualDraft(max.fundingSat).fee);
+assert.equal(maxPreview.netSat + maxPreview.feeSat, 300000);
+console.log('✓ 300,000-sat Max shows a 122-sat fee and 299,878 sats arriving');
+
+const partial = previewBoardFunding(wallet, ark, 100000);
+assert.equal(partial.netSat, 100000, 'the mining fee is paid from Savings, not deducted again from the deposit');
+assert.equal(partial.chainFeeSat, actualDraft(100000).fee);
+assert.ok(partial.chainFeeSat > max.chainFeeSat, 'partial deposits include the change output fee');
+console.log('✓ partial deposits include the change output and preserve the correct received amount');
+
+ark.info.boardFees = { baseFeeSat: 25, ppm: 1000 };
+const service = previewBoardFunding(wallet, ark, max.fundingSat);
+assert.equal(service.serviceFeeSat, 325);
+assert.equal(service.feeSat, 447);
+assert.equal(service.netSat, 299553);
+assert.equal(service.netSat + service.feeSat, 300000);
+console.log('✓ nonzero service fees are included in the total and deducted exactly once');
+
+ark.info.boardFees = {};
+wallet.utxos = [
+ { txid: 'a'.repeat(64), vout: 0, value: 200000, chain: 0, index: 0, confirmed: true },
+ { txid: 'b'.repeat(64), vout: 0, value: 100000, chain: 0, index: 1, confirmed: true },
+];
+wallet.feeRates.halfHourFee = 1.6;
+const multi = previewBoardFunding(wallet, ark, 0, true);
+assert.equal(multi.chainFeeSat, actualDraft(multi.fundingSat).fee);
+assert.equal(multi.netSat + multi.feeSat, 300000);
+assert.ok(multi.feeSat > max.feeSat);
+assert.throws(() => previewBoardFunding(wallet, ark, 300000), /Insufficient funds/);
+console.log('✓ multiple inputs, fee-rate rounding, and insufficient funds follow the actual funding transaction');
+process.exit(0);