@2bad/walt 1.0.1 → 2.0.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.
package/README.md CHANGED
@@ -42,6 +42,7 @@ await source.disconnect()
42
42
  - [Exchange](examples/exchange.md): available pairs, quotes, exchanges and limit orders
43
43
  - [Transactions](examples/transactions.md): history, details and reading a transaction
44
44
  - [Errors](examples/errors.md): `WaltRequestError`, `WaltAuthError` and what is retried
45
+ - [Command line](examples/cli.md): the read-only `walt` command for balances, history, prices and orders
45
46
 
46
47
  A runnable script is in [examples/portfolio.ts](examples/portfolio.ts).
47
48
 
@@ -60,6 +61,7 @@ new WaltClient(options: WaltClientOptions)
60
61
  type WaltClient = {
61
62
  accounts(): Promise<AccountsResponse>
62
63
  portfolio(): Promise<Portfolio>
64
+ balances(fiatCurrency: FiatCurrency): Promise<Balances>
63
65
  pnl(): Promise<PnlResponse>
64
66
  portfolioHistory(dateFrom: Date | string, resolution: '1d' | '1m'): Promise<PortfolioHistory>
65
67
  frozenBalances<T = unknown>(): Promise<T[]>
@@ -74,6 +76,7 @@ type WaltClient = {
74
76
  charts(currency: string, fiatCurrency: FiatCurrency): Promise<Charts>
75
77
  priceForFiatAtTime(crypto: string, fiat: FiatCurrency, amount: number | string, time: Date | string | number): Promise<PriceAtTime>
76
78
  localCurrency(): Promise<FiatCurrency>
79
+ candles(requests: CandleRequest[]): Promise<CandleSeries[]>
77
80
 
78
81
  availableExchanges(): Promise<AvailableExchanges>
79
82
  quote(fromCurrency: string, toCurrency: string, fromAmount: number): Promise<ExchangeQuote>
@@ -85,6 +88,11 @@ type WaltClient = {
85
88
  limitOrderBounds(baseCurrency: string): Promise<LimitOrderBounds>
86
89
  limitOrderTokens(): Promise<LimitOrderToken[]>
87
90
 
91
+ perpContracts(contractId?: number): Promise<PerpContract[]>
92
+ perpSystemStatus(): Promise<PerpSystemStatus>
93
+ perpPositions(options?: { statuses?: PerpPositionStatus[]; contractId?: number }): Promise<PerpPosition[]>
94
+ perpOrders(options?: { contractId?: number; statuses?: PerpOrderStatus[]; limit?: number }): Promise<PerpOrdersPage>
95
+
88
96
  transactions(limit?: number, cursor?: string): Promise<TransactionsPage>
89
97
  accountTransactions(currency: string, limit?: number, cursor?: string): Promise<TransactionsPage>
90
98
  allTransactions(limit?: number): AsyncGenerator<Transaction>
@@ -0,0 +1,23 @@
1
+ export type Balance = {
2
+ /** `REGULAR` for spot, `HOLD_LIMIT` for USDT reserved by open limit orders, `EARN_V2` for staked funds. */
3
+ account_type: string;
4
+ currency: string;
5
+ asset_currency: string;
6
+ total_balance: string;
7
+ available_balance: string;
8
+ balance_hold: string;
9
+ is_always_on_main: boolean;
10
+ category: string;
11
+ rate_to_fiat: string;
12
+ rate_to_usd: string;
13
+ available_balance_fiat_amount: string;
14
+ available_balance_usd_amount: string;
15
+ };
16
+ export type Balances = {
17
+ accounts: Balance[];
18
+ fiat_currency: string;
19
+ /** Spot, staked and limit order holds together. */
20
+ overall_available_balance_fiat_amount: string;
21
+ /** Also `true` when the perps account has no open position. */
22
+ has_open_perp_position: boolean;
23
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ export type CandleRequest = {
2
+ /** `<currency>/USD/SPOT`, or `<base>/USD/PERP_TRADE` for a perps contract. */
3
+ ticker: string;
4
+ /** Seen: `5m`, `15m`. */
5
+ resolution: string;
6
+ limit: number;
7
+ };
8
+ export type Candle = {
9
+ o: string;
10
+ h: string;
11
+ l: string;
12
+ c: string;
13
+ v: string;
14
+ };
15
+ export type CandleSeries = {
16
+ ticker: string;
17
+ resolution: string;
18
+ /** Opening time of the first candle, in ms. */
19
+ start_ts: number;
20
+ candles: Candle[];
21
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,155 @@
1
+ export type PerpContract = {
2
+ contract_id: number;
3
+ contract_name: string;
4
+ base_asset: string;
5
+ quote_asset: string;
6
+ venue_status: string;
7
+ is_active: boolean;
8
+ price_decimals: number;
9
+ size_decimals: number;
10
+ tick_size: string;
11
+ step_size: string;
12
+ min_size_base: string;
13
+ max_size_base: string;
14
+ max_size_quote: string;
15
+ min_size_quote: string;
16
+ max_leverage: number;
17
+ maker_fee_pct: string;
18
+ taker_fee_pct: string;
19
+ base_taker_fee_pct: string;
20
+ taker_fee_subscription_id: number;
21
+ price_slippage: string;
22
+ tpsl_buffer_pct: string;
23
+ funding_rate_hourly: string;
24
+ next_funding_at: string;
25
+ open_interest_usd: string;
26
+ volume_24h_usd: string;
27
+ trading_hours_state: unknown;
28
+ market_data: {
29
+ server_time: string;
30
+ /** `fresh`, or `stale` with both prices `null`. */
31
+ status: string;
32
+ mark_price: string | null;
33
+ index_price: string | null;
34
+ source_timestamp: string;
35
+ expires_at: string;
36
+ };
37
+ /** Fractions in basis points: `1000` is 10%, so 10x leverage. */
38
+ margin: {
39
+ default_initial_margin_fraction: number;
40
+ min_initial_margin_fraction: number;
41
+ maintenance_margin_fraction: number;
42
+ closeout_margin_fraction: number;
43
+ };
44
+ };
45
+ export type PerpSystemStatus = {
46
+ actions: {
47
+ open_enabled: boolean;
48
+ cancel_enabled: boolean;
49
+ close_enabled: boolean;
50
+ add_margin_enabled: boolean;
51
+ remove_margin_enabled: boolean;
52
+ auto_close_enabled: boolean;
53
+ };
54
+ exchange_status: {
55
+ exchange_partner_tech_name: string;
56
+ operational_state: string;
57
+ is_trading_halted: boolean;
58
+ message: unknown;
59
+ current_status: unknown;
60
+ upcoming_maintenance: unknown;
61
+ last_changed_at: string;
62
+ last_checked_at: string;
63
+ };
64
+ };
65
+ export type PerpOrdersPage = {
66
+ orders: PerpOrder[];
67
+ next_cursor: unknown;
68
+ /** `sort_ts` of the last order on the page, `null` on an empty page. */
69
+ last_sort_ts: string | null;
70
+ };
71
+ export type PerpPositionStatus = 'open' | 'closed';
72
+ export type PerpOrderStatus = 'pending' | 'processing' | 'processed' | 'filled' | 'partially_filled' | 'failed' | 'canceled';
73
+ type PerpPositionCore = {
74
+ contract_id: number;
75
+ contract_name: string;
76
+ contract_base_asset: string;
77
+ contract_quote_asset: string;
78
+ /** Seen: `long`, `short`. */
79
+ side: string;
80
+ /** Seen: `closed`. */
81
+ status: string;
82
+ leverage: number;
83
+ /** Seen: `isolated`. */
84
+ margin_type: string;
85
+ value_base: string;
86
+ margin_quote: string;
87
+ avg_entry_price: string;
88
+ liquidation_price: string;
89
+ funding_paid_quote: string;
90
+ created_at: string;
91
+ updated_at: string;
92
+ synced_at: string;
93
+ };
94
+ export type PerpPosition = PerpPositionCore & {
95
+ trading_agent_code: string;
96
+ /** Priced at the current mark, closed positions included, so it is not the P/L a closed position realised. */
97
+ valuation: {
98
+ server_time: string;
99
+ status: string;
100
+ mark_price: string;
101
+ index_price: string;
102
+ source_timestamp: string;
103
+ expires_at: string;
104
+ unrealized_pnl_quote: string;
105
+ equity_quote: string;
106
+ };
107
+ auto_close: unknown;
108
+ };
109
+ export type PerpOrder = {
110
+ order_id: string;
111
+ contract_id: number;
112
+ contract_name: string;
113
+ contract_base_asset: string;
114
+ contract_quote_asset: string;
115
+ side: string;
116
+ /** Seen: `market`. */
117
+ type: string;
118
+ /** Seen: `open`, `close`, `stop_loss`, `take_profit`. */
119
+ category: string;
120
+ status: string;
121
+ error_code: unknown;
122
+ leverage: number;
123
+ base_size: string;
124
+ base_price: string;
125
+ accounting_base_amount: string;
126
+ accounting_quote_amount: string;
127
+ filled_price: string | null;
128
+ filled_base_amount: string | null;
129
+ filled_quote_amount: string | null;
130
+ pnl_quote: string | null;
131
+ pnl_quote_charged: unknown;
132
+ roi: string | null;
133
+ trigger_price_quote: string | null;
134
+ limit_price_quote: string | null;
135
+ /** Unix seconds. */
136
+ valid_to: number | null;
137
+ liquidation_fee: unknown;
138
+ liquidation_fee_charged: unknown;
139
+ integrator_fee: string | null;
140
+ integrator_fee_charged: string | null;
141
+ venue_fee: string | null;
142
+ total_fee: string | null;
143
+ total_fee_charged: string | null;
144
+ margin_returned_amount: string | null;
145
+ created_at: string;
146
+ updated_at: string;
147
+ filled_at: string | null;
148
+ canceled_at: unknown;
149
+ sort_ts: string;
150
+ position: PerpPositionCore & {
151
+ fee_quote: unknown;
152
+ close_price: string;
153
+ };
154
+ };
155
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ // source/bin/quiet-localstorage.ts
2
+ const nodeOptions = (process.env["NODE_OPTIONS"] ?? "").split(/\s+/);
3
+ if (![...process.execArgv, ...nodeOptions].some((arg) => arg.startsWith("--localstorage-file"))) Object.defineProperty(globalThis, "localStorage", {
4
+ value: void 0,
5
+ configurable: true,
6
+ writable: true
7
+ });
8
+ export {};
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import "./quiet-localstorage.js";
3
+ import { COMMANDS, UsageError, run } from "../cli/run.js";
4
+ import { WaltClient } from "../client.js";
5
+ import { TelegramTokenSource } from "../telegram/auth.js";
6
+ import { WaltAuthError, WaltRequestError } from "../transport.js";
7
+ import { TelegramClient } from "teleproto";
8
+ import { StringSession } from "teleproto/sessions/index.js";
9
+ import { readFileSync } from "node:fs";
10
+ import { stdin, stdout } from "node:process";
11
+ import { createInterface } from "node:readline/promises";
12
+ import { parseArgs } from "node:util";
13
+ // source/bin/walt.ts
14
+ /**
15
+ * Signs in to Telegram interactively and prints the session string to save as `TG_SESSION`.
16
+ *
17
+ * @returns The process exit code.
18
+ */
19
+ const login = async () => {
20
+ const rl = createInterface({
21
+ input: stdin,
22
+ output: stdout
23
+ });
24
+ const ask = (question) => rl.question(question);
25
+ const apiId = Number(process.env["TG_API_ID"] || await ask("API id (from https://my.telegram.org): "));
26
+ if (!Number.isSafeInteger(apiId) || apiId <= 0) {
27
+ rl.close();
28
+ console.error("The API id must be the number shown at https://my.telegram.org");
29
+ return 1;
30
+ }
31
+ const apiHash = process.env["TG_API_HASH"] || await ask("API hash: ");
32
+ const client = new TelegramClient(new StringSession(""), apiId, apiHash, { connectionRetries: 3 });
33
+ await client.start({
34
+ phoneNumber: () => ask("Phone (+country...): "),
35
+ phoneCode: () => ask("Code you received: "),
36
+ password: () => ask("2FA password (blank if none): "),
37
+ onError: (error) => console.error(error)
38
+ });
39
+ rl.close();
40
+ console.log("\nLogged in. Save this as TG_SESSION (keep it secret):\n");
41
+ console.log(client.session.save());
42
+ await client.disconnect();
43
+ return 0;
44
+ };
45
+ const USAGE = `Usage: walt <command> [options]
46
+
47
+ ${COMMANDS}
48
+ login sign in to Telegram and print TG_SESSION
49
+
50
+ Options:
51
+ --json print JSON instead of a table
52
+ --fiat CODE currency for values (default USD)
53
+ --env-file PATH read TG_API_ID, TG_API_HASH and TG_SESSION from a file
54
+ -h, --help show this help
55
+ -v, --version show the version
56
+
57
+ Needs TG_API_ID and TG_API_HASH from https://my.telegram.org and TG_SESSION from \`walt login\`.
58
+ Every command is read-only.`;
59
+ const fail = (message, code) => {
60
+ console.error(`walt: ${message}`);
61
+ process.exit(code);
62
+ };
63
+ const parse = () => {
64
+ try {
65
+ return parseArgs({
66
+ allowPositionals: true,
67
+ options: {
68
+ json: { type: "boolean" },
69
+ fiat: { type: "string" },
70
+ "env-file": { type: "string" },
71
+ asset: { type: "string" },
72
+ limit: { type: "string" },
73
+ from: { type: "string" },
74
+ monthly: { type: "boolean" },
75
+ all: { type: "boolean" },
76
+ closed: { type: "boolean" },
77
+ help: {
78
+ type: "boolean",
79
+ short: "h"
80
+ },
81
+ version: {
82
+ type: "boolean",
83
+ short: "v"
84
+ }
85
+ }
86
+ });
87
+ } catch (error) {
88
+ return fail(`${error.message}\nRun \`walt --help\` for usage.`, 2);
89
+ }
90
+ };
91
+ const { values, positionals } = parse();
92
+ const [command, ...args] = positionals;
93
+ if (values.version) {
94
+ const manifest = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
95
+ console.log(manifest.version);
96
+ process.exit(0);
97
+ }
98
+ if (values.help || command === void 0) {
99
+ console.log(USAGE);
100
+ process.exit(command === void 0 && !values.help ? 2 : 0);
101
+ }
102
+ if (values["env-file"] !== void 0) try {
103
+ process.loadEnvFile(values["env-file"]);
104
+ } catch (error) {
105
+ fail(`cannot read ${values["env-file"]}: ${error.message}`, 2);
106
+ }
107
+ if (command === "login") process.exit(await login());
108
+ const session = process.env["TG_SESSION"];
109
+ const apiId = Number(process.env["TG_API_ID"]);
110
+ const apiHash = process.env["TG_API_HASH"];
111
+ if (!session || !apiId || !apiHash) fail("set TG_API_ID, TG_API_HASH and TG_SESSION first (run `walt login` for the session)", 2);
112
+ const source = new TelegramTokenSource({
113
+ session: session ?? "",
114
+ apiId,
115
+ apiHash: apiHash ?? ""
116
+ });
117
+ const client = new WaltClient({ tokenProvider: (rejected) => source.token(rejected) });
118
+ let exitCode = 0;
119
+ try {
120
+ console.log(await run(client, command, args, values));
121
+ } catch (error) {
122
+ if (error instanceof UsageError) {
123
+ console.error(`walt: ${error.message}\nRun \`walt --help\` for usage.`);
124
+ exitCode = 2;
125
+ } else if (error instanceof WaltRequestError) {
126
+ const code = error.body?.code;
127
+ console.error(`walt: ${error.message}${typeof code === "string" ? ` (${code})` : ""}`);
128
+ exitCode = 1;
129
+ } else if (error instanceof WaltAuthError) {
130
+ console.error(`walt: sign-in failed: ${error.message}`);
131
+ exitCode = 1;
132
+ } else {
133
+ console.error(`walt: ${error instanceof Error ? error.message : String(error)}`);
134
+ exitCode = 1;
135
+ }
136
+ } finally {
137
+ await source.disconnect();
138
+ }
139
+ process.exit(exitCode);
140
+ export {};
@@ -0,0 +1,22 @@
1
+ import { WaltClient } from "../client.js";
2
+ export type CliOptions = {
3
+ json?: boolean;
4
+ fiat?: string;
5
+ asset?: string;
6
+ limit?: string;
7
+ from?: string;
8
+ monthly?: boolean;
9
+ all?: boolean;
10
+ closed?: boolean;
11
+ };
12
+ export declare class UsageError extends Error {}
13
+ export declare const COMMANDS = "Commands:\n balances every asset: spot, staked and held by limit orders\n history [--monthly] balance, deposits, withdrawals and P/L per day or month\n [--from DATE] start date, YYYY-MM-DD (default: 30 days or 12 months back)\n transactions latest transactions\n [--asset SYM] only this asset\n [--limit N] how many (default 20)\n price SYM... current price and change over 24h, 7d and 30d\n quote FROM TO AMOUNT what an exchange would give right now, without making it\n orders [--all] open limit orders, or every order with --all\n perps [--closed] open perpetual positions, or closed ones\n earn [--all] your earn stakes, or every active campaign with --all";
14
+ /**
15
+ * Runs one read-only command and returns what to print.
16
+ *
17
+ * @param client - A signed-in client.
18
+ * @param command - The command name, such as `balances`.
19
+ * @param args - Positional arguments after the command.
20
+ * @param options - Parsed flags.
21
+ */
22
+ export declare const run: (client: WaltClient, command: string, args: string[], options?: CliOptions) => Promise<string>;
@@ -0,0 +1,507 @@
1
+ import { WaltRequestError } from "../transport.js";
2
+ // source/cli/run.ts
3
+ var UsageError = class extends Error {};
4
+ const COMMANDS = `Commands:
5
+ balances every asset: spot, staked and held by limit orders
6
+ history [--monthly] balance, deposits, withdrawals and P/L per day or month
7
+ [--from DATE] start date, YYYY-MM-DD (default: 30 days or 12 months back)
8
+ transactions latest transactions
9
+ [--asset SYM] only this asset
10
+ [--limit N] how many (default 20)
11
+ price SYM... current price and change over 24h, 7d and 30d
12
+ quote FROM TO AMOUNT what an exchange would give right now, without making it
13
+ orders [--all] open limit orders, or every order with --all
14
+ perps [--closed] open perpetual positions, or closed ones
15
+ earn [--all] your earn stakes, or every active campaign with --all`;
16
+ const fixed = (value, digits = 2) => {
17
+ const parsed = Number(value);
18
+ return value === null || value === void 0 || value === "" || !Number.isFinite(parsed) ? "-" : parsed.toFixed(digits);
19
+ };
20
+ const trimmed = (value) => {
21
+ const parsed = Number(value);
22
+ if (value === null || value === void 0 || value === "" || !Number.isFinite(parsed)) return "-";
23
+ return String(Number(parsed.toFixed(8)));
24
+ };
25
+ const significant = (value) => String(Number(value.toPrecision(8)));
26
+ const percent = (value) => Number.isFinite(value) ? `${value >= 0 ? "+" : ""}${(value * 100).toFixed(2)}%` : "-";
27
+ const change = (now, then) => then === void 0 || then === 0 ? "-" : percent(now / then - 1);
28
+ const minute = (timestamp) => timestamp.slice(0, 16).replace("T", " ");
29
+ const isoDate = (date) => date.toISOString().slice(0, 10);
30
+ const ACCOUNT_KINDS = {
31
+ REGULAR: "spot",
32
+ EARN_V2: "earn",
33
+ HOLD_LIMIT: "orders"
34
+ };
35
+ const positiveInteger = (value, fallback) => {
36
+ if (value === void 0) return fallback;
37
+ const parsed = Number(value);
38
+ if (!Number.isSafeInteger(parsed) || parsed < 1) throw new UsageError(`--limit must be a whole number, got ${value}`);
39
+ return parsed;
40
+ };
41
+ const balances = async (client, fiat) => {
42
+ const data = await client.balances(fiat);
43
+ const total = Number(data.overall_available_balance_fiat_amount);
44
+ const rows = data.accounts.filter((account) => Number(account.total_balance) > 0).map((account) => ({
45
+ account,
46
+ value: Number(account.available_balance_fiat_amount)
47
+ })).sort((a, b) => b.value - a.value).map(({ account, value }) => ({
48
+ asset: account.currency,
49
+ kind: ACCOUNT_KINDS[account.account_type] ?? account.account_type.toLowerCase(),
50
+ amount: trimmed(account.total_balance),
51
+ value: value.toFixed(2),
52
+ share: total > 0 ? `${(value / total * 100).toFixed(1)}%` : "-"
53
+ }));
54
+ return {
55
+ columns: [
56
+ {
57
+ key: "asset",
58
+ title: "ASSET"
59
+ },
60
+ {
61
+ key: "kind",
62
+ title: "KIND"
63
+ },
64
+ {
65
+ key: "amount",
66
+ title: "AMOUNT",
67
+ numeric: true
68
+ },
69
+ {
70
+ key: "value",
71
+ title: `VALUE ${data.fiat_currency}`,
72
+ numeric: true
73
+ },
74
+ {
75
+ key: "share",
76
+ title: "SHARE",
77
+ numeric: true
78
+ }
79
+ ],
80
+ rows,
81
+ footer: [`total ${total.toFixed(2)} ${data.fiat_currency}`]
82
+ };
83
+ };
84
+ const history = async (client, options) => {
85
+ const start = /* @__PURE__ */ new Date();
86
+ if (options.monthly) start.setUTCMonth(start.getUTCMonth() - 11, 1);
87
+ else start.setUTCDate(start.getUTCDate() - 29);
88
+ const from = options.from ?? isoDate(start);
89
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(from)) throw new UsageError(`--from must be YYYY-MM-DD, got ${from}`);
90
+ const data = await client.portfolioHistory(from, options.monthly ? "1m" : "1d");
91
+ const period = (dateFrom) => options.monthly ? dateFrom.slice(0, 7) : dateFrom.slice(0, 10);
92
+ const rows = data.chart.data_points.map((point) => ({
93
+ period: period(point.date_from),
94
+ balance: fixed(point.close_amount_fiat),
95
+ deposits: fixed(point.deposits_details.total),
96
+ withdrawals: fixed(point.withdrawals_details.total),
97
+ pnl: fixed(point.total_pnl),
98
+ pnlPercent: point.total_pnl_percent === null ? "-" : `${fixed(point.total_pnl_percent)}%`
99
+ }));
100
+ const total = data.total_period_details;
101
+ return {
102
+ columns: [
103
+ {
104
+ key: "period",
105
+ title: options.monthly ? "MONTH" : "DAY"
106
+ },
107
+ {
108
+ key: "balance",
109
+ title: `BALANCE ${data.fiat_currency}`,
110
+ numeric: true
111
+ },
112
+ {
113
+ key: "deposits",
114
+ title: "DEPOSITS",
115
+ numeric: true
116
+ },
117
+ {
118
+ key: "withdrawals",
119
+ title: "WITHDRAWALS",
120
+ numeric: true
121
+ },
122
+ {
123
+ key: "pnl",
124
+ title: "P/L",
125
+ numeric: true
126
+ },
127
+ {
128
+ key: "pnlPercent",
129
+ title: "P/L %",
130
+ numeric: true
131
+ }
132
+ ],
133
+ rows,
134
+ footer: [`since ${from}: P/L ${fixed(total.total_pnl)} ${data.fiat_currency}` + (total.total_pnl_percent === null ? "" : ` (${fixed(total.total_pnl_percent)}%)`) + `, deposits ${fixed(total.deposits_details.total)}, withdrawals ${fixed(total.withdrawals_details.total)}`, "as reported by Walt"]
135
+ };
136
+ };
137
+ const transactions = async (client, options) => {
138
+ const limit = positiveInteger(options.limit, 20);
139
+ return {
140
+ columns: [
141
+ {
142
+ key: "time",
143
+ title: "TIME (UTC)"
144
+ },
145
+ {
146
+ key: "type",
147
+ title: "TYPE"
148
+ },
149
+ {
150
+ key: "gateway",
151
+ title: "VIA"
152
+ },
153
+ {
154
+ key: "amount",
155
+ title: "AMOUNT",
156
+ numeric: true
157
+ },
158
+ {
159
+ key: "asset",
160
+ title: "ASSET"
161
+ },
162
+ {
163
+ key: "status",
164
+ title: "STATUS"
165
+ }
166
+ ],
167
+ rows: (options.asset ? await client.accountTransactions(options.asset, limit) : await client.transactions(limit)).transactions.map((row) => ({
168
+ time: minute(row.created_at),
169
+ type: row.type,
170
+ gateway: row.gateway,
171
+ amount: row.amount,
172
+ asset: row.currency,
173
+ status: row.status
174
+ }))
175
+ };
176
+ };
177
+ const price = async (client, symbols, fiat) => {
178
+ if (symbols.length === 0) throw new UsageError("price needs at least one symbol, such as `walt price BTC`");
179
+ const rows = await Promise.all(symbols.map(async (symbol) => {
180
+ const charts = await client.charts(symbol, fiat);
181
+ const rates = (points) => points.map((point) => Number(point.rate));
182
+ const day = rates(charts.day);
183
+ const last = day.at(-1) ?? rates(charts.month).at(-1);
184
+ if (last === void 0) return {
185
+ asset: symbol,
186
+ price: "-",
187
+ day: "-",
188
+ week: "-",
189
+ month: "-"
190
+ };
191
+ return {
192
+ asset: symbol,
193
+ price: significant(last),
194
+ day: change(last, day[0]),
195
+ week: change(last, rates(charts.week)[0]),
196
+ month: change(last, rates(charts.month)[0])
197
+ };
198
+ }));
199
+ return {
200
+ columns: [
201
+ {
202
+ key: "asset",
203
+ title: "ASSET"
204
+ },
205
+ {
206
+ key: "price",
207
+ title: `PRICE ${fiat}`,
208
+ numeric: true
209
+ },
210
+ {
211
+ key: "day",
212
+ title: "24H",
213
+ numeric: true
214
+ },
215
+ {
216
+ key: "week",
217
+ title: "7D",
218
+ numeric: true
219
+ },
220
+ {
221
+ key: "month",
222
+ title: "30D",
223
+ numeric: true
224
+ }
225
+ ],
226
+ rows
227
+ };
228
+ };
229
+ const quoteSizes = (amount) => {
230
+ const [whole = "", fraction = ""] = amount.split(".");
231
+ const sizes = [amount, ...[
232
+ 6,
233
+ 4,
234
+ 2,
235
+ 0
236
+ ].filter((places) => places < fraction.length).map((places) => places === 0 ? whole : `${whole}.${fraction.slice(0, places)}`)];
237
+ return [...new Set(sizes)].filter((size) => Number(size) > 0);
238
+ };
239
+ const quote = async (client, args) => {
240
+ const [from, to, amount] = args;
241
+ if (!from || !to || amount === void 0 || !/^\d+(\.\d+)?$/.test(amount) || !(Number(amount) > 0)) throw new UsageError("usage: walt quote FROM TO AMOUNT, such as `walt quote USDT BTC 100`");
242
+ let result;
243
+ let size = amount;
244
+ let rejection;
245
+ for (const candidate of quoteSizes(amount)) try {
246
+ result = await client.quote(from, to, Number(candidate));
247
+ size = candidate;
248
+ break;
249
+ } catch (error) {
250
+ if (!(error instanceof WaltRequestError) || error.body?.code !== "invalid_amount") throw error;
251
+ rejection = error;
252
+ }
253
+ if (result === void 0) throw rejection;
254
+ const sale = result.to_currency === "USDT" && result.from_currency !== "USDT";
255
+ const price = result.from_amount <= 0 || result.to_amount <= 0 ? "-" : sale ? `${significant(result.to_amount / result.from_amount)} USDT/${result.from_currency}` : `${significant(result.from_amount / result.to_amount)} ${result.from_currency}/${result.to_currency}`;
256
+ const footer = [`order size ${result.min_amount} to ${result.max_amount} ${from}`, "fees included, nothing was exchanged"];
257
+ if (size !== amount) footer.unshift(`amount cut to ${size} ${from}, the most decimals it accepts`);
258
+ return {
259
+ columns: [
260
+ {
261
+ key: "pay",
262
+ title: "PAY",
263
+ numeric: true
264
+ },
265
+ {
266
+ key: "receive",
267
+ title: "RECEIVE",
268
+ numeric: true
269
+ },
270
+ {
271
+ key: "price",
272
+ title: "PRICE",
273
+ numeric: true
274
+ }
275
+ ],
276
+ rows: [{
277
+ pay: `${result.from_amount} ${result.from_currency}`,
278
+ receive: `${result.to_amount} ${result.to_currency}`,
279
+ price
280
+ }],
281
+ footer
282
+ };
283
+ };
284
+ const orders = async (client, options) => {
285
+ const list = await client.limitOrders(options.all ? { limit: 100 } : {
286
+ status: ["OPEN", "CANCELLING"],
287
+ limit: 100
288
+ });
289
+ const held = list.filter((order) => order.status === "OPEN").reduce((sum, order) => sum + Number(order.total), 0);
290
+ return {
291
+ columns: [
292
+ {
293
+ key: "created",
294
+ title: "CREATED (UTC)"
295
+ },
296
+ {
297
+ key: "asset",
298
+ title: "ASSET"
299
+ },
300
+ {
301
+ key: "side",
302
+ title: "SIDE"
303
+ },
304
+ {
305
+ key: "limit",
306
+ title: "LIMIT",
307
+ numeric: true
308
+ },
309
+ {
310
+ key: "quantity",
311
+ title: "QUANTITY",
312
+ numeric: true
313
+ },
314
+ {
315
+ key: "total",
316
+ title: "TOTAL",
317
+ numeric: true
318
+ },
319
+ {
320
+ key: "fee",
321
+ title: "FEE",
322
+ numeric: true
323
+ },
324
+ {
325
+ key: "status",
326
+ title: "STATUS"
327
+ }
328
+ ],
329
+ rows: list.map((order) => ({
330
+ created: minute(order.created_at),
331
+ asset: order.base_currency,
332
+ side: order.side,
333
+ limit: order.price_limit,
334
+ quantity: order.quantity,
335
+ total: `${order.total} ${order.fee_currency}`,
336
+ fee: order.fee,
337
+ status: order.status
338
+ })),
339
+ footer: [`${held.toFixed(2)} held by open orders`]
340
+ };
341
+ };
342
+ const perps = async (client, options) => {
343
+ const positions = await client.perpPositions({ statuses: [options.closed ? "closed" : "open"] });
344
+ const columns = [
345
+ {
346
+ key: "contract",
347
+ title: "CONTRACT"
348
+ },
349
+ {
350
+ key: "side",
351
+ title: "SIDE"
352
+ },
353
+ {
354
+ key: "leverage",
355
+ title: "LEV",
356
+ numeric: true
357
+ },
358
+ {
359
+ key: "size",
360
+ title: "SIZE",
361
+ numeric: true
362
+ },
363
+ {
364
+ key: "entry",
365
+ title: "ENTRY",
366
+ numeric: true
367
+ },
368
+ {
369
+ key: "margin",
370
+ title: "MARGIN",
371
+ numeric: true
372
+ }
373
+ ];
374
+ if (options.closed) columns.push({
375
+ key: "opened",
376
+ title: "OPENED (UTC)"
377
+ }, {
378
+ key: "closed",
379
+ title: "CLOSED (UTC)"
380
+ });
381
+ else columns.push({
382
+ key: "mark",
383
+ title: "MARK",
384
+ numeric: true
385
+ }, {
386
+ key: "liquidation",
387
+ title: "LIQUIDATION",
388
+ numeric: true
389
+ }, {
390
+ key: "pnl",
391
+ title: "UNREALISED",
392
+ numeric: true
393
+ });
394
+ return {
395
+ columns,
396
+ rows: positions.map((position) => ({
397
+ contract: position.contract_name,
398
+ side: position.side,
399
+ leverage: `${position.leverage}x`,
400
+ size: position.value_base,
401
+ entry: position.avg_entry_price,
402
+ margin: fixed(position.margin_quote),
403
+ opened: minute(position.created_at),
404
+ closed: minute(position.updated_at),
405
+ mark: position.valuation.mark_price,
406
+ liquidation: fixed(position.liquidation_price, 4),
407
+ pnl: fixed(position.valuation.unrealized_pnl_quote)
408
+ }))
409
+ };
410
+ };
411
+ const earn = async (client, options) => {
412
+ const { campaigns } = await client.earnCampaigns();
413
+ return {
414
+ columns: [
415
+ {
416
+ key: "campaign",
417
+ title: "CAMPAIGN"
418
+ },
419
+ {
420
+ key: "asset",
421
+ title: "ASSET"
422
+ },
423
+ {
424
+ key: "staked",
425
+ title: "STAKED",
426
+ numeric: true
427
+ },
428
+ {
429
+ key: "paid",
430
+ title: "PAID OUT",
431
+ numeric: true
432
+ },
433
+ {
434
+ key: "apy",
435
+ title: "APY",
436
+ numeric: true
437
+ },
438
+ {
439
+ key: "status",
440
+ title: "STATUS"
441
+ }
442
+ ],
443
+ rows: campaigns.filter((campaign) => options.all ? campaign.status === "active" : campaign.account !== null && Number(campaign.account.balance) > 0).map((campaign) => ({
444
+ campaign: campaign.name,
445
+ asset: campaign.account?.currency ?? campaign.deposit_currency,
446
+ staked: trimmed(campaign.account?.balance),
447
+ paid: trimmed(campaign.account?.paid_out_amount),
448
+ apy: `${fixed(Number(campaign.current_apy) * 100)}%`,
449
+ status: campaign.status
450
+ }))
451
+ };
452
+ };
453
+ const render = (report) => {
454
+ if (report.rows.length === 0) return ["nothing to show", ...report.footer ?? []].join("\n");
455
+ const widths = report.columns.map((column) => Math.max(column.title.length, ...report.rows.map((row) => (row[column.key] ?? "").length)));
456
+ const line = (cells) => cells.map((cell, index) => {
457
+ const width = widths[index] ?? 0;
458
+ return report.columns[index]?.numeric ? cell.padStart(width) : cell.padEnd(width);
459
+ }).join(" ").trimEnd();
460
+ return [
461
+ line(report.columns.map((column) => column.title)),
462
+ ...report.rows.map((row) => line(report.columns.map((column) => row[column.key] ?? ""))),
463
+ ...report.footer ?? []
464
+ ].join("\n");
465
+ };
466
+ /**
467
+ * Runs one read-only command and returns what to print.
468
+ *
469
+ * @param client - A signed-in client.
470
+ * @param command - The command name, such as `balances`.
471
+ * @param args - Positional arguments after the command.
472
+ * @param options - Parsed flags.
473
+ */
474
+ const run = async (client, command, args, options = {}) => {
475
+ const fiat = (options.fiat ?? "USD").toUpperCase();
476
+ let report;
477
+ switch (command) {
478
+ case "balances":
479
+ report = await balances(client, fiat);
480
+ break;
481
+ case "history":
482
+ report = await history(client, options);
483
+ break;
484
+ case "transactions":
485
+ report = await transactions(client, options);
486
+ break;
487
+ case "price":
488
+ report = await price(client, args, fiat);
489
+ break;
490
+ case "quote":
491
+ report = await quote(client, args);
492
+ break;
493
+ case "orders":
494
+ report = await orders(client, options);
495
+ break;
496
+ case "perps":
497
+ report = await perps(client, options);
498
+ break;
499
+ case "earn":
500
+ report = await earn(client, options);
501
+ break;
502
+ default: throw new UsageError(`unknown command: ${command}`);
503
+ }
504
+ if (!options.json) return render(report);
505
+ return JSON.stringify(report.rows.map((row) => Object.fromEntries(report.columns.map((column) => [column.key, row[column.key] ?? null]))), null, 2);
506
+ };
507
+ export { COMMANDS, UsageError, run };
package/build/client.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { AccountsResponse, Charts, Coin, CoinDetails, FiatCurrency, PriceAtTime } from "./api/accounts.js";
2
2
  import { PnlResponse, PortfolioHistory } from "./api/analytics.js";
3
+ import { Balances } from "./api/balances.js";
4
+ import { CandleRequest, CandleSeries } from "./api/candles.js";
3
5
  import { EarnCampaignsResponse, EarnRate, EarnTransaction, EarnTransactionsPage } from "./api/earn.js";
4
6
  import { AvailableExchanges, Exchange, ExchangeQuote, ExchangeRequest } from "./api/exchange.js";
5
7
  import { LimitOrder, LimitOrderBounds, LimitOrderQuote, LimitOrderToken } from "./api/limit-orders.js";
8
+ import { PerpContract, PerpOrderStatus, PerpOrdersPage, PerpPosition, PerpPositionStatus, PerpSystemStatus } from "./api/perps.js";
6
9
  import { Transaction, TransactionDetails, TransactionGateway, TransactionsPage } from "./api/transactions.js";
7
10
  export type WaltClientOptions = {
8
11
  /** Called again with the rejected token after a 401, so the provider can renew it before its expiry. */
@@ -36,6 +39,12 @@ export declare class WaltClient {
36
39
  private readonly maxConcurrent;
37
40
  constructor(options: WaltClientOptions);
38
41
  accounts(): Promise<AccountsResponse>;
42
+ /**
43
+ * Every balance with its account type, including USDT held by open limit orders, which `accounts()` omits.
44
+ *
45
+ * @param fiatCurrency - Currency of the fiat amounts and the overall total.
46
+ */
47
+ balances(fiatCurrency: FiatCurrency): Promise<Balances>;
39
48
  coins(fiatCurrency: FiatCurrency): Promise<Coin[]>;
40
49
  localCurrency(): Promise<FiatCurrency>;
41
50
  transactions(limit?: number, cursor?: string): Promise<TransactionsPage>;
@@ -115,6 +124,35 @@ export declare class WaltClient {
115
124
  limitOrderBounds(baseCurrency: string): Promise<LimitOrderBounds>;
116
125
  /** Every currency a limit order can buy. */
117
126
  limitOrderTokens(): Promise<LimitOrderToken[]>;
127
+ /** @param contractId - Only this contract. */
128
+ perpContracts(contractId?: number): Promise<PerpContract[]>;
129
+ perpSystemStatus(): Promise<PerpSystemStatus>;
130
+ /**
131
+ * @param options - Filters, all optional.
132
+ * @param options.statuses - Only positions in these states. Without it, closed positions are included.
133
+ * @param options.contractId - Only this contract.
134
+ */
135
+ perpPositions(options?: {
136
+ statuses?: PerpPositionStatus[];
137
+ contractId?: number;
138
+ }): Promise<PerpPosition[]>;
139
+ /**
140
+ * @param options - Filters, all optional.
141
+ * @param options.contractId - Only this contract.
142
+ * @param options.statuses - Only orders in these states.
143
+ * @param options.limit - Most orders to return.
144
+ */
145
+ perpOrders(options?: {
146
+ contractId?: number;
147
+ statuses?: PerpOrderStatus[];
148
+ limit?: number;
149
+ }): Promise<PerpOrdersPage>;
150
+ /**
151
+ * OHLCV candles for several tickers in one request.
152
+ *
153
+ * @param requests - One per series wanted.
154
+ */
155
+ candles(requests: CandleRequest[]): Promise<CandleSeries[]>;
118
156
  coinDetails(currency: string, fiatCurrency: FiatCurrency): Promise<CoinDetails>;
119
157
  charts(currency: string, fiatCurrency: FiatCurrency): Promise<Charts>;
120
158
  priceForFiatAtTime(cryptoCurrency: string, localCurrency: FiatCurrency, amount: number | string, time: Date | string | number): Promise<PriceAtTime>;
package/build/client.js CHANGED
@@ -32,6 +32,16 @@ var WaltClient = class {
32
32
  accounts() {
33
33
  return this.transport.get("/api/v1/accounts/");
34
34
  }
35
+ /**
36
+ * Every balance with its account type, including USDT held by open limit orders, which `accounts()` omits.
37
+ *
38
+ * @param fiatCurrency - Currency of the fiat amounts and the overall total.
39
+ */
40
+ async balances(fiatCurrency) {
41
+ const query = new URLSearchParams({ fiat_currency: fiatCurrency });
42
+ const { data } = await this.transport.get(`/portmone/public-api/v1/user/accounts?${query.toString()}`);
43
+ return data;
44
+ }
35
45
  async coins(fiatCurrency) {
36
46
  const { coins } = await this.transport.get(`/api/v1/coins/list/${fiatCurrency}/`);
37
47
  return coins;
@@ -222,6 +232,51 @@ var WaltClient = class {
222
232
  const { tokens } = await this.transport.get("/emporos/public-api/orders/limit/tokens");
223
233
  return tokens;
224
234
  }
235
+ /** @param contractId - Only this contract. */
236
+ async perpContracts(contractId) {
237
+ const query = contractId === void 0 ? "" : `?${new URLSearchParams({ contract_id: String(contractId) }).toString()}`;
238
+ const { contracts } = await this.transport.get(`/perps/public-api/v1/contracts/${query}`);
239
+ return contracts;
240
+ }
241
+ perpSystemStatus() {
242
+ return this.transport.get("/perps/public-api/v1/system-status/");
243
+ }
244
+ /**
245
+ * @param options - Filters, all optional.
246
+ * @param options.statuses - Only positions in these states. Without it, closed positions are included.
247
+ * @param options.contractId - Only this contract.
248
+ */
249
+ async perpPositions(options = {}) {
250
+ const query = new URLSearchParams();
251
+ if (options.contractId !== void 0) query.set("contract_id", String(options.contractId));
252
+ for (const status of options.statuses ?? []) query.append("statuses", status);
253
+ const search = query.size === 0 ? "" : `?${query.toString()}`;
254
+ const { positions } = await this.transport.get(`/perps/public-api/v1/positions/${search}`);
255
+ return positions;
256
+ }
257
+ /**
258
+ * @param options - Filters, all optional.
259
+ * @param options.contractId - Only this contract.
260
+ * @param options.statuses - Only orders in these states.
261
+ * @param options.limit - Most orders to return.
262
+ */
263
+ perpOrders(options = {}) {
264
+ const query = new URLSearchParams();
265
+ if (options.contractId !== void 0) query.set("contract_id", String(options.contractId));
266
+ if (options.limit !== void 0) query.set("limit", String(options.limit));
267
+ for (const status of options.statuses ?? []) query.append("statuses", status);
268
+ const search = query.size === 0 ? "" : `?${query.toString()}`;
269
+ return this.transport.get(`/perps/public-api/v2/orders/${search}`);
270
+ }
271
+ /**
272
+ * OHLCV candles for several tickers in one request.
273
+ *
274
+ * @param requests - One per series wanted.
275
+ */
276
+ async candles(requests) {
277
+ const { responses } = await this.transport.post("/kandelabr/public-api/v1/candles/batch", { requests });
278
+ return responses;
279
+ }
225
280
  coinDetails(currency, fiatCurrency) {
226
281
  return this.transport.get(`/api/v1/coins/details/${encodeURIComponent(currency)}/${encodeURIComponent(fiatCurrency)}/`);
227
282
  }
package/build/index.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { Account, AccountAction, AccountActions, AccountAddress, AccountsResponse, ChartPoint, Charts, Coin, CoinDetails, FiatCurrency, PriceAtTime, TradingHoursPeriod, TradingHoursState } from "./api/accounts.js";
2
2
  import { PnlPosition, PnlResponse, PnlTotals, PortfolioFlows, PortfolioHistory, PortfolioPeriod } from "./api/analytics.js";
3
+ import { Balance, Balances } from "./api/balances.js";
4
+ import { Candle, CandleRequest, CandleSeries } from "./api/candles.js";
3
5
  import { EarnAccessPolicy, EarnAccount, EarnAmount, EarnBonus, EarnCampaign, EarnCampaignsResponse, EarnDepegInfo, EarnInterestRate, EarnPayoutPool, EarnRate, EarnTransaction, EarnTransactionsPage } from "./api/earn.js";
4
6
  import { AvailableExchanges, Exchange, ExchangeQuote, ExchangeRequest } from "./api/exchange.js";
5
7
  import { LimitOrder, LimitOrderBounds, LimitOrderQuote, LimitOrderToken } from "./api/limit-orders.js";
8
+ import { PerpContract, PerpOrder, PerpOrderStatus, PerpOrdersPage, PerpPosition, PerpPositionStatus, PerpSystemStatus } from "./api/perps.js";
6
9
  import { NormalizedTransaction, Transaction, TransactionDetails, TransactionGateway, TransactionLeg, TransactionsPage, normalizeTransaction } from "./api/transactions.js";
7
10
  import { Portfolio, PortfolioEntry, WaltClient, WaltClientOptions } from "./client.js";
8
11
  import { TelegramTokenSource, TelegramTokenSourceOptions, TokenState, TokenStorage } from "./telegram/auth.js";
9
12
  import { WaltAuthError, WaltRequestError } from "./transport.js";
10
- export { type Account, type AccountAction, type AccountActions, type AccountAddress, type AccountsResponse, type AvailableExchanges, type ChartPoint, type Charts, type Coin, type CoinDetails, type EarnAccessPolicy, type EarnAccount, type EarnAmount, type EarnBonus, type EarnCampaign, type EarnCampaignsResponse, type EarnDepegInfo, type EarnInterestRate, type EarnPayoutPool, type EarnRate, type EarnTransaction, type EarnTransactionsPage, type Exchange, type ExchangeQuote, type ExchangeRequest, type FiatCurrency, type LimitOrder, type LimitOrderBounds, type LimitOrderQuote, type LimitOrderToken, type NormalizedTransaction, type PnlPosition, type PnlResponse, type PnlTotals, type Portfolio, type PortfolioEntry, type PortfolioFlows, type PortfolioHistory, type PortfolioPeriod, type PriceAtTime, TelegramTokenSource, type TelegramTokenSourceOptions, type TokenState, type TokenStorage, type TradingHoursPeriod, type TradingHoursState, type Transaction, type TransactionDetails, type TransactionGateway, type TransactionLeg, type TransactionsPage, WaltAuthError, WaltClient, type WaltClientOptions, WaltRequestError, normalizeTransaction };
13
+ export { type Account, type AccountAction, type AccountActions, type AccountAddress, type AccountsResponse, type AvailableExchanges, type Balance, type Balances, type Candle, type CandleRequest, type CandleSeries, type ChartPoint, type Charts, type Coin, type CoinDetails, type EarnAccessPolicy, type EarnAccount, type EarnAmount, type EarnBonus, type EarnCampaign, type EarnCampaignsResponse, type EarnDepegInfo, type EarnInterestRate, type EarnPayoutPool, type EarnRate, type EarnTransaction, type EarnTransactionsPage, type Exchange, type ExchangeQuote, type ExchangeRequest, type FiatCurrency, type LimitOrder, type LimitOrderBounds, type LimitOrderQuote, type LimitOrderToken, type NormalizedTransaction, type PerpContract, type PerpOrder, type PerpOrderStatus, type PerpOrdersPage, type PerpPosition, type PerpPositionStatus, type PerpSystemStatus, type PnlPosition, type PnlResponse, type PnlTotals, type Portfolio, type PortfolioEntry, type PortfolioFlows, type PortfolioHistory, type PortfolioPeriod, type PriceAtTime, TelegramTokenSource, type TelegramTokenSourceOptions, type TokenState, type TokenStorage, type TradingHoursPeriod, type TradingHoursState, type Transaction, type TransactionDetails, type TransactionGateway, type TransactionLeg, type TransactionsPage, WaltAuthError, WaltClient, type WaltClientOptions, WaltRequestError, normalizeTransaction };
@@ -23,6 +23,11 @@ const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
23
23
  503,
24
24
  504
25
25
  ]);
26
+ const BEARER_SERVICES = [
27
+ "/emporos/",
28
+ "/portmone/",
29
+ "/kandelabr/"
30
+ ];
26
31
  const MAX_RETRY_DELAY_MS = 3e4;
27
32
  const hex = (bytes) => randomBytes(bytes).toString("hex");
28
33
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -121,7 +126,7 @@ var Transport = class {
121
126
  const headers = {
122
127
  ...BROWSER_HEADERS,
123
128
  accept: "application/json",
124
- authorization: path.startsWith("/emporos/") ? `Bearer ${jwt}` : jwt,
129
+ authorization: BEARER_SERVICES.some((prefix) => path.startsWith(prefix)) ? `Bearer ${jwt}` : jwt,
125
130
  referer: `${this.baseUrl}/main?ep=menu${userId === null ? "" : `&tgWebAppUserId=${userId}`}`,
126
131
  baggage: this.baggage,
127
132
  "sentry-trace": `${this.traceId}-${hex(8)}-0`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2bad/walt",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Client for the Walt (ex. Wallet) API in Telegram to track a crypto portfolio and exchange between assets",
5
5
  "keywords": [
6
6
  "telegram",
@@ -36,7 +36,7 @@
36
36
  }
37
37
  },
38
38
  "bin": {
39
- "telegram-login": "./build/bin/login.js"
39
+ "walt": "./build/bin/walt.js"
40
40
  },
41
41
  "files": [
42
42
  "build"
@@ -74,7 +74,7 @@
74
74
  "fix": "run-p fix:**",
75
75
  "fix:code": "oxlint --fix ./source",
76
76
  "fix:format": "oxfmt ./source ./examples",
77
- "telegram:login": "node --env-file-if-exists=.env source/bin/login.ts",
77
+ "telegram:login": "run-s build && node --env-file-if-exists=.env build/bin/walt.js login",
78
78
  "example": "run-s build && node --env-file-if-exists=.env examples/portfolio.ts",
79
79
  "preversion": "run-s check test build",
80
80
  "test": "vitest run",
@@ -1,30 +0,0 @@
1
- #!/usr/bin/env node
2
- import { TelegramClient } from "teleproto";
3
- import { StringSession } from "teleproto/sessions/index.js";
4
- import { createInterface } from "node:readline/promises";
5
- import { stdin, stdout } from "node:process";
6
- // source/bin/login.ts
7
- const rl = createInterface({
8
- input: stdin,
9
- output: stdout
10
- });
11
- const ask = (question) => rl.question(question);
12
- const apiId = Number(process.env["TG_API_ID"] || await ask("API id (from https://my.telegram.org): "));
13
- if (!Number.isSafeInteger(apiId) || apiId <= 0) {
14
- console.error("The API id must be the number shown at https://my.telegram.org");
15
- process.exit(1);
16
- }
17
- const apiHash = process.env["TG_API_HASH"] || await ask("API hash: ");
18
- const client = new TelegramClient(new StringSession(""), apiId, apiHash, { connectionRetries: 3 });
19
- await client.start({
20
- phoneNumber: () => ask("Phone (+country...): "),
21
- phoneCode: () => ask("Code you received: "),
22
- password: () => ask("2FA password (blank if none): "),
23
- onError: (error) => console.error(error)
24
- });
25
- rl.close();
26
- console.log("\nLogged in. Save this as TG_SESSION (keep it secret):\n");
27
- console.log(client.session.save());
28
- await client.disconnect();
29
- process.exit(0);
30
- export {};