@agentlayer.tech/wallet 0.1.0

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.
Files changed (96) hide show
  1. package/.openclaw/AGENTS.md +98 -0
  2. package/.openclaw/extensions/agent-wallet/README.md +127 -0
  3. package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
  5. package/.openclaw/extensions/agent-wallet/package.json +11 -0
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
  7. package/CHANGELOG.md +42 -0
  8. package/LICENSE +104 -0
  9. package/README.md +332 -0
  10. package/RELEASING.md +204 -0
  11. package/agent-wallet/.env.example +62 -0
  12. package/agent-wallet/AGENTS.md +129 -0
  13. package/agent-wallet/README.md +527 -0
  14. package/agent-wallet/agent_wallet/__init__.py +11 -0
  15. package/agent-wallet/agent_wallet/approval.py +161 -0
  16. package/agent-wallet/agent_wallet/bootstrap.py +178 -0
  17. package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
  18. package/agent-wallet/agent_wallet/config.py +382 -0
  19. package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
  20. package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
  21. package/agent-wallet/agent_wallet/exceptions.py +9 -0
  22. package/agent-wallet/agent_wallet/file_ops.py +34 -0
  23. package/agent-wallet/agent_wallet/http_client.py +25 -0
  24. package/agent-wallet/agent_wallet/models.py +66 -0
  25. package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
  26. package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
  27. package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
  28. package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
  29. package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
  30. package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
  31. package/agent-wallet/agent_wallet/providers/bags.py +259 -0
  32. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
  33. package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
  34. package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
  35. package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
  36. package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
  37. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
  38. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
  39. package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
  40. package/agent-wallet/agent_wallet/solana_stake.py +103 -0
  41. package/agent-wallet/agent_wallet/solana_tx.py +93 -0
  42. package/agent-wallet/agent_wallet/spending_limits.py +101 -0
  43. package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
  44. package/agent-wallet/agent_wallet/user_wallets.py +355 -0
  45. package/agent-wallet/agent_wallet/validation.py +31 -0
  46. package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
  47. package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
  48. package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
  49. package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
  50. package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
  51. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
  52. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
  53. package/agent-wallet/examples/bootstrap_wallet.py +21 -0
  54. package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
  55. package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
  56. package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
  57. package/agent-wallet/openclaw.plugin.json +138 -0
  58. package/agent-wallet/pyproject.toml +31 -0
  59. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
  60. package/agent-wallet/scripts/build_release_bundle.py +188 -0
  61. package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
  62. package/agent-wallet/scripts/install_agent_wallet.py +505 -0
  63. package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
  64. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
  65. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
  66. package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
  67. package/agent-wallet/scripts/security_utils.py +37 -0
  68. package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
  69. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
  70. package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
  71. package/bin/openclaw-agent-wallet.mjs +487 -0
  72. package/install-from-github.sh +134 -0
  73. package/package.json +61 -0
  74. package/setup.sh +40 -0
  75. package/wdk-btc-wallet/README.md +325 -0
  76. package/wdk-btc-wallet/bootstrap.sh +22 -0
  77. package/wdk-btc-wallet/package-lock.json +1839 -0
  78. package/wdk-btc-wallet/package.json +18 -0
  79. package/wdk-btc-wallet/run-local.sh +21 -0
  80. package/wdk-btc-wallet/src/config.js +160 -0
  81. package/wdk-btc-wallet/src/json.js +35 -0
  82. package/wdk-btc-wallet/src/local_vault.js +432 -0
  83. package/wdk-btc-wallet/src/network_state.js +84 -0
  84. package/wdk-btc-wallet/src/server.js +257 -0
  85. package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
  86. package/wdk-evm-wallet/README.md +183 -0
  87. package/wdk-evm-wallet/bootstrap.sh +8 -0
  88. package/wdk-evm-wallet/package-lock.json +2340 -0
  89. package/wdk-evm-wallet/package.json +23 -0
  90. package/wdk-evm-wallet/run-local.sh +12 -0
  91. package/wdk-evm-wallet/src/config.js +274 -0
  92. package/wdk-evm-wallet/src/json.js +35 -0
  93. package/wdk-evm-wallet/src/local_vault.js +430 -0
  94. package/wdk-evm-wallet/src/network_state.js +92 -0
  95. package/wdk-evm-wallet/src/server.js +575 -0
  96. package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ const NETWORK_FILE = "network.json";
