hodl-wallet 2.0.4 → 2.0.6

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 (83) hide show
  1. package/README.md +79 -2
  2. package/dist/agent-cli.js +75 -0
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-errors.d.ts +1 -1
  5. package/dist/agent-errors.js.map +1 -1
  6. package/dist/index.js +148 -82
  7. package/dist/index.js.map +1 -1
  8. package/dist/network/arb.d.ts +1 -0
  9. package/dist/network/arb.js +2 -1
  10. package/dist/network/arb.js.map +1 -1
  11. package/dist/network/avax.d.ts +1 -0
  12. package/dist/network/avax.js +2 -1
  13. package/dist/network/avax.js.map +1 -1
  14. package/dist/network/bsc.d.ts +1 -0
  15. package/dist/network/bsc.js +2 -1
  16. package/dist/network/bsc.js.map +1 -1
  17. package/dist/network/eth.d.ts +1 -0
  18. package/dist/network/eth.js +2 -1
  19. package/dist/network/eth.js.map +1 -1
  20. package/dist/network/ftm.d.ts +1 -0
  21. package/dist/network/ftm.js +2 -1
  22. package/dist/network/ftm.js.map +1 -1
  23. package/dist/network/hyperliquid.d.ts +1 -0
  24. package/dist/network/hyperliquid.js +2 -1
  25. package/dist/network/hyperliquid.js.map +1 -1
  26. package/dist/network/lib/BitcoinNetwork.d.ts +2 -0
  27. package/dist/network/lib/BitcoinNetwork.js +23 -15
  28. package/dist/network/lib/BitcoinNetwork.js.map +1 -1
  29. package/dist/network/lib/Web3Network.js +14 -2
  30. package/dist/network/lib/Web3Network.js.map +1 -1
  31. package/dist/network/op.d.ts +1 -0
  32. package/dist/network/op.js +2 -1
  33. package/dist/network/op.js.map +1 -1
  34. package/dist/network/pol.d.ts +1 -0
  35. package/dist/network/pol.js +2 -1
  36. package/dist/network/pol.js.map +1 -1
  37. package/dist/network/types.d.ts +1 -0
  38. package/dist/network-registry.d.ts +1 -0
  39. package/dist/network-registry.js +3 -0
  40. package/dist/network-registry.js.map +1 -1
  41. package/dist/persist.d.ts +2 -0
  42. package/dist/persist.js +21 -0
  43. package/dist/persist.js.map +1 -1
  44. package/dist/profile-lock.d.ts +4 -0
  45. package/dist/profile-lock.js +9 -1
  46. package/dist/profile-lock.js.map +1 -1
  47. package/dist/swap/bitcoin.d.ts +34 -0
  48. package/dist/swap/bitcoin.js +179 -0
  49. package/dist/swap/bitcoin.js.map +1 -0
  50. package/dist/swap/chain.d.ts +49 -0
  51. package/dist/swap/chain.js +255 -0
  52. package/dist/swap/chain.js.map +1 -0
  53. package/dist/swap/config.d.ts +68 -0
  54. package/dist/swap/config.js +46 -0
  55. package/dist/swap/config.js.map +1 -0
  56. package/dist/swap/http.d.ts +14 -0
  57. package/dist/swap/http.js +70 -0
  58. package/dist/swap/http.js.map +1 -0
  59. package/dist/swap/providers.d.ts +24 -0
  60. package/dist/swap/providers.js +424 -0
  61. package/dist/swap/providers.js.map +1 -0
  62. package/dist/swap/routes.d.ts +145 -0
  63. package/dist/swap/routes.js +48 -0
  64. package/dist/swap/routes.js.map +1 -0
  65. package/dist/swap/service.d.ts +104 -0
  66. package/dist/swap/service.js +512 -0
  67. package/dist/swap/service.js.map +1 -0
  68. package/dist/swap/storage.d.ts +3 -0
  69. package/dist/swap/storage.js +28 -0
  70. package/dist/swap/storage.js.map +1 -0
  71. package/dist/swap/types.d.ts +140 -0
  72. package/dist/swap/types.js +2 -0
  73. package/dist/swap/types.js.map +1 -0
  74. package/dist/swap/ui.d.ts +3 -0
  75. package/dist/swap/ui.js +257 -0
  76. package/dist/swap/ui.js.map +1 -0
  77. package/dist/transfer-service.d.ts +38 -0
  78. package/dist/transfer-service.js +181 -0
  79. package/dist/transfer-service.js.map +1 -0
  80. package/dist/wallet-service.d.ts +22 -6
  81. package/dist/wallet-service.js +36 -171
  82. package/dist/wallet-service.js.map +1 -1
  83. package/package.json +99 -92
@@ -0,0 +1,140 @@
1
+ import type { WalletAccount } from '../network/types.js';
2
+ export type SwapRouteId = 'bsc-btc' | 'btc-bsc';
3
+ export type SwapProviderId = 'chainflip' | 'thorchain';
4
+ export type SwapState = 'quoted' | 'preparing' | 'approval_pending' | 'deposit_pending' | 'deposit_confirmed' | 'swapping' | 'payout_pending' | 'btc_pending' | 'btc_confirming' | 'output_pending' | 'output_confirming' | 'completed' | 'partial_pending' | 'partial_completed' | 'refund_pending' | 'refunded' | 'failed' | 'needs_attention';
5
+ export type SwapInput = {
6
+ routeId: SwapRouteId;
7
+ from: string;
8
+ to: string;
9
+ amount: string;
10
+ amountBaseUnits: string;
11
+ };
12
+ export type SwapFee = {
13
+ label: string;
14
+ asset: string;
15
+ amountBaseUnits: string;
16
+ decimals: number;
17
+ };
18
+ export type ProviderQuote = {
19
+ provider: SwapProviderId;
20
+ expectedBaseUnits: string;
21
+ minimumBaseUnits: string;
22
+ expiresAt: number;
23
+ estimatedSeconds: number | null;
24
+ fees: SwapFee[];
25
+ details: Record<string, unknown>;
26
+ };
27
+ export type SwapQuote = SwapInput & ProviderQuote & {
28
+ id: string;
29
+ createdAt: number;
30
+ funding: FundingEstimate;
31
+ netOutputBaseUnits: string;
32
+ costBps: number;
33
+ costUsd: string;
34
+ reference: SwapPrices;
35
+ };
36
+ export type SwapPrices = {
37
+ btc: string;
38
+ bnb: string;
39
+ usdt: string;
40
+ updatedAt: number;
41
+ };
42
+ export type SwapPlan = {
43
+ depositAddress: string;
44
+ expiresAt: number;
45
+ providerId?: string;
46
+ router?: string;
47
+ memo?: string;
48
+ expirySeconds?: number;
49
+ channelExpiryBlock?: string;
50
+ };
51
+ export type SwapStep = {
52
+ kind: 'reset_approval' | 'approve' | 'deposit';
53
+ rawTransaction: string;
54
+ hash: string;
55
+ nonce?: string;
56
+ confirmed: boolean;
57
+ broadcastAttempted: boolean;
58
+ };
59
+ export type SwapOperation = {
60
+ id: string;
61
+ quote: SwapQuote;
62
+ state: SwapState;
63
+ createdAt: number;
64
+ updatedAt: number;
65
+ lastCheckedAt?: number;
66
+ message?: string;
67
+ updateError?: string;
68
+ plan?: SwapPlan;
69
+ openingChannel?: boolean;
70
+ steps: SwapStep[];
71
+ payoutHash?: string;
72
+ payoutBaseUnits?: string;
73
+ confirmations?: number;
74
+ refundHash?: string;
75
+ refundBaseUnits?: string;
76
+ refundConfirmations?: number;
77
+ history: Array<{
78
+ state: SwapState;
79
+ at: number;
80
+ }>;
81
+ };
82
+ export type ProviderProgress = {
83
+ settlementComplete?: boolean;
84
+ state: 'deposit_pending' | 'deposit_confirmed' | 'swapping' | 'payout_pending' | 'refund_pending' | 'needs_attention';
85
+ payoutHash?: string;
86
+ payoutBaseUnits?: string;
87
+ refundHash?: string;
88
+ refundBaseUnits?: string;
89
+ message?: string;
90
+ };
91
+ export interface SwapProvider {
92
+ id: SwapProviderId;
93
+ quote(input: SwapInput): Promise<ProviderQuote>;
94
+ prepare(quote: SwapQuote): Promise<SwapPlan>;
95
+ validate(quote: SwapQuote, plan: SwapPlan): Promise<void>;
96
+ status(operation: SwapOperation): Promise<ProviderProgress>;
97
+ }
98
+ export type ChainReceipt = {
99
+ state: 'not_found' | 'pending' | 'confirmed' | 'reverted';
100
+ confirmations: number;
101
+ };
102
+ export type BitcoinInput = {
103
+ txid: string;
104
+ vout: number;
105
+ value: number;
106
+ };
107
+ export type FundingEstimate = {
108
+ asset: 'BNB' | 'BTC';
109
+ decimals: number;
110
+ price: 'bnb' | 'btc';
111
+ budgetBaseUnits: string;
112
+ rate: string;
113
+ units: number;
114
+ inputs?: BitcoinInput[];
115
+ };
116
+ export type FundingBalance = {
117
+ sufficientAsset: boolean;
118
+ sufficientFee: boolean;
119
+ };
120
+ export interface SwapChain {
121
+ availableBalance(address: string): Promise<bigint>;
122
+ estimate(input: SwapInput, offer: ProviderQuote): Promise<FundingEstimate>;
123
+ balance(input: SwapInput, funding: FundingEstimate): Promise<FundingBalance>;
124
+ checkFunding(quote: SwapQuote, steps: SwapStep[]): Promise<void>;
125
+ assertAvailable(address: string): Promise<void>;
126
+ nextStep(quote: SwapQuote, plan: SwapPlan, steps: SwapStep[]): Promise<SwapStep['kind']>;
127
+ sign(quote: SwapQuote, plan: SwapPlan, kind: SwapStep['kind'], account: WalletAccount): Promise<SwapStep>;
128
+ broadcast(step: SwapStep): Promise<void>;
129
+ receipt(hash: string): Promise<ChainReceipt>;
130
+ blockNumber(): Promise<bigint>;
131
+ payoutPayment(hash: string, address: string): Promise<{
132
+ amount: bigint;
133
+ confirmations: number;
134
+ }>;
135
+ refundPayment(hash: string, address: string): Promise<{
136
+ amount: bigint;
137
+ confirmations: number;
138
+ }>;
139
+ }
140
+ export declare const terminalSwapStates: readonly SwapState[];
@@ -0,0 +1,2 @@
1
+ export const terminalSwapStates = ['completed', 'partial_completed', 'refunded', 'failed'];
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../swap/types.ts"],"names":[],"mappings":"AA6GA,MAAM,CAAC,MAAM,kBAAkB,GAAyB,CAAC,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { SwapService } from './service.js';
2
+ export declare function startSwapMenu(service: SwapService): Promise<void>;
3
+ export declare function trackSwapsMenu(service: SwapService): Promise<void>;
@@ -0,0 +1,257 @@
1
+ import crypto from 'node:crypto';
2
+ import readline from 'node:readline';
3
+ import { setTimeout as delay } from 'node:timers/promises';
4
+ import inquirer from 'inquirer';
5
+ import Table from 'cli-table3';
6
+ import ora from 'ora';
7
+ import { formatUnits, normalizeDecimal, parseDecimalToUnits } from '../amounts.js';
8
+ import { swapConfig } from './config.js';
9
+ import { terminalSwapStates } from './types.js';
10
+ const labels = {
11
+ quoted: 'Quote ready', preparing: 'Preparing deposit', approval_pending: 'Waiting for token approval',
12
+ deposit_pending: 'Deposit sent · waiting for confirmation', deposit_confirmed: 'Deposit recognized',
13
+ swapping: 'Exchanging assets', payout_pending: 'Preparing payout', btc_pending: 'BTC sent · waiting for inclusion',
14
+ btc_confirming: 'BTC received · confirming', output_pending: 'Payout sent · waiting for inclusion',
15
+ output_confirming: 'Payout received · confirming', completed: 'Completed', partial_pending: 'Payout and refund · awaiting confirmation',
16
+ partial_completed: 'Partially exchanged · payout received and remainder returned', refund_pending: 'Refund pending',
17
+ refunded: 'Swap refunded', failed: 'Not completed · deposit not transferred', needs_attention: 'Needs attention'
18
+ };
19
+ const providerNames = { chainflip: 'Chainflip', thorchain: 'THORChain' };
20
+ async function withSpinner(text, work) {
21
+ const spinner = ora({ text, spinner: 'dots' }).start();
22
+ try {
23
+ return await work();
24
+ }
25
+ finally {
26
+ spinner.stop();
27
+ }
28
+ }
29
+ export async function startSwapMenu(service) {
30
+ try {
31
+ const { source, destination } = service.route;
32
+ const { route } = await inquirer.prompt({
33
+ type: 'list', name: 'route', message: 'Swap:',
34
+ choices: [
35
+ { name: `${source.label} → ${destination.label}`, value: 'new' },
36
+ { name: 'Track Swaps', value: 'track' },
37
+ { name: 'Go Back', value: 'back' }
38
+ ]
39
+ });
40
+ if (route === 'back')
41
+ return;
42
+ if (route === 'track') {
43
+ await trackSwapsMenu(service);
44
+ return;
45
+ }
46
+ const existing = (await withSpinner('Checking existing swaps…', () => service.list())).find(swap => !terminalSwapStates.includes(swap.state));
47
+ if (existing) {
48
+ console.log(`An unfinished swap already exists: ${existing.requestId}. Open Track Swaps to check its status.`);
49
+ return;
50
+ }
51
+ const available = await withSpinner('Checking available balance…', () => service.availableBalance());
52
+ if (available <= 0n) {
53
+ console.log(`No ${source.label} available to swap.`);
54
+ return;
55
+ }
56
+ const balance = formatUnits(available, source.decimals);
57
+ const { amount } = await inquirer.prompt({
58
+ type: 'input', name: 'amount', message: `${source.label} to exchange (max, empty to cancel):`,
59
+ validate(value) {
60
+ if (!value.trim() || value.trim().toLowerCase() === 'max')
61
+ return true;
62
+ try {
63
+ return parseDecimalToUnits(normalizeDecimal(value.trim()), source.decimals) <= available
64
+ || `Insufficient ${source.label}. Available: ${balance}.`;
65
+ }
66
+ catch (error) {
67
+ return error.message;
68
+ }
69
+ }
70
+ });
71
+ if (!amount.trim())
72
+ return;
73
+ const ownAddress = await withSpinner(`Loading ${destination.label} account…`, () => service.destination());
74
+ const addresses = ownAddress ? [{ name: 'My account', address: ownAddress }] : [];
75
+ if (!ownAddress)
76
+ console.log(`No ${destination.label} account is configured in this profile. Create or import an account on that network, then return to Swap; or enter an external address.`);
77
+ const { destination: to } = await inquirer.prompt({
78
+ type: 'autocomplete', name: 'destination', message: `Receive ${destination.label} at:`,
79
+ source: (_answers, input = '') => {
80
+ const search = (input || '').trim();
81
+ const matches = addresses.filter(entry => entry.name.toLowerCase().includes(search.toLowerCase()) || entry.address.toLowerCase().includes(search.toLowerCase())).map(entry => ({ name: `${entry.name} (${entry.address})`, value: entry.address }));
82
+ if (search && !matches.some(entry => entry.value === search))
83
+ matches.push({ name: search, value: search });
84
+ return [
85
+ ...(!ownAddress && !search ? [{ name: 'My account', value: '', disabled: `${destination.label} account not configured` }] : []),
86
+ ...matches, { name: 'Go Back', value: '' }
87
+ ];
88
+ }
89
+ });
90
+ if (!to)
91
+ return;
92
+ const maximum = amount.trim().toLowerCase() === 'max';
93
+ let comparison = await withSpinner('Comparing Chainflip and THORChain, including network fees…', () => service.quote(maximum ? balance : amount.trim(), to));
94
+ if (maximum && source.asset === 'BTC' && comparison.quotes.length) {
95
+ const reserve = comparison.quotes.reduce((largest, quote) => {
96
+ const fee = parseDecimalToUnits(quote.funding.budget, source.decimals);
97
+ return fee > largest ? fee : largest;
98
+ }, 0n) + BigInt(swapConfig.bitcoin.changeDustSats);
99
+ if (available <= reserve) {
100
+ console.log('Insufficient BTC for the network fee and required change.');
101
+ return;
102
+ }
103
+ const maximumAmount = formatUnits(available - reserve, source.decimals);
104
+ console.log(`Max: ${maximumAmount} BTC · reserving ${formatUnits(reserve, source.decimals)} BTC for network fees and required change.`);
105
+ comparison = await withSpinner('Updating quotes for max amount…', () => service.quote(maximumAmount, to));
106
+ }
107
+ for (const unavailable of comparison.unavailable)
108
+ console.log(`${unavailable.provider}: ${unavailable.reason}`);
109
+ comparison.quotes = comparison.quotes.filter(quote => {
110
+ if (quote.sufficientAsset && quote.sufficientFee)
111
+ return true;
112
+ console.log(`${providerNames[quote.provider]}: insufficient ${!quote.sufficientAsset ? source.label : quote.funding.asset + ' for the deposit and network fee budget'}.`);
113
+ return false;
114
+ });
115
+ if (!comparison.quotes.length) {
116
+ console.log('No executable quote available. No funds were sent.');
117
+ return;
118
+ }
119
+ comparison.recommendedQuoteId = comparison.quotes[0].quoteId;
120
+ const { quoteId } = await inquirer.prompt({
121
+ type: 'list', name: 'quoteId', message: 'Choose a quote:',
122
+ choices: [
123
+ ...comparison.quotes.map(quote => ({
124
+ name: `${providerNames[quote.provider]} · ≈ ${quote.destination.estimated} ${quote.destination.asset} · ≈ ${quote.estimatedTotalCost.percent}% cost${quote.quoteId === comparison.recommendedQuoteId ? ' · best after gas' : ''}`,
125
+ value: quote.quoteId
126
+ })), { name: 'Cancel', value: 'cancel' }
127
+ ]
128
+ });
129
+ if (quoteId === 'cancel')
130
+ return;
131
+ const quote = comparison.quotes.find(item => item.quoteId === quoteId);
132
+ console.log(`\n ${quote.source.amount} ${quote.source.asset} → ≈ ${quote.destination.estimated} ${quote.destination.asset}`);
133
+ console.log(` ${source.label} → ${destination.label} · ${providerNames[quote.provider]} · ${quote.execution}\n`);
134
+ const table = new Table({
135
+ wordWrap: true,
136
+ colWidths: [11, Math.max(40, Math.min(70, (process.stdout.columns || 80) - 14))],
137
+ chars: { 'top-left': '╭', 'top-right': '╮', 'bottom-left': '╰', 'bottom-right': '╯' },
138
+ style: { 'padding-left': 1, 'padding-right': 1, compact: true }
139
+ });
140
+ table.push({ From: quote.from }, { To: quote.to }, { Minimum: `${quote.destination.minimum} ${quote.destination.asset} · full swap · ${quote.slippagePercent}% tolerance` }, { Cost: `≈ ${quote.estimatedTotalCost.percent}% (USD ${Number(quote.estimatedTotalCost.usd).toFixed(2)}) · incl. price impact + gas` }, { 'Network fee': `Up to ${quote.funding.budget} ${quote.funding.asset} extra on ${quote.source.network.toUpperCase()}` }, { Time: `${quote.estimatedSeconds === null ? '' : `≈ ${Math.ceil(quote.estimatedSeconds / 60)} min + `}${quote.destination.confirmations} ${quote.destination.asset} confirmations to complete` }, { Refund: `${source.label} → sender above, minus fees` }, { Expires: new Date(quote.expiresAt).toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, ' UTC') });
141
+ console.log(table.toString());
142
+ console.log(`\n Provider fees already deducted from estimated ${destination.asset}. Source network fee is extra.`);
143
+ if (quote.refund.partialResultPossible)
144
+ console.log(` Partial fill possible: ${destination.asset} received + remaining ${source.asset} refunded minus fees.\n The minimum above applies only to a full swap.`);
145
+ console.log(` Once exchanged, delayed ${destination.asset} cannot auto-refund to ${source.asset}.\n`);
146
+ const { confirmed } = await inquirer.prompt({
147
+ type: 'confirm', name: 'confirmed', default: false, message: 'Accept destination, cost and minimum, and swap?'
148
+ });
149
+ if (!confirmed)
150
+ return;
151
+ const operation = await withSpinner('Preparing and submitting swap…', () => service.execute(quoteId, crypto.randomUUID()));
152
+ showSwap(operation);
153
+ if (!terminalSwapStates.includes(operation.state))
154
+ await followSwap(service, operation, true);
155
+ }
156
+ catch (error) {
157
+ console.error(error instanceof Error ? error.message : 'Swap could not be started.');
158
+ }
159
+ }
160
+ export async function trackSwapsMenu(service) {
161
+ try {
162
+ const refreshed = new Set();
163
+ const swaps = await withSpinner('Updating swap statuses…', async () => {
164
+ const entries = await service.list();
165
+ for (let i = 0; i < entries.length; i++) {
166
+ if (!terminalSwapStates.includes(entries[i].state)) {
167
+ entries[i] = await service.status(entries[i].requestId);
168
+ refreshed.add(entries[i].requestId);
169
+ }
170
+ }
171
+ return entries;
172
+ });
173
+ if (!swaps.length) {
174
+ console.log('No swaps recorded.');
175
+ return;
176
+ }
177
+ const { requestId } = await inquirer.prompt({
178
+ type: 'list', name: 'requestId', message: 'Select a swap:',
179
+ choices: [
180
+ ...swaps.map(swap => ({ name: `${swap.createdAt} · ${swap.quote.source.amount} ${swap.quote.source.asset} (${swap.quote.source.network}) → ${swap.quote.destination.asset} (${swap.quote.destination.network}) · ${labels[swap.state]}${swap.updateError ? ' · Status update unavailable' : ''} · ${swap.requestId}`, value: swap.requestId })),
181
+ { name: 'Go back', value: 'back' }
182
+ ]
183
+ });
184
+ if (requestId === 'back')
185
+ return;
186
+ const current = refreshed.has(requestId) ? swaps.find(swap => swap.requestId === requestId) :
187
+ await withSpinner('Checking swap status…', () => service.status(requestId));
188
+ showSwap(current, current.state !== swaps.find(swap => swap.requestId === requestId).state);
189
+ if (terminalSwapStates.includes(current.state))
190
+ return;
191
+ await followSwap(service, current, false);
192
+ }
193
+ catch (error) {
194
+ console.error(error instanceof Error ? error.message : 'Could not update swaps.');
195
+ }
196
+ }
197
+ async function followSwap(service, initial, resume) {
198
+ console.log('Updating automatically. Press Enter to return to the menu. Leaving this screen does not cancel funds already sent.');
199
+ const controller = new AbortController();
200
+ const spinner = ora({ text: 'Updating swap…', spinner: 'dots', discardStdin: false }).start();
201
+ controller.signal.addEventListener('abort', () => spinner.stop(), { once: true });
202
+ const input = readline.createInterface({ input: process.stdin, output: process.stdout });
203
+ input.once('line', () => controller.abort());
204
+ input.once('close', () => controller.abort());
205
+ let previous = swapDisplayKey(initial);
206
+ try {
207
+ while (!controller.signal.aborted) {
208
+ const operation = await (resume ? service.resume(initial.requestId) : service.status(initial.requestId));
209
+ if (controller.signal.aborted)
210
+ return;
211
+ const signature = swapDisplayKey(operation);
212
+ if (signature !== previous) {
213
+ spinner.stop();
214
+ showSwap(operation);
215
+ previous = signature;
216
+ }
217
+ if (terminalSwapStates.includes(operation.state) || operation.state === 'needs_attention')
218
+ return;
219
+ spinner.start(`${labels[operation.state]}${operation.message ? ` · ${operation.message}` : ''} · Enter to return`);
220
+ try {
221
+ await delay(swapConfig.pollIntervalMs, undefined, { signal: controller.signal });
222
+ }
223
+ catch {
224
+ if (!controller.signal.aborted)
225
+ throw new Error('Progress timer failed.');
226
+ }
227
+ }
228
+ }
229
+ finally {
230
+ spinner.stop();
231
+ input.close();
232
+ }
233
+ }
234
+ function swapDisplayKey(operation) {
235
+ return JSON.stringify([operation.state, operation.confirmations, operation.updateError, operation.transactions, operation.delayed, operation.message, operation.payout, operation.refund, operation.refundConfirmations]);
236
+ }
237
+ function showSwap(swap, showHeading = true) {
238
+ if (showHeading)
239
+ console.log(`\nSwap ${swap.requestId}: ${labels[swap.state]} · ${swap.quote.source.asset} (${swap.quote.source.network}) → ${swap.quote.destination.asset} (${swap.quote.destination.network})`);
240
+ if (swap.payout)
241
+ console.log(`${swap.quote.destination.asset} confirmations: ${swap.confirmations}/${swap.requiredConfirmations}`);
242
+ for (const transaction of swap.transactions)
243
+ console.log(`${transaction.kind}: ${transaction.explorer}`);
244
+ if (swap.payout)
245
+ console.log(`${swap.quote.destination.asset} payout: ${swap.payout.amount} ${swap.quote.destination.asset} · ${swap.payout.explorer}`);
246
+ if (swap.refund) {
247
+ console.log(`${swap.quote.source.asset} refund: ${swap.refund.amount} ${swap.quote.source.asset} · ${swap.refund.explorer}`);
248
+ console.log(`${swap.quote.source.network.toUpperCase()} refund confirmations: ${swap.refundConfirmations}/${swap.requiredRefundConfirmations}`);
249
+ }
250
+ if (swap.message)
251
+ console.log(swap.message);
252
+ if (swap.updateError)
253
+ console.log(`Could not update or continue: ${swap.updateError} Last successful check: ${swap.lastCheckedAt || 'none'}.`);
254
+ if (swap.delayed)
255
+ console.log('This swap is taking longer than expected. Keep its ID and transaction links; do not send another deposit.');
256
+ }
257
+ //# sourceMappingURL=ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../swap/ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import Persist from './persist.js';
2
+ import type { BaseNetworkContract, NetworkPlugin, PreparedTransfer } from './network/types.js';
3
+ type StoredSendRequest = PreparedTransfer & {
4
+ fingerprint: string;
5
+ state: 'prepared' | 'broadcasting' | 'submitted' | 'confirmed' | 'failed' | 'broadcast_unknown';
6
+ network: string;
7
+ createdAt: string;
8
+ updatedAt: string;
9
+ };
10
+ type TransferRequest = {
11
+ wallet: string;
12
+ to: string;
13
+ asset: string;
14
+ amount: string;
15
+ dryRun: boolean;
16
+ requestId?: string;
17
+ };
18
+ export type TransferResult = Omit<PreparedTransfer, 'rawTransaction'> & {
19
+ network: string;
20
+ status: StoredSendRequest['state'] | 'dry-run';
21
+ requestId?: string;
22
+ };
23
+ export declare class TransferService {
24
+ private readonly db;
25
+ private readonly plugin;
26
+ private readonly network;
27
+ constructor(db: Persist, plugin: NetworkPlugin, network: BaseNetworkContract);
28
+ list(): Promise<Array<TransferResult & {
29
+ createdAt: string;
30
+ }>>;
31
+ send(request: TransferRequest): Promise<TransferResult>;
32
+ private save;
33
+ private failed;
34
+ private transactionStatus;
35
+ private prepare;
36
+ private publicTransfer;
37
+ }
38
+ export {};
@@ -0,0 +1,181 @@
1
+ import crypto from 'node:crypto';
2
+ import { normalizeDecimal } from './amounts.js';
3
+ import { AgentError } from './agent-errors.js';
4
+ import Persist from './persist.js';
5
+ import { assertNoActiveSwap } from './swap/service.js';
6
+ const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
7
+ // The caller owns the connected database and holds its profile lock.
8
+ export class TransferService {
9
+ db;
10
+ plugin;
11
+ network;
12
+ constructor(db, plugin, network) {
13
+ this.db = db;
14
+ this.plugin = plugin;
15
+ this.network = network;
16
+ }
17
+ async list() {
18
+ const entries = await this.db.entries('sendRequest') || [];
19
+ try {
20
+ return entries.filter(([, stored]) => stored.network === this.plugin.id)
21
+ .map(([id, stored]) => ({ ...this.publicTransfer(stored, stored.state, id), createdAt: stored.createdAt }));
22
+ }
23
+ finally {
24
+ Persist.clearSensitiveData(entries);
25
+ }
26
+ }
27
+ async send(request) {
28
+ const asset = request.asset.toUpperCase();
29
+ if (asset !== this.plugin.nativeToken && !this.plugin.tokens[asset]) {
30
+ throw new AgentError('INVALID_ARGUMENT', `Asset ${asset} is not configured for ${this.plugin.id}.`, 2);
31
+ }
32
+ let amount;
33
+ try {
34
+ amount = request.amount.trim().toLowerCase() === 'max' ? 'max' : normalizeDecimal(request.amount);
35
+ }
36
+ catch (error) {
37
+ throw new AgentError('INVALID_ARGUMENT', error.message, 2);
38
+ }
39
+ if (!request.dryRun && (!request.requestId || !REQUEST_ID_PATTERN.test(request.requestId))) {
40
+ throw new AgentError('INVALID_ARGUMENT', 'A valid request ID is required.', 2);
41
+ }
42
+ const account = await this.db.get('account', this.plugin.NetworkClass.name);
43
+ if (!account) {
44
+ throw new AgentError('ACCOUNT_NOT_FOUND', `Wallet profile has no ${this.plugin.family} account.`, 3);
45
+ }
46
+ let stored;
47
+ try {
48
+ if (request.dryRun) {
49
+ const prepared = await this.prepare(account, request.to, amount, asset);
50
+ try {
51
+ return this.publicTransfer(prepared, 'dry-run');
52
+ }
53
+ finally {
54
+ Persist.clearSensitiveData(prepared);
55
+ }
56
+ }
57
+ const requestId = request.requestId;
58
+ stored = await this.db.get('sendRequest', requestId) ?? undefined;
59
+ if (amount === 'max' && stored)
60
+ amount = stored.amount;
61
+ const fingerprint = () => crypto.createHash('sha256').update(JSON.stringify({
62
+ wallet: request.wallet,
63
+ network: this.plugin.id,
64
+ chainId: this.plugin.chainId ?? null,
65
+ from: this.plugin.family === 'evm' ? account.address.toLowerCase() : account.address,
66
+ to: this.plugin.family === 'evm' ? request.to.toLowerCase() : request.to,
67
+ asset,
68
+ amount
69
+ })).digest('hex');
70
+ if (stored && stored.fingerprint !== fingerprint()) {
71
+ throw new AgentError('IDEMPOTENCY_CONFLICT', 'Request ID was already used with different transfer parameters.', 3);
72
+ }
73
+ if (stored) {
74
+ if (stored.state === 'failed')
75
+ throw this.failed(stored, requestId);
76
+ if (stored.state === 'confirmed')
77
+ return this.publicTransfer(stored, stored.state, requestId);
78
+ if (stored.state !== 'prepared') {
79
+ const status = await this.transactionStatus(stored, requestId);
80
+ if (status.state !== 'not_found') {
81
+ stored.state = status.state;
82
+ await this.save(stored, requestId);
83
+ if (stored.state === 'failed')
84
+ throw this.failed(stored, requestId);
85
+ return this.publicTransfer(stored, stored.state, requestId);
86
+ }
87
+ if (stored.state === 'submitted') {
88
+ throw new AgentError('BROADCAST_UNKNOWN', 'The previously submitted transaction is no longer reported by the provider.', 5, { transactionHash: stored.transactionHash, requestId });
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ if (this.plugin.id === 'bsc' || this.plugin.id === 'btc')
94
+ await assertNoActiveSwap(this.db);
95
+ const unresolved = (await this.list()).find(candidate => candidate.status === 'prepared' || candidate.status === 'broadcasting' || candidate.status === 'broadcast_unknown');
96
+ if (unresolved) {
97
+ throw new AgentError('BROADCAST_UNKNOWN', 'Another transfer on this network has unresolved broadcast state.', 5, { requestId: unresolved.requestId, transactionHash: unresolved.transactionHash });
98
+ }
99
+ const prepared = await this.prepare(account, request.to, amount, asset);
100
+ const now = new Date().toISOString();
101
+ if (amount === 'max')
102
+ amount = prepared.amount;
103
+ stored = { ...prepared, fingerprint: fingerprint(), state: 'prepared', network: this.plugin.id, createdAt: now, updatedAt: now };
104
+ Persist.clearSensitiveData(prepared);
105
+ await this.save(stored, requestId);
106
+ }
107
+ stored.state = 'broadcasting';
108
+ await this.save(stored, requestId);
109
+ let status;
110
+ try {
111
+ await this.network.sendSignedTransaction(stored.rawTransaction);
112
+ status = this.plugin.family === 'bitcoin' ? 'submitted' : 'confirmed';
113
+ }
114
+ catch {
115
+ try {
116
+ const observed = await this.network.getTransactionStatus(stored.transactionHash);
117
+ status = observed.state === 'not_found' ? 'broadcast_unknown' : observed.state;
118
+ }
119
+ catch {
120
+ status = 'broadcast_unknown';
121
+ }
122
+ }
123
+ stored.state = status;
124
+ await this.save(stored, requestId);
125
+ if (status === 'failed')
126
+ throw this.failed(stored, requestId);
127
+ if (status === 'broadcast_unknown') {
128
+ throw new AgentError('BROADCAST_UNKNOWN', 'The provider did not confirm whether the signed transaction was accepted.', 5, { transactionHash: stored.transactionHash, requestId });
129
+ }
130
+ return this.publicTransfer(stored, status, requestId);
131
+ }
132
+ finally {
133
+ Persist.clearSensitiveData(account);
134
+ Persist.clearSensitiveData(stored);
135
+ }
136
+ }
137
+ async save(stored, requestId) {
138
+ stored.updatedAt = new Date().toISOString();
139
+ await this.db.set('sendRequest', requestId, stored);
140
+ await this.db.flush();
141
+ }
142
+ failed(stored, requestId) {
143
+ return new AgentError('TRANSFER_FAILED', 'The transaction was mined but reverted.', 5, { transactionHash: stored.transactionHash, requestId });
144
+ }
145
+ async transactionStatus(stored, requestId) {
146
+ try {
147
+ return await this.network.getTransactionStatus(stored.transactionHash);
148
+ }
149
+ catch {
150
+ throw new AgentError('NETWORK_ERROR', 'Unable to update the saved transaction status. Retry with the same request ID.', 4, { transactionHash: stored.transactionHash, requestId });
151
+ }
152
+ }
153
+ async prepare(account, to, amount, asset) {
154
+ if (!this.network.validateAddress(to)) {
155
+ throw new AgentError('INVALID_ARGUMENT', 'Invalid recipient address.', 2);
156
+ }
157
+ try {
158
+ return await this.network.prepareTransfer(account, to, amount, asset);
159
+ }
160
+ catch (error) {
161
+ if (error instanceof TypeError)
162
+ throw new AgentError('INVALID_ARGUMENT', error.message, 2);
163
+ throw new AgentError('TRANSFER_FAILED', error.message, 5);
164
+ }
165
+ }
166
+ publicTransfer(prepared, status, requestId) {
167
+ return {
168
+ ...(requestId ? { requestId } : {}),
169
+ status,
170
+ network: this.plugin.id,
171
+ from: prepared.from,
172
+ to: prepared.to,
173
+ asset: prepared.asset,
174
+ amount: prepared.amount,
175
+ amountBaseUnits: prepared.amountBaseUnits,
176
+ fee: prepared.fee,
177
+ transactionHash: prepared.transactionHash
178
+ };
179
+ }
180
+ }
181
+ //# sourceMappingURL=transfer-service.js.map