aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ark.ts11
-rw-r--r--lib/arkRepository.ts143
2 files changed, 153 insertions, 1 deletions
diff --git a/lib/ark.ts b/lib/ark.ts
index 3fc33ef6..5f695f6e 100644
--- a/lib/ark.ts
+++ b/lib/ark.ts
@@ -3,6 +3,7 @@ if (!globalThis.EventSource) globalThis.EventSource = EventSource as any;
import config from "$config";
import { SingleKey, Wallet, Ramps, RestArkProvider, VtxoManager } from "@arkade-os/sdk";
+import { ValkeyWalletRepository, ValkeyContractRepository } from "$lib/arkRepository";
import { db } from "$lib/db";
import { l, warn } from "$lib/logging";
@@ -21,7 +22,15 @@ const getWallet = async () => {
const { arkPrivateKey, arkServerUrl, esploraUrl } = config.ark;
const identity = SingleKey.fromHex(arkPrivateKey);
- wallet = await Wallet.create({ identity, arkServerUrl, esploraUrl });
+ wallet = await Wallet.create({
+ identity,
+ arkServerUrl,
+ esploraUrl,
+ storage: {
+ walletRepository: new ValkeyWalletRepository(),
+ contractRepository: new ValkeyContractRepository(),
+ },
+ });
return wallet;
};
diff --git a/lib/arkRepository.ts b/lib/arkRepository.ts
new file mode 100644
index 00000000..e1b8291d
--- /dev/null
+++ b/lib/arkRepository.ts
@@ -0,0 +1,143 @@
+import type {
+ WalletRepository,
+ WalletState,
+ ContractRepository,
+ ContractFilter,
+ ExtendedVirtualCoin,
+ ExtendedCoin,
+ ArkTransaction,
+ Contract,
+} from "@arkade-os/sdk";
+import { db } from "$lib/db";
+
+const PREFIX = "ark:repo";
+
+function mergeByKey<T>(
+ existing: T[],
+ incoming: T[],
+ toKey: (item: T) => string,
+): T[] {
+ const next = new Map<string, T>();
+ for (const item of existing) next.set(toKey(item), item);
+ for (const item of incoming) next.set(toKey(item), item);
+ return Array.from(next.values());
+}
+
+function txKey(tx: ArkTransaction): string {
+ const k = tx.key;
+ return `${k.boardingTxid}:${k.commitmentTxid}:${k.arkTxid}`;
+}
+
+async function getJson<T>(key: string): Promise<T | null> {
+ const raw = await db.get(key);
+ if (!raw) return null;
+ return JSON.parse(String(raw));
+}
+
+async function setJson(key: string, value: any): Promise<void> {
+ await db.set(key, JSON.stringify(value));
+}
+
+export class ValkeyWalletRepository implements WalletRepository {
+ readonly version = 1 as const;
+
+ async getVtxos(address: string): Promise<ExtendedVirtualCoin[]> {
+ return (await getJson(`${PREFIX}:vtxos:${address}`)) ?? [];
+ }
+
+ async saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void> {
+ const existing = await this.getVtxos(address);
+ const merged = mergeByKey(existing, vtxos, (v) => `${v.txid}:${v.vout}`);
+ await setJson(`${PREFIX}:vtxos:${address}`, merged);
+ }
+
+ async deleteVtxos(address: string): Promise<void> {
+ await db.del(`${PREFIX}:vtxos:${address}`);
+ }
+
+ async getUtxos(address: string): Promise<ExtendedCoin[]> {
+ return (await getJson(`${PREFIX}:utxos:${address}`)) ?? [];
+ }
+
+ async saveUtxos(address: string, utxos: ExtendedCoin[]): Promise<void> {
+ const existing = await this.getUtxos(address);
+ const merged = mergeByKey(existing, utxos, (u) => `${u.txid}:${u.vout}`);
+ await setJson(`${PREFIX}:utxos:${address}`, merged);
+ }
+
+ async deleteUtxos(address: string): Promise<void> {
+ await db.del(`${PREFIX}:utxos:${address}`);
+ }
+
+ async getTransactionHistory(address: string): Promise<ArkTransaction[]> {
+ return (await getJson(`${PREFIX}:txs:${address}`)) ?? [];
+ }
+
+ async saveTransactions(address: string, txs: ArkTransaction[]): Promise<void> {
+ const existing = await this.getTransactionHistory(address);
+ const merged = mergeByKey(existing, txs, txKey);
+ await setJson(`${PREFIX}:txs:${address}`, merged);
+ }
+
+ async deleteTransactions(address: string): Promise<void> {
+ await db.del(`${PREFIX}:txs:${address}`);
+ }
+
+ async getWalletState(): Promise<WalletState | null> {
+ return getJson(`${PREFIX}:state`);
+ }
+
+ async saveWalletState(state: WalletState): Promise<void> {
+ await setJson(`${PREFIX}:state`, state);
+ }
+
+ async clear(): Promise<void> {
+ const keys = await db.keys(`${PREFIX}:*`);
+ if (keys.length > 0) await db.del(keys);
+ }
+
+ async [Symbol.asyncDispose](): Promise<void> {}
+}
+
+export class ValkeyContractRepository implements ContractRepository {
+ readonly version = 1 as const;
+
+ async getContracts(filter?: ContractFilter): Promise<Contract[]> {
+ const all: Contract[] = (await getJson(`${PREFIX}:contracts`)) ?? [];
+ if (!filter) return all;
+
+ const matches = <T>(value: T, criterion?: T | T[]) => {
+ if (criterion === undefined) return true;
+ return Array.isArray(criterion)
+ ? criterion.includes(value)
+ : value === criterion;
+ };
+
+ return all.filter(
+ (c) =>
+ matches(c.script, filter.script) &&
+ matches(c.state, filter.state) &&
+ matches(c.type, filter.type),
+ );
+ }
+
+ async saveContract(contract: Contract): Promise<void> {
+ const all: Contract[] = (await getJson(`${PREFIX}:contracts`)) ?? [];
+ const idx = all.findIndex((c) => c.script === contract.script);
+ if (idx >= 0) all[idx] = contract;
+ else all.push(contract);
+ await setJson(`${PREFIX}:contracts`, all);
+ }
+
+ async deleteContract(script: string): Promise<void> {
+ const all: Contract[] = (await getJson(`${PREFIX}:contracts`)) ?? [];
+ const filtered = all.filter((c) => c.script !== script);
+ await setJson(`${PREFIX}:contracts`, filtered);
+ }
+
+ async clear(): Promise<void> {
+ await db.del(`${PREFIX}:contracts`);
+ }
+
+ async [Symbol.asyncDispose](): Promise<void> {}
+}