5
+
6
+ function assertValidNetwork(network, fieldName = "network") {
7
+ const normalized = String(network ?? "").trim();
8
+ if (!["bitcoin", "testnet", "regtest"].includes(normalized)) {
9
+ throw new Error(`${fieldName} must be one of: bitcoin, testnet, regtest.`);
10
+ }
11
+ return normalized;
12
+ }
13
+
14
+ export class BtcNetworkState {
15
+ constructor(config) {
16
+ this.config = config;
17
+ }
18
+
19
+ async getActiveNetwork() {
20
+ const state = await this.#loadState();
21
+ return state.network;
22
+ }
23
+
24
+ async getNetworkInfo(networkOverride = undefined) {
25
+ const activeNetwork = networkOverride
26
+ ? assertValidNetwork(networkOverride)
27
+ : await this.getActiveNetwork();
28
+ return {
29
+ activeNetwork,
30
+ profiles: this.config.networkProfiles,
31
+ selectedProfile: this.config.networkProfiles[activeNetwork],
32
+ };
33
+ }
34
+
35
+ async setActiveNetwork({ network }) {
36
+ const nextNetwork = assertValidNetwork(network);
37
+ await this.#ensureLayout();
38
+ await fs.writeFile(this.#statePath(), JSON.stringify({ network: nextNetwork }, null, 2), {
39
+ encoding: "utf8",
40
+ mode: 0o600,
41
+ });
42
+ return this.getNetworkInfo(nextNetwork);
43
+ }
44
+
45
+ async resolveRuntimeConfig(networkOverride = undefined) {
46
+ const activeNetwork = networkOverride
47
+ ? assertValidNetwork(networkOverride, "network")
48
+ : await this.getActiveNetwork();
49
+ const profile = this.config.networkProfiles[activeNetwork];
50
+ return {
51
+ ...this.config,
52
+ network: activeNetwork,
53
+ electrumProtocol: profile.electrumProtocol,
54
+ electrumHost: profile.electrumHost,
55
+ electrumPort: profile.electrumPort,
56
+ };
57
+ }
58
+
59
+ async #ensureLayout() {
60
+ await fs.mkdir(this.config.dataDir, { recursive: true, mode: 0o700 });
61
+ try {
62
+ await fs.access(this.#statePath());
63
+ } catch {
64
+ await fs.writeFile(
65
+ this.#statePath(),
66
+ JSON.stringify({ network: this.config.network }, null, 2),
67
+ { encoding: "utf8", mode: 0o600 }
68
+ );
69
+ }
70
+ }
71
+
72
+ async #loadState() {
73
+ await this.#ensureLayout();
74
+ const raw = await fs.readFile(this.#statePath(), "utf8");
75
+ const parsed = JSON.parse(raw);
76
+ return {
77
+ network: assertValidNetwork(parsed.network),
78
+ };
79
+ }
80
+
81
+ #statePath() {
82
+ return path.join(this.config.dataDir, NETWORK_FILE);
83
+ }
84
+ }
@@ -0,0 +1,257 @@
1
+ import "dotenv/config";
2
+
3
+ import crypto from "node:crypto";
4
+ import { createServer } from "node:http";
5
+
6
+ import { loadConfig } from "./config.js";
7
+ import { jsonSafe, readJsonBody, sendJson } from "./json.js";
8
+ import { LocalBtcVault } from "./local_vault.js";
9
+ import { BtcNetworkState } from "./network_state.js";
10
+ import { WdkBtcWalletService } from "./wdk_btc_wallet.js";
11
+
12
+ const config = loadConfig();
13
+ const service = new WdkBtcWalletService(config);
14
+ const vault = new LocalBtcVault(config);
15
+ const networkState = new BtcNetworkState(config);
16
+
17
+ function notFound(response) {
18
+ sendJson(response, 404, { ok: false, error: "Not Found" });
19
+ }
20
+
21
+ function unauthorized(response) {
22
+ response.setHeader("WWW-Authenticate", 'Bearer realm="wdk-btc-wallet"');
23
+ sendJson(response, 401, { ok: false, error: "Unauthorized." });
24
+ }
25
+
26
+ function pathRequiresAuth(pathname) {
27
+ return pathname !== "/health";
28
+ }
29
+
30
+ function isAuthorized(request) {
31
+ const header = String(request.headers.authorization || "").trim();
32
+ if (!header.startsWith("Bearer ")) {
33
+ return false;
34
+ }
35
+ const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
36
+ const expected = Buffer.from(String(config.authToken || ""), "utf8");
37
+ if (provided.length === 0 || provided.length !== expected.length) {
38
+ return false;
39
+ }
40
+ return crypto.timingSafeEqual(provided, expected);
41
+ }
42
+
43
+ async function withResolvedSeed(body = {}) {
44
+ const resolved = await vault.resolveSeedPhrase({
45
+ walletId: body.walletId,
46
+ seedPhrase: body.seedPhrase,
47
+ });
48
+ return {
49
+ ...body,
50
+ seedPhrase: resolved.seedPhrase,
51
+ walletId: resolved.walletId ?? body.walletId ?? null,
52
+ credentialSource: resolved.source,
53
+ unlockExpiresAt: resolved.unlockExpiresAt ?? null,
54
+ };
55
+ }
56
+
57
+ async function withResolvedNetwork(body = {}) {
58
+ const runtimeConfig = await networkState.resolveRuntimeConfig(body.network);
59
+ return {
60
+ ...body,
61
+ network: runtimeConfig.network,
62
+ };
63
+ }
64
+
65
+ async function handleRequest(request, response) {
66
+ try {
67
+ const url = new URL(request.url || "/", "http://localhost");
68
+ const { method = "GET" } = request;
69
+
70
+ if (pathRequiresAuth(url.pathname) && !isAuthorized(request)) {
71
+ return unauthorized(response);
72
+ }
73
+
74
+ if (method === "GET" && url.pathname === "/health") {
75
+ const runtimeConfig = await networkState.resolveRuntimeConfig();
76
+ const networkInfo = await networkState.getNetworkInfo();
77
+ return sendJson(response, 200, {
78
+ ok: true,
79
+ service: "wdk-btc-wallet",
80
+ version: "0.1.0",
81
+ wallet: "bitcoin",
82
+ network: runtimeConfig.network,
83
+ bip: config.bip,
84
+ host: config.host,
85
+ dataDir: config.dataDir,
86
+ authRequired: config.authRequired,
87
+ unlockTimeoutSeconds: config.unlockTimeoutSeconds,
88
+ availableNetworks: Object.keys(config.networkProfiles),
89
+ electrum: {
90
+ protocol: runtimeConfig.electrumProtocol,
91
+ host: runtimeConfig.electrumHost,
92
+ port: runtimeConfig.electrumPort,
93
+ },
94
+ networkProfiles: networkInfo.profiles,
95
+ source: "wdk",
96
+ });
97
+ }
98
+
99
+ if (method === "POST" && url.pathname === "/v1/btc/seed-phrase/generate") {
100
+ const body = await readJsonBody(request);
101
+ return sendJson(response, 200, {
102
+ ok: true,
103
+ data: service.generateSeedPhrase(body.words ?? 12),
104
+ });
105
+ }
106
+
107
+ if (method === "GET" && url.pathname === "/v1/btc/wallets") {
108
+ const activeNetwork = await networkState.getActiveNetwork();
109
+ return sendJson(response, 200, {
110
+ ok: true,
111
+ data: (await vault.listWallets()).map((wallet) => ({
112
+ ...wallet,
113
+ activeNetwork,
114
+ })),
115
+ });
116
+ }
117
+
118
+ if (method === "GET" && url.pathname === "/v1/btc/network") {
119
+ return sendJson(response, 200, {
120
+ ok: true,
121
+ data: await networkState.getNetworkInfo(),
122
+ });
123
+ }
124
+
125
+ if (method === "POST" && url.pathname === "/v1/btc/network/set") {
126
+ const body = await readJsonBody(request);
127
+ return sendJson(response, 200, {
128
+ ok: true,
129
+ data: await networkState.setActiveNetwork(body),
130
+ });
131
+ }
132
+
133
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/get") {
134
+ const body = await readJsonBody(request);
135
+ const activeNetwork = await networkState.getActiveNetwork();
136
+ return sendJson(response, 200, {
137
+ ok: true,
138
+ data: {
139
+ ...(await vault.getWallet(body)),
140
+ activeNetwork,
141
+ },
142
+ });
143
+ }
144
+
145
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/create") {
146
+ const body = await withResolvedNetwork(await readJsonBody(request));
147
+ return sendJson(response, 200, {
148
+ ok: true,
149
+ data: await vault.createWallet(body),
150
+ });
151
+ }
152
+
153
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/import") {
154
+ const body = await withResolvedNetwork(await readJsonBody(request));
155
+ return sendJson(response, 200, {
156
+ ok: true,
157
+ data: await vault.importWallet(body),
158
+ });
159
+ }
160
+
161
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/unlock") {
162
+ const body = await readJsonBody(request);
163
+ return sendJson(response, 200, {
164
+ ok: true,
165
+ data: await vault.unlockWallet(body),
166
+ });
167
+ }
168
+
169
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/lock") {
170
+ const body = await readJsonBody(request);
171
+ return sendJson(response, 200, {
172
+ ok: true,
173
+ data: await vault.lockWallet(body),
174
+ });
175
+ }
176
+
177
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/reveal-seed") {
178
+ const body = await readJsonBody(request);
179
+ return sendJson(response, 200, {
180
+ ok: true,
181
+ data: await vault.revealSeedPhrase(body),
182
+ });
183
+ }
184
+
185
+ if (method === "POST" && url.pathname === "/v1/btc/wallets/change-password") {
186
+ const body = await readJsonBody(request);
187
+ return sendJson(response, 200, {
188
+ ok: true,
189
+ data: await vault.changePassword(body),
190
+ });
191
+ }
192
+
193
+ if (method === "POST" && url.pathname === "/v1/btc/address/resolve") {
194
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
195
+ const data = await service.resolveAddress(body);
196
+ return sendJson(response, 200, { ok: true, data });
197
+ }
198
+
199
+ if (method === "POST" && url.pathname === "/v1/btc/balance/get") {
200
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
201
+ const data = await service.getBalance(body);
202
+ return sendJson(response, 200, { ok: true, data });
203
+ }
204
+
205
+ if (method === "POST" && url.pathname === "/v1/btc/transfers/get") {
206
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
207
+ const data = await service.getTransfers(body);
208
+ return sendJson(response, 200, { ok: true, data });
209
+ }
210
+
211
+ if (method === "POST" && url.pathname === "/v1/btc/max-spendable/get") {
212
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
213
+ const data = await service.getMaxSpendable(body);
214
+ return sendJson(response, 200, { ok: true, data });
215
+ }
216
+
217
+ if (method === "POST" && url.pathname === "/v1/btc/fee-rates/get") {
218
+ const body = await withResolvedNetwork(await readJsonBody(request));
219
+ const data = await service.getFeeRates(body);
220
+ return sendJson(response, 200, { ok: true, data });
221
+ }
222
+
223
+ if (method === "POST" && url.pathname === "/v1/btc/transfer/quote") {
224
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
225
+ const data = await service.quoteTransfer(body);
226
+ return sendJson(response, 200, { ok: true, data: jsonSafe(data) });
227
+ }
228
+
229
+ if (method === "POST" && url.pathname === "/v1/btc/transfer/send") {
230
+ const body = await withResolvedNetwork(await withResolvedSeed(await readJsonBody(request)));
231
+ const data = await service.sendTransfer(body);
232
+ return sendJson(response, 200, { ok: true, data: jsonSafe(data) });
233
+ }
234
+
235
+ return notFound(response);
236
+ } catch (error) {
237
+ return sendJson(response, 400, {
238
+ ok: false,
239
+ error: error instanceof Error ? error.message : String(error),
240
+ });
241
+ }
242
+ }
243
+
244
+ const server = createServer((request, response) => {
245
+ handleRequest(request, response).catch((error) => {
246
+ sendJson(response, 500, {
247
+ ok: false,
248
+ error: error instanceof Error ? error.message : String(error),
249
+ });
250
+ });
251
+ });
252
+
253
+ server.listen(config.port, config.host, () => {
254
+ console.log(
255
+ `wdk-btc-wallet listening on ${config.host}:${config.port} (${config.network}, bip${config.bip})`
256
+ );
257
+ });
@@ -0,0 +1,332 @@
1
+ import WDK from "@tetherto/wdk";
2
+ import WalletManagerBtc, {
3
+ ElectrumSsl,
4
+ ElectrumTcp,
5
+ ElectrumTls,
6
+ ElectrumWs,
7
+ } from "@tetherto/wdk-wallet-btc";
8
+
9
+ function assertNonEmptyString(value, fieldName) {
10
+ if (typeof value !== "string" || !value.trim()) {
11
+ throw new Error(`${fieldName} is required.`);
12
+ }
13
+ return value.trim();
14
+ }
15
+
16
+ function assertValidSeedPhrase(seedPhrase) {
17
+ const mnemonic = assertNonEmptyString(seedPhrase, "seedPhrase");
18
+ if (!WDK.isValidSeed(mnemonic)) {
19
+ throw new Error("seedPhrase must be a valid BIP-39 seed phrase.");
20
+ }
21
+ return mnemonic;
22
+ }
23
+
24
+ function assertValidNetwork(network, fieldName = "network") {
25
+ if (network === undefined || network === null || network === "") {
26
+ return null;
27
+ }
28
+ const normalized = String(network).trim();
29
+ if (!["bitcoin", "testnet", "regtest"].includes(normalized)) {
30
+ throw new Error(`${fieldName} must be one of: bitcoin, testnet, regtest.`);
31
+ }
32
+ return normalized;
33
+ }
34
+
35
+ function assertNonNegativeInteger(value, fieldName) {
36
+ if (typeof value === "boolean") {
37
+ throw new Error(`${fieldName} must be a non-negative integer.`);
38
+ }
39
+ const parsed = Number(value);
40
+ if (!Number.isInteger(parsed) || parsed < 0) {
41
+ throw new Error(`${fieldName} must be a non-negative integer.`);
42
+ }
43
+ return parsed;
44
+ }
45
+
46
+ function assertPositiveInteger(value, fieldName) {
47
+ const parsed = assertNonNegativeInteger(value, fieldName);
48
+ if (parsed <= 0) {
49
+ throw new Error(`${fieldName} must be greater than zero.`);
50
+ }
51
+ return parsed;
52
+ }
53
+
54
+ function buildElectrumClient(config) {
55
+ const clientConfig = {
56
+ host: config.electrumHost,
57
+ port: config.electrumPort,
58
+ network: config.network,
59
+ };
60
+ if (config.electrumProtocol === "tcp") {
61
+ return new ElectrumTcp(clientConfig);
62
+ }
63
+ if (config.electrumProtocol === "tls") {
64
+ return new ElectrumTls(clientConfig);
65
+ }
66
+ if (config.electrumProtocol === "ssl") {
67
+ return new ElectrumSsl(clientConfig);
68
+ }
69
+ if (config.electrumProtocol === "ws") {
70
+ return new ElectrumWs(clientConfig);
71
+ }
72
+ throw new Error(`Unsupported Electrum protocol: ${config.electrumProtocol}`);
73
+ }
74
+
75
+ async function maybeDispose(value) {
76
+ if (value && typeof value.dispose === "function") {
77
+ await value.dispose();
78
+ }
79
+ if (value && typeof value.close === "function") {
80
+ await value.close();
81
+ }
82
+ }
83
+
84
+ export class WdkBtcWalletService {
85
+ constructor(config) {
86
+ this.config = config;
87
+ }
88
+
89
+ generateSeedPhrase(words = 12) {
90
+ const count = assertPositiveInteger(words, "words");
91
+ if (count !== 12) {
92
+ throw new Error(
93
+ "Only 12-word seed phrase generation is exposed by this service because that is the documented WDK helper surface."
94
+ );
95
+ }
96
+ return {
97
+ seedPhrase: WDK.getRandomSeedPhrase(),
98
+ wordCount: count,
99
+ source: "wdk",
100
+ };
101
+ }
102
+
103
+ async resolveAddress({ seedPhrase, accountIndex = 0, derivationPath = "", network }) {
104
+ return this.#withAccount(
105
+ { seedPhrase, accountIndex, derivationPath, network },
106
+ async (account, runtimeConfig) => ({
107
+ network: runtimeConfig.network,
108
+ bip: runtimeConfig.bip,
109
+ accountIndex: derivationPath ? null : accountIndex,
110
+ derivationPath: derivationPath || null,
111
+ address: await account.getAddress(),
112
+ source: "wdk-wallet-btc",
113
+ })
114
+ );
115
+ }
116
+
117
+ async getBalance({ seedPhrase, accountIndex = 0, derivationPath = "", network }) {
118
+ return this.#withAccount({ seedPhrase, accountIndex, derivationPath, network }, async (
119
+ account,
120
+ runtimeConfig
121
+ ) => {
122
+ const address = await account.getAddress();
123
+ const balance = await account.getBalance();
124
+ return {
125
+ network: runtimeConfig.network,
126
+ bip: runtimeConfig.bip,
127
+ accountIndex: derivationPath ? null : accountIndex,
128
+ derivationPath: derivationPath || null,
129
+ address,
130
+ balance,
131
+ source: "wdk-wallet-btc",
132
+ };
133
+ });
134
+ }
135
+
136
+ async getTransfers({
137
+ seedPhrase,
138
+ accountIndex = 0,
139
+ derivationPath = "",
140
+ network,
141
+ direction = "all",
142
+ limit = 10,
143
+ skip = 0,
144
+ }) {
145
+ return this.#withAccount(
146
+ { seedPhrase, accountIndex, derivationPath, network },
147
+ async (account, runtimeConfig) => {
148
+ if (!["incoming", "outgoing", "all"].includes(direction)) {
149
+ throw new Error("direction must be one of: incoming, outgoing, all.");
150
+ }
151
+ const options = {
152
+ direction,
153
+ limit: assertNonNegativeInteger(limit, "limit"),
154
+ skip: assertNonNegativeInteger(skip, "skip"),
155
+ };
156
+ const address = await account.getAddress();
157
+ const transfers = await account.getTransfers(options);
158
+ return {
159
+ network: runtimeConfig.network,
160
+ bip: runtimeConfig.bip,
161
+ accountIndex: derivationPath ? null : accountIndex,
162
+ derivationPath: derivationPath || null,
163
+ address,
164
+ options,
165
+ transfers,
166
+ source: "wdk-wallet-btc",
167
+ };
168
+ }
169
+ );
170
+ }
171
+
172
+ async getMaxSpendable({
173
+ seedPhrase,
174
+ accountIndex = 0,
175
+ derivationPath = "",
176
+ network,
177
+ feeRate,
178
+ }) {
179
+ return this.#withAccount(
180
+ { seedPhrase, accountIndex, derivationPath, network },
181
+ async (account, runtimeConfig) => {
182
+ const address = await account.getAddress();
183
+ const options = {};
184
+ if (feeRate !== undefined) {
185
+ options.feeRate = assertPositiveInteger(feeRate, "feeRate");
186
+ }
187
+ const maxSpendable = await account.getMaxSpendable(options);
188
+ return {
189
+ network: runtimeConfig.network,
190
+ bip: runtimeConfig.bip,
191
+ accountIndex: derivationPath ? null : accountIndex,
192
+ derivationPath: derivationPath || null,
193
+ address,
194
+ options,
195
+ maxSpendable,
196
+ source: "wdk-wallet-btc",
197
+ };
198
+ }
199
+ );
200
+ }
201
+
202
+ async getFeeRates({ seedPhrase = "", network } = {}) {
203
+ return this.#withWallet(
204
+ { seedPhrase: seedPhrase || WDK.getRandomSeedPhrase(), network },
205
+ async (wallet, runtimeConfig) => {
206
+ const feeRates = await wallet.getFeeRates();
207
+ return {
208
+ network: runtimeConfig.network,
209
+ feeRates,
210
+ source: "wdk-wallet-btc",
211
+ };
212
+ }
213
+ );
214
+ }
215
+
216
+ async quoteTransfer({
217
+ seedPhrase,
218
+ to,
219
+ value,
220
+ accountIndex = 0,
221
+ derivationPath = "",
222
+ network,
223
+ feeRate,
224
+ confirmationTarget,
225
+ }) {
226
+ return this.#withAccount(
227
+ { seedPhrase, accountIndex, derivationPath, network },
228
+ async (account, runtimeConfig) => {
229
+ const tx = this.#buildTransaction({ to, value, feeRate, confirmationTarget });
230
+ const quote = await account.quoteSendTransaction(tx);
231
+ return {
232
+ network: runtimeConfig.network,
233
+ bip: runtimeConfig.bip,
234
+ accountIndex: derivationPath ? null : accountIndex,
235
+ derivationPath: derivationPath || null,
236
+ transaction: tx,
237
+ quote,
238
+ source: "wdk-wallet-btc",
239
+ };
240
+ }
241
+ );
242
+ }
243
+
244
+ async sendTransfer({
245
+ seedPhrase,
246
+ to,
247
+ value,
248
+ accountIndex = 0,
249
+ derivationPath = "",
250
+ network,
251
+ feeRate,
252
+ confirmationTarget,
253
+ }) {
254
+ return this.#withAccount(
255
+ { seedPhrase, accountIndex, derivationPath, network },
256
+ async (account, runtimeConfig) => {
257
+ if (typeof account.sendTransaction !== "function") {
258
+ throw new Error("The current WDK BTC account does not expose sendTransaction.");
259
+ }
260
+ const tx = this.#buildTransaction({ to, value, feeRate, confirmationTarget });
261
+ const result = await account.sendTransaction(tx);
262
+ return {
263
+ network: runtimeConfig.network,
264
+ bip: runtimeConfig.bip,
265
+ accountIndex: derivationPath ? null : accountIndex,
266
+ derivationPath: derivationPath || null,
267
+ transaction: tx,
268
+ result,
269
+ source: "wdk-wallet-btc",
270
+ };
271
+ }
272
+ );
273
+ }
274
+
275
+ #buildTransaction({ to, value, feeRate, confirmationTarget }) {
276
+ const tx = {
277
+ to: assertNonEmptyString(to, "to"),
278
+ value: assertPositiveInteger(value, "value"),
279
+ };
280
+ if (feeRate !== undefined) {
281
+ tx.feeRate = assertPositiveInteger(feeRate, "feeRate");
282
+ }
283
+ if (confirmationTarget !== undefined) {
284
+ tx.confirmationTarget = assertPositiveInteger(
285
+ confirmationTarget,
286
+ "confirmationTarget"
287
+ );
288
+ }
289
+ return tx;
290
+ }
291
+
292
+ #resolveRuntimeConfig(networkOverride) {
293
+ const network = assertValidNetwork(networkOverride) || this.config.network;
294
+ const profile = this.config.networkProfiles?.[network];
295
+ if (!profile) {
296
+ throw new Error(`Missing Electrum profile for network: ${network}`);
297
+ }
298
+ return {
299
+ ...this.config,
300
+ network,
301
+ electrumProtocol: profile.electrumProtocol,
302
+ electrumHost: profile.electrumHost,
303
+ electrumPort: profile.electrumPort,
304
+ };
305
+ }
306
+
307
+ async #withWallet({ seedPhrase, network }, callback) {
308
+ const mnemonic = assertValidSeedPhrase(seedPhrase);
309
+ const runtimeConfig = this.#resolveRuntimeConfig(network);
310
+ const client = buildElectrumClient(runtimeConfig);
311
+ const wallet = new WalletManagerBtc(mnemonic, {
312
+ client,
313
+ network: runtimeConfig.network,
314
+ bip: runtimeConfig.bip,
315
+ });
316
+ try {
317
+ return await callback(wallet, runtimeConfig);
318
+ } finally {
319
+ await maybeDispose(wallet);
320
+ await maybeDispose(client);
321
+ }
322
+ }
323
+
324
+ async #withAccount({ seedPhrase, accountIndex, derivationPath, network }, callback) {
325
+ return this.#withWallet({ seedPhrase, network }, async (wallet, runtimeConfig) => {
326
+ const account = derivationPath
327
+ ? await wallet.getAccountByPath(assertNonEmptyString(derivationPath, "derivationPath"))
328
+ : await wallet.getAccount(assertNonNegativeInteger(accountIndex, "accountIndex"));
329
+ return await callback(account, runtimeConfig);
330
+ });
331
+ }
332
+ }