@arkade-os/sdk
Version:
Bitcoin wallet SDK with Taproot and Ark integration
172 lines (171 loc) • 6.57 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletRepositoryImpl = void 0;
const base_1 = require("@scure/base");
const btc_signer_1 = require("@scure/btc-signer");
const getVtxosStorageKey = (address) => `vtxos:${address}`;
const getUtxosStorageKey = (address) => `utxos:${address}`;
const getTransactionsStorageKey = (address) => `tx:${address}`;
const walletStateStorageKey = "wallet:state";
// Utility functions for (de)serializing complex structures
const toHex = (b) => (b ? base_1.hex.encode(b) : undefined);
const fromHex = (h) => h ? base_1.hex.decode(h) : undefined;
const serializeTapLeaf = ([cb, s]) => ({
cb: base_1.hex.encode(btc_signer_1.TaprootControlBlock.encode(cb)),
s: base_1.hex.encode(s),
});
const serializeVtxo = (v) => ({
...v,
tapTree: toHex(v.tapTree),
forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
extraWitness: v.extraWitness?.map(toHex),
});
const serializeUtxo = (u) => ({
...u,
tapTree: toHex(u.tapTree),
forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
extraWitness: u.extraWitness?.map(toHex),
});
const deserializeTapLeaf = (t) => {
const cb = btc_signer_1.TaprootControlBlock.decode(fromHex(t.cb));
const s = fromHex(t.s);
return [cb, s];
};
const deserializeVtxo = (o) => ({
...o,
createdAt: new Date(o.createdAt),
tapTree: fromHex(o.tapTree),
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
extraWitness: o.extraWitness?.map(fromHex),
});
const deserializeUtxo = (o) => ({
...o,
tapTree: fromHex(o.tapTree),
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
extraWitness: o.extraWitness?.map(fromHex),
});
class WalletRepositoryImpl {
constructor(storage) {
this.storage = storage;
}
async getVtxos(address) {
const stored = await this.storage.getItem(getVtxosStorageKey(address));
if (!stored)
return [];
try {
const parsed = JSON.parse(stored);
return parsed.map(deserializeVtxo);
}
catch (error) {
console.error(`Failed to parse VTXOs for address ${address}:`, error);
return [];
}
}
async saveVtxos(address, vtxos) {
const storedVtxos = await this.getVtxos(address);
for (const vtxo of vtxos) {
const existing = storedVtxos.findIndex((v) => v.txid === vtxo.txid && v.vout === vtxo.vout);
if (existing !== -1) {
storedVtxos[existing] = vtxo;
}
else {
storedVtxos.push(vtxo);
}
}
await this.storage.setItem(getVtxosStorageKey(address), JSON.stringify(storedVtxos.map(serializeVtxo)));
}
async removeVtxo(address, vtxoId) {
const vtxos = await this.getVtxos(address);
const [txid, vout] = vtxoId.split(":");
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
await this.storage.setItem(getVtxosStorageKey(address), JSON.stringify(filtered.map(serializeVtxo)));
}
async clearVtxos(address) {
await this.storage.removeItem(getVtxosStorageKey(address));
}
async getUtxos(address) {
const stored = await this.storage.getItem(getUtxosStorageKey(address));
if (!stored)
return [];
try {
const parsed = JSON.parse(stored);
return parsed.map(deserializeUtxo);
}
catch (error) {
console.error(`Failed to parse UTXOs for address ${address}:`, error);
return [];
}
}
async saveUtxos(address, utxos) {
const storedUtxos = await this.getUtxos(address);
utxos.forEach((utxo) => {
const existing = storedUtxos.findIndex((u) => u.txid === utxo.txid && u.vout === utxo.vout);
if (existing !== -1) {
storedUtxos[existing] = utxo;
}
else {
storedUtxos.push(utxo);
}
});
await this.storage.setItem(getUtxosStorageKey(address), JSON.stringify(storedUtxos.map(serializeUtxo)));
}
async removeUtxo(address, utxoId) {
const utxos = await this.getUtxos(address);
const [txid, vout] = utxoId.split(":");
const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
await this.storage.setItem(getUtxosStorageKey(address), JSON.stringify(filtered.map(serializeUtxo)));
}
async clearUtxos(address) {
await this.storage.removeItem(getUtxosStorageKey(address));
}
async getTransactionHistory(address) {
const storageKey = getTransactionsStorageKey(address);
const stored = await this.storage.getItem(storageKey);
if (!stored)
return [];
try {
return JSON.parse(stored);
}
catch (error) {
console.error(`Failed to parse transactions for address ${address}:`, error);
return [];
}
}
async saveTransactions(address, txs) {
const storedTransactions = await this.getTransactionHistory(address);
for (const tx of txs) {
const existing = storedTransactions.findIndex((t) => t.key === tx.key);
if (existing !== -1) {
storedTransactions[existing] = tx;
}
else {
storedTransactions.push(tx);
}
}
await this.storage.setItem(getTransactionsStorageKey(address), JSON.stringify(storedTransactions));
}
async clearTransactions(address) {
await this.storage.removeItem(getTransactionsStorageKey(address));
}
async getWalletState() {
const stored = await this.storage.getItem(walletStateStorageKey);
if (!stored)
return null;
try {
const state = JSON.parse(stored);
return state;
}
catch (error) {
console.error("Failed to parse wallet state:", error);
return null;
}
}
async saveWalletState(state) {
await this.storage.setItem(walletStateStorageKey, JSON.stringify(state));
}
}
exports.WalletRepositoryImpl = WalletRepositoryImpl;