@kaleidorg/wallet-engine 1.0.0-beta.4 → 1.0.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +29 -10
- package/dist/adapters/ArkadeAdapter.d.ts +78 -14
- package/dist/adapters/ArkadeAdapter.d.ts.map +1 -1
- package/dist/adapters/ArkadeAdapter.js +653 -161
- package/dist/adapters/ArkadeAdapter.js.map +1 -1
- package/dist/adapters/IProtocolAdapter.d.ts +195 -18
- package/dist/adapters/IProtocolAdapter.d.ts.map +1 -1
- package/dist/adapters/IProtocolAdapter.js +6 -2
- package/dist/adapters/IProtocolAdapter.js.map +1 -1
- package/dist/adapters/RgbAdapter.d.ts +70 -27
- package/dist/adapters/RgbAdapter.d.ts.map +1 -1
- package/dist/adapters/RgbAdapter.js +464 -370
- package/dist/adapters/RgbAdapter.js.map +1 -1
- package/dist/adapters/SparkAdapter.d.ts +93 -15
- package/dist/adapters/SparkAdapter.d.ts.map +1 -1
- package/dist/adapters/SparkAdapter.js +833 -168
- package/dist/adapters/SparkAdapter.js.map +1 -1
- package/dist/adapters/arkade.d.ts +15 -0
- package/dist/adapters/arkade.d.ts.map +1 -0
- package/dist/adapters/arkade.js +15 -0
- package/dist/adapters/arkade.js.map +1 -0
- package/dist/adapters/flashnet.d.ts +15 -0
- package/dist/adapters/flashnet.d.ts.map +1 -0
- package/dist/adapters/flashnet.js +17 -0
- package/dist/adapters/flashnet.js.map +1 -0
- package/dist/adapters/native.d.ts +17 -0
- package/dist/adapters/native.d.ts.map +1 -0
- package/dist/adapters/native.js +17 -0
- package/dist/adapters/native.js.map +1 -0
- package/dist/adapters/rgb.d.ts +11 -0
- package/dist/adapters/rgb.d.ts.map +1 -0
- package/dist/adapters/rgb.js +11 -0
- package/dist/adapters/rgb.js.map +1 -0
- package/dist/adapters/spark.d.ts +12 -0
- package/dist/adapters/spark.d.ts.map +1 -0
- package/dist/adapters/spark.js +14 -0
- package/dist/adapters/spark.js.map +1 -0
- package/dist/adapters/wdk/ArkadeWdkAdapter.d.ts +53 -19
- package/dist/adapters/wdk/ArkadeWdkAdapter.d.ts.map +1 -1
- package/dist/adapters/wdk/ArkadeWdkAdapter.js +366 -90
- package/dist/adapters/wdk/ArkadeWdkAdapter.js.map +1 -1
- package/dist/adapters/wdk/BaseWdkAdapter.d.ts +40 -0
- package/dist/adapters/wdk/BaseWdkAdapter.d.ts.map +1 -0
- package/dist/adapters/wdk/BaseWdkAdapter.js +71 -0
- package/dist/adapters/wdk/BaseWdkAdapter.js.map +1 -0
- package/dist/adapters/wdk/LiquidWdkAdapter.d.ts +6 -13
- package/dist/adapters/wdk/LiquidWdkAdapter.d.ts.map +1 -1
- package/dist/adapters/wdk/LiquidWdkAdapter.js +14 -32
- package/dist/adapters/wdk/LiquidWdkAdapter.js.map +1 -1
- package/dist/adapters/wdk/RgbCore.d.ts +64 -0
- package/dist/adapters/wdk/RgbCore.d.ts.map +1 -0
- package/dist/adapters/wdk/RgbCore.js +111 -0
- package/dist/adapters/wdk/RgbCore.js.map +1 -0
- package/dist/adapters/wdk/RgbLibWasmAdapter.d.ts +277 -0
- package/dist/adapters/wdk/RgbLibWasmAdapter.d.ts.map +1 -0
- package/dist/adapters/wdk/RgbLibWasmAdapter.js +728 -0
- package/dist/adapters/wdk/RgbLibWasmAdapter.js.map +1 -0
- package/dist/adapters/wdk/RgbLibWdkAdapter.d.ts +104 -0
- package/dist/adapters/wdk/RgbLibWdkAdapter.d.ts.map +1 -0
- package/dist/adapters/wdk/RgbLibWdkAdapter.js +249 -0
- package/dist/adapters/wdk/RgbLibWdkAdapter.js.map +1 -0
- package/dist/adapters/wdk/RlnWdkAdapter.d.ts +27 -14
- package/dist/adapters/wdk/RlnWdkAdapter.d.ts.map +1 -1
- package/dist/adapters/wdk/RlnWdkAdapter.js +111 -87
- package/dist/adapters/wdk/RlnWdkAdapter.js.map +1 -1
- package/dist/adapters/wdk/SparkWdkAdapter.d.ts +74 -41
- package/dist/adapters/wdk/SparkWdkAdapter.d.ts.map +1 -1
- package/dist/adapters/wdk/SparkWdkAdapter.js +706 -249
- package/dist/adapters/wdk/SparkWdkAdapter.js.map +1 -1
- package/dist/adapters/wdk/index.d.ts +17 -0
- package/dist/adapters/wdk/index.d.ts.map +1 -0
- package/dist/adapters/wdk/index.js +17 -0
- package/dist/adapters/wdk/index.js.map +1 -0
- package/dist/adapters/wdk/wasm-rgb.d.ts +15 -0
- package/dist/adapters/wdk/wasm-rgb.d.ts.map +1 -0
- package/dist/adapters/wdk/wasm-rgb.js +15 -0
- package/dist/adapters/wdk/wasm-rgb.js.map +1 -0
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/index.d.ts.map +1 -1
- package/dist/capabilities/index.js +17 -2
- package/dist/capabilities/index.js.map +1 -1
- package/dist/capabilities/operations.d.ts +22 -0
- package/dist/capabilities/operations.d.ts.map +1 -0
- package/dist/capabilities/operations.js +62 -0
- package/dist/capabilities/operations.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/disclosure/index.d.ts +1 -1
- package/dist/disclosure/index.js +1 -1
- package/dist/disclosure/index.js.map +1 -1
- package/dist/format.d.ts +11 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +10 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +21 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -32
- package/dist/index.js.map +1 -1
- package/dist/lib/arkade-client-manager.d.ts +64 -24
- package/dist/lib/arkade-client-manager.d.ts.map +1 -1
- package/dist/lib/arkade-client-manager.js +240 -65
- package/dist/lib/arkade-client-manager.js.map +1 -1
- package/dist/lib/arkade-converters.d.ts +39 -0
- package/dist/lib/arkade-converters.d.ts.map +1 -0
- package/dist/lib/arkade-converters.js +148 -0
- package/dist/lib/arkade-converters.js.map +1 -0
- package/dist/lib/arkade-helpers.d.ts +110 -0
- package/dist/lib/arkade-helpers.d.ts.map +1 -0
- package/dist/lib/arkade-helpers.js +227 -0
- package/dist/lib/arkade-helpers.js.map +1 -0
- package/dist/lib/arkade-swaps-client-manager.d.ts +55 -0
- package/dist/lib/arkade-swaps-client-manager.d.ts.map +1 -0
- package/dist/lib/arkade-swaps-client-manager.js +127 -0
- package/dist/lib/arkade-swaps-client-manager.js.map +1 -0
- package/dist/lib/arkade-vtxo-lifecycle.d.ts +116 -0
- package/dist/lib/arkade-vtxo-lifecycle.d.ts.map +1 -0
- package/dist/lib/arkade-vtxo-lifecycle.js +184 -0
- package/dist/lib/arkade-vtxo-lifecycle.js.map +1 -0
- package/dist/lib/flashnet-client-manager.d.ts +26 -9
- package/dist/lib/flashnet-client-manager.d.ts.map +1 -1
- package/dist/lib/flashnet-client-manager.js +97 -13
- package/dist/lib/flashnet-client-manager.js.map +1 -1
- package/dist/lib/kaleido-client-manager.d.ts +38 -3
- package/dist/lib/kaleido-client-manager.d.ts.map +1 -1
- package/dist/lib/kaleido-client-manager.js +79 -10
- package/dist/lib/kaleido-client-manager.js.map +1 -1
- package/dist/lib/ln-message-sign.d.ts +20 -0
- package/dist/lib/ln-message-sign.d.ts.map +1 -0
- package/dist/lib/ln-message-sign.js +90 -0
- package/dist/lib/ln-message-sign.js.map +1 -0
- package/dist/lib/log.d.ts +15 -0
- package/dist/lib/log.d.ts.map +1 -0
- package/dist/lib/log.js +16 -0
- package/dist/lib/log.js.map +1 -0
- package/dist/lib/orchestra-client.d.ts +149 -0
- package/dist/lib/orchestra-client.d.ts.map +1 -0
- package/dist/lib/orchestra-client.js +178 -0
- package/dist/lib/orchestra-client.js.map +1 -0
- package/dist/lib/psbt-signer.d.ts +60 -0
- package/dist/lib/psbt-signer.d.ts.map +1 -0
- package/dist/lib/psbt-signer.js +161 -0
- package/dist/lib/psbt-signer.js.map +1 -0
- package/dist/lib/rgb-converters.d.ts +62 -0
- package/dist/lib/rgb-converters.d.ts.map +1 -0
- package/dist/lib/rgb-converters.js +179 -0
- package/dist/lib/rgb-converters.js.map +1 -0
- package/dist/lib/rgb-fee-policy.d.ts +41 -0
- package/dist/lib/rgb-fee-policy.d.ts.map +1 -0
- package/dist/lib/rgb-fee-policy.js +52 -0
- package/dist/lib/rgb-fee-policy.js.map +1 -0
- package/dist/lib/rgb-helpers.d.ts +54 -0
- package/dist/lib/rgb-helpers.d.ts.map +1 -0
- package/dist/lib/rgb-helpers.js +89 -0
- package/dist/lib/rgb-helpers.js.map +1 -0
- package/dist/lib/spark-activity.d.ts +5 -0
- package/dist/lib/spark-activity.d.ts.map +1 -0
- package/dist/lib/spark-activity.js +11 -0
- package/dist/lib/spark-activity.js.map +1 -0
- package/dist/lib/spark-balance-cache.d.ts +58 -0
- package/dist/lib/spark-balance-cache.d.ts.map +1 -0
- package/dist/lib/spark-balance-cache.js +86 -0
- package/dist/lib/spark-balance-cache.js.map +1 -0
- package/dist/lib/spark-client-manager.d.ts +64 -10
- package/dist/lib/spark-client-manager.d.ts.map +1 -1
- package/dist/lib/spark-client-manager.js +191 -35
- package/dist/lib/spark-client-manager.js.map +1 -1
- package/dist/lib/spark-converters.d.ts +64 -0
- package/dist/lib/spark-converters.d.ts.map +1 -0
- package/dist/lib/spark-converters.js +242 -0
- package/dist/lib/spark-converters.js.map +1 -0
- package/dist/lib/spark-helpers.d.ts +72 -0
- package/dist/lib/spark-helpers.d.ts.map +1 -0
- package/dist/lib/spark-helpers.js +151 -0
- package/dist/lib/spark-helpers.js.map +1 -0
- package/dist/lib/spark-sent-token-records.d.ts +43 -0
- package/dist/lib/spark-sent-token-records.d.ts.map +1 -0
- package/dist/lib/spark-sent-token-records.js +105 -0
- package/dist/lib/spark-sent-token-records.js.map +1 -0
- package/dist/lib/wallet-seed.d.ts +31 -0
- package/dist/lib/wallet-seed.d.ts.map +1 -0
- package/dist/lib/wallet-seed.js +58 -0
- package/dist/lib/wallet-seed.js.map +1 -0
- package/dist/lib/zbase32.d.ts +3 -0
- package/dist/lib/zbase32.d.ts.map +1 -0
- package/dist/lib/zbase32.js +64 -0
- package/dist/lib/zbase32.js.map +1 -0
- package/dist/manager/ProtocolManager.d.ts +54 -3
- package/dist/manager/ProtocolManager.d.ts.map +1 -1
- package/dist/manager/ProtocolManager.js +118 -41
- package/dist/manager/ProtocolManager.js.map +1 -1
- package/dist/ports/index.d.ts +20 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +23 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/receive/unifiedReceive.d.ts +12 -0
- package/dist/receive/unifiedReceive.d.ts.map +1 -1
- package/dist/receive/unifiedReceive.js +35 -4
- package/dist/receive/unifiedReceive.js.map +1 -1
- package/dist/registry/createWdkRegistry.d.ts +10 -2
- package/dist/registry/createWdkRegistry.d.ts.map +1 -1
- package/dist/registry/createWdkRegistry.js +14 -7
- package/dist/registry/createWdkRegistry.js.map +1 -1
- package/dist/router/destination.d.ts +2 -2
- package/dist/router/destination.d.ts.map +1 -1
- package/dist/router/destination.js +34 -11
- package/dist/router/destination.js.map +1 -1
- package/dist/router/index.d.ts +39 -3
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +113 -4
- package/dist/router/index.js.map +1 -1
- package/dist/router/preference.d.ts +53 -0
- package/dist/router/preference.d.ts.map +1 -0
- package/dist/router/preference.js +81 -0
- package/dist/router/preference.js.map +1 -0
- package/dist/swap/KaleidoswapSwap.d.ts +1 -1
- package/dist/swap/KaleidoswapSwap.d.ts.map +1 -1
- package/dist/swap/KaleidoswapSwap.js +37 -20
- package/dist/swap/KaleidoswapSwap.js.map +1 -1
- package/dist/swap/index.d.ts +8 -0
- package/dist/swap/index.d.ts.map +1 -0
- package/dist/swap/index.js +8 -0
- package/dist/swap/index.js.map +1 -0
- package/dist/types/arkade.d.ts +1 -1
- package/dist/types/base.d.ts +35 -25
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/base.js +28 -2
- package/dist/types/base.js.map +1 -1
- package/dist/types/cross-l2.d.ts +1 -1
- package/dist/types/flashnet.d.ts +20 -0
- package/dist/types/flashnet.d.ts.map +1 -1
- package/dist/types/flashnet.js +34 -6
- package/dist/types/flashnet.js.map +1 -1
- package/dist/types/rgb.d.ts +18 -4
- package/dist/types/rgb.d.ts.map +1 -1
- package/dist/types/spark.d.ts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +2 -2
- package/dist/utils.js.map +1 -1
- package/package.json +68 -14
|
@@ -1,314 +1,806 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Arkade Protocol Adapter
|
|
3
|
-
* Implements IProtocolAdapter using @arkade-os/sdk.
|
|
4
|
-
*
|
|
3
|
+
* Implements IProtocolAdapter using @arkade-os/sdk v0.4.x.
|
|
4
|
+
*
|
|
5
|
+
* SDK API facts for v0.4.x:
|
|
6
|
+
* - `wallet.getBalance()` includes `assets: { assetId, amount }[]`
|
|
7
|
+
* - `wallet.assetManager.getAssetDetails(assetId)` resolves supply + metadata
|
|
8
|
+
* - `wallet.send({ address, assets: [...] })` sends Arkade-native assets
|
|
9
|
+
* - `wallet.sendBitcoin({ address, amount })` still sends BTC
|
|
10
|
+
* - `wallet.getTransactionHistory()` → ArkTransaction[]
|
|
11
|
+
* where ArkTransaction = { key, type: TxType, amount: number, settled: boolean, createdAt: number }
|
|
12
|
+
* - `TxType.TxSent = "SENT"`, `TxType.TxReceived = "RECEIVED"`
|
|
13
|
+
* - `WalletBalance.boarding.total` (number), `.settled`, `.preconfirmed`, `.available`, `.recoverable`, `.total`
|
|
5
14
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
15
|
+
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
16
|
+
import { HDKey } from "@scure/bip32";
|
|
17
|
+
import { signLnMessage, verifyLnMessage } from "../lib/ln-message-sign.js";
|
|
18
|
+
import { log } from "../lib/log.js";
|
|
19
|
+
import { arkadeClientManager } from "../lib/arkade-client-manager.js";
|
|
20
|
+
import { arkadeSwapsClientManager } from "../lib/arkade-swaps-client-manager.js";
|
|
21
|
+
import { Ramps, isSpendable, } from "@arkade-os/sdk";
|
|
22
|
+
import { PROTOCOL_OPERATIONS } from "../capabilities/operations.js";
|
|
23
|
+
import { formatSats, formatUnits, getAssetMetadata, getAssetName, getAssetPrecision, getAssetTicker, normalizeVtxos, selectVtxosByExpiry, sortVtxosByExpiry, toNumber, toPositiveIntegerBigInt, toStringValue, } from "../lib/arkade-helpers.js";
|
|
24
|
+
import { convertArkTxToUnifiedAll } from "../lib/arkade-converters.js";
|
|
25
|
+
import { ProtocolError, ConnectionError, } from "../types/base.js";
|
|
26
|
+
/** Bare bolt11 prefixes (lnbc / lntb / lnbcrt / lnsb) — case-insensitive. */
|
|
27
|
+
function isLightningInvoice(value) {
|
|
28
|
+
if (!value)
|
|
29
|
+
return false;
|
|
30
|
+
const lower = value.trim().toLowerCase();
|
|
31
|
+
// Strip a `lightning:` URI prefix if present.
|
|
32
|
+
const body = lower.startsWith("lightning:") ? lower.slice("lightning:".length) : lower;
|
|
33
|
+
return /^ln(bc|tb|bcrt|sb)/.test(body);
|
|
34
|
+
}
|
|
35
|
+
function stripLightningPrefix(value) {
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
return trimmed.toLowerCase().startsWith("lightning:")
|
|
38
|
+
? trimmed.slice("lightning:".length)
|
|
39
|
+
: trimmed;
|
|
40
|
+
}
|
|
41
|
+
function isArkadeAddress(value) {
|
|
42
|
+
return /^(ark1|tark1)/i.test(value.trim());
|
|
43
|
+
}
|
|
8
44
|
export class ArkadeAdapter {
|
|
9
45
|
constructor() {
|
|
10
|
-
this.protocolName =
|
|
11
|
-
this.supportedLayers = [
|
|
12
|
-
this.version =
|
|
46
|
+
this.protocolName = "ARKADE";
|
|
47
|
+
this.supportedLayers = ["BTC_ARKADE", "BTC_L1", "ARKADE_ARKADE"];
|
|
48
|
+
this.version = "1.0.0";
|
|
49
|
+
this.capabilities = PROTOCOL_OPERATIONS.ARKADE;
|
|
13
50
|
this.config = null;
|
|
51
|
+
this.assetDetailsCache = new Map();
|
|
14
52
|
}
|
|
15
|
-
//
|
|
53
|
+
// =========================================================================
|
|
16
54
|
// Connection Management
|
|
17
|
-
//
|
|
55
|
+
// =========================================================================
|
|
18
56
|
async connect(config) {
|
|
19
57
|
const arkadeConfig = config;
|
|
20
58
|
if (!arkadeConfig.mnemonic) {
|
|
21
|
-
throw new ConnectionError(
|
|
59
|
+
throw new ConnectionError("Wallet recovery secret is required for Arkade wallet", "ARKADE");
|
|
22
60
|
}
|
|
23
61
|
if (!arkadeConfig.arkServerUrl) {
|
|
24
|
-
throw new ConnectionError(
|
|
62
|
+
throw new ConnectionError("arkServerUrl is required for Arkade wallet", "ARKADE");
|
|
25
63
|
}
|
|
26
64
|
try {
|
|
27
65
|
await arkadeClientManager.initialize(arkadeConfig);
|
|
28
66
|
this.config = arkadeConfig;
|
|
29
|
-
|
|
67
|
+
this.assetDetailsCache.clear();
|
|
68
|
+
log.info("[ArkadeAdapter] Connected to Arkade successfully");
|
|
69
|
+
// Initialize the Boltz swap client in the background. Failures are
|
|
70
|
+
// non-fatal — swaps just stay unavailable until the next connect.
|
|
71
|
+
const wallet = arkadeClientManager.getWallet();
|
|
72
|
+
arkadeSwapsClientManager.initialize(wallet).catch((error) => {
|
|
73
|
+
log.warn("[ArkadeAdapter] Boltz swaps init failed (Lightning swaps unavailable):", error);
|
|
74
|
+
});
|
|
30
75
|
}
|
|
31
76
|
catch (error) {
|
|
32
77
|
const msg = error instanceof Error ? error.message : String(error);
|
|
33
|
-
throw new ConnectionError(`Failed to connect to Arkade: ${msg}`,
|
|
78
|
+
throw new ConnectionError(`Failed to connect to Arkade: ${msg}`, "ARKADE");
|
|
34
79
|
}
|
|
35
80
|
}
|
|
36
81
|
async disconnect() {
|
|
82
|
+
// Dispose Boltz swaps client first (stops SwapManager monitoring) so it
|
|
83
|
+
// doesn't try to use the wallet after we tear it down.
|
|
84
|
+
await arkadeSwapsClientManager.dispose();
|
|
37
85
|
await arkadeClientManager.disconnect();
|
|
38
86
|
this.config = null;
|
|
39
|
-
|
|
87
|
+
this.assetDetailsCache.clear();
|
|
88
|
+
log.info("[ArkadeAdapter] Disconnected from Arkade");
|
|
40
89
|
}
|
|
41
90
|
isConnected() {
|
|
42
91
|
return arkadeClientManager.isInitialized();
|
|
43
92
|
}
|
|
44
93
|
async getConnectionInfo() {
|
|
45
94
|
if (!this.isConnected()) {
|
|
46
|
-
throw new ProtocolError(
|
|
95
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
47
96
|
}
|
|
48
97
|
return {
|
|
49
|
-
protocol:
|
|
98
|
+
protocol: "ARKADE",
|
|
50
99
|
connected: true,
|
|
51
|
-
network: this.config?.network ??
|
|
100
|
+
network: this.config?.network ?? "signet",
|
|
52
101
|
syncStatus: { synced: true, progress: 100 },
|
|
53
102
|
};
|
|
54
103
|
}
|
|
55
|
-
//
|
|
104
|
+
// =========================================================================
|
|
56
105
|
// Asset Operations
|
|
57
|
-
//
|
|
106
|
+
// =========================================================================
|
|
58
107
|
async listAssets() {
|
|
59
108
|
if (!this.isConnected()) {
|
|
60
|
-
throw new ProtocolError(
|
|
109
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
61
110
|
}
|
|
62
111
|
try {
|
|
63
112
|
const wallet = arkadeClientManager.getWallet();
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const preconfirmed =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
113
|
+
const rawBalance = await wallet.getBalance();
|
|
114
|
+
const balance = await this.getWalletBalanceSummary(wallet);
|
|
115
|
+
const totalSats = balance.total;
|
|
116
|
+
const preconfirmed = balance.preconfirmed;
|
|
117
|
+
const btcAsset = {
|
|
118
|
+
id: "BTC",
|
|
119
|
+
name: "Bitcoin (Arkade)",
|
|
120
|
+
ticker: "BTC",
|
|
121
|
+
precision: 8,
|
|
122
|
+
protocol: "ARKADE",
|
|
123
|
+
layer: "BTC_ARKADE",
|
|
124
|
+
balance: {
|
|
125
|
+
total: totalSats,
|
|
126
|
+
// preconfirmed VTXOs are spendable (isSpendable = !vtxo.isSpent in the SDK),
|
|
127
|
+
// so they count as available just like settled ones.
|
|
128
|
+
available: balance.available,
|
|
129
|
+
pending: 0,
|
|
130
|
+
locked: 0,
|
|
131
|
+
totalDisplay: formatSats(totalSats),
|
|
132
|
+
availableDisplay: formatSats(balance.available),
|
|
133
|
+
},
|
|
134
|
+
capabilities: {
|
|
135
|
+
canSend: true,
|
|
136
|
+
canReceive: true,
|
|
137
|
+
canSwap: false,
|
|
138
|
+
supportsLightning: false,
|
|
139
|
+
supportsOnchain: true,
|
|
140
|
+
},
|
|
141
|
+
metadata: {
|
|
142
|
+
boarding: balance.boardingTotal,
|
|
143
|
+
settled: balance.settled,
|
|
144
|
+
preconfirmed,
|
|
145
|
+
recoverable: balance.recoverable,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const rawAssets = Array.isArray(rawBalance?.assets) ? rawBalance.assets : [];
|
|
149
|
+
const arkadeAssets = await Promise.all(rawAssets
|
|
150
|
+
.filter((entry) => toStringValue(entry?.assetId) !== "" && toNumber(entry?.amount) > 0)
|
|
151
|
+
.map(async (entry) => {
|
|
152
|
+
const assetId = toStringValue(entry.assetId);
|
|
153
|
+
const amount = toNumber(entry.amount);
|
|
154
|
+
const details = await this.getCachedAssetDetails(wallet, assetId);
|
|
155
|
+
const metadata = getAssetMetadata(details);
|
|
156
|
+
const precision = getAssetPrecision(metadata);
|
|
157
|
+
const ticker = getAssetTicker(assetId, metadata);
|
|
158
|
+
const name = getAssetName(assetId, ticker, metadata);
|
|
159
|
+
const icon = typeof metadata?.icon === "string" ? metadata.icon : undefined;
|
|
160
|
+
const asset = {
|
|
161
|
+
id: assetId,
|
|
162
|
+
name,
|
|
163
|
+
ticker,
|
|
164
|
+
precision,
|
|
165
|
+
protocol: "ARKADE",
|
|
166
|
+
layer: "ARKADE_ARKADE",
|
|
75
167
|
balance: {
|
|
76
|
-
total:
|
|
77
|
-
available:
|
|
78
|
-
pending:
|
|
168
|
+
total: amount,
|
|
169
|
+
available: amount,
|
|
170
|
+
pending: 0,
|
|
79
171
|
locked: 0,
|
|
80
|
-
totalDisplay:
|
|
81
|
-
availableDisplay:
|
|
172
|
+
totalDisplay: formatUnits(amount, precision),
|
|
173
|
+
availableDisplay: formatUnits(amount, precision),
|
|
82
174
|
},
|
|
175
|
+
icon,
|
|
83
176
|
capabilities: {
|
|
84
177
|
canSend: true,
|
|
85
178
|
canReceive: true,
|
|
86
179
|
canSwap: false,
|
|
87
180
|
supportsLightning: false,
|
|
88
|
-
supportsOnchain:
|
|
181
|
+
supportsOnchain: false,
|
|
89
182
|
},
|
|
183
|
+
// Don't spread `details` — the Arkade SDK returns BigInt /
|
|
184
|
+
// Uint8Array fields (totalSupply, raw identifiers) that crash
|
|
185
|
+
// chrome.runtime.sendMessage with "Could not serialize message".
|
|
90
186
|
metadata: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
preconfirmed,
|
|
94
|
-
recoverable: this.toNumber(balance?.recoverable),
|
|
187
|
+
arkadeAssetId: assetId,
|
|
188
|
+
decimals: precision,
|
|
95
189
|
},
|
|
96
|
-
}
|
|
190
|
+
};
|
|
191
|
+
return asset;
|
|
192
|
+
}));
|
|
193
|
+
return [btcAsset, ...arkadeAssets];
|
|
97
194
|
}
|
|
98
195
|
catch (error) {
|
|
99
196
|
const msg = error instanceof Error ? error.message : String(error);
|
|
100
|
-
throw new ProtocolError(`Failed to list assets: ${msg}`,
|
|
197
|
+
throw new ProtocolError(`Failed to list assets: ${msg}`, "ARKADE", "LIST_ASSETS_ERROR");
|
|
101
198
|
}
|
|
102
199
|
}
|
|
103
200
|
async getAsset(assetId) {
|
|
104
201
|
const assets = await this.listAssets();
|
|
105
|
-
const asset = assets.find(a => a.id === assetId || a.ticker === assetId);
|
|
202
|
+
const asset = assets.find((a) => a.id === assetId || a.ticker === assetId);
|
|
106
203
|
if (!asset) {
|
|
107
|
-
throw new ProtocolError(`Asset not found: ${assetId}`,
|
|
204
|
+
throw new ProtocolError(`Asset not found: ${assetId}`, "ARKADE", "ASSET_NOT_FOUND");
|
|
108
205
|
}
|
|
109
206
|
return asset;
|
|
110
207
|
}
|
|
111
208
|
async getAssetBalance(assetId) {
|
|
209
|
+
if (assetId === "BTC" || assetId.toLowerCase() === "btc") {
|
|
210
|
+
const asset = await this.getAsset("BTC");
|
|
211
|
+
return asset.balance;
|
|
212
|
+
}
|
|
112
213
|
const asset = await this.getAsset(assetId);
|
|
113
214
|
return asset.balance;
|
|
114
215
|
}
|
|
115
|
-
async refreshBalances() {
|
|
116
|
-
|
|
216
|
+
async refreshBalances() {
|
|
217
|
+
// Balances are fetched live on each call
|
|
218
|
+
}
|
|
219
|
+
// =========================================================================
|
|
117
220
|
// Transaction Operations
|
|
118
|
-
//
|
|
221
|
+
// =========================================================================
|
|
119
222
|
async listTransactions(filter) {
|
|
120
223
|
if (!this.isConnected()) {
|
|
121
|
-
throw new ProtocolError(
|
|
224
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
122
225
|
}
|
|
123
226
|
try {
|
|
124
227
|
const wallet = arkadeClientManager.getWallet();
|
|
125
228
|
const history = await wallet.getTransactionHistory();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
229
|
+
const resolveDetails = (assetId) => this.getCachedAssetDetails(wallet, assetId);
|
|
230
|
+
const expanded = await Promise.all((history ?? []).map((item) => convertArkTxToUnifiedAll(item, resolveDetails)));
|
|
231
|
+
const validTxs = expanded.flat();
|
|
232
|
+
return validTxs
|
|
233
|
+
.filter((tx) => {
|
|
130
234
|
if (!filter)
|
|
131
235
|
return true;
|
|
236
|
+
if (filter.asset && tx.asset?.id !== filter.asset)
|
|
237
|
+
return false;
|
|
132
238
|
if (filter.type && tx.type !== filter.type)
|
|
133
239
|
return false;
|
|
134
240
|
if (filter.status && tx.status !== filter.status)
|
|
135
241
|
return false;
|
|
242
|
+
if (filter.fromTimestamp && tx.timestamp < filter.fromTimestamp)
|
|
243
|
+
return false;
|
|
244
|
+
if (filter.toTimestamp && tx.timestamp > filter.toTimestamp)
|
|
245
|
+
return false;
|
|
136
246
|
return true;
|
|
137
247
|
})
|
|
138
248
|
.slice(filter?.offset ?? 0, filter?.limit ? (filter.offset ?? 0) + filter.limit : undefined);
|
|
139
249
|
}
|
|
140
250
|
catch (error) {
|
|
141
251
|
const msg = error instanceof Error ? error.message : String(error);
|
|
142
|
-
throw new ProtocolError(`Failed to list transactions: ${msg}`,
|
|
252
|
+
throw new ProtocolError(`Failed to list transactions: ${msg}`, "ARKADE", "LIST_TRANSACTIONS_ERROR");
|
|
143
253
|
}
|
|
144
254
|
}
|
|
145
255
|
async getTransaction(txId) {
|
|
146
256
|
const txs = await this.listTransactions();
|
|
147
|
-
const tx = txs.find(t => t.id === txId);
|
|
257
|
+
const tx = txs.find((t) => t.id === txId);
|
|
148
258
|
if (!tx) {
|
|
149
|
-
throw new ProtocolError(`Transaction not found: ${txId}`,
|
|
259
|
+
throw new ProtocolError(`Transaction not found: ${txId}`, "ARKADE", "TX_NOT_FOUND");
|
|
150
260
|
}
|
|
151
261
|
return tx;
|
|
152
262
|
}
|
|
153
|
-
//
|
|
263
|
+
// =========================================================================
|
|
154
264
|
// Payment Operations
|
|
155
|
-
//
|
|
265
|
+
// =========================================================================
|
|
156
266
|
async createInvoice(request) {
|
|
157
267
|
if (!this.isConnected()) {
|
|
158
|
-
throw new ProtocolError(
|
|
268
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const wallet = arkadeClientManager.getWallet();
|
|
272
|
+
const address = await wallet.getAddress();
|
|
273
|
+
return {
|
|
274
|
+
invoice: address,
|
|
275
|
+
paymentHash: "",
|
|
276
|
+
amount: request.amount,
|
|
277
|
+
expiresAt: Date.now() + (request.expirySeconds ?? 3600) * 1000,
|
|
278
|
+
description: request.description ?? "Arkade receiving address",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
283
|
+
throw new ProtocolError(`Failed to create invoice: ${msg}`, "ARKADE", "CREATE_INVOICE_ERROR");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Generate a Boltz reverse-swap Lightning invoice that, when paid, lands
|
|
288
|
+
* the funds in this Arkade wallet as a VTXO. Requires amount > 0 — Boltz
|
|
289
|
+
* can't issue an amountless invoice. The embedded `SwapManager` claims
|
|
290
|
+
* the VHTLC automatically once the LN payment settles.
|
|
291
|
+
*/
|
|
292
|
+
async createArkadeLightningInvoice(request) {
|
|
293
|
+
if (!this.isConnected()) {
|
|
294
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
295
|
+
}
|
|
296
|
+
if (!request.amount || request.amount <= 0) {
|
|
297
|
+
throw new ProtocolError("Amount is required for Boltz Lightning invoices into Arkade", "ARKADE", "INVALID_AMOUNT");
|
|
298
|
+
}
|
|
299
|
+
if (!arkadeSwapsClientManager.isInitialized()) {
|
|
300
|
+
throw new ProtocolError("Lightning swaps are not ready yet. Try again in a moment.", "ARKADE", "SWAPS_NOT_READY");
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const swaps = arkadeSwapsClientManager.getClient();
|
|
304
|
+
const result = await swaps.createLightningInvoice({
|
|
305
|
+
amount: request.amount,
|
|
306
|
+
description: request.description,
|
|
307
|
+
});
|
|
308
|
+
return {
|
|
309
|
+
invoice: result.invoice,
|
|
310
|
+
paymentHash: result.paymentHash ?? "",
|
|
311
|
+
amount: request.amount,
|
|
312
|
+
expiresAt: Date.now() + (request.expirySeconds ?? 3600) * 1000,
|
|
313
|
+
description: request.description ?? "Boltz reverse swap into Arkade",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
318
|
+
throw new ProtocolError(`Failed to create Boltz Lightning invoice: ${msg}`, "ARKADE", "CREATE_INVOICE_ERROR");
|
|
159
319
|
}
|
|
160
|
-
const wallet = arkadeClientManager.getWallet();
|
|
161
|
-
const address = await wallet.getAddress();
|
|
162
|
-
return {
|
|
163
|
-
invoice: address,
|
|
164
|
-
paymentHash: '',
|
|
165
|
-
amount: request.amount,
|
|
166
|
-
expiresAt: Date.now() + (request.expirySeconds ?? 3600) * 1000,
|
|
167
|
-
description: request.description ?? 'Arkade receiving address',
|
|
168
|
-
};
|
|
169
320
|
}
|
|
170
321
|
async decodeInvoice(invoice) {
|
|
171
|
-
|
|
322
|
+
// Arkade uses addresses, not bolt11 invoices
|
|
323
|
+
return {
|
|
324
|
+
paymentHash: "",
|
|
325
|
+
expiresAt: 0,
|
|
326
|
+
destination: invoice,
|
|
327
|
+
};
|
|
172
328
|
}
|
|
173
329
|
async sendPayment(request) {
|
|
174
330
|
if (!this.isConnected()) {
|
|
175
|
-
throw new ProtocolError(
|
|
331
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
332
|
+
}
|
|
333
|
+
// Lightning invoice → Boltz submarine swap (Arkade → Lightning).
|
|
334
|
+
// The swap library extracts the amount from the invoice itself, so
|
|
335
|
+
// amountless invoices cannot be paid this way (Boltz rejects them
|
|
336
|
+
// with "0 is less than minimal of 333"). Reject early with a clear
|
|
337
|
+
// error rather than letting Boltz's cryptic message bubble up.
|
|
338
|
+
if (isLightningInvoice(request.invoice)) {
|
|
339
|
+
if (!arkadeSwapsClientManager.isInitialized()) {
|
|
340
|
+
throw new ProtocolError("Lightning swaps are not ready yet. Try again in a moment.", "ARKADE", "SWAPS_NOT_READY");
|
|
341
|
+
}
|
|
342
|
+
const invoiceBody = stripLightningPrefix(request.invoice);
|
|
343
|
+
try {
|
|
344
|
+
const swaps = arkadeSwapsClientManager.getClient();
|
|
345
|
+
const result = await swaps.sendLightningPayment({ invoice: invoiceBody });
|
|
346
|
+
return {
|
|
347
|
+
paymentHash: result.preimage ?? result.txid ?? "",
|
|
348
|
+
amount: result.amount ?? request.amount ?? 0,
|
|
349
|
+
fee: 0,
|
|
350
|
+
// Boltz submarine swap; the swap can still fail in the HODL/claim
|
|
351
|
+
// phase. Caller polls `getPaymentStatus` to reach a terminal state.
|
|
352
|
+
status: "pending",
|
|
353
|
+
timestamp: Date.now(),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
358
|
+
// Translate Boltz error strings to actionable messages.
|
|
359
|
+
if (/less than minimal/i.test(msg)) {
|
|
360
|
+
throw new ProtocolError("Arkade can't pay amountless Lightning invoices. Please use a different route or ask the recipient for an invoice with an amount.", "ARKADE", "INVALID_AMOUNT");
|
|
361
|
+
}
|
|
362
|
+
if (/vHTLC.*already exists/i.test(msg)) {
|
|
363
|
+
throw new ProtocolError("A swap for this invoice is already in progress. Wait for it to complete or refund before retrying.", "ARKADE", "SWAP_IN_PROGRESS");
|
|
364
|
+
}
|
|
365
|
+
throw new ProtocolError(`Failed to send Lightning payment via Boltz: ${msg}`, "ARKADE", "SEND_PAYMENT_ERROR");
|
|
366
|
+
}
|
|
176
367
|
}
|
|
368
|
+
// Non-Lightning destinations: existing Ark / on-chain BTC path.
|
|
177
369
|
if (!request.amount || request.amount <= 0) {
|
|
178
|
-
throw new ProtocolError(
|
|
370
|
+
throw new ProtocolError("Amount is required for Arkade payments", "ARKADE", "INVALID_AMOUNT");
|
|
179
371
|
}
|
|
180
372
|
try {
|
|
181
373
|
const wallet = arkadeClientManager.getWallet();
|
|
374
|
+
const selectedVtxos = await this.selectSpendableBtcVtxos(wallet, request.amount);
|
|
182
375
|
const txid = await wallet.sendBitcoin({
|
|
183
|
-
address: request.invoice,
|
|
184
|
-
amount: request.amount,
|
|
376
|
+
address: request.invoice, // Ark or on-chain address
|
|
377
|
+
amount: request.amount, // satoshis
|
|
378
|
+
...(selectedVtxos ? { selectedVtxos } : {}),
|
|
185
379
|
});
|
|
186
380
|
return {
|
|
187
381
|
paymentHash: txid,
|
|
188
382
|
amount: request.amount,
|
|
189
383
|
fee: 0,
|
|
190
|
-
|
|
384
|
+
// Ark VTXO sends are immediately valid once sendBitcoin resolves.
|
|
385
|
+
// On-chain destinations still need confirmation, so callers should
|
|
386
|
+
// keep polling via getPaymentStatus.
|
|
387
|
+
status: (isArkadeAddress(request.invoice) ? "confirmed" : "pending"),
|
|
191
388
|
timestamp: Date.now(),
|
|
192
389
|
};
|
|
193
390
|
}
|
|
194
391
|
catch (error) {
|
|
195
392
|
const msg = error instanceof Error ? error.message : String(error);
|
|
196
|
-
throw new ProtocolError(`Failed to send payment: ${msg}`,
|
|
393
|
+
throw new ProtocolError(`Failed to send payment: ${msg}`, "ARKADE", "SEND_PAYMENT_ERROR");
|
|
197
394
|
}
|
|
198
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Resolve a payment's terminal state from the SDK's transaction history.
|
|
398
|
+
* `paymentHash` is the txid returned by `sendBitcoin` / `sendLightningPayment`
|
|
399
|
+
* (Boltz returns a preimage as a fallback if there's no on-chain txid yet —
|
|
400
|
+
* in that case we don't have a history row and the payment stays pending).
|
|
401
|
+
*/
|
|
199
402
|
async getPaymentStatus(paymentHash) {
|
|
200
|
-
|
|
403
|
+
if (!this.isConnected() || !paymentHash) {
|
|
404
|
+
return { paymentHash, status: "pending" };
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
const wallet = arkadeClientManager.getWallet();
|
|
408
|
+
const history = (await wallet.getTransactionHistory());
|
|
409
|
+
const match = history.find((entry) => {
|
|
410
|
+
const key = entry?.key;
|
|
411
|
+
const id = typeof key === "string" ? key : key?.txid;
|
|
412
|
+
return id === paymentHash;
|
|
413
|
+
});
|
|
414
|
+
if (!match) {
|
|
415
|
+
return { paymentHash, status: "pending" };
|
|
416
|
+
}
|
|
417
|
+
// Arkade's reference wallet treats SENT history rows as settled while
|
|
418
|
+
// leaving unsettled RECEIVED rows as preconfirmed/pending.
|
|
419
|
+
const isSent = match.type === "SENT";
|
|
420
|
+
return {
|
|
421
|
+
paymentHash,
|
|
422
|
+
status: (isSent || match.settled ? "confirmed" : "pending"),
|
|
423
|
+
amount: match.amount,
|
|
424
|
+
timestamp: Date.now(),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
log.warn("[ArkadeAdapter] getPaymentStatus history lookup failed:", error);
|
|
429
|
+
return { paymentHash, status: "pending" };
|
|
430
|
+
}
|
|
201
431
|
}
|
|
202
|
-
//
|
|
432
|
+
// =========================================================================
|
|
203
433
|
// Address Operations
|
|
204
|
-
//
|
|
434
|
+
// =========================================================================
|
|
205
435
|
async getReceiveAddress(assetId) {
|
|
206
436
|
if (!this.isConnected()) {
|
|
207
|
-
throw new ProtocolError(
|
|
437
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
208
438
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
439
|
+
try {
|
|
440
|
+
const wallet = arkadeClientManager.getWallet();
|
|
441
|
+
// 'onchain' or 'boarding' → return on-chain boarding address
|
|
442
|
+
if (assetId === "onchain" || assetId === "boarding") {
|
|
443
|
+
const address = await wallet.getBoardingAddress();
|
|
444
|
+
return { address, format: "BTC_ADDRESS", asset: "BTC" };
|
|
445
|
+
}
|
|
446
|
+
// Default → Ark address (off-chain)
|
|
447
|
+
const address = await wallet.getAddress();
|
|
448
|
+
return {
|
|
449
|
+
address,
|
|
450
|
+
format: "ARKADE_ADDRESS",
|
|
451
|
+
asset: assetId && assetId !== "BTC" ? assetId : "BTC",
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
456
|
+
throw new ProtocolError(`Failed to get receive address: ${msg}`, "ARKADE", "GET_ADDRESS_ERROR");
|
|
213
457
|
}
|
|
214
|
-
const address = await wallet.getAddress();
|
|
215
|
-
return { address, format: 'ARKADE_ADDRESS', asset: 'BTC' };
|
|
216
458
|
}
|
|
217
|
-
//
|
|
459
|
+
// =========================================================================
|
|
218
460
|
// Node & Balance Operations
|
|
219
|
-
//
|
|
461
|
+
// =========================================================================
|
|
220
462
|
async getNodeInfo() {
|
|
221
463
|
if (!this.isConnected()) {
|
|
222
|
-
throw new ProtocolError(
|
|
464
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
const wallet = arkadeClientManager.getWallet();
|
|
468
|
+
const balance = await this.getWalletBalanceSummary(wallet);
|
|
469
|
+
const spendableSats = balance.available;
|
|
470
|
+
return {
|
|
471
|
+
channelsBalanceMsat: spendableSats * 1000,
|
|
472
|
+
maxPayableMsat: spendableSats * 1000,
|
|
473
|
+
onchainBalanceMsat: balance.boardingConfirmed * 1000,
|
|
474
|
+
pendingOnchainBalanceMsat: balance.boardingUnconfirmed * 1000,
|
|
475
|
+
maxReceivableMsat: 0,
|
|
476
|
+
inboundLiquidityMsats: 0,
|
|
477
|
+
connectedPeers: [],
|
|
478
|
+
utxos: 0,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
483
|
+
throw new ProtocolError(`Failed to get node info: ${msg}`, "ARKADE", "NODE_INFO_ERROR");
|
|
223
484
|
}
|
|
224
|
-
const wallet = arkadeClientManager.getWallet();
|
|
225
|
-
const balance = await wallet.getBalance();
|
|
226
|
-
const spendable = this.toNumber(balance?.available);
|
|
227
|
-
return {
|
|
228
|
-
channelsBalanceMsat: spendable * 1000,
|
|
229
|
-
maxPayableMsat: spendable * 1000,
|
|
230
|
-
onchainBalanceMsat: this.toNumber(balance?.boarding?.total) * 1000,
|
|
231
|
-
pendingOnchainBalanceMsat: 0,
|
|
232
|
-
maxReceivableMsat: 0,
|
|
233
|
-
inboundLiquidityMsats: 0,
|
|
234
|
-
connectedPeers: [],
|
|
235
|
-
utxos: 0,
|
|
236
|
-
};
|
|
237
485
|
}
|
|
238
486
|
async getBtcBalance() {
|
|
239
487
|
if (!this.isConnected()) {
|
|
240
|
-
throw new ProtocolError(
|
|
488
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
241
489
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
490
|
+
try {
|
|
491
|
+
const wallet = arkadeClientManager.getWallet();
|
|
492
|
+
const balance = await this.getWalletBalanceSummary(wallet);
|
|
493
|
+
// preconfirmed VTXOs are spendable (isSpendable = !vtxo.isSpent in the SDK),
|
|
494
|
+
// so include them in `confirmed` so the Withdraw UI sees the full spendable balance.
|
|
495
|
+
const confirmed = balance.available; // settled + preconfirmed
|
|
496
|
+
const total = balance.total;
|
|
497
|
+
const unconfirmed = Math.max(total - confirmed, 0);
|
|
498
|
+
return { confirmed, unconfirmed, total };
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
502
|
+
throw new ProtocolError(`Failed to get BTC balance: ${msg}`, "ARKADE", "BALANCE_ERROR");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async listChannels() {
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
async listPayments() {
|
|
509
|
+
const txs = await this.listTransactions();
|
|
510
|
+
return { payments: txs };
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Get all VTXOs, sorted by batchExpiry ascending (expiry-first).
|
|
514
|
+
* This ensures UI consumers see soon-to-expire VTXOs first, and any
|
|
515
|
+
* manual coin selection naturally picks the shortest-lived coins.
|
|
516
|
+
*/
|
|
517
|
+
async getVtxos() {
|
|
518
|
+
if (!this.isConnected()) {
|
|
519
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const wallet = arkadeClientManager.getWallet();
|
|
523
|
+
const vtxos = await wallet.getVtxos();
|
|
524
|
+
const sorted = sortVtxosByExpiry(vtxos);
|
|
525
|
+
return normalizeVtxos(sorted).map((vtxo) => ({
|
|
526
|
+
txid: vtxo.txid,
|
|
527
|
+
vout: vtxo.vout,
|
|
528
|
+
value: vtxo.value,
|
|
529
|
+
state: vtxo.state,
|
|
530
|
+
batchTxid: vtxo.batchTxid,
|
|
531
|
+
batchExpiry: vtxo.batchExpiry,
|
|
532
|
+
createdAt: vtxo.createdAt,
|
|
533
|
+
assets: vtxo.assets,
|
|
534
|
+
}));
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
538
|
+
throw new ProtocolError(`Failed to get VTXOs: ${msg}`, "ARKADE", "GET_VTXOS_ERROR");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async getBoardingUtxos() {
|
|
542
|
+
if (!this.isConnected()) {
|
|
543
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const wallet = arkadeClientManager.getWallet();
|
|
547
|
+
const utxos = await wallet.getBoardingUtxos();
|
|
548
|
+
return (utxos ?? []).map((u) => ({
|
|
549
|
+
txid: u.txid,
|
|
550
|
+
vout: u.vout,
|
|
551
|
+
value: u.value,
|
|
552
|
+
confirmed: u.status?.confirmed ?? false,
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
557
|
+
throw new ProtocolError(`Failed to get boarding UTXOs: ${msg}`, "ARKADE", "GET_BOARDING_UTXOS_ERROR");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Onboard — settle boarding UTXOs into VTXOs via a Commitment Transaction.
|
|
562
|
+
* Requires at least one confirmed boarding UTXO.
|
|
563
|
+
* Returns the commitment txid.
|
|
564
|
+
*/
|
|
565
|
+
async onboard() {
|
|
566
|
+
if (!this.isConnected()) {
|
|
567
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
568
|
+
}
|
|
569
|
+
try {
|
|
570
|
+
const wallet = arkadeClientManager.getWallet();
|
|
571
|
+
// Get current fee info from server
|
|
572
|
+
const info = await wallet.arkProvider.getInfo();
|
|
573
|
+
const commitmentTxid = await new Ramps(wallet).onboard(info.fees);
|
|
574
|
+
return { txid: commitmentTxid };
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
578
|
+
throw new ProtocolError(`Onboard failed: ${msg}`, "ARKADE", "ONBOARD_ERROR");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Offboard — collaborative exit: convert VTXOs back to an on-chain Bitcoin UTXO.
|
|
583
|
+
* @param address Bitcoin on-chain destination (bc1/tb1)
|
|
584
|
+
* @param amount Optional sats to offboard; undefined = exit all
|
|
585
|
+
*/
|
|
586
|
+
async offboard(address, amount) {
|
|
587
|
+
if (!this.isConnected()) {
|
|
588
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
589
|
+
}
|
|
590
|
+
if (!address) {
|
|
591
|
+
throw new ProtocolError("Destination address required for offboard", "ARKADE", "INVALID_ADDRESS");
|
|
592
|
+
}
|
|
593
|
+
if (amount !== undefined && (!Number.isInteger(amount) || amount <= 0)) {
|
|
594
|
+
throw new ProtocolError(`Invalid offboard amount: ${amount} (must be a positive integer of sats)`, "ARKADE", "INVALID_AMOUNT");
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
const wallet = arkadeClientManager.getWallet();
|
|
598
|
+
const info = await wallet.arkProvider.getInfo();
|
|
599
|
+
const exitTxid = await new Ramps(wallet).offboard(address, info.fees, amount !== undefined ? BigInt(amount) : undefined);
|
|
600
|
+
return { txid: exitTxid };
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
604
|
+
throw new ProtocolError(`Offboard failed: ${msg}`, "ARKADE", "OFFBOARD_ERROR");
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async listTransfers(_options) {
|
|
608
|
+
return { transfers: [] };
|
|
609
|
+
}
|
|
610
|
+
// =========================================================================
|
|
611
|
+
// Asset / On-chain Send
|
|
612
|
+
// =========================================================================
|
|
613
|
+
async sendAsset(params) {
|
|
614
|
+
if (!this.isConnected()) {
|
|
615
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
616
|
+
}
|
|
617
|
+
const request = (params ?? {});
|
|
618
|
+
const assetId = toStringValue(request.assetId);
|
|
619
|
+
const amount = toPositiveIntegerBigInt(request.amount);
|
|
620
|
+
const recipientId = toStringValue(request.recipientId);
|
|
621
|
+
if (!assetId) {
|
|
622
|
+
throw new ProtocolError("Asset ID is required", "ARKADE", "INVALID_ASSET");
|
|
623
|
+
}
|
|
624
|
+
if (!recipientId) {
|
|
625
|
+
throw new ProtocolError("Recipient address is required", "ARKADE", "INVALID_ADDRESS");
|
|
626
|
+
}
|
|
627
|
+
if (amount <= 0n) {
|
|
628
|
+
throw new ProtocolError("Amount must be greater than zero", "ARKADE", "INVALID_AMOUNT");
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
const wallet = arkadeClientManager.getWallet();
|
|
632
|
+
const txid = await wallet.send({
|
|
633
|
+
address: recipientId,
|
|
634
|
+
assets: [{ assetId, amount }],
|
|
635
|
+
});
|
|
636
|
+
return { txid };
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
640
|
+
throw new ProtocolError(`Failed to send Arkade asset: ${msg}`, "ARKADE", "SEND_ASSET_ERROR");
|
|
641
|
+
}
|
|
642
|
+
}
|
|
255
643
|
async sendBtcOnchain(params) {
|
|
256
644
|
if (!this.isConnected()) {
|
|
257
|
-
throw new ProtocolError(
|
|
645
|
+
throw new ProtocolError("Not connected", "ARKADE", "NOT_CONNECTED");
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const wallet = arkadeClientManager.getWallet();
|
|
649
|
+
const selectedVtxos = await this.selectSpendableBtcVtxos(wallet, params.amount);
|
|
650
|
+
const txid = await wallet.sendBitcoin({
|
|
651
|
+
address: params.address,
|
|
652
|
+
amount: params.amount,
|
|
653
|
+
...(selectedVtxos ? { selectedVtxos } : {}),
|
|
654
|
+
});
|
|
655
|
+
return { txid };
|
|
258
656
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
657
|
+
catch (error) {
|
|
658
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
659
|
+
throw new ProtocolError(`Failed to send BTC: ${msg}`, "ARKADE", "SEND_BTC_ERROR");
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// =========================================================================
|
|
663
|
+
// Swap Operations (Not supported)
|
|
664
|
+
// =========================================================================
|
|
665
|
+
supportsSwaps() {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
async getSwapQuote(_request) {
|
|
669
|
+
throw new ProtocolError("Not supported", "ARKADE", "NOT_SUPPORTED");
|
|
670
|
+
}
|
|
671
|
+
async executeSwap(_quote) {
|
|
672
|
+
throw new ProtocolError("Not supported", "ARKADE", "NOT_SUPPORTED");
|
|
262
673
|
}
|
|
263
|
-
|
|
674
|
+
async getSwapStatus(_swapId) {
|
|
675
|
+
throw new ProtocolError("Not supported", "ARKADE", "NOT_SUPPORTED");
|
|
676
|
+
}
|
|
677
|
+
// =========================================================================
|
|
678
|
+
// Message Signing
|
|
679
|
+
// =========================================================================
|
|
680
|
+
async signMessage(message) {
|
|
681
|
+
if (!this.config?.mnemonic) {
|
|
682
|
+
throw new ProtocolError("Wallet mnemonic not available", "ARKADE", "NOT_CONNECTED");
|
|
683
|
+
}
|
|
684
|
+
const seed = mnemonicToSeedSync(this.config.mnemonic);
|
|
685
|
+
const node = HDKey.fromMasterSeed(seed).derive("m/138'/1");
|
|
686
|
+
if (!node.privateKey) {
|
|
687
|
+
throw new ProtocolError("Failed to derive message-signing key", "ARKADE", "KEY_DERIVATION_ERROR");
|
|
688
|
+
}
|
|
689
|
+
return signLnMessage(message, node.privateKey);
|
|
690
|
+
}
|
|
691
|
+
async verifyMessage(message, signature) {
|
|
692
|
+
return verifyLnMessage(message, signature);
|
|
693
|
+
}
|
|
694
|
+
// =========================================================================
|
|
264
695
|
// Private Helpers
|
|
265
|
-
//
|
|
266
|
-
|
|
696
|
+
// =========================================================================
|
|
697
|
+
/**
|
|
698
|
+
* Pre-select spendable BTC VTXOs for a sendBitcoin call using the
|
|
699
|
+
* expiry-first policy. Returns `undefined` (not an empty array) when no
|
|
700
|
+
* override should be applied — that lets the SDK fall back to its own
|
|
701
|
+
* selection when the fetch fails, the spendable set can't cover the target,
|
|
702
|
+
* or the target is non-positive. Mixed-asset VTXOs are filtered out.
|
|
703
|
+
*/
|
|
704
|
+
async selectSpendableBtcVtxos(wallet, targetSats) {
|
|
705
|
+
if (!Number.isFinite(targetSats) || targetSats <= 0)
|
|
706
|
+
return undefined;
|
|
267
707
|
try {
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
type: isSend ? 'send' : 'receive',
|
|
287
|
-
status: tx.settled || !isSend ? 'confirmed' : 'pending',
|
|
288
|
-
timestamp, amount: amountSats,
|
|
289
|
-
amountDisplay: this.formatSats(amountSats),
|
|
290
|
-
fee: 0, feeDisplay: '0.00000000',
|
|
291
|
-
asset: btcAsset,
|
|
292
|
-
protocolData: { type: tx.type, settled: tx.settled, key: tx.key },
|
|
293
|
-
};
|
|
708
|
+
const raw = await wallet.getVtxos();
|
|
709
|
+
const list = Array.isArray(raw)
|
|
710
|
+
? raw
|
|
711
|
+
: Array.isArray(raw?.vtxos)
|
|
712
|
+
? raw.vtxos
|
|
713
|
+
: [];
|
|
714
|
+
const spendableBtc = list.filter((vtxo) => {
|
|
715
|
+
if (!isSpendable(vtxo))
|
|
716
|
+
return false;
|
|
717
|
+
// Exclude VTXOs carrying assets — sendBitcoin would either reject
|
|
718
|
+
// them or accidentally burn the asset side. Pure BTC only.
|
|
719
|
+
const assets = vtxo.assets;
|
|
720
|
+
if (Array.isArray(assets) && assets.length > 0)
|
|
721
|
+
return false;
|
|
722
|
+
return true;
|
|
723
|
+
});
|
|
724
|
+
const selected = selectVtxosByExpiry(spendableBtc, targetSats);
|
|
725
|
+
return selected ?? undefined;
|
|
294
726
|
}
|
|
295
|
-
catch {
|
|
296
|
-
|
|
727
|
+
catch (error) {
|
|
728
|
+
log.warn("[ArkadeAdapter] selectSpendableBtcVtxos failed; falling back to SDK default selection:", error);
|
|
729
|
+
return undefined;
|
|
297
730
|
}
|
|
298
731
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
732
|
+
async getWalletBalanceSummary(wallet) {
|
|
733
|
+
const balance = await wallet.getBalance();
|
|
734
|
+
const normalized = {
|
|
735
|
+
boardingConfirmed: toNumber(balance?.boarding?.confirmed),
|
|
736
|
+
boardingUnconfirmed: toNumber(balance?.boarding?.unconfirmed),
|
|
737
|
+
boardingTotal: toNumber(balance?.boarding?.total),
|
|
738
|
+
settled: toNumber(balance?.settled),
|
|
739
|
+
preconfirmed: toNumber(balance?.preconfirmed),
|
|
740
|
+
available: toNumber(balance?.available),
|
|
741
|
+
recoverable: toNumber(balance?.recoverable),
|
|
742
|
+
total: toNumber(balance?.total),
|
|
743
|
+
};
|
|
744
|
+
let normalizedVtxos = [];
|
|
745
|
+
try {
|
|
746
|
+
normalizedVtxos = normalizeVtxos(await wallet.getVtxos());
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
log.warn("[ArkadeAdapter] Failed to derive balance from VTXOs, falling back to wallet.getBalance()", error);
|
|
307
750
|
}
|
|
308
|
-
|
|
751
|
+
if (normalizedVtxos.length === 0) {
|
|
752
|
+
// Mirror the vtxo path: boarding UTXOs must be counted in total even
|
|
753
|
+
// when there are no VTXOs. The SDK's top-level balance.total omits the
|
|
754
|
+
// boarding portion, so we compute it the same way as the vtxo path below.
|
|
755
|
+
const available = normalized.settled + normalized.preconfirmed;
|
|
756
|
+
return {
|
|
757
|
+
...normalized,
|
|
758
|
+
available,
|
|
759
|
+
total: normalized.boardingTotal + available + normalized.recoverable,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
const vtxoSummary = normalizedVtxos.reduce((summary, vtxo) => {
|
|
763
|
+
if (vtxo.state === "swept") {
|
|
764
|
+
summary.recoverable += vtxo.value;
|
|
765
|
+
}
|
|
766
|
+
else if (vtxo.state === "preconfirmed") {
|
|
767
|
+
summary.preconfirmed += vtxo.value;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
summary.settled += vtxo.value;
|
|
771
|
+
}
|
|
772
|
+
return summary;
|
|
773
|
+
}, {
|
|
774
|
+
settled: 0,
|
|
775
|
+
preconfirmed: 0,
|
|
776
|
+
recoverable: 0,
|
|
777
|
+
});
|
|
778
|
+
const available = vtxoSummary.settled + vtxoSummary.preconfirmed;
|
|
779
|
+
const total = normalized.boardingTotal + available + vtxoSummary.recoverable;
|
|
780
|
+
return {
|
|
781
|
+
...normalized,
|
|
782
|
+
settled: vtxoSummary.settled,
|
|
783
|
+
preconfirmed: vtxoSummary.preconfirmed,
|
|
784
|
+
available,
|
|
785
|
+
recoverable: vtxoSummary.recoverable,
|
|
786
|
+
total,
|
|
787
|
+
};
|
|
309
788
|
}
|
|
310
|
-
|
|
311
|
-
|
|
789
|
+
async getCachedAssetDetails(wallet, assetId) {
|
|
790
|
+
if (this.assetDetailsCache.has(assetId)) {
|
|
791
|
+
return this.assetDetailsCache.get(assetId) ?? null;
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
const details = await wallet.assetManager.getAssetDetails(assetId);
|
|
795
|
+
const normalized = details && typeof details === "object" ? details : null;
|
|
796
|
+
this.assetDetailsCache.set(assetId, normalized);
|
|
797
|
+
return normalized;
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
log.warn("[ArkadeAdapter] Failed to fetch asset details for", assetId, error);
|
|
801
|
+
this.assetDetailsCache.set(assetId, null);
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
312
804
|
}
|
|
313
805
|
}
|
|
314
806
|
//# sourceMappingURL=ArkadeAdapter.js.map
|