hodl-wallet 2.1.0 → 2.3.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 (117) hide show
  1. package/README.md +11 -11
  2. package/dist/app/account.d.ts +7 -0
  3. package/dist/app/account.js +248 -0
  4. package/dist/app/account.js.map +1 -0
  5. package/dist/app/format.d.ts +5 -0
  6. package/dist/app/format.js +33 -0
  7. package/dist/app/format.js.map +1 -0
  8. package/dist/app/history.d.ts +48 -0
  9. package/dist/app/history.js +115 -0
  10. package/dist/app/history.js.map +1 -0
  11. package/dist/app/index.d.ts +12 -0
  12. package/dist/app/index.js +9 -0
  13. package/dist/app/index.js.map +1 -0
  14. package/dist/app/menu.d.ts +13 -0
  15. package/dist/app/menu.js +93 -0
  16. package/dist/app/menu.js.map +1 -0
  17. package/dist/app/output.d.ts +17 -0
  18. package/dist/app/output.js +107 -0
  19. package/dist/app/output.js.map +1 -0
  20. package/dist/app/session.d.ts +125 -0
  21. package/dist/app/session.js +2 -0
  22. package/dist/app/session.js.map +1 -0
  23. package/dist/app/swap.d.ts +5 -0
  24. package/dist/app/swap.js +274 -0
  25. package/dist/app/swap.js.map +1 -0
  26. package/dist/app/transfer.d.ts +3 -0
  27. package/dist/app/transfer.js +144 -0
  28. package/dist/app/transfer.js.map +1 -0
  29. package/dist/app/ui.d.ts +73 -0
  30. package/dist/app/ui.js +15 -0
  31. package/dist/app/ui.js.map +1 -0
  32. package/dist/browser-core.d.ts +7 -1
  33. package/dist/browser-core.js +6 -1
  34. package/dist/browser-core.js.map +1 -1
  35. package/dist/config.d.ts +11 -0
  36. package/dist/config.js +12 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/hodl-file.d.ts +31 -0
  39. package/dist/hodl-file.js +78 -0
  40. package/dist/hodl-file.js.map +1 -0
  41. package/dist/index.js +24 -1096
  42. package/dist/index.js.map +1 -1
  43. package/dist/network/abis/erc20.d.ts +35 -13
  44. package/dist/network/abis/erc20.js +6 -26
  45. package/dist/network/abis/erc20.js.map +1 -1
  46. package/dist/network/arb.d.ts +5 -0
  47. package/dist/network/arb.js +5 -0
  48. package/dist/network/arb.js.map +1 -1
  49. package/dist/network/avax.d.ts +6 -0
  50. package/dist/network/avax.js +6 -0
  51. package/dist/network/avax.js.map +1 -1
  52. package/dist/network/bsc.d.ts +6 -0
  53. package/dist/network/bsc.js +6 -0
  54. package/dist/network/bsc.js.map +1 -1
  55. package/dist/network/eth.d.ts +6 -0
  56. package/dist/network/eth.js +6 -0
  57. package/dist/network/eth.js.map +1 -1
  58. package/dist/network/ftm.d.ts +6 -0
  59. package/dist/network/ftm.js +6 -0
  60. package/dist/network/ftm.js.map +1 -1
  61. package/dist/network/lib/BitcoinNetwork.d.ts +3 -1
  62. package/dist/network/lib/BitcoinNetwork.js +58 -13
  63. package/dist/network/lib/BitcoinNetwork.js.map +1 -1
  64. package/dist/network/lib/Web3Network.d.ts +10 -4
  65. package/dist/network/lib/Web3Network.js +141 -73
  66. package/dist/network/lib/Web3Network.js.map +1 -1
  67. package/dist/network/op.d.ts +5 -0
  68. package/dist/network/op.js +5 -0
  69. package/dist/network/op.js.map +1 -1
  70. package/dist/network/pol.d.ts +6 -0
  71. package/dist/network/pol.js +6 -0
  72. package/dist/network/pol.js.map +1 -1
  73. package/dist/network/types.d.ts +35 -0
  74. package/dist/network-registry.d.ts +6 -1
  75. package/dist/network-registry.js +28 -0
  76. package/dist/network-registry.js.map +1 -1
  77. package/dist/node-session.d.ts +53 -0
  78. package/dist/node-session.js +359 -0
  79. package/dist/node-session.js.map +1 -0
  80. package/dist/persist.d.ts +14 -7
  81. package/dist/persist.js +83 -108
  82. package/dist/persist.js.map +1 -1
  83. package/dist/request.d.ts +7 -0
  84. package/dist/request.js +26 -0
  85. package/dist/request.js.map +1 -0
  86. package/dist/swap/bitcoin.d.ts +1 -1
  87. package/dist/swap/bitcoin.js +7 -6
  88. package/dist/swap/bitcoin.js.map +1 -1
  89. package/dist/swap/chain.js +25 -40
  90. package/dist/swap/chain.js.map +1 -1
  91. package/dist/swap/config.d.ts +12 -0
  92. package/dist/swap/config.js +6 -0
  93. package/dist/swap/config.js.map +1 -1
  94. package/dist/swap/http.d.ts +2 -1
  95. package/dist/swap/http.js +8 -4
  96. package/dist/swap/http.js.map +1 -1
  97. package/dist/swap/providers.d.ts +15 -0
  98. package/dist/swap/providers.js +187 -1
  99. package/dist/swap/providers.js.map +1 -1
  100. package/dist/swap/routes.d.ts +28 -0
  101. package/dist/swap/routes.js +5 -3
  102. package/dist/swap/routes.js.map +1 -1
  103. package/dist/swap/service.d.ts +2 -1
  104. package/dist/swap/service.js +16 -5
  105. package/dist/swap/service.js.map +1 -1
  106. package/dist/swap/types.d.ts +3 -1
  107. package/dist/swap/types.js.map +1 -1
  108. package/dist/transfer-service.js +1 -2
  109. package/dist/transfer-service.js.map +1 -1
  110. package/dist/ui-inquirer.d.ts +37 -0
  111. package/dist/ui-inquirer.js +97 -0
  112. package/dist/ui-inquirer.js.map +1 -0
  113. package/dist/wallet-store.d.ts +2 -1
  114. package/package.json +17 -14
  115. package/dist/swap/ui.d.ts +0 -3
  116. package/dist/swap/ui.js +0 -257
  117. package/dist/swap/ui.js.map +0 -1
