@2bad/walt 2.0.0 → 2.1.1
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 +10 -1
- package/build/api/candles.d.ts +2 -1
- package/build/bin/walt.js +14 -2
- package/build/cli/run.d.ts +1 -1
- package/build/cli/run.js +16 -13
- package/build/client.d.ts +7 -4
- package/build/client.js +26 -6
- package/build/core.d.ts +13 -0
- package/build/core.js +5 -0
- package/build/index.d.ts +3 -2
- package/build/index.js +4 -3
- package/build/refresh.d.ts +60 -0
- package/build/refresh.js +131 -0
- package/build/telegram/auth.d.ts +6 -35
- package/build/telegram/auth.js +18 -118
- package/build/telegram/file-storage.d.ts +1 -1
- package/build/transport.js +6 -5
- package/package.json +14 -8
package/README.md
CHANGED
|
@@ -21,6 +21,9 @@ npm install @2bad/walt
|
|
|
21
21
|
|
|
22
22
|
ESM only. Requires Node.js 26 or later.
|
|
23
23
|
|
|
24
|
+
`@2bad/walt/client` exports everything except `TelegramTokenSource`.
|
|
25
|
+
It imports no Node.js API and no Telegram library, for runtimes such as Cloudflare Workers.
|
|
26
|
+
|
|
24
27
|
## Quick start
|
|
25
28
|
|
|
26
29
|
```ts
|
|
@@ -36,7 +39,7 @@ await source.disconnect()
|
|
|
36
39
|
|
|
37
40
|
## Examples
|
|
38
41
|
|
|
39
|
-
- [Connecting](examples/connecting.md): Telegram credentials, token storage, retries and pacing
|
|
42
|
+
- [Connecting](examples/connecting.md): Telegram credentials, token storage, refreshing without Telegram, retries and pacing
|
|
40
43
|
- [Balances](examples/balances.md): accounts, portfolio, profit and loss, staked funds
|
|
41
44
|
- [Prices](examples/prices.md): coin rates, charts and past prices
|
|
42
45
|
- [Exchange](examples/exchange.md): available pairs, quotes, exchanges and limit orders
|
|
@@ -56,6 +59,12 @@ type TelegramTokenSource = {
|
|
|
56
59
|
disconnect(): Promise<void>
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
new RefreshTokenSource(options: RefreshTokenSourceOptions)
|
|
63
|
+
|
|
64
|
+
type RefreshTokenSource = {
|
|
65
|
+
token(rejected?: string): Promise<string>
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
new WaltClient(options: WaltClientOptions)
|
|
60
69
|
|
|
61
70
|
type WaltClient = {
|
package/build/api/candles.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type CandleRequest = {
|
|
2
2
|
/** `<currency>/USD/SPOT`, or `<base>/USD/PERP_TRADE` for a perps contract. */
|
|
3
3
|
ticker: string;
|
|
4
|
-
/**
|
|
4
|
+
/** Accepted: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`, `1w`, `1mo`. */
|
|
5
5
|
resolution: string;
|
|
6
|
+
/** At least 1. The whole batch may ask for 3000 candles at most. */
|
|
6
7
|
limit: number;
|
|
7
8
|
};
|
|
8
9
|
export type Candle = {
|
package/build/bin/walt.js
CHANGED
|
@@ -46,6 +46,7 @@ const USAGE = `Usage: walt <command> [options]
|
|
|
46
46
|
|
|
47
47
|
${COMMANDS}
|
|
48
48
|
login sign in to Telegram and print TG_SESSION
|
|
49
|
+
token sign in as a new device and print its token state as JSON
|
|
49
50
|
|
|
50
51
|
Options:
|
|
51
52
|
--json print JSON instead of a table
|
|
@@ -109,15 +110,26 @@ const session = process.env["TG_SESSION"];
|
|
|
109
110
|
const apiId = Number(process.env["TG_API_ID"]);
|
|
110
111
|
const apiHash = process.env["TG_API_HASH"];
|
|
111
112
|
if (!session || !apiId || !apiHash) fail("set TG_API_ID, TG_API_HASH and TG_SESSION first (run `walt login` for the session)", 2);
|
|
113
|
+
if (command === "token") console.log = console.error;
|
|
114
|
+
let issued = null;
|
|
112
115
|
const source = new TelegramTokenSource({
|
|
113
116
|
session: session ?? "",
|
|
114
117
|
apiId,
|
|
115
|
-
apiHash: apiHash ?? ""
|
|
118
|
+
apiHash: apiHash ?? "",
|
|
119
|
+
...command === "token" && { storage: {
|
|
120
|
+
load: async () => null,
|
|
121
|
+
save: async (state) => {
|
|
122
|
+
issued = state;
|
|
123
|
+
}
|
|
124
|
+
} }
|
|
116
125
|
});
|
|
117
126
|
const client = new WaltClient({ tokenProvider: (rejected) => source.token(rejected) });
|
|
118
127
|
let exitCode = 0;
|
|
119
128
|
try {
|
|
120
|
-
|
|
129
|
+
if (command === "token") {
|
|
130
|
+
await source.token();
|
|
131
|
+
stdout.write(`${JSON.stringify(issued)}\n`);
|
|
132
|
+
} else console.log(await run(client, command, args, values));
|
|
121
133
|
} catch (error) {
|
|
122
134
|
if (error instanceof UsageError) {
|
|
123
135
|
console.error(`walt: ${error.message}\nRun \`walt --help\` for usage.`);
|
package/build/cli/run.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type CliOptions = {
|
|
|
10
10
|
closed?: boolean;
|
|
11
11
|
};
|
|
12
12
|
export declare class UsageError extends Error {}
|
|
13
|
-
export declare const COMMANDS = "Commands:\n balances
|
|
13
|
+
export declare const COMMANDS = "Commands:\n balances [--all] every asset: spot, staked and held by limit orders,\n and with --all the ones worth less than 0.01\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
14
|
/**
|
|
15
15
|
* Runs one read-only command and returns what to print.
|
|
16
16
|
*
|
package/build/cli/run.js
CHANGED
|
@@ -2,7 +2,8 @@ import { WaltRequestError } from "../transport.js";
|
|
|
2
2
|
// source/cli/run.ts
|
|
3
3
|
var UsageError = class extends Error {};
|
|
4
4
|
const COMMANDS = `Commands:
|
|
5
|
-
balances
|
|
5
|
+
balances [--all] every asset: spot, staked and held by limit orders,
|
|
6
|
+
and with --all the ones worth less than 0.01
|
|
6
7
|
history [--monthly] balance, deposits, withdrawals and P/L per day or month
|
|
7
8
|
[--from DATE] start date, YYYY-MM-DD (default: 30 days or 12 months back)
|
|
8
9
|
transactions latest transactions
|
|
@@ -22,7 +23,7 @@ const trimmed = (value) => {
|
|
|
22
23
|
if (value === null || value === void 0 || value === "" || !Number.isFinite(parsed)) return "-";
|
|
23
24
|
return String(Number(parsed.toFixed(8)));
|
|
24
25
|
};
|
|
25
|
-
const significant = (value) => String(Number(value.toPrecision(8)));
|
|
26
|
+
const significant = (value) => Number.isFinite(value) ? String(Number(value.toPrecision(8))) : "-";
|
|
26
27
|
const percent = (value) => Number.isFinite(value) ? `${value >= 0 ? "+" : ""}${(value * 100).toFixed(2)}%` : "-";
|
|
27
28
|
const change = (now, then) => then === void 0 || then === 0 ? "-" : percent(now / then - 1);
|
|
28
29
|
const minute = (timestamp) => timestamp.slice(0, 16).replace("T", " ");
|
|
@@ -38,13 +39,15 @@ const positiveInteger = (value, fallback) => {
|
|
|
38
39
|
if (!Number.isSafeInteger(parsed) || parsed < 1) throw new UsageError(`--limit must be a whole number, got ${value}`);
|
|
39
40
|
return parsed;
|
|
40
41
|
};
|
|
41
|
-
const balances = async (client, fiat) => {
|
|
42
|
+
const balances = async (client, fiat, options) => {
|
|
42
43
|
const data = await client.balances(fiat);
|
|
43
44
|
const total = Number(data.overall_available_balance_fiat_amount);
|
|
44
|
-
const
|
|
45
|
+
const held = data.accounts.filter((account) => Number(account.total_balance) > 0).map((account) => ({
|
|
45
46
|
account,
|
|
46
47
|
value: Number(account.available_balance_fiat_amount)
|
|
47
|
-
}))
|
|
48
|
+
}));
|
|
49
|
+
const shown = options.all ? held : held.filter(({ value }) => !(value < .005));
|
|
50
|
+
const rows = shown.sort((a, b) => b.value - a.value).map(({ account, value }) => ({
|
|
48
51
|
asset: account.currency,
|
|
49
52
|
kind: ACCOUNT_KINDS[account.account_type] ?? account.account_type.toLowerCase(),
|
|
50
53
|
amount: trimmed(account.total_balance),
|
|
@@ -78,7 +81,7 @@ const balances = async (client, fiat) => {
|
|
|
78
81
|
}
|
|
79
82
|
],
|
|
80
83
|
rows,
|
|
81
|
-
footer: [`total ${total.toFixed(2)} ${data.fiat_currency}`]
|
|
84
|
+
footer: [`total ${total.toFixed(2)} ${data.fiat_currency}`, ...shown.length < held.length ? [`${held.length - shown.length} worth less than 0.01 not shown, --all lists them`] : []]
|
|
82
85
|
};
|
|
83
86
|
};
|
|
84
87
|
const history = async (client, options) => {
|
|
@@ -168,7 +171,7 @@ const transactions = async (client, options) => {
|
|
|
168
171
|
time: minute(row.created_at),
|
|
169
172
|
type: row.type,
|
|
170
173
|
gateway: row.gateway,
|
|
171
|
-
amount: row.amount,
|
|
174
|
+
amount: trimmed(row.amount),
|
|
172
175
|
asset: row.currency,
|
|
173
176
|
status: row.status
|
|
174
177
|
}))
|
|
@@ -336,7 +339,7 @@ const orders = async (client, options) => {
|
|
|
336
339
|
fee: order.fee,
|
|
337
340
|
status: order.status
|
|
338
341
|
})),
|
|
339
|
-
footer: [`${held.toFixed(2)} held by open orders`]
|
|
342
|
+
footer: [`${held.toFixed(2)} USDT held by open orders`]
|
|
340
343
|
};
|
|
341
344
|
};
|
|
342
345
|
const perps = async (client, options) => {
|
|
@@ -397,13 +400,13 @@ const perps = async (client, options) => {
|
|
|
397
400
|
contract: position.contract_name,
|
|
398
401
|
side: position.side,
|
|
399
402
|
leverage: `${position.leverage}x`,
|
|
400
|
-
size: position.value_base,
|
|
401
|
-
entry: position.avg_entry_price,
|
|
403
|
+
size: trimmed(position.value_base),
|
|
404
|
+
entry: significant(Number(position.avg_entry_price)),
|
|
402
405
|
margin: fixed(position.margin_quote),
|
|
403
406
|
opened: minute(position.created_at),
|
|
404
407
|
closed: minute(position.updated_at),
|
|
405
|
-
mark: position.valuation.mark_price,
|
|
406
|
-
liquidation:
|
|
408
|
+
mark: significant(Number(position.valuation.mark_price)),
|
|
409
|
+
liquidation: significant(Number(position.liquidation_price)),
|
|
407
410
|
pnl: fixed(position.valuation.unrealized_pnl_quote)
|
|
408
411
|
}))
|
|
409
412
|
};
|
|
@@ -476,7 +479,7 @@ const run = async (client, command, args, options = {}) => {
|
|
|
476
479
|
let report;
|
|
477
480
|
switch (command) {
|
|
478
481
|
case "balances":
|
|
479
|
-
report = await balances(client, fiat);
|
|
482
|
+
report = await balances(client, fiat, options);
|
|
480
483
|
break;
|
|
481
484
|
case "history":
|
|
482
485
|
report = await history(client, options);
|
package/build/client.d.ts
CHANGED
|
@@ -28,9 +28,9 @@ export type Portfolio = {
|
|
|
28
28
|
/** Spot balances above zero, largest fiat value first. */
|
|
29
29
|
entries: PortfolioEntry[];
|
|
30
30
|
entriesTotal: number;
|
|
31
|
-
/**
|
|
31
|
+
/** `entriesTotal + staked`. */
|
|
32
32
|
total: number;
|
|
33
|
-
/**
|
|
33
|
+
/** What the API total counts beyond spot balances, itemised by `earnCampaigns()`. */
|
|
34
34
|
staked: number;
|
|
35
35
|
fiatCurrency: string;
|
|
36
36
|
};
|
|
@@ -150,7 +150,7 @@ export declare class WaltClient {
|
|
|
150
150
|
/**
|
|
151
151
|
* OHLCV candles for several tickers in one request.
|
|
152
152
|
*
|
|
153
|
-
* @param requests - One per series wanted.
|
|
153
|
+
* @param requests - One per series wanted, 20 at most.
|
|
154
154
|
*/
|
|
155
155
|
candles(requests: CandleRequest[]): Promise<CandleSeries[]>;
|
|
156
156
|
coinDetails(currency: string, fiatCurrency: FiatCurrency): Promise<CoinDetails>;
|
|
@@ -171,6 +171,9 @@ export declare class WaltClient {
|
|
|
171
171
|
* @param request - The exchange, in the shape the API takes it.
|
|
172
172
|
*/
|
|
173
173
|
createExchange(request: ExchangeRequest): Promise<Exchange>;
|
|
174
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Non-zero spot balances and the total, which also counts staked funds.
|
|
176
|
+
* A USD account is priced at the latest candle close, any other at the rates `accounts()` reports.
|
|
177
|
+
*/
|
|
175
178
|
portfolio(): Promise<Portfolio>;
|
|
176
179
|
}
|
package/build/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Transport, WaltAuthError } from "./transport.js";
|
|
2
2
|
// source/client.ts
|
|
3
3
|
const BASE_URL = "https://walletbot.me";
|
|
4
|
+
const CANDLE_BATCH_SIZE = 20;
|
|
4
5
|
const calendarDate = (date) => [
|
|
5
6
|
date.getUTCFullYear(),
|
|
6
7
|
date.getUTCMonth() + 1,
|
|
@@ -271,7 +272,7 @@ var WaltClient = class {
|
|
|
271
272
|
/**
|
|
272
273
|
* OHLCV candles for several tickers in one request.
|
|
273
274
|
*
|
|
274
|
-
* @param requests - One per series wanted.
|
|
275
|
+
* @param requests - One per series wanted, 20 at most.
|
|
275
276
|
*/
|
|
276
277
|
async candles(requests) {
|
|
277
278
|
const { responses } = await this.transport.post("/kandelabr/public-api/v1/candles/batch", { requests });
|
|
@@ -317,12 +318,15 @@ var WaltClient = class {
|
|
|
317
318
|
createExchange(request) {
|
|
318
319
|
return this.transport.post("/api/v1/exchange/create_exchange/", request, 0);
|
|
319
320
|
}
|
|
320
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* Non-zero spot balances and the total, which also counts staked funds.
|
|
323
|
+
* A USD account is priced at the latest candle close, any other at the rates `accounts()` reports.
|
|
324
|
+
*/
|
|
321
325
|
async portfolio() {
|
|
322
326
|
const data = await this.accounts();
|
|
323
327
|
if (!Array.isArray(data.accounts)) throw new Error("accounts response carries no accounts array");
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
328
|
+
const accountsTotal = decimal(data.available_balance_total_fiat_amount);
|
|
329
|
+
if (accountsTotal === null) throw new Error(`accounts total is not a number: ${String(data.available_balance_total_fiat_amount)}`);
|
|
326
330
|
const entries = [];
|
|
327
331
|
for (const account of data.accounts) {
|
|
328
332
|
const balance = decimal(account.available_balance);
|
|
@@ -336,12 +340,28 @@ var WaltClient = class {
|
|
|
336
340
|
fiatValue
|
|
337
341
|
});
|
|
338
342
|
}
|
|
343
|
+
const staked = accountsTotal - entries.reduce((sum, entry) => sum + entry.fiatValue, 0);
|
|
344
|
+
if (data.available_balance_total_fiat_currency === "USD") {
|
|
345
|
+
const requests = entries.map((entry) => ({
|
|
346
|
+
ticker: `${entry.currency}/USD/SPOT`,
|
|
347
|
+
resolution: "1m",
|
|
348
|
+
limit: 1
|
|
349
|
+
}));
|
|
350
|
+
const batches = Array.from({ length: Math.ceil(requests.length / CANDLE_BATCH_SIZE) }, (_, index) => requests.slice(index * CANDLE_BATCH_SIZE, (index + 1) * CANDLE_BATCH_SIZE));
|
|
351
|
+
const series = (await Promise.all(batches.map((batch) => this.candles(batch)))).flat();
|
|
352
|
+
const closes = new Map(series.map((item) => [item.ticker, decimal(item.candles.at(-1)?.c)]));
|
|
353
|
+
for (const entry of entries) {
|
|
354
|
+
const close = closes.get(`${entry.currency}/USD/SPOT`) ?? null;
|
|
355
|
+
if (close === null) throw new Error(`${entry.currency} has no candle price`);
|
|
356
|
+
entry.fiatValue = Number(entry.amount) * close;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
339
359
|
const entriesTotal = entries.reduce((sum, entry) => sum + entry.fiatValue, 0);
|
|
340
360
|
return {
|
|
341
361
|
entries: entries.sort((a, b) => b.fiatValue - a.fiatValue),
|
|
342
362
|
entriesTotal,
|
|
343
|
-
total,
|
|
344
|
-
staked
|
|
363
|
+
total: entriesTotal + staked,
|
|
364
|
+
staked,
|
|
345
365
|
fiatCurrency: data.available_balance_total_fiat_currency
|
|
346
366
|
};
|
|
347
367
|
}
|
package/build/core.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Account, AccountAction, AccountActions, AccountAddress, AccountsResponse, ChartPoint, Charts, Coin, CoinDetails, FiatCurrency, PriceAtTime, TradingHoursPeriod, TradingHoursState } from "./api/accounts.js";
|
|
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";
|
|
5
|
+
import { EarnAccessPolicy, EarnAccount, EarnAmount, EarnBonus, EarnCampaign, EarnCampaignsResponse, EarnDepegInfo, EarnInterestRate, EarnPayoutPool, EarnRate, EarnTransaction, EarnTransactionsPage } from "./api/earn.js";
|
|
6
|
+
import { AvailableExchanges, Exchange, ExchangeQuote, ExchangeRequest } from "./api/exchange.js";
|
|
7
|
+
import { LimitOrder, LimitOrderBounds, LimitOrderQuote, LimitOrderToken } from "./api/limit-orders.js";
|
|
8
|
+
import { PerpContract, PerpOrder, PerpOrderStatus, PerpOrdersPage, PerpPosition, PerpPositionStatus, PerpSystemStatus } from "./api/perps.js";
|
|
9
|
+
import { NormalizedTransaction, Transaction, TransactionDetails, TransactionGateway, TransactionLeg, TransactionsPage, normalizeTransaction } from "./api/transactions.js";
|
|
10
|
+
import { Portfolio, PortfolioEntry, WaltClient, WaltClientOptions } from "./client.js";
|
|
11
|
+
import { RefreshTokenSource, RefreshTokenSourceOptions, TokenState, TokenStorage } from "./refresh.js";
|
|
12
|
+
import { WaltAuthError, WaltRequestError } from "./transport.js";
|
|
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, RefreshTokenSource, type RefreshTokenSourceOptions, type TokenState, type TokenStorage, type TradingHoursPeriod, type TradingHoursState, type Transaction, type TransactionDetails, type TransactionGateway, type TransactionLeg, type TransactionsPage, WaltAuthError, WaltClient, type WaltClientOptions, WaltRequestError, normalizeTransaction };
|
package/build/core.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { normalizeTransaction } from "./api/transactions.js";
|
|
2
|
+
import { WaltClient } from "./client.js";
|
|
3
|
+
import { RefreshTokenSource } from "./refresh.js";
|
|
4
|
+
import { WaltAuthError, WaltRequestError } from "./transport.js";
|
|
5
|
+
export { RefreshTokenSource, WaltAuthError, WaltClient, WaltRequestError, normalizeTransaction };
|
package/build/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { LimitOrder, LimitOrderBounds, LimitOrderQuote, LimitOrderToken } from "
|
|
|
8
8
|
import { PerpContract, PerpOrder, PerpOrderStatus, PerpOrdersPage, PerpPosition, PerpPositionStatus, PerpSystemStatus } from "./api/perps.js";
|
|
9
9
|
import { NormalizedTransaction, Transaction, TransactionDetails, TransactionGateway, TransactionLeg, TransactionsPage, normalizeTransaction } from "./api/transactions.js";
|
|
10
10
|
import { Portfolio, PortfolioEntry, WaltClient, WaltClientOptions } from "./client.js";
|
|
11
|
-
import {
|
|
11
|
+
import { RefreshTokenSource, RefreshTokenSourceOptions, TokenState, TokenStorage } from "./refresh.js";
|
|
12
12
|
import { WaltAuthError, WaltRequestError } from "./transport.js";
|
|
13
|
-
|
|
13
|
+
import { TelegramTokenSource, TelegramTokenSourceOptions } from "./telegram/auth.js";
|
|
14
|
+
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, RefreshTokenSource, type RefreshTokenSourceOptions, 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 };
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WaltAuthError, WaltRequestError } from "./transport.js";
|
|
2
2
|
import { WaltClient } from "./client.js";
|
|
3
|
+
import { normalizeTransaction } from "./api/transactions.js";
|
|
4
|
+
import { RefreshTokenSource } from "./refresh.js";
|
|
3
5
|
import { TelegramTokenSource } from "./telegram/auth.js";
|
|
4
|
-
|
|
5
|
-
export { TelegramTokenSource, WaltAuthError, WaltClient, WaltRequestError, normalizeTransaction };
|
|
6
|
+
export { RefreshTokenSource, TelegramTokenSource, WaltAuthError, WaltClient, WaltRequestError, normalizeTransaction };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type TokenState = {
|
|
2
|
+
jwt: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
deviceSerial: string;
|
|
7
|
+
};
|
|
8
|
+
export type TokenStorage = {
|
|
9
|
+
load: () => Promise<TokenState | null>;
|
|
10
|
+
save: (state: TokenState) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export type RefreshTokenSourceOptions = {
|
|
13
|
+
/** Where to start. Without it the state comes from `storage.load()`. */
|
|
14
|
+
state?: TokenState;
|
|
15
|
+
/** `save` receives the new state after every renewal, rotated refresh token included. */
|
|
16
|
+
storage?: TokenStorage | undefined;
|
|
17
|
+
/** Origin of the web app, sent as `origin` and `referer`. */
|
|
18
|
+
baseUrl?: string | undefined;
|
|
19
|
+
/** Where the refresh requests go. */
|
|
20
|
+
authUrl?: string | undefined;
|
|
21
|
+
fetch?: typeof fetch | undefined;
|
|
22
|
+
};
|
|
23
|
+
export type AuthResponse = {
|
|
24
|
+
jwt: string;
|
|
25
|
+
refresh_token: string;
|
|
26
|
+
session_id: string;
|
|
27
|
+
access_token_expires_at: string;
|
|
28
|
+
access_token_expires_in: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Renews the access token from a stored refresh token over HTTPS, without Telegram.
|
|
32
|
+
* Throws `WaltAuthError` once the refresh token is rejected; a new token state is then needed.
|
|
33
|
+
*/
|
|
34
|
+
export declare class RefreshTokenSource {
|
|
35
|
+
protected readonly baseUrl: string;
|
|
36
|
+
protected readonly storage: TokenStorage | undefined;
|
|
37
|
+
protected state: TokenState | null;
|
|
38
|
+
private readonly authUrl;
|
|
39
|
+
private readonly fetchImpl;
|
|
40
|
+
private restored;
|
|
41
|
+
private pending;
|
|
42
|
+
constructor(options: RefreshTokenSourceOptions);
|
|
43
|
+
/**
|
|
44
|
+
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
45
|
+
*/
|
|
46
|
+
token(rejected?: string): Promise<string>;
|
|
47
|
+
private fresh;
|
|
48
|
+
private resolve;
|
|
49
|
+
private restore;
|
|
50
|
+
/**
|
|
51
|
+
* Called when there is no refresh token to use, or the server rejected it.
|
|
52
|
+
*
|
|
53
|
+
* @param cause - The rejection, if there was one.
|
|
54
|
+
* @returns The new jwt.
|
|
55
|
+
*/
|
|
56
|
+
protected signIn(cause?: unknown): Promise<string>;
|
|
57
|
+
private refresh;
|
|
58
|
+
protected post(path: string, body: Record<string, unknown>): Promise<AuthResponse>;
|
|
59
|
+
protected store(data: AuthResponse, deviceSerial: string): Promise<string>;
|
|
60
|
+
}
|
package/build/refresh.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { BROWSER_HEADERS } from "./browser.js";
|
|
2
|
+
import { WaltAuthError } from "./transport.js";
|
|
3
|
+
// source/refresh.ts
|
|
4
|
+
const BASE_URL = "https://walletbot.me";
|
|
5
|
+
const AUTH_URL = "https://alectryon.walletbot.me";
|
|
6
|
+
const EXPIRY_SKEW_MS = 3e4;
|
|
7
|
+
var AuthEndpointError = class extends Error {
|
|
8
|
+
status;
|
|
9
|
+
constructor(message, status) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const isRejection = (error) => error instanceof AuthEndpointError && error.status >= 400 && error.status < 500 && error.status !== 429;
|
|
15
|
+
/**
|
|
16
|
+
* Renews the access token from a stored refresh token over HTTPS, without Telegram.
|
|
17
|
+
* Throws `WaltAuthError` once the refresh token is rejected; a new token state is then needed.
|
|
18
|
+
*/
|
|
19
|
+
var RefreshTokenSource = class {
|
|
20
|
+
baseUrl;
|
|
21
|
+
storage;
|
|
22
|
+
state;
|
|
23
|
+
authUrl;
|
|
24
|
+
fetchImpl;
|
|
25
|
+
restored;
|
|
26
|
+
pending = null;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.state = options.state ?? null;
|
|
29
|
+
this.storage = options.storage;
|
|
30
|
+
this.restored = options.state !== void 0;
|
|
31
|
+
this.baseUrl = options.baseUrl ?? BASE_URL;
|
|
32
|
+
this.authUrl = options.authUrl ?? AUTH_URL;
|
|
33
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
37
|
+
*/
|
|
38
|
+
async token(rejected) {
|
|
39
|
+
if (this.state && rejected !== void 0 && rejected === this.state.jwt) this.state = {
|
|
40
|
+
...this.state,
|
|
41
|
+
expiresAt: 0
|
|
42
|
+
};
|
|
43
|
+
if (this.state && this.fresh(this.state)) return this.state.jwt;
|
|
44
|
+
this.pending ??= this.resolve();
|
|
45
|
+
try {
|
|
46
|
+
return await this.pending;
|
|
47
|
+
} finally {
|
|
48
|
+
this.pending = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
fresh(state) {
|
|
52
|
+
return Date.now() < state.expiresAt - EXPIRY_SKEW_MS;
|
|
53
|
+
}
|
|
54
|
+
async resolve() {
|
|
55
|
+
await this.restore();
|
|
56
|
+
const state = this.state;
|
|
57
|
+
if (state && this.fresh(state)) return state.jwt;
|
|
58
|
+
if (state?.refreshToken) try {
|
|
59
|
+
return await this.refresh(state);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!isRejection(error)) throw error;
|
|
62
|
+
this.state = {
|
|
63
|
+
...state,
|
|
64
|
+
refreshToken: ""
|
|
65
|
+
};
|
|
66
|
+
return this.signIn(error);
|
|
67
|
+
}
|
|
68
|
+
return this.signIn();
|
|
69
|
+
}
|
|
70
|
+
async restore() {
|
|
71
|
+
if (this.restored || !this.storage) return;
|
|
72
|
+
this.restored = true;
|
|
73
|
+
this.state = await this.storage.load();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Called when there is no refresh token to use, or the server rejected it.
|
|
77
|
+
*
|
|
78
|
+
* @param cause - The rejection, if there was one.
|
|
79
|
+
* @returns The new jwt.
|
|
80
|
+
*/
|
|
81
|
+
signIn(cause) {
|
|
82
|
+
const message = this.state ? "refresh token rejected, a new token state is needed" : "no token state to refresh";
|
|
83
|
+
return Promise.reject(new WaltAuthError(message, { cause }));
|
|
84
|
+
}
|
|
85
|
+
async refresh(state) {
|
|
86
|
+
const data = await this.post("/alectryon/public-api/auth-refresh", {
|
|
87
|
+
session_id: state.sessionId,
|
|
88
|
+
refresh_token: state.refreshToken,
|
|
89
|
+
device_serial: state.deviceSerial
|
|
90
|
+
});
|
|
91
|
+
return this.store(data, state.deviceSerial);
|
|
92
|
+
}
|
|
93
|
+
async post(path, body) {
|
|
94
|
+
const { fetchImpl } = this;
|
|
95
|
+
const response = await fetchImpl(`${this.authUrl}${path}`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
...BROWSER_HEADERS,
|
|
99
|
+
accept: "*/*",
|
|
100
|
+
"sec-fetch-site": new URL(this.authUrl).origin === new URL(this.baseUrl).origin ? "same-origin" : "same-site",
|
|
101
|
+
"content-type": "application/json",
|
|
102
|
+
origin: this.baseUrl,
|
|
103
|
+
referer: `${this.baseUrl}/`,
|
|
104
|
+
"x-wallet-tz-name": Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
105
|
+
"x-wallet-tz-offset": String(-(/* @__PURE__ */ new Date()).getTimezoneOffset())
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify(body)
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) throw new AuthEndpointError(`${path} failed (${response.status}): ${await response.text()}`, response.status);
|
|
110
|
+
return await response.json();
|
|
111
|
+
}
|
|
112
|
+
async store(data, deviceSerial) {
|
|
113
|
+
let expiresAt;
|
|
114
|
+
if (Number.isFinite(data.access_token_expires_in)) expiresAt = Date.now() + data.access_token_expires_in * 1e3;
|
|
115
|
+
else {
|
|
116
|
+
const parsed = Date.parse(data.access_token_expires_at);
|
|
117
|
+
expiresAt = Number.isNaN(parsed) ? Date.now() + 3e5 : parsed;
|
|
118
|
+
}
|
|
119
|
+
const state = {
|
|
120
|
+
jwt: data.jwt,
|
|
121
|
+
refreshToken: data.refresh_token,
|
|
122
|
+
sessionId: data.session_id,
|
|
123
|
+
expiresAt,
|
|
124
|
+
deviceSerial
|
|
125
|
+
};
|
|
126
|
+
this.state = state;
|
|
127
|
+
await this.storage?.save(state);
|
|
128
|
+
return state.jwt;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
export { RefreshTokenSource };
|
package/build/telegram/auth.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
jwt: string;
|
|
3
|
-
refreshToken: string;
|
|
4
|
-
sessionId: string;
|
|
5
|
-
expiresAt: number;
|
|
6
|
-
deviceSerial: string;
|
|
7
|
-
};
|
|
8
|
-
export type TokenStorage = {
|
|
9
|
-
load: () => Promise<TokenState | null>;
|
|
10
|
-
save: (state: TokenState) => Promise<void>;
|
|
11
|
-
};
|
|
1
|
+
import { RefreshTokenSource, TokenStorage } from "../refresh.js";
|
|
12
2
|
export type TelegramTokenSourceOptions = {
|
|
13
3
|
session: string;
|
|
14
4
|
apiId: number;
|
|
@@ -24,34 +14,15 @@ export type TelegramTokenSourceOptions = {
|
|
|
24
14
|
storage?: TokenStorage | false;
|
|
25
15
|
maxFloodWaitMs?: number;
|
|
26
16
|
};
|
|
27
|
-
|
|
17
|
+
/** Signs in over Telegram, then renews like `RefreshTokenSource`, signing in again when the refresh token is rejected. */
|
|
18
|
+
export declare class TelegramTokenSource extends RefreshTokenSource {
|
|
28
19
|
private readonly client;
|
|
29
20
|
private readonly bot;
|
|
30
|
-
private readonly baseUrl;
|
|
31
|
-
private readonly authUrl;
|
|
32
|
-
private readonly fetchImpl;
|
|
33
|
-
private readonly storage;
|
|
34
21
|
private readonly maxFloodWaitMs;
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private jwt;
|
|
38
|
-
private refreshToken;
|
|
39
|
-
private sessionId;
|
|
40
|
-
private expiresAt;
|
|
41
|
-
private restored;
|
|
42
|
-
private pending;
|
|
22
|
+
private readonly knownDeviceSerial;
|
|
23
|
+
private readonly newDeviceSerial;
|
|
43
24
|
constructor(options: TelegramTokenSourceOptions);
|
|
44
|
-
|
|
45
|
-
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
46
|
-
*/
|
|
47
|
-
token(rejected?: string): Promise<string>;
|
|
48
|
-
private fresh;
|
|
49
|
-
private resolve;
|
|
50
|
-
private restore;
|
|
51
|
-
private authenticate;
|
|
25
|
+
protected signIn(): Promise<string>;
|
|
52
26
|
private requestInitData;
|
|
53
|
-
private refresh;
|
|
54
|
-
private post;
|
|
55
|
-
private store;
|
|
56
27
|
disconnect(): Promise<void>;
|
|
57
28
|
}
|
package/build/telegram/auth.js
CHANGED
|
@@ -1,97 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RefreshTokenSource } from "../refresh.js";
|
|
2
2
|
import { defaultTokenPath, fileTokenStorage } from "./file-storage.js";
|
|
3
3
|
import { Api, TelegramClient, errors } from "teleproto";
|
|
4
4
|
import { StringSession } from "teleproto/sessions/index.js";
|
|
5
5
|
// source/telegram/auth.ts
|
|
6
|
-
const BASE_URL = "https://walletbot.me";
|
|
7
|
-
const AUTH_URL = "https://alectryon.walletbot.me";
|
|
8
|
-
const EXPIRY_SKEW_MS = 3e4;
|
|
9
6
|
const MAX_FLOOD_WAIT_MS = 6e4;
|
|
10
7
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
constructor(message, status) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.status = status;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
var TelegramTokenSource = class {
|
|
8
|
+
/** Signs in over Telegram, then renews like `RefreshTokenSource`, signing in again when the refresh token is rejected. */
|
|
9
|
+
var TelegramTokenSource = class extends RefreshTokenSource {
|
|
19
10
|
client;
|
|
20
11
|
bot;
|
|
21
|
-
baseUrl;
|
|
22
|
-
authUrl;
|
|
23
|
-
fetchImpl;
|
|
24
|
-
storage;
|
|
25
12
|
maxFloodWaitMs;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
jwt = null;
|
|
29
|
-
refreshToken = null;
|
|
30
|
-
sessionId = null;
|
|
31
|
-
expiresAt = 0;
|
|
32
|
-
restored = false;
|
|
33
|
-
pending = null;
|
|
13
|
+
knownDeviceSerial;
|
|
14
|
+
newDeviceSerial = `weba:${crypto.randomUUID()}`;
|
|
34
15
|
constructor(options) {
|
|
16
|
+
super({
|
|
17
|
+
storage: options.storage === false ? void 0 : options.storage ?? fileTokenStorage(defaultTokenPath(options.session)),
|
|
18
|
+
baseUrl: options.baseUrl,
|
|
19
|
+
authUrl: options.authUrl,
|
|
20
|
+
fetch: options.fetch
|
|
21
|
+
});
|
|
35
22
|
this.client = new TelegramClient(new StringSession(options.session), options.apiId, options.apiHash, { connectionRetries: 3 });
|
|
36
23
|
this.bot = options.bot ?? "walt";
|
|
37
|
-
this.
|
|
38
|
-
this.deviceSerialKnown = options.deviceSerial !== void 0;
|
|
39
|
-
this.baseUrl = options.baseUrl ?? BASE_URL;
|
|
40
|
-
this.authUrl = options.authUrl ?? AUTH_URL;
|
|
41
|
-
this.fetchImpl = options.fetch ?? fetch;
|
|
42
|
-
this.storage = options.storage === false ? void 0 : options.storage ?? fileTokenStorage(defaultTokenPath(options.session));
|
|
24
|
+
this.knownDeviceSerial = options.deviceSerial;
|
|
43
25
|
this.maxFloodWaitMs = options.maxFloodWaitMs ?? MAX_FLOOD_WAIT_MS;
|
|
44
26
|
}
|
|
45
|
-
|
|
46
|
-
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
47
|
-
*/
|
|
48
|
-
async token(rejected) {
|
|
49
|
-
if (rejected !== void 0 && rejected === this.jwt) this.expiresAt = 0;
|
|
50
|
-
if (this.fresh()) return this.jwt;
|
|
51
|
-
this.pending ??= this.resolve();
|
|
52
|
-
try {
|
|
53
|
-
return await this.pending;
|
|
54
|
-
} finally {
|
|
55
|
-
this.pending = null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
fresh() {
|
|
59
|
-
return this.jwt !== null && Date.now() < this.expiresAt - EXPIRY_SKEW_MS;
|
|
60
|
-
}
|
|
61
|
-
async resolve() {
|
|
62
|
-
await this.restore();
|
|
63
|
-
if (this.fresh()) return this.jwt;
|
|
64
|
-
if (this.refreshToken) try {
|
|
65
|
-
return await this.refresh();
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (!(error instanceof AuthEndpointError && error.status >= 400 && error.status < 500 && error.status !== 429)) throw error;
|
|
68
|
-
this.refreshToken = null;
|
|
69
|
-
}
|
|
70
|
-
return this.authenticate();
|
|
71
|
-
}
|
|
72
|
-
async restore() {
|
|
73
|
-
if (this.restored || !this.storage) return;
|
|
74
|
-
this.restored = true;
|
|
75
|
-
const state = await this.storage.load();
|
|
76
|
-
if (!state) return;
|
|
77
|
-
this.jwt = state.jwt;
|
|
78
|
-
this.refreshToken = state.refreshToken;
|
|
79
|
-
this.sessionId = state.sessionId;
|
|
80
|
-
this.expiresAt = state.expiresAt;
|
|
81
|
-
this.deviceSerial = state.deviceSerial;
|
|
82
|
-
this.deviceSerialKnown = true;
|
|
83
|
-
}
|
|
84
|
-
async authenticate() {
|
|
27
|
+
async signIn() {
|
|
85
28
|
const initData = await this.requestInitData();
|
|
86
|
-
const
|
|
87
|
-
const
|
|
29
|
+
const known = this.state?.deviceSerial ?? this.knownDeviceSerial;
|
|
30
|
+
const serial = known === void 0 ? { device_serial_fallback: this.newDeviceSerial } : { device_serial: known };
|
|
31
|
+
const data = await this.post("/alectryon/public-api/auth", {
|
|
88
32
|
web_view_init_data_raw: initData,
|
|
89
33
|
ep: "menu",
|
|
90
34
|
device_serial_multi_domain_backup_supported: true,
|
|
91
35
|
...serial
|
|
92
|
-
})
|
|
93
|
-
this.
|
|
94
|
-
return token;
|
|
36
|
+
});
|
|
37
|
+
return this.store(data, known ?? this.newDeviceSerial);
|
|
95
38
|
}
|
|
96
39
|
async requestInitData(retryAfterFlood = true) {
|
|
97
40
|
if (!this.client.connected) await this.client.connect();
|
|
@@ -114,49 +57,6 @@ var TelegramTokenSource = class {
|
|
|
114
57
|
if (!initData) throw new Error("no tgWebAppData in web view url");
|
|
115
58
|
return initData;
|
|
116
59
|
}
|
|
117
|
-
async refresh() {
|
|
118
|
-
return this.store(await this.post("/alectryon/public-api/auth-refresh", {
|
|
119
|
-
session_id: this.sessionId,
|
|
120
|
-
refresh_token: this.refreshToken,
|
|
121
|
-
device_serial: this.deviceSerial
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
async post(path, body) {
|
|
125
|
-
const response = await this.fetchImpl(`${this.authUrl}${path}`, {
|
|
126
|
-
method: "POST",
|
|
127
|
-
headers: {
|
|
128
|
-
...BROWSER_HEADERS,
|
|
129
|
-
accept: "*/*",
|
|
130
|
-
"sec-fetch-site": new URL(this.authUrl).origin === new URL(this.baseUrl).origin ? "same-origin" : "same-site",
|
|
131
|
-
"content-type": "application/json",
|
|
132
|
-
origin: this.baseUrl,
|
|
133
|
-
referer: `${this.baseUrl}/`,
|
|
134
|
-
"x-wallet-tz-name": Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
135
|
-
"x-wallet-tz-offset": String(-(/* @__PURE__ */ new Date()).getTimezoneOffset())
|
|
136
|
-
},
|
|
137
|
-
body: JSON.stringify(body)
|
|
138
|
-
});
|
|
139
|
-
if (!response.ok) throw new AuthEndpointError(`${path} failed (${response.status}): ${await response.text()}`, response.status);
|
|
140
|
-
return await response.json();
|
|
141
|
-
}
|
|
142
|
-
async store(data) {
|
|
143
|
-
this.jwt = data.jwt;
|
|
144
|
-
this.refreshToken = data.refresh_token;
|
|
145
|
-
this.sessionId = data.session_id;
|
|
146
|
-
if (Number.isFinite(data.access_token_expires_in)) this.expiresAt = Date.now() + data.access_token_expires_in * 1e3;
|
|
147
|
-
else {
|
|
148
|
-
const parsed = Date.parse(data.access_token_expires_at);
|
|
149
|
-
this.expiresAt = Number.isNaN(parsed) ? Date.now() + 3e5 : parsed;
|
|
150
|
-
}
|
|
151
|
-
await this.storage?.save({
|
|
152
|
-
jwt: this.jwt,
|
|
153
|
-
refreshToken: this.refreshToken,
|
|
154
|
-
sessionId: this.sessionId,
|
|
155
|
-
expiresAt: this.expiresAt,
|
|
156
|
-
deviceSerial: this.deviceSerial
|
|
157
|
-
});
|
|
158
|
-
return this.jwt;
|
|
159
|
-
}
|
|
160
60
|
async disconnect() {
|
|
161
61
|
await this.client.disconnect();
|
|
162
62
|
}
|
package/build/transport.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BROWSER_HEADERS, SENTRY_PUBLIC_KEY, SENTRY_RELEASE } from "./browser.js";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
2
|
// source/transport.ts
|
|
4
3
|
var WaltAuthError = class extends Error {};
|
|
5
4
|
var WaltRequestError = class extends Error {
|
|
@@ -29,7 +28,7 @@ const BEARER_SERVICES = [
|
|
|
29
28
|
"/kandelabr/"
|
|
30
29
|
];
|
|
31
30
|
const MAX_RETRY_DELAY_MS = 3e4;
|
|
32
|
-
const hex = (bytes) =>
|
|
31
|
+
const hex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
33
32
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
34
33
|
const parseBody = (text) => {
|
|
35
34
|
try {
|
|
@@ -44,8 +43,9 @@ const retryAfterOf = (response) => {
|
|
|
44
43
|
};
|
|
45
44
|
const telegramUserIdOf = (token) => {
|
|
46
45
|
try {
|
|
47
|
-
const payload = token.split(".")[1] ?? "";
|
|
48
|
-
const
|
|
46
|
+
const payload = (token.split(".")[1] ?? "").replaceAll("-", "+").replaceAll("_", "/");
|
|
47
|
+
const bytes = Uint8Array.from(atob(payload), (char) => char.charCodeAt(0));
|
|
48
|
+
const id = JSON.parse(new TextDecoder().decode(bytes)).web_view_init_data?.user?.id;
|
|
49
49
|
return typeof id === "number" ? id : null;
|
|
50
50
|
} catch {
|
|
51
51
|
return null;
|
|
@@ -136,9 +136,10 @@ var Transport = class {
|
|
|
136
136
|
headers["content-type"] = "application/json";
|
|
137
137
|
headers["origin"] = this.baseUrl;
|
|
138
138
|
}
|
|
139
|
+
const { fetchImpl } = this;
|
|
139
140
|
let response;
|
|
140
141
|
try {
|
|
141
|
-
response = await
|
|
142
|
+
response = await fetchImpl(`${this.baseUrl}${path}`, {
|
|
142
143
|
method,
|
|
143
144
|
headers,
|
|
144
145
|
body: method === "POST" ? JSON.stringify(body) : null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2bad/walt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
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",
|
|
@@ -33,6 +33,12 @@
|
|
|
33
33
|
"types": "./build/index.d.ts",
|
|
34
34
|
"default": "./build/index.js"
|
|
35
35
|
}
|
|
36
|
+
},
|
|
37
|
+
"./client": {
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./build/core.d.ts",
|
|
40
|
+
"default": "./build/core.js"
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
},
|
|
38
44
|
"bin": {
|
|
@@ -42,28 +48,28 @@
|
|
|
42
48
|
"build"
|
|
43
49
|
],
|
|
44
50
|
"dependencies": {
|
|
45
|
-
"teleproto": "1.232.
|
|
51
|
+
"teleproto": "1.232.1"
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
48
54
|
"@2bad/axiom": "2.1.0",
|
|
49
55
|
"@2bad/tsconfig": "4.0.0",
|
|
50
56
|
"@arethetypeswrong/cli": "0.18.5",
|
|
51
57
|
"@types/node": "26.6.6",
|
|
52
|
-
"@vitest/coverage-v8": "5.0
|
|
58
|
+
"@vitest/coverage-v8": "5.1.0",
|
|
53
59
|
"npm-run-all2": "9.0.4",
|
|
54
|
-
"oxfmt": "0.
|
|
55
|
-
"oxlint": "1.
|
|
60
|
+
"oxfmt": "0.76.0",
|
|
61
|
+
"oxlint": "1.91.0",
|
|
56
62
|
"publint": "0.3.25",
|
|
57
|
-
"tsdown": "0.23.
|
|
63
|
+
"tsdown": "0.23.5",
|
|
58
64
|
"typescript": "7.0.2",
|
|
59
|
-
"vitest": "5.0
|
|
65
|
+
"vitest": "5.1.0"
|
|
60
66
|
},
|
|
61
67
|
"engines": {
|
|
62
68
|
"node": ">=26"
|
|
63
69
|
},
|
|
64
70
|
"volta": {
|
|
65
71
|
"node": "26.13.0",
|
|
66
|
-
"pnpm": "12.
|
|
72
|
+
"pnpm": "12.18.0"
|
|
67
73
|
},
|
|
68
74
|
"scripts": {
|
|
69
75
|
"build": "tsdown",
|