From b1e3435613ef092d43353d511b632a8de64efc5d Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 18 Feb 2026 07:49:25 -0800 Subject: migrate funds to tb --- lib/migrate.ts | 33 ++++++++++++++++++++ lib/tb.ts | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/migrate.ts b/lib/migrate.ts index 79f9546c..d4bfd61a 100644 --- a/lib/migrate.ts +++ b/lib/migrate.ts @@ -4,6 +4,8 @@ import { l, warn } from "$lib/logging"; import { createBalanceAccount, createCreditAccounts, + createFundAccount, + tbFundCredit, tbSetBalance, tbSetPending, tbSetCredit, @@ -176,4 +178,35 @@ export const migrateAutowithdraw = async () => { return count; }; +export const migrateFundsToTB = async () => { + const migrated = await db.get("tb:funds-migrated"); + if (migrated) return 0; + + let count = 0; + + for await (const k of scan("fund:*")) { + try { + // Skip sub-keys like fund:name:payments, fund:name:managers, fund:limit + const parts = k.split(":"); + if (parts.length !== 2) continue; + const name = parts[1]; + if (name === "limit") continue; + + const keyType = await db.type(k); + if (keyType !== "string") continue; + + const balance = Number.parseInt((await db.get(k)) || "0"); + await createFundAccount(name); + if (balance > 0) await tbFundCredit(name, balance); + count++; + } catch (e) { + warn("failed to migrate fund", k, e.message); + } + } + + await db.set("tb:funds-migrated", Date.now().toString()); + l(`Migrated ${count} funds to TigerBeetle`); + return count; +}; + export default migrate; diff --git a/lib/tb.ts b/lib/tb.ts index 9a4dc7c8..ae1ac576 100644 --- a/lib/tb.ts +++ b/lib/tb.ts @@ -6,6 +6,7 @@ import { AccountFlags, TransferFlags, } from "tigerbeetle-node"; +import { createHash } from "crypto"; let client: any; @@ -57,6 +58,11 @@ function creditId(uid: string, type: string): bigint { return uuidToBigInt(uid) ^ (ledger << 64n); } +function fundAccountId(name: string): bigint { + const hash = createHash("sha256").update(`fund:${name}`).digest("hex"); + return BigInt(`0x${hash.slice(0, 32)}`) ^ (6n << 64n); +} + function u128(n: bigint): bigint { return n & ((1n << 128n) - 1n); } @@ -591,21 +597,97 @@ export async function tbSetCredit(uid: string, type: string, target: number) { } } -// Debit from a fund:* key (Redis only, no credits) -export async function fundDebit( - fundKey: string, +export async function createFundAccount(name: string) { + const accounts = [ + { + id: u128(fundAccountId(name)), + ledger: LEDGER_SATS, + code: 1, + flags: AccountFlags.debits_must_not_exceed_credits, + debits_pending: 0n, + debits_posted: 0n, + credits_pending: 0n, + credits_posted: 0n, + user_data_128: 0n, + user_data_64: 0n, + user_data_32: 0, + reserved: 0, + timestamp: 0n, + }, + ]; + + const results = await client.createAccounts(accounts); + for (const r of results) { + if (r.result !== CreateAccountError.exists) { + warn("TB fund account creation:", r.index, r.result); + } + } +} + +export async function tbFundCredit(name: string, amount: number) { + await createFundAccount(name); + const transfers = [ + { + id: nextTransferId(), + debit_account_id: u128(HOUSE_SATS), + credit_account_id: u128(fundAccountId(name)), + amount: BigInt(amount) * MSATS, + pending_id: 0n, + user_data_128: 0n, + user_data_64: 0n, + user_data_32: 0, + timeout: 0, + ledger: LEDGER_SATS, + code: 1, + flags: 0, + timestamp: 0n, + }, + ]; + + const results = await client.createTransfers(transfers); + for (const r of results) { + warn("TB fund credit transfer error:", r.index, r.result); + } +} + +export async function tbFundDebit( + name: string, amount: number, errMsg: string, ): Promise { - const { db } = await import("$lib/db"); - const bal = Number.parseInt((await db.get(fundKey)) || "0"); - if (bal < amount) { + const transfers = [ + { + id: nextTransferId(), + debit_account_id: u128(fundAccountId(name)), + credit_account_id: u128(HOUSE_SATS), + amount: BigInt(amount) * MSATS, + pending_id: 0n, + user_data_128: 0n, + user_data_64: 0n, + user_data_32: 0, + timeout: 0, + ledger: LEDGER_SATS, + code: 1, + flags: 0, + timestamp: 0n, + }, + ]; + + const results = await client.createTransfers(transfers); + if (results.length > 0) { + const account = await getAccount(fundAccountId(name)); + const bal = accountBalance(account); return { err: `${errMsg} ⚡️${bal} / ${amount}` }; } - await db.decrBy(fundKey, amount); return 0; } +export async function getFundBalance(name: string): Promise { + const account = await getAccount(fundAccountId(name)); + if (!account) return null; + return accountBalance(account); +} + // Migration helper: multiply existing balance by MULTIPLIER to convert sats→microsats // Called by migrateToMicrosats() - adds (balance * 999999) to turn X into X*1000000 // NOTE: This migration should only run ONCE - guarded by tb:microsats flag in migrate.ts -- cgit v1.2.3