@@ -0,0 +1,93 @@
1
+ import { loadAccount } from './account.js';
2
+ import { errorMessage, formatTokenAmount } from './format.js';
3
+ import { transferRows } from './history.js';
4
+ import { showAddress, showError, transfersTable } from './output.js';
5
+ import { transferFunds } from './transfer.js';
6
+ /** No account exists after the account menu closed. Hosts that can show the menu again may do so quietly. */
7
+ export class AccountNotInitialized extends Error {
8
+ constructor() {
9
+ super('Failed to initialize account.');
10
+ this.name = 'AccountNotInitialized';
11
+ }
12
+ }
13
+ export async function showBalance(ui, session) {
14
+ if (!await session.hasAccount()) {
15
+ showError(ui, 'Account not initialized.');
16
+ return;
17
+ }
18
+ const balances = await session.tokenBalances();
19
+ ui.table({
20
+ head: ['Token', 'Balance'],
21
+ tone: 'blue',
22
+ colWidths: [21, 22],
23
+ rows: balances.map(([token, balance]) => [token, formatTokenAmount(token, balance)])
24
+ });
25
+ }
26
+ /** Sent transfers together with the network's ones in and out. Saved ones still show when the network cannot be read. */
27
+ export async function showTransactions(ui, session) {
28
+ const spinner = ui.spinner('Checking transfers...');
29
+ let sent, recent;
30
+ try {
31
+ sent = await session.sentTransfers();
32
+ recent = session.network.recentTransfers ? await session.recentTransfers() : { transfers: [] };
33
+ }
34
+ finally {
35
+ spinner.stop();
36
+ }
37
+ if (recent.error)
38
+ ui.print(`Could not check recent transfers: ${recent.error}`);
39
+ const known = [await session.address(), ...(await session.contacts?.list() ?? []).map(contact => contact.address)];
40
+ const { rows, hidden } = transferRows(sent, recent.transfers, known);
41
+ if (hidden)
42
+ ui.print(`Hidden ${hidden} ${hidden === 1 ? 'transfer' : 'transfers'} that look like address poisoning.`);
43
+ ui.table(transfersTable(session, rows));
44
+ }
45
+ export async function mainMenu(ui, session) {
46
+ for (;;) {
47
+ const action = await ui.select({
48
+ message: 'What would you like to do?',
49
+ choices: [
50
+ { name: 'Transfer Funds', value: 'transferFunds' },
51
+ { name: 'Show Balance', value: 'balance' },
52
+ { name: 'Show Transfers', value: 'showTransactions' },
53
+ { name: 'Account Settings', value: 'account' },
54
+ ...session.menuActions().map(item => ({ name: item.name, value: item })),
55
+ { name: 'Exit', value: 'exit' }
56
+ ]
57
+ });
58
+ if (action === 'exit')
59
+ return;
60
+ if (action === 'transferFunds')
61
+ await transferFunds(ui, session);
62
+ else if (action === 'balance')
63
+ await showBalance(ui, session);
64
+ else if (action === 'showTransactions')
65
+ await showTransactions(ui, session);
66
+ else if (action === 'account')
67
+ await loadAccount(ui, session, true);
68
+ else
69
+ await action.run(ui);
70
+ }
71
+ }
72
+ export async function initialize(ui, session) {
73
+ try {
74
+ await session.start();
75
+ if (await session.hasAccount()) {
76
+ await showAddress(ui, session);
77
+ return;
78
+ }
79
+ await loadAccount(ui, session);
80
+ if (!await session.hasAccount()) {
81
+ throw new AccountNotInitialized();
82
+ }
83
+ }
84
+ catch (error) {
85
+ throw new Error(`Initialization failed: ${errorMessage(error)}`, { cause: error });
86
+ }
87
+ }
88
+ /** The signed-in experience: pick or create an account, show its address, then the main menu. */
89
+ export async function runWallet(ui, session) {
90
+ await initialize(ui, session);
91
+ await mainMenu(ui, session);
92
+ }
93
+ //# sourceMappingURL=menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../app/menu.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { TransferRow, SentTransfer, WalletSession } from './session.js';
2
+ import type { TableSpec, Tone, Ui } from './ui.js';
3
+ export declare const WELCOME_ART = " \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2588 \u2588\u2003\u2588\u2580\u2588\u2003\u2588\u2580\u2584\u2003\u2588 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\n \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2588\u2580\u2588\u2003\u2588\u2584\u2588\u2003\u2588\u2584\u2580\u2003\u2588\u2584\u2584 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\n \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 WALLET \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591";
4
+ export declare function welcome(ui: Ui): void;
5
+ export declare function farewell(ui: Ui): void;
6
+ export declare function notice(ui: Ui, tone: Tone, head: string, text: string): void;
7
+ export declare function showError(ui: Ui, message: string, data?: unknown): void;
8
+ export declare function showTransactionError(ui: Ui, error: unknown): void;
9
+ export declare function showAddress(ui: Ui, session: WalletSession): Promise<void>;
10
+ /** Sent transfers, one row each followed by its explorer link. Columns follow what the host records. */
11
+ export declare function sentTransfersTable(session: Pick<WalletSession, 'capabilities' | 'contacts'>, transfers: SentTransfer[], options: {
12
+ status: boolean;
13
+ tone: Tone;
14
+ empty?: string;
15
+ }): TableSpec;
16
+ /** Transfers in and out laid out as sent transfers, incoming amounts marked with +, one row each followed by its explorer link. */
17
+ export declare function transfersTable(session: Pick<WalletSession, 'capabilities' | 'contacts'>, transfers: TransferRow[]): TableSpec;
@@ -0,0 +1,107 @@
1
+ import { errorMessage, formatDate, formatTokenAmount } from './format.js';
2
+ export const WELCOME_ART = ` ░░░░░░░░░░░░░░ █ █\u2003█▀█\u2003█▀▄\u2003█ ░░░░░░░░░░░░░░
3
+ ░░░░░░░░░░░░░░ █▀█\u2003█▄█\u2003█▄▀\u2003█▄▄ ░░░░░░░░░░░░░░
4
+ ░░░░░░░░░░░░░░ ──────── WALLET ░░░░░░░░░░░░░░`;
5
+ const EXIT_PHRASES = [
6
+ "Buy the rumor, sell the news",
7
+ "The trend is your friend",
8
+ "Don't fight the tape",
9
+ "Cut your losses and let your profits run",
10
+ "Be fearful when others are greedy, and greedy when others are fearful",
11
+ "The market can remain irrational longer than you can remain solvent",
12
+ "Bulls make money, bears make money, pigs get slaughtered",
13
+ "No one is bigger than the market",
14
+ "Don't catch a falling knife",
15
+ "Past performance is not indicative of future results",
16
+ "The stock market is a device for transferring money from the impatient to the patient",
17
+ "Time in the market beats timing the market",
18
+ "Buy low, sell high",
19
+ "Diversification is the only free lunch in investing",
20
+ "The four most dangerous words in investing are: 'This time it's different'",
21
+ "Markets can remain irrational a lot longer than you and I can remain solvent",
22
+ "Risk comes from not knowing what you're doing",
23
+ "In the short run, the market is a voting machine. In the long run, it's a weighing machine",
24
+ "Invest in yourself. Your career is the engine of your wealth",
25
+ "Who has the gold makes the rules",
26
+ "The best time to invest was yesterday. The second best time is now",
27
+ "Don't put all your eggs in one basket",
28
+ "Knowledge is power in the world of investing",
29
+ "Patience is a virtue in the stock market",
30
+ "The market is never wrong, but opinions often are"
31
+ ];
32
+ export function welcome(ui) {
33
+ ui.banner(WELCOME_ART);
34
+ }
35
+ export function farewell(ui) {
36
+ ui.table({
37
+ head: ['✨ Good bye!'],
38
+ tone: 'yellow',
39
+ wordWrap: true,
40
+ rows: [[EXIT_PHRASES[Math.floor(Math.random() * EXIT_PHRASES.length)]]]
41
+ });
42
+ }
43
+ export function notice(ui, tone, head, text) {
44
+ ui.table({ head: [head], tone, rows: [[text]] });
45
+ }
46
+ export function showError(ui, message, data) {
47
+ ui.table({ head: [message], tone: 'red', wordWrap: true, ...(data ? { rows: [[String(data)]] } : {}) });
48
+ }
49
+ export function showTransactionError(ui, error) {
50
+ const reason = typeof error === 'object' && error !== null && 'reason' in error
51
+ ? String(error.reason)
52
+ : null;
53
+ const message = errorMessage(error);
54
+ const data = reason ? reason.replace(/(\w+):/g, "\n$1:").trim() : null;
55
+ showError(ui, message, data);
56
+ }
57
+ export async function showAddress(ui, session) {
58
+ ui.table({ head: [`${session.plugin.name} Address`], tone: 'green', rows: [[await session.address()]] });
59
+ }
60
+ /** Sent transfers, one row each followed by its explorer link. Columns follow what the host records. */
61
+ export function sentTransfersTable(session, transfers, options) {
62
+ const withContact = Boolean(session.contacts);
63
+ const withBalance = session.capabilities.balanceHistory;
64
+ const head = ['Date', 'Recipient', ...(withContact ? ['Contact'] : []), 'Token', 'Amount',
65
+ ...(withBalance ? ['Balance'] : []), ...(options.status ? ['Status'] : [])];
66
+ const rows = [];
67
+ if (options.empty && transfers.length === 0)
68
+ rows.push([{ colSpan: head.length, content: options.empty }]);
69
+ for (const tx of transfers) {
70
+ rows.push([
71
+ formatDate(tx.timestamp), tx.recipient,
72
+ ...(withContact ? [tx.contact ?? '-'] : []),
73
+ tx.token, formatTokenAmount(tx.token, tx.amount),
74
+ ...(withBalance ? [tx.balance !== undefined ? formatTokenAmount(tx.token, tx.balance) : '-'] : []),
75
+ ...(options.status ? [tx.status || '-'] : [])
76
+ ]);
77
+ rows.push([{ colSpan: head.length, content: tx.url }]);
78
+ }
79
+ return { head, rows, tone: options.tone };
80
+ }
81
+ /** A transfer's state as one character: done, failed, still on its way, or not known after broadcasting. */
82
+ function statusIcon(status) {
83
+ if (status === undefined || status === '')
84
+ return '-';
85
+ return { confirmed: '✔', failed: '✖', broadcast_unknown: '?' }[status] ?? '…';
86
+ }
87
+ /** Transfers in and out laid out as sent transfers, incoming amounts marked with +, one row each followed by its explorer link. */
88
+ export function transfersTable(session, transfers) {
89
+ const withContact = Boolean(session.contacts);
90
+ const withBalance = session.capabilities.balanceHistory;
91
+ const head = ['Date', 'Address', ...(withContact ? ['Contact'] : []), 'Token', 'Amount', ...(withBalance ? ['Balance'] : []), 'S'];
92
+ const rows = [];
93
+ if (transfers.length === 0)
94
+ rows.push([{ colSpan: head.length, content: 'No transaction history available.' }]);
95
+ for (const tx of transfers) {
96
+ rows.push([
97
+ tx.timestamp !== undefined ? formatDate(tx.timestamp) : '-', tx.counterparty,
98
+ ...(withContact ? [tx.contact ?? '-'] : []),
99
+ tx.token, (tx.direction === 'In' ? '+' : '') + formatTokenAmount(tx.token, tx.amount),
100
+ ...(withBalance ? [tx.balance !== undefined ? formatTokenAmount(tx.token, tx.balance) : '-'] : []),
101
+ statusIcon(tx.status)
102
+ ]);
103
+ rows.push([{ colSpan: head.length, content: tx.url }]);
104
+ }
105
+ return { head, rows, tone: 'blue' };
106
+ }
107
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../app/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ import type { BaseNetworkContract, NetworkPlugin } from '../network/types.js';
2
+ import type { TransferResult } from '../transfer-service.js';
3
+ import type { Cell, Ui } from './ui.js';
4
+ export interface AccountDetails {
5
+ address: string;
6
+ privateKey: string;
7
+ mnemonic?: string;
8
+ }
9
+ export interface Contact {
10
+ address: string;
11
+ name: string;
12
+ }
13
+ export interface SentTransfer {
14
+ timestamp: string | number | Date;
15
+ recipient: string;
16
+ contact?: string;
17
+ token: string;
18
+ amount: string | number;
19
+ balance?: string | number;
20
+ status?: string;
21
+ url: string;
22
+ }
23
+ export interface TransferRow {
24
+ /** Absent while unconfirmed. */
25
+ timestamp?: string | number | Date;
26
+ direction: 'In' | 'Out';
27
+ /** The sender of an incoming transfer, the recipient of an outgoing one. */
28
+ counterparty: string;
29
+ contact?: string;
30
+ token: string;
31
+ amount: string | number;
32
+ balance?: string | number;
33
+ status?: string;
34
+ url: string;
35
+ }
36
+ /** A failed scan leaves the transfers as saved and says why in `error`. */
37
+ export interface RecentTransfers {
38
+ transfers: TransferRow[];
39
+ error?: string;
40
+ }
41
+ export interface TransferDraft {
42
+ to: string;
43
+ asset: string;
44
+ /** A decimal amount, or `max`. */
45
+ amount: string;
46
+ }
47
+ export interface PendingTransfer {
48
+ requestId: string;
49
+ to: string;
50
+ asset: string;
51
+ amount: string;
52
+ status: string;
53
+ }
54
+ export interface TransferPort {
55
+ pending(): Promise<PendingTransfer[]>;
56
+ /** What `max` would send and the fee it would pay. Sends nothing. */
57
+ preview(draft: {
58
+ to: string;
59
+ asset: string;
60
+ }): Promise<{
61
+ amount: string;
62
+ fee: {
63
+ amount: string;
64
+ asset: string;
65
+ };
66
+ }>;
67
+ /**
68
+ * Optional. Prepares the transfer and returns what the user must see before it is signed.
69
+ * A resumed transfer is reviewed as saved. When present, `send` must send exactly what was reviewed.
70
+ */
71
+ review?(draft: TransferDraft, resume?: string): Promise<Cell[][]>;
72
+ send(draft: TransferDraft, requestId: string): Promise<TransferResult>;
73
+ /** Stores whatever the host keeps about a sent transfer and describes it for display. */
74
+ record(result: TransferResult, draft: TransferDraft): Promise<SentTransfer>;
75
+ }
76
+ /** Optional address book. Absent hosts show no contact columns or prompts. */
77
+ export interface ContactsPort {
78
+ list(): Promise<Contact[]>;
79
+ get(address: string): Promise<string | undefined>;
80
+ set(address: string, name: string): Promise<void>;
81
+ delete(address: string): Promise<void>;
82
+ clear(): Promise<void>;
83
+ }
84
+ /** A menu entry only some hosts have, such as swaps or file import. */
85
+ export interface HostAction {
86
+ name: string;
87
+ /** Import actions return true when they replaced the account. */
88
+ run(ui: Ui): Promise<boolean | void>;
89
+ }
90
+ export interface SessionCapabilities {
91
+ /** Create a new account over the current one from Account Settings. Importing over it is always offered. */
92
+ replaceAccount: boolean;
93
+ /** Sent-transfer rows carry the balance after sending. */
94
+ balanceHistory: boolean;
95
+ /** The first-run account menu offers Switch Network. */
96
+ switchNetworkFirst: boolean;
97
+ }
98
+ export type NewAccount = 'existing-mnemonic' | 'random' | 12 | 24;
99
+ /** What the shared flows need from an unlocked wallet. Nothing here prompts. */
100
+ export interface WalletSession {
101
+ readonly capabilities: SessionCapabilities;
102
+ readonly plugin: NetworkPlugin;
103
+ /** The selected network. Flows use it only to validate input. Read it again after selectNetwork. */
104
+ readonly network: BaseNetworkContract;
105
+ readonly transfers: TransferPort;
106
+ readonly contacts?: ContactsPort;
107
+ start(): Promise<void>;
108
+ networks(): NetworkPlugin[];
109
+ selectNetwork(plugin: NetworkPlugin): Promise<void>;
110
+ hasAccount(): Promise<boolean>;
111
+ address(): Promise<string>;
112
+ hasMnemonic(): Promise<boolean>;
113
+ createAccount(kind: NewAccount): Promise<void>;
114
+ importMnemonic(mnemonic: string): Promise<void>;
115
+ importPrivateKey(privateKey: string): Promise<void>;
116
+ /** A detached copy. The caller clears it. */
117
+ accountDetails(): Promise<AccountDetails | null>;
118
+ tokenBalances(): Promise<Array<[string, string]>>;
119
+ sentTransfers(): Promise<SentTransfer[]>;
120
+ /** The network's transfers in and out saved so far, after adding what a scan finds. Only called when the selected network lists them. */
121
+ recentTransfers(): Promise<RecentTransfers>;
122
+ importActions(): HostAction[];
123
+ exportActions(): HostAction[];
124
+ menuActions(): HostAction[];
125
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../app/session.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import type { SwapService } from '../swap/service.js';
2
+ import type { Ui } from './ui.js';
3
+ /** What the swap flows need from a swap service. */
4
+ export type SwapPort = Pick<SwapService, 'route' | 'list' | 'status' | 'resume' | 'availableBalance' | 'destination' | 'quote' | 'execute'>;
5
+ export declare function swapMenu(ui: Ui, service: SwapPort): Promise<void>;
@@ -0,0 +1,274 @@
1
+ import { randomUUID } from '#environment';
2
+ import { formatUnits, normalizeDecimal, parseDecimalToUnits } from '../amounts.js';
3
+ import { swapConfig } from '../swap/config.js';
4
+ import { terminalSwapStates } from '../swap/types.js';
5
+ import { errorMessage } from './format.js';
6
+ import { isPromptAborted } from './ui.js';
7
+ const labels = {
8
+ quoted: 'Quote ready', preparing: 'Preparing deposit', approval_pending: 'Waiting for token approval',
9
+ deposit_pending: 'Deposit sent · waiting for confirmation', deposit_confirmed: 'Deposit recognized',
10
+ swapping: 'Exchanging assets', payout_pending: 'Preparing payout', btc_pending: 'BTC sent · waiting for inclusion',
11
+ btc_confirming: 'BTC received · confirming', output_pending: 'Payout sent · waiting for inclusion',
12
+ output_confirming: 'Payout received · confirming', completed: 'Completed', partial_pending: 'Payout and refund · awaiting confirmation',
13
+ partial_completed: 'Partially exchanged · payout received and remainder returned', refund_pending: 'Refund pending',
14
+ refunded: 'Swap refunded', failed: 'Not completed · deposit not transferred', needs_attention: 'Needs attention'
15
+ };
16
+ const providerNames = { chainflip: 'Chainflip', thorchain: 'THORChain', 'near-intents': 'NEAR Intents' };
17
+ async function withSpinner(ui, text, work) {
18
+ const spinner = ui.spinner(text);
19
+ try {
20
+ return await work();
21
+ }
22
+ finally {
23
+ spinner.stop();
24
+ }
25
+ }
26
+ /** Prompt aborts end the flow like any other prompt; anything else is reported and returns to the menu. */
27
+ function report(ui, error, fallback) {
28
+ if (isPromptAborted(error))
29
+ throw error;
30
+ ui.print(error instanceof Error ? errorMessage(error) : fallback);
31
+ }
32
+ export async function swapMenu(ui, service) {
33
+ const { source, destination } = service.route;
34
+ const route = await ui.select({
35
+ message: 'Swap:',
36
+ choices: [
37
+ { name: `${source.label} → ${destination.label}`, value: 'new' },
38
+ { name: 'Track Swaps', value: 'track' },
39
+ { name: 'Go Back', value: 'back', back: true }
40
+ ]
41
+ });
42
+ if (route === 'back')
43
+ return;
44
+ if (route === 'track')
45
+ await trackSwaps(ui, service);
46
+ else
47
+ await startSwap(ui, service);
48
+ }
49
+ async function startSwap(ui, service) {
50
+ const { source, destination } = service.route;
51
+ try {
52
+ const existing = (await withSpinner(ui, 'Checking existing swaps…', () => service.list())).find(swap => !terminalSwapStates.includes(swap.state));
53
+ if (existing) {
54
+ ui.print(`An unfinished swap already exists: ${existing.requestId}. Open Track Swaps to check its status.`);
55
+ return;
56
+ }
57
+ const available = await withSpinner(ui, 'Checking available balance…', () => service.availableBalance());
58
+ if (available <= 0n) {
59
+ ui.print(`No ${source.label} available to swap.`);
60
+ return;
61
+ }
62
+ const balance = formatUnits(available, source.decimals);
63
+ const amount = await ui.input({
64
+ message: `${source.label} to exchange (max, empty to cancel):`,
65
+ validate(value) {
66
+ if (!value.trim() || value.trim().toLowerCase() === 'max')
67
+ return true;
68
+ try {
69
+ return parseDecimalToUnits(normalizeDecimal(value.trim()), source.decimals) <= available
70
+ || `Insufficient ${source.label}. Available: ${balance}.`;
71
+ }
72
+ catch (error) {
73
+ return error.message;
74
+ }
75
+ }
76
+ });
77
+ if (!amount.trim())
78
+ return;
79
+ const ownAddress = await withSpinner(ui, `Loading ${destination.label} account…`, () => service.destination());
80
+ const addresses = ownAddress ? [{ name: 'My account', address: ownAddress }] : [];
81
+ if (!ownAddress)
82
+ ui.print(`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.`);
83
+ const to = await ui.autocomplete({
84
+ message: `Receive ${destination.label} at:`,
85
+ source: input => {
86
+ const search = input.trim();
87
+ 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 }));
88
+ if (search && !matches.some(entry => entry.value === search))
89
+ matches.push({ name: search, value: search });
90
+ return [
91
+ ...(!ownAddress && !search ? [{ name: 'My account', value: '', disabled: `${destination.label} account not configured` }] : []),
92
+ ...matches, { name: 'Go Back', value: '', back: true }
93
+ ];
94
+ }
95
+ });
96
+ if (!to)
97
+ return;
98
+ const maximum = amount.trim().toLowerCase() === 'max';
99
+ let comparison = await withSpinner(ui, 'Comparing Chainflip, THORChain and NEAR Intents, including network fees…', () => service.quote(maximum ? balance : amount.trim(), to));
100
+ if (maximum && source.asset === 'BTC' && comparison.quotes.length) {
101
+ const reserve = comparison.quotes.reduce((largest, quote) => {
102
+ const fee = parseDecimalToUnits(quote.funding.budget, source.decimals);
103
+ return fee > largest ? fee : largest;
104
+ }, 0n) + BigInt(swapConfig.bitcoin.changeDustSats);
105
+ if (available <= reserve) {
106
+ ui.print('Insufficient BTC for the network fee and required change.');
107
+ return;
108
+ }
109
+ const maximumAmount = formatUnits(available - reserve, source.decimals);
110
+ ui.print(`Max: ${maximumAmount} BTC · reserving ${formatUnits(reserve, source.decimals)} BTC for network fees and required change.`);
111
+ comparison = await withSpinner(ui, 'Updating quotes for max amount…', () => service.quote(maximumAmount, to));
112
+ }
113
+ for (const unavailable of comparison.unavailable)
114
+ ui.print(`${unavailable.provider}: ${unavailable.reason}`);
115
+ comparison.quotes = comparison.quotes.filter(quote => {
116
+ if (quote.sufficientAsset && quote.sufficientFee)
117
+ return true;
118
+ ui.print(`${providerNames[quote.provider]}: insufficient ${!quote.sufficientAsset ? source.label : quote.funding.asset + ' for the deposit and network fee budget'}.`);
119
+ return false;
120
+ });
121
+ if (!comparison.quotes.length) {
122
+ ui.print('No executable quote available. No funds were sent.');
123
+ return;
124
+ }
125
+ comparison.recommendedQuoteId = comparison.quotes[0].quoteId;
126
+ const quoteId = await ui.select({
127
+ message: 'Choose a quote:',
128
+ choices: [
129
+ ...comparison.quotes.map(quote => ({
130
+ name: `${providerNames[quote.provider]} · ≈ ${quote.destination.estimated} ${quote.destination.asset} · ≈ ${quote.estimatedTotalCost.percent}% cost${quote.quoteId === comparison.recommendedQuoteId ? ' · best after gas' : ''}`,
131
+ value: quote.quoteId
132
+ })), { name: 'Cancel', value: 'cancel', back: true }
133
+ ]
134
+ });
135
+ if (quoteId === 'cancel')
136
+ return;
137
+ const quote = comparison.quotes.find(item => item.quoteId === quoteId);
138
+ ui.table({
139
+ head: [{ colSpan: 2, content: `${quote.source.amount} ${quote.source.asset} → ≈ ${quote.destination.estimated} ${quote.destination.asset}` }],
140
+ tone: 'blue',
141
+ rows: [
142
+ ['Route', `${source.label} → ${destination.label} · ${providerNames[quote.provider]} · ${quote.execution}`],
143
+ ['From', quote.from], ['To', quote.to],
144
+ ['Minimum', `${quote.destination.minimum} ${quote.destination.asset} · full swap · ${quote.slippagePercent}% tolerance`],
145
+ ['Cost', `≈ ${quote.estimatedTotalCost.percent}% (USD ${Number(quote.estimatedTotalCost.usd).toFixed(2)}) · incl. price impact + gas`],
146
+ ['Network fee', `Up to ${quote.funding.budget} ${quote.funding.asset} extra on ${quote.source.network.toUpperCase()}`],
147
+ ['Time', `${quote.estimatedSeconds === null ? '' : `≈ ${Math.ceil(quote.estimatedSeconds / 60)} min + `}${quote.destination.confirmations} ${quote.destination.asset} confirmations to complete`],
148
+ ['Refund', `${source.label} → sender above, minus fees`],
149
+ ['Expires', new Date(quote.expiresAt).toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, ' UTC')]
150
+ ]
151
+ });
152
+ ui.print(`Provider fees already deducted from estimated ${destination.asset}. Source network fee is extra.`);
153
+ if (quote.refund.partialResultPossible) {
154
+ ui.print(`Partial fill possible: ${destination.asset} received + remaining ${source.asset} refunded minus fees.`);
155
+ ui.print('The minimum above applies only to a full swap.');
156
+ }
157
+ ui.print(`Once exchanged, delayed ${destination.asset} cannot auto-refund to ${source.asset}.`);
158
+ const confirmed = await ui.confirm({ message: 'Accept destination, cost and minimum, and swap?', default: false });
159
+ if (!confirmed)
160
+ return;
161
+ const operation = await withSpinner(ui, 'Preparing and submitting swap…', () => service.execute(quoteId, randomUUID()));
162
+ showSwap(ui, operation);
163
+ if (!terminalSwapStates.includes(operation.state))
164
+ await followSwap(ui, service, operation, true);
165
+ }
166
+ catch (error) {
167
+ report(ui, error, 'Swap could not be started.');
168
+ }
169
+ }
170
+ async function trackSwaps(ui, service) {
171
+ try {
172
+ const refreshed = new Set();
173
+ const swaps = await withSpinner(ui, 'Updating swap statuses…', async () => {
174
+ const entries = await service.list();
175
+ for (let i = 0; i < entries.length; i++) {
176
+ if (!terminalSwapStates.includes(entries[i].state)) {
177
+ entries[i] = await service.status(entries[i].requestId);
178
+ refreshed.add(entries[i].requestId);
179
+ }
180
+ }
181
+ return entries;
182
+ });
183
+ if (!swaps.length) {
184
+ ui.print('No swaps recorded.');
185
+ return;
186
+ }
187
+ const requestId = await ui.select({
188
+ message: 'Select a swap:',
189
+ choices: [
190
+ ...swaps.map(swap => ({ name: swapRow(swap), value: swap.requestId })),
191
+ { name: 'Go back', value: 'back', back: true }
192
+ ]
193
+ });
194
+ if (requestId === 'back')
195
+ return;
196
+ const current = refreshed.has(requestId) ? swaps.find(swap => swap.requestId === requestId) :
197
+ await withSpinner(ui, 'Checking swap status…', () => service.status(requestId));
198
+ showSwap(ui, current, current.state !== swaps.find(swap => swap.requestId === requestId).state);
199
+ if (terminalSwapStates.includes(current.state))
200
+ return;
201
+ await followSwap(ui, service, current, false);
202
+ }
203
+ catch (error) {
204
+ report(ui, error, 'Could not update swaps.');
205
+ }
206
+ }
207
+ /** Waits, or less once the person leaves. */
208
+ function pause(ms, leaving) {
209
+ let timer;
210
+ return Promise.race([leaving, new Promise(resolve => { timer = setTimeout(resolve, ms); })])
211
+ .finally(() => clearTimeout(timer));
212
+ }
213
+ /** Polls until the swap settles, needs attention, or the person leaves. A request under way always finishes first. */
214
+ async function followSwap(ui, service, initial, resume) {
215
+ ui.print('Updating automatically. Press Enter to return to the menu. Leaving this screen does not cancel funds already sent.');
216
+ const watch = ui.watch('Updating swap…');
217
+ let left = false;
218
+ let aborted;
219
+ const leaving = watch.left.then(() => { left = true; }, error => { aborted = error; });
220
+ const settled = () => {
221
+ if (aborted)
222
+ throw aborted;
223
+ return left;
224
+ };
225
+ let previous = swapDisplayKey(initial);
226
+ try {
227
+ while (!settled()) {
228
+ const operation = await (resume ? service.resume(initial.requestId) : service.status(initial.requestId));
229
+ if (settled())
230
+ return;
231
+ const signature = swapDisplayKey(operation);
232
+ if (signature !== previous) {
233
+ watch.pause();
234
+ showSwap(ui, operation);
235
+ previous = signature;
236
+ }
237
+ if (terminalSwapStates.includes(operation.state) || operation.state === 'needs_attention')
238
+ return;
239
+ watch.update(`${labels[operation.state]}${operation.message ? ` · ${operation.message}` : ''} · Enter to return`);
240
+ await pause(swapConfig.pollIntervalMs, leaving);
241
+ }
242
+ }
243
+ finally {
244
+ watch.stop();
245
+ }
246
+ }
247
+ function swapRow(swap) {
248
+ const stale = swap.updateError && !terminalSwapStates.includes(swap.state) ? ' · update unavailable' : '';
249
+ return `${swap.createdAt.slice(0, 16).replace('T', ' ')} · ${swap.quote.source.amount} ${swap.quote.source.asset} → ${swap.quote.destination.asset} · ${labels[swap.state].split(' · ')[0]}${stale}`;
250
+ }
251
+ function swapDisplayKey(operation) {
252
+ return JSON.stringify([operation.state, operation.confirmations, operation.updateError, operation.transactions, operation.delayed, operation.message, operation.payout, operation.refund, operation.refundConfirmations]);
253
+ }
254
+ function showSwap(ui, swap, showHeading = true) {
255
+ if (showHeading)
256
+ ui.print(`Swap ${swap.requestId}: ${labels[swap.state]} · ${swap.quote.source.asset} (${swap.quote.source.network}) → ${swap.quote.destination.asset} (${swap.quote.destination.network})`);
257
+ if (swap.payout)
258
+ ui.print(`${swap.quote.destination.asset} confirmations: ${swap.confirmations}/${swap.requiredConfirmations}`);
259
+ for (const transaction of swap.transactions)
260
+ ui.print(`${transaction.kind}: ${transaction.explorer}`);
261
+ if (swap.payout)
262
+ ui.print(`${swap.quote.destination.asset} payout: ${swap.payout.amount} ${swap.quote.destination.asset} · ${swap.payout.explorer}`);
263
+ if (swap.refund) {
264
+ ui.print(`${swap.quote.source.asset} refund: ${swap.refund.amount} ${swap.quote.source.asset} · ${swap.refund.explorer}`);
265
+ ui.print(`${swap.quote.source.network.toUpperCase()} refund confirmations: ${swap.refundConfirmations}/${swap.requiredRefundConfirmations}`);
266
+ }
267
+ if (swap.message)
268
+ ui.print(swap.message);
269
+ if (swap.updateError)
270
+ ui.print(`Could not update or continue: ${swap.updateError} Last successful check: ${swap.lastCheckedAt || 'none'}.`);
271
+ if (swap.delayed)
272
+ ui.print('This swap is taking longer than expected. Keep its ID and transaction links; do not send another deposit.');
273
+ }
274
+ //# sourceMappingURL=swap.js.map