aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-15 16:58:08 -0800
committerAdam Soltys <[email protected]>2026-02-15 16:58:08 -0800
commitba2fbfe2901db35bb0cc688757f7e88dc52ff7b6 (patch)
tree372e2911aeeb0e770a749e37d818a55a9e79cad3
parent4d3377ba1d37147b194c1fb0504edee3c73211a5 (diff)
downloadcoinos-server-ba2fbfe2901db35bb0cc688757f7e88dc52ff7b6.tar.gz
coinos-server-ba2fbfe2901db35bb0cc688757f7e88dc52ff7b6.zip
fix custodial bitcoin sends
-rw-r--r--lib/ark.ts16
-rw-r--r--routes/payments.ts24
2 files changed, 28 insertions, 12 deletions
diff --git a/lib/ark.ts b/lib/ark.ts
index 52f441d7..3522c9cf 100644
--- a/lib/ark.ts
+++ b/lib/ark.ts
@@ -24,6 +24,7 @@ const getWallet = async () => {
};
let refreshing = false;
+const failedBoardingOutpoints = new Set<string>();
const withTimeout = <T>(promise: Promise<T>, ms: number, label: string) =>
Promise.race([
@@ -89,11 +90,14 @@ export const refreshArkWallet = async () => {
const info = await provider.getInfo();
const ramps = new Ramps(w);
- // Try each boarding UTXO, freshest first
- const sorted = [...confirmed].sort(
- (a: any, b: any) =>
- (b.status?.block_height || 0) - (a.status?.block_height || 0),
- );
+ // Try each boarding UTXO, freshest first, skipping known failures
+ const outpointKey = (u: any) => `${u.txid}:${u.vout}`;
+ const sorted = [...confirmed]
+ .filter((u: any) => !failedBoardingOutpoints.has(outpointKey(u)))
+ .sort(
+ (a: any, b: any) =>
+ (b.status?.block_height || 0) - (a.status?.block_height || 0),
+ );
for (const utxo of sorted) {
try {
@@ -103,8 +107,10 @@ export const refreshArkWallet = async () => {
"ark onboard",
);
l("ark onboarded boarding utxo:", utxo.value, "sats, txid:", txid);
+ failedBoardingOutpoints.clear();
break;
} catch (e: any) {
+ failedBoardingOutpoints.add(outpointKey(utxo));
warn("ark onboard failed:", utxo.value, "sats:", e.message);
}
}
diff --git a/routes/payments.ts b/routes/payments.ts
index 6bed1309..4bdd0d30 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -89,13 +89,23 @@ export default {
aid,
});
} else {
- p = await sendInternal({
- invoice,
- amount,
- memo,
- recipient,
- sender: user,
- });
+ // Check if recipient account is non-custodial (vault)
+ const recipientAccount = invoice?.aid ? await g(`account:${invoice.aid}`) : null;
+ if (recipientAccount?.pubkey || recipientAccount?.seed) {
+ p = await sendOnchain({
+ amount: amount || invoice.amount,
+ address: invoice.hash,
+ user,
+ });
+ } else {
+ p = await sendInternal({
+ invoice,
+ amount,
+ memo,
+ recipient,
+ sender: user,
+ });
+ }
}
} else if (fund) {
p = await debit({