aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark/manager.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-10 07:09:45 -0700
committerAdam Soltys <[email protected]>2026-09-10 07:09:45 -0700
commit6113cf567fa0830233d5c3d524361f24a3f78caa (patch)
treef5ce3bf5e45602547d983797b57b94c8d7ae7dc5 /src/ark/manager.js
parent435e9886620deff3d66286e3b85e97ed8f5fae53 (diff)
downloadcoinosv3-6113cf567fa0830233d5c3d524361f24a3f78caa.tar.gz
coinosv3-6113cf567fa0830233d5c3d524361f24a3f78caa.zip
Ark: persist vtxo bytes as base64 (a third smaller than hex); hex still read, hex still on the wire.
The live coins' signed bytes are most of a deep wallet's state — 560KB of hex, which localStorage then holds as UTF-16. vtxoBytesToStr/FromStr in proto.js persist base64 and read either form, _addVtxo writes base64, and pruneArkState converts hex to base64 on every save, so an existing state migrates itself the first time it saves. Measured on a live deep wallet: 904KB → 665KB (-26%), all 41 coins decoding identically. The sync wire (slimArkForSync) keeps sending hex for now: a device still on an older build would otherwise fail to decode every coin merged from a newer one. Flip it once pre-2026-09-10 builds are gone. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
Diffstat (limited to 'src/ark/manager.js')
-rw-r--r--src/ark/manager.js22
1 files changed, 12 insertions, 10 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index d40a65b..c0e972b 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -21,8 +21,7 @@ import {
getArkInfo, handshake, encodeAddress, decodeAddress, blindMailboxId,
readMailbox, decodeVtxo, arkIdFromServerPubkey, GrpcError,
grpcStream, decodeMailboxMessage, mailboxRequestBytes,
- getVtxoStatus, VTXO_STATE_SPENT,
-} from './proto.js';
+ getVtxoStatus, VTXO_STATE_SPENT, vtxoBytesToStr, vtxoBytesFromStr, vtxoBytesNormalize } from './proto.js';
import {
buildArkoorSend, cosignWithServer, cosignPackageWithServer, buildAllSignedVtxos,
registerVtxoTransactions, postArkoorMessage, txid,
@@ -101,9 +100,12 @@ export function pruneArkState(state, now = Date.now()) {
for (const id of a.htlcVtxoIds || []) name(id, ts);
}
for (const v of state.vtxos || []) {
- if (v.state !== 'spent' || !v.bytes || inFlight.has(v.id)) continue;
- const ts = lastNamed.get(v.id);
- if (ts && now - ts > SPENT_BYTES_AFTER) delete v.bytes;
+ if (!v.bytes) continue;
+ if (v.state === 'spent' && !inFlight.has(v.id)) {
+ const ts = lastNamed.get(v.id);
+ if (ts && now - ts > SPENT_BYTES_AFTER) { delete v.bytes; continue; }
+ }
+ v.bytes = vtxoBytesNormalize(v.bytes); // hex from older builds → base64
}
for (const a of actions) {
if (a.step !== 'done') continue;
@@ -198,7 +200,7 @@ export class ArkManager {
// spendable here and must not be shown.
const coinOwner = (() => {
const v = (this.state.vtxos || []).find((x) => x.bytes);
- try { return v ? decodeVtxo(hex.decode(v.bytes)).serverPubkey : null; } catch { return null; }
+ try { return v ? decodeVtxo(vtxoBytesFromStr(v.bytes)).serverPubkey : null; } catch { return null; }
})();
if ((this.state.serverPubkey && this.state.serverPubkey !== this.info.serverPubkey)
|| (coinOwner && coinOwner !== this.info.serverPubkey)) {
@@ -249,7 +251,7 @@ export class ArkManager {
_addVtxo(decoded, bytes, keyIndex, state = 'spendable') {
if (this._vtxo(decoded.id)) return false;
this.state.vtxos.push({
- id: decoded.id, bytes: hex.encode(bytes), keyIndex,
+ id: decoded.id, bytes: vtxoBytesToStr(bytes), keyIndex,
amountSat: decoded.amountSat, expiryHeight: decoded.expiryHeight, state,
});
return true;
@@ -259,7 +261,7 @@ export class ArkManager {
// fee estimates decode every coin on every render without this.
const hit = this._decodeCache && this._decodeCache.get(v.id);
if (hit) return hit;
- const d = decodeVtxo(hex.decode(v.bytes));
+ const d = decodeVtxo(vtxoBytesFromStr(v.bytes));
(this._decodeCache ||= new Map()).set(v.id, d);
if (this._decodeCache.size > 200) this._decodeCache.delete(this._decodeCache.keys().next().value);
return d;
@@ -667,7 +669,7 @@ export class ArkManager {
const keys = inputRecs.map((v) => this._keyForVtxo(v));
const spk = hex.decode(action.spkHex);
const inputIdRaws = decoded.map((d) => d.point.raw);
- await registerVtxoTransactions(this.arkUrl, inputRecs.map((v) => hex.decode(v.bytes)));
+ await registerVtxoTransactions(this.arkUrl, inputRecs.map((v) => vtxoBytesFromStr(v.bytes)));
const attestations = keys.map((k) =>
offboardAttestation({ netSat: action.netSat, spk, inputIdRaws }, k.privkey));
const { txBytes, serverNonces } = await prepareOffboard(this.arkUrl, {
@@ -1772,7 +1774,7 @@ export class ArkManager {
const inputRecs = action.inputIds.map((id) => this._vtxo(id));
if (action.step === 'created') {
- const inputBytes = inputRecs.map((v) => hex.decode(v.bytes));
+ const inputBytes = inputRecs.map((v) => vtxoBytesFromStr(v.bytes));
await registerVtxoTransactions(this.arkUrl, inputBytes);
let unlockHash;
try {