@2bad/walt 0.0.0-stage → 1.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.
@@ -0,0 +1,26 @@
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
+ const apiHash = process.env["TG_API_HASH"] || await ask("API hash: ");
14
+ const client = new TelegramClient(new StringSession(""), apiId, apiHash, { connectionRetries: 3 });
15
+ await client.start({
16
+ phoneNumber: () => ask("Phone (+country...): "),
17
+ phoneCode: () => ask("Code you received: "),
18
+ password: () => ask("2FA password (blank if none): "),
19
+ onError: (error) => console.error(error)
20
+ });
21
+ rl.close();
22
+ console.log("\nLogged in. Save this as TG_SESSION (keep it secret):\n");
23
+ console.log(client.session.save());
24
+ await client.disconnect();
25
+ process.exit(0);
26
+ export {};
@@ -0,0 +1,19 @@
1
+ /** The headers every request from the Walt web app carries in Chrome on Windows, captured from real traffic. */
2
+ export declare const BROWSER_HEADERS: {
3
+ 'accept-encoding': string;
4
+ 'accept-language': string;
5
+ 'cache-control': string;
6
+ dnt: string;
7
+ pragma: string;
8
+ priority: string;
9
+ 'sec-ch-ua': string;
10
+ 'sec-ch-ua-mobile': string;
11
+ 'sec-ch-ua-platform': string;
12
+ 'sec-fetch-dest': string;
13
+ 'sec-fetch-mode': string;
14
+ 'sec-fetch-site': string;
15
+ 'sec-gpc': string;
16
+ 'user-agent': string;
17
+ };
18
+ export declare const SENTRY_RELEASE = "10.0.84666";
19
+ export declare const SENTRY_PUBLIC_KEY = "544a92e441a24f17aa6b08e34e728ed2";
@@ -0,0 +1,21 @@
1
+ // source/browser.ts
2
+ /** The headers every request from the Walt web app carries in Chrome on Windows, captured from real traffic. */
3
+ const BROWSER_HEADERS = {
4
+ "accept-encoding": "gzip, deflate, br, zstd",
5
+ "accept-language": "en-US,en;q=0.9",
6
+ "cache-control": "no-cache",
7
+ dnt: "1",
8
+ pragma: "no-cache",
9
+ priority: "u=1, i",
10
+ "sec-ch-ua": "\"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"156\", \"Google Chrome\";v=\"156\"",
11
+ "sec-ch-ua-mobile": "?0",
12
+ "sec-ch-ua-platform": "\"Windows\"",
13
+ "sec-fetch-dest": "empty",
14
+ "sec-fetch-mode": "cors",
15
+ "sec-fetch-site": "same-origin",
16
+ "sec-gpc": "1",
17
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"
18
+ };
19
+ const SENTRY_RELEASE = "10.0.84666";
20
+ const SENTRY_PUBLIC_KEY = "544a92e441a24f17aa6b08e34e728ed2";
21
+ export { BROWSER_HEADERS, SENTRY_PUBLIC_KEY, SENTRY_RELEASE };
@@ -0,0 +1,138 @@
1
+ import { AccountsResponse, Charts, Coin, CoinDetails, FiatCurrency, PriceAtTime } from "./api/accounts.js";
2
+ import { PnlResponse, PortfolioHistory } from "./api/analytics.js";
3
+ import { EarnCampaignsResponse, EarnRate, EarnTransaction, EarnTransactionsPage } from "./api/earn.js";
4
+ import { AvailableExchanges, Exchange, ExchangeQuote, ExchangeRequest } from "./api/exchange.js";
5
+ import { LimitOrder, LimitOrderBounds, LimitOrderQuote, LimitOrderToken } from "./api/limit-orders.js";
6
+ import { Transaction, TransactionDetails, TransactionGateway, TransactionsPage } from "./api/transactions.js";
7
+ export type WaltClientOptions = {
8
+ /** Called again with the rejected token after a 401, so the provider can renew it before its expiry. */
9
+ tokenProvider: (rejected?: string) => Promise<string>;
10
+ baseUrl?: string;
11
+ fetch?: typeof fetch;
12
+ /** Extra attempts after a request fails on a transient status. Default 3, `0` disables retrying. */
13
+ retries?: number;
14
+ /** Base for the exponential backoff between those attempts, in ms. Default 500. */
15
+ retryDelayMs?: number;
16
+ /** Requests allowed in flight at once. Default 4, `Infinity` removes the limit. */
17
+ maxConcurrent?: number;
18
+ };
19
+ export type PortfolioEntry = {
20
+ currency: string;
21
+ amount: string;
22
+ fiatValue: number;
23
+ };
24
+ export type Portfolio = {
25
+ /** Spot balances above zero, largest fiat value first. */
26
+ entries: PortfolioEntry[];
27
+ entriesTotal: number;
28
+ /** The API's own total, which includes staked funds. */
29
+ total: number;
30
+ /** `total - entriesTotal`, itemised by `earnCampaigns()`. */
31
+ staked: number;
32
+ fiatCurrency: string;
33
+ };
34
+ export declare class WaltClient {
35
+ private readonly transport;
36
+ private readonly maxConcurrent;
37
+ constructor(options: WaltClientOptions);
38
+ accounts(): Promise<AccountsResponse>;
39
+ coins(fiatCurrency: FiatCurrency): Promise<Coin[]>;
40
+ localCurrency(): Promise<FiatCurrency>;
41
+ transactions(limit?: number, cursor?: string): Promise<TransactionsPage>;
42
+ accountTransactions(currency: string, limit?: number, cursor?: string): Promise<TransactionsPage>;
43
+ private walk;
44
+ allTransactions(limit?: number): AsyncGenerator<Transaction>;
45
+ allAccountTransactions(currency: string, limit?: number): AsyncGenerator<Transaction>;
46
+ /**
47
+ * `transactionDetails()` for each listing row, several in flight, yielded in input order.
48
+ * A lookup that still fails after its retries ends the iteration.
49
+ *
50
+ * @param transactions - Listing rows, typically `allTransactions()`.
51
+ * @yields One detail record per row.
52
+ */
53
+ detailsFor(transactions: AsyncIterable<Transaction> | Iterable<Transaction>): AsyncGenerator<TransactionDetails>;
54
+ /**
55
+ * @param id - Transaction id from a listing row.
56
+ * @param gateway - The row's `gateway`. Some records, such as `xstocks_dividends`, 404 without it.
57
+ */
58
+ transactionDetails(id: number | string, gateway?: TransactionGateway | (string & {})): Promise<TransactionDetails>;
59
+ unconfirmedTransactions<T = unknown>(limit?: number): Promise<{
60
+ transactions: T[];
61
+ size: number;
62
+ }>;
63
+ frozenBalances<T = unknown>(): Promise<T[]>;
64
+ /** Every earn campaign. Staked balances are here, not in `accounts()`. */
65
+ earnCampaigns(): Promise<EarnCampaignsResponse>;
66
+ /**
67
+ * Deposits, withdrawals and payouts within one campaign, newest first.
68
+ *
69
+ * @param campaignId - `campaign_id` from `earnCampaigns()`.
70
+ * @param limit - Rows per page. The API defaults to 10.
71
+ * @param after - The previous page, to fetch the one that follows it.
72
+ */
73
+ earnCampaignTransactions(campaignId: number | string, limit?: number, after?: EarnTransactionsPage): Promise<EarnTransactionsPage>;
74
+ /**
75
+ * Every row of `earnCampaignTransactions()`, page by page. The cursors stay set past the last row,
76
+ * so a page shorter than `limit` ends the walk.
77
+ *
78
+ * @param campaignId - `campaign_id` from `earnCampaigns()`.
79
+ * @param limit - Rows per page.
80
+ * @yields One row per deposit, withdrawal or payout.
81
+ */
82
+ allEarnCampaignTransactions(campaignId: number | string, limit?: number): AsyncGenerator<EarnTransaction>;
83
+ earnRate(from: string, to: string): Promise<EarnRate>;
84
+ pnl(): Promise<PnlResponse>;
85
+ /**
86
+ * Balance, P/L, deposits and withdrawals per day or per calendar month, plus totals for the whole range.
87
+ *
88
+ * @param dateFrom - Start of the first period, as a `Date` or `YYYY-MM-DD`. A `Date` counts by its UTC day.
89
+ * @param resolution - `1d` for days, `1m` for months.
90
+ */
91
+ portfolioHistory(dateFrom: Date | string, resolution: '1d' | '1m'): Promise<PortfolioHistory>;
92
+ /**
93
+ * @param options - Filters, all optional.
94
+ * @param options.status - Only orders in these states, such as `OPEN`.
95
+ * @param options.limit - Most orders to return.
96
+ */
97
+ limitOrders(options?: {
98
+ status?: string[];
99
+ limit?: number;
100
+ }): Promise<LimitOrder[]>;
101
+ limitOrder(id: string): Promise<LimitOrder>;
102
+ /**
103
+ * Fee and fill for a limit order, without placing it. Only `BUY` is accepted.
104
+ *
105
+ * @param baseCurrency - What is bought, from `limitOrderTokens()`.
106
+ * @param side - `SELL` is a valid value that the API currently rejects.
107
+ * @param priceLimit - Highest price to pay per unit.
108
+ * @param size - `amount` to spend, or `quantity` of `baseCurrency` to receive.
109
+ */
110
+ limitOrderQuote(baseCurrency: string, side: 'BUY' | 'SELL', priceLimit: number | string, size: {
111
+ amount: number | string;
112
+ } | {
113
+ quantity: number | string;
114
+ }): Promise<LimitOrderQuote>;
115
+ limitOrderBounds(baseCurrency: string): Promise<LimitOrderBounds>;
116
+ /** Every currency a limit order can buy. */
117
+ limitOrderTokens(): Promise<LimitOrderToken[]>;
118
+ coinDetails(currency: string, fiatCurrency: FiatCurrency): Promise<CoinDetails>;
119
+ charts(currency: string, fiatCurrency: FiatCurrency): Promise<Charts>;
120
+ priceForFiatAtTime(cryptoCurrency: string, localCurrency: FiatCurrency, amount: number | string, time: Date | string | number): Promise<PriceAtTime>;
121
+ availableExchanges(): Promise<AvailableExchanges>;
122
+ /**
123
+ * Executable rate, spread included, unlike the display rates.
124
+ *
125
+ * @param fromCurrency - What is paid.
126
+ * @param toCurrency - What is received.
127
+ * @param fromAmount - Amount of `fromCurrency`; `0` returns the rate and limits only.
128
+ */
129
+ quote(fromCurrency: string, toCurrency: string, fromAmount: number): Promise<ExchangeQuote>;
130
+ /**
131
+ * Moves real money and is never retried. After a failure, check balances before trying again.
132
+ *
133
+ * @param request - The exchange, in the shape the API takes it.
134
+ */
135
+ createExchange(request: ExchangeRequest): Promise<Exchange>;
136
+ /** Non-zero spot balances against the API total, which also counts staked funds. */
137
+ portfolio(): Promise<Portfolio>;
138
+ }
@@ -0,0 +1,294 @@
1
+ import { Transport, WaltAuthError } from "./transport.js";
2
+ // source/client.ts
3
+ const BASE_URL = "https://walletbot.me";
4
+ const calendarDate = (date) => [
5
+ date.getUTCFullYear(),
6
+ date.getUTCMonth() + 1,
7
+ date.getUTCDate()
8
+ ].map((part) => String(part).padStart(2, "0")).join("-");
9
+ const decimal = (value) => {
10
+ if (typeof value !== "string" || value.trim() === "") return null;
11
+ const parsed = Number(value);
12
+ return Number.isFinite(parsed) ? parsed : null;
13
+ };
14
+ var WaltClient = class {
15
+ transport;
16
+ maxConcurrent;
17
+ constructor(options) {
18
+ if (!options.tokenProvider) throw new WaltAuthError("tokenProvider is required");
19
+ const retries = options.retries ?? 3;
20
+ this.maxConcurrent = options.maxConcurrent ?? 4;
21
+ if (!(this.maxConcurrent >= 1)) throw new RangeError(`maxConcurrent must be at least 1, got ${this.maxConcurrent}`);
22
+ if (!Number.isInteger(retries) || retries < 0) throw new RangeError(`retries must be a whole number of at least 0, got ${retries}`);
23
+ this.transport = new Transport({
24
+ tokenProvider: options.tokenProvider,
25
+ baseUrl: options.baseUrl ?? BASE_URL,
26
+ fetch: options.fetch ?? fetch,
27
+ retries,
28
+ retryDelayMs: options.retryDelayMs ?? 500,
29
+ maxConcurrent: this.maxConcurrent
30
+ });
31
+ }
32
+ accounts() {
33
+ return this.transport.get("/api/v1/accounts/");
34
+ }
35
+ async coins(fiatCurrency) {
36
+ const { coins } = await this.transport.get(`/api/v1/coins/list/${fiatCurrency}/`);
37
+ return coins;
38
+ }
39
+ async localCurrency() {
40
+ const { local_currency } = await this.transport.get("/api/v1/currencies/local_currency/");
41
+ return local_currency;
42
+ }
43
+ transactions(limit, cursor) {
44
+ const query = new URLSearchParams();
45
+ if (limit !== void 0) query.set("limit", String(limit));
46
+ if (cursor !== void 0) query.set("cursor", cursor);
47
+ const search = query.size === 0 ? "" : `?${query.toString()}`;
48
+ return this.transport.get(`/api/v1/transactions/${search}`);
49
+ }
50
+ accountTransactions(currency, limit, cursor) {
51
+ const query = new URLSearchParams({ crypto_currency: currency });
52
+ if (limit !== void 0) query.set("limit", String(limit));
53
+ if (cursor !== void 0) query.set("cursor", cursor);
54
+ return this.transport.get(`/api/v1/transactions/single-account/?${query.toString()}`);
55
+ }
56
+ async *walk(page) {
57
+ let cursor;
58
+ const seen = /* @__PURE__ */ new Set();
59
+ for (;;) {
60
+ const current = await page(cursor);
61
+ if (current.transactions.length === 0) return;
62
+ yield* current.transactions;
63
+ if (current.next_cursor === null) return;
64
+ if (seen.has(current.next_cursor)) throw new Error("transactions pagination returned a cursor twice");
65
+ seen.add(current.next_cursor);
66
+ cursor = current.next_cursor;
67
+ }
68
+ }
69
+ allTransactions(limit) {
70
+ return this.walk((cursor) => this.transactions(limit, cursor));
71
+ }
72
+ allAccountTransactions(currency, limit) {
73
+ return this.walk((cursor) => this.accountTransactions(currency, limit, cursor));
74
+ }
75
+ /**
76
+ * `transactionDetails()` for each listing row, several in flight, yielded in input order.
77
+ * A lookup that still fails after its retries ends the iteration.
78
+ *
79
+ * @param transactions - Listing rows, typically `allTransactions()`.
80
+ * @yields One detail record per row.
81
+ */
82
+ async *detailsFor(transactions) {
83
+ const width = Math.max(1, Math.min(this.maxConcurrent, 32));
84
+ const inFlight = [];
85
+ try {
86
+ for await (const transaction of transactions) {
87
+ const lookup = this.transactionDetails(transaction.id, transaction.gateway);
88
+ Promise.allSettled([lookup]);
89
+ inFlight.push(lookup);
90
+ while (inFlight.length >= width) {
91
+ const next = inFlight.shift();
92
+ if (next) yield await next;
93
+ }
94
+ }
95
+ while (inFlight.length > 0) {
96
+ const next = inFlight.shift();
97
+ if (next) yield await next;
98
+ }
99
+ } finally {
100
+ await Promise.allSettled(inFlight);
101
+ }
102
+ }
103
+ /**
104
+ * @param id - Transaction id from a listing row.
105
+ * @param gateway - The row's `gateway`. Some records, such as `xstocks_dividends`, 404 without it.
106
+ */
107
+ transactionDetails(id, gateway) {
108
+ const query = gateway ? `?${new URLSearchParams({ transaction_gateway: gateway }).toString()}` : "";
109
+ return this.transport.get(`/api/v1/transactions/details/${encodeURIComponent(String(id))}/${query}`);
110
+ }
111
+ unconfirmedTransactions(limit) {
112
+ const query = limit === void 0 ? "" : `?${new URLSearchParams({ limit: String(limit) }).toString()}`;
113
+ return this.transport.get(`/api/v1/transactions/unconfirmed/${query}`);
114
+ }
115
+ frozenBalances() {
116
+ return this.transport.get("/api/v1/accounts/frozen");
117
+ }
118
+ /** Every earn campaign. Staked balances are here, not in `accounts()`. */
119
+ earnCampaigns() {
120
+ return this.transport.get("/earn/public-api/v1/campaigns/");
121
+ }
122
+ /**
123
+ * Deposits, withdrawals and payouts within one campaign, newest first.
124
+ *
125
+ * @param campaignId - `campaign_id` from `earnCampaigns()`.
126
+ * @param limit - Rows per page. The API defaults to 10.
127
+ * @param after - The previous page, to fetch the one that follows it.
128
+ */
129
+ earnCampaignTransactions(campaignId, limit, after) {
130
+ const query = new URLSearchParams();
131
+ if (limit !== void 0) query.set("limit", String(limit));
132
+ query.set("merge_with_transfer_requests", "true");
133
+ if (after?.deposit_acc_next_from != null) query.set("deposit_acc_offset_id", String(after.deposit_acc_next_from));
134
+ if (after?.payout_acc_next_from != null) query.set("payout_acc_offset_id", String(after.payout_acc_next_from));
135
+ if (after?.transfer_request_next_before_datetime != null) query.set("transfer_request_before_datetime", after.transfer_request_next_before_datetime);
136
+ return this.transport.get(`/earn/public-api/v1/campaigns/${encodeURIComponent(String(campaignId))}/transactions?${query.toString()}`);
137
+ }
138
+ /**
139
+ * Every row of `earnCampaignTransactions()`, page by page. The cursors stay set past the last row,
140
+ * so a page shorter than `limit` ends the walk.
141
+ *
142
+ * @param campaignId - `campaign_id` from `earnCampaigns()`.
143
+ * @param limit - Rows per page.
144
+ * @yields One row per deposit, withdrawal or payout.
145
+ */
146
+ async *allEarnCampaignTransactions(campaignId, limit = 100) {
147
+ const seen = /* @__PURE__ */ new Set();
148
+ let page;
149
+ for (;;) {
150
+ page = await this.earnCampaignTransactions(campaignId, limit, page);
151
+ yield* page.transactions;
152
+ if (page.transactions.length < limit) return;
153
+ const cursor = [
154
+ page.deposit_acc_next_from,
155
+ page.payout_acc_next_from,
156
+ page.transfer_request_next_before_datetime
157
+ ].join("|");
158
+ if (seen.has(cursor)) throw new Error("earn transactions pagination returned a cursor twice");
159
+ seen.add(cursor);
160
+ }
161
+ }
162
+ earnRate(from, to) {
163
+ return this.transport.get(`/earn/public-api/v1/rate/${encodeURIComponent(from)}/${encodeURIComponent(to)}`);
164
+ }
165
+ pnl() {
166
+ return this.transport.get("/customer-analytics/public-api/v1/pnl/assets/");
167
+ }
168
+ /**
169
+ * Balance, P/L, deposits and withdrawals per day or per calendar month, plus totals for the whole range.
170
+ *
171
+ * @param dateFrom - Start of the first period, as a `Date` or `YYYY-MM-DD`. A `Date` counts by its UTC day.
172
+ * @param resolution - `1d` for days, `1m` for months.
173
+ */
174
+ portfolioHistory(dateFrom, resolution) {
175
+ const query = new URLSearchParams({
176
+ date_from: dateFrom instanceof Date ? calendarDate(dateFrom) : dateFrom,
177
+ resolution
178
+ });
179
+ return this.transport.get(`/customer-analytics/public-api/v1/portfolio/private/?${query.toString()}`);
180
+ }
181
+ /**
182
+ * @param options - Filters, all optional.
183
+ * @param options.status - Only orders in these states, such as `OPEN`.
184
+ * @param options.limit - Most orders to return.
185
+ */
186
+ async limitOrders(options = {}) {
187
+ const query = new URLSearchParams();
188
+ for (const status of options.status ?? []) query.append("status", status);
189
+ if (options.limit !== void 0) query.set("limit", String(options.limit));
190
+ const search = query.size === 0 ? "" : `?${query.toString()}`;
191
+ const { items } = await this.transport.get(`/emporos/public-api/orders/limit${search}`);
192
+ return items;
193
+ }
194
+ limitOrder(id) {
195
+ const query = new URLSearchParams({ id });
196
+ return this.transport.get(`/emporos/public-api/orders/limit/by-id?${query.toString()}`);
197
+ }
198
+ /**
199
+ * Fee and fill for a limit order, without placing it. Only `BUY` is accepted.
200
+ *
201
+ * @param baseCurrency - What is bought, from `limitOrderTokens()`.
202
+ * @param side - `SELL` is a valid value that the API currently rejects.
203
+ * @param priceLimit - Highest price to pay per unit.
204
+ * @param size - `amount` to spend, or `quantity` of `baseCurrency` to receive.
205
+ */
206
+ limitOrderQuote(baseCurrency, side, priceLimit, size) {
207
+ const query = new URLSearchParams({
208
+ base_currency: baseCurrency,
209
+ side,
210
+ price_limit: String(priceLimit)
211
+ });
212
+ if ("amount" in size) query.set("amount", String(size.amount));
213
+ else query.set("quantity", String(size.quantity));
214
+ return this.transport.get(`/emporos/public-api/orders/limit/quote?${query.toString()}`);
215
+ }
216
+ limitOrderBounds(baseCurrency) {
217
+ const query = new URLSearchParams({ base_currency: baseCurrency });
218
+ return this.transport.get(`/emporos/public-api/orders/limit/bounds?${query.toString()}`);
219
+ }
220
+ /** Every currency a limit order can buy. */
221
+ async limitOrderTokens() {
222
+ const { tokens } = await this.transport.get("/emporos/public-api/orders/limit/tokens");
223
+ return tokens;
224
+ }
225
+ coinDetails(currency, fiatCurrency) {
226
+ return this.transport.get(`/api/v1/coins/details/${encodeURIComponent(currency)}/${encodeURIComponent(fiatCurrency)}/`);
227
+ }
228
+ charts(currency, fiatCurrency) {
229
+ return this.transport.get(`/api/v1/charts/${encodeURIComponent(currency)}/${encodeURIComponent(fiatCurrency)}/`);
230
+ }
231
+ priceForFiatAtTime(cryptoCurrency, localCurrency, amount, time) {
232
+ const query = new URLSearchParams({
233
+ crypto_currency: cryptoCurrency,
234
+ local_currency: localCurrency,
235
+ amount: String(amount),
236
+ time: time instanceof Date ? time.toISOString() : String(time)
237
+ });
238
+ return this.transport.get(`/api/v1/exchange_rates/price_for_fiat_at_time/?${query.toString()}`);
239
+ }
240
+ availableExchanges() {
241
+ return this.transport.get("/api/v1/exchange/get-available-exchanges/");
242
+ }
243
+ /**
244
+ * Executable rate, spread included, unlike the display rates.
245
+ *
246
+ * @param fromCurrency - What is paid.
247
+ * @param toCurrency - What is received.
248
+ * @param fromAmount - Amount of `fromCurrency`; `0` returns the rate and limits only.
249
+ */
250
+ quote(fromCurrency, toCurrency, fromAmount) {
251
+ return this.transport.post("/api/v1/exchange/convert/", {
252
+ from_amount: fromAmount,
253
+ from_currency: fromCurrency,
254
+ to_currency: toCurrency
255
+ });
256
+ }
257
+ /**
258
+ * Moves real money and is never retried. After a failure, check balances before trying again.
259
+ *
260
+ * @param request - The exchange, in the shape the API takes it.
261
+ */
262
+ createExchange(request) {
263
+ return this.transport.post("/api/v1/exchange/create_exchange/", request, 0);
264
+ }
265
+ /** Non-zero spot balances against the API total, which also counts staked funds. */
266
+ async portfolio() {
267
+ const data = await this.accounts();
268
+ if (!Array.isArray(data.accounts)) throw new Error("accounts response carries no accounts array");
269
+ const total = decimal(data.available_balance_total_fiat_amount);
270
+ if (total === null) throw new Error(`accounts total is not a number: ${String(data.available_balance_total_fiat_amount)}`);
271
+ const entries = [];
272
+ for (const account of data.accounts) {
273
+ const balance = decimal(account.available_balance);
274
+ if (balance === null) throw new Error(`${account.currency} balance is not a number: ${String(account.available_balance)}`);
275
+ if (balance <= 0) continue;
276
+ const fiatValue = decimal(account.available_balance_fiat_amount);
277
+ if (fiatValue === null) throw new Error(`${account.currency} fiat value is not a number: ${String(account.available_balance_fiat_amount)}`);
278
+ entries.push({
279
+ currency: account.currency,
280
+ amount: account.available_balance,
281
+ fiatValue
282
+ });
283
+ }
284
+ const entriesTotal = entries.reduce((sum, entry) => sum + entry.fiatValue, 0);
285
+ return {
286
+ entries: entries.sort((a, b) => b.fiatValue - a.fiatValue),
287
+ entriesTotal,
288
+ total,
289
+ staked: total - entriesTotal,
290
+ fiatCurrency: data.available_balance_total_fiat_currency
291
+ };
292
+ }
293
+ };
294
+ export { WaltClient };
@@ -0,0 +1,10 @@
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 { EarnAccessPolicy, EarnAccount, EarnAmount, EarnBonus, EarnCampaign, EarnCampaignsResponse, EarnDepegInfo, EarnInterestRate, EarnPayoutPool, EarnRate, EarnTransaction, EarnTransactionsPage } from "./api/earn.js";
4
+ import { AvailableExchanges, Exchange, ExchangeQuote, ExchangeRequest } from "./api/exchange.js";
5
+ import { LimitOrder, LimitOrderBounds, LimitOrderQuote, LimitOrderToken } from "./api/limit-orders.js";
6
+ import { NormalizedTransaction, Transaction, TransactionDetails, TransactionGateway, TransactionLeg, TransactionsPage, normalizeTransaction } from "./api/transactions.js";
7
+ import { Portfolio, PortfolioEntry, WaltClient, WaltClientOptions } from "./client.js";
8
+ import { TelegramTokenSource, TelegramTokenSourceOptions, TokenState, TokenStorage } from "./telegram/auth.js";
9
+ 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 };
package/build/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { normalizeTransaction } from "./api/transactions.js";
2
+ import { WaltClient } from "./client.js";
3
+ import { TelegramTokenSource } from "./telegram/auth.js";
4
+ import { WaltAuthError, WaltRequestError } from "./transport.js";
5
+ export { TelegramTokenSource, WaltAuthError, WaltClient, WaltRequestError, normalizeTransaction };
@@ -0,0 +1,57 @@
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 TelegramTokenSourceOptions = {
13
+ session: string;
14
+ apiId: number;
15
+ apiHash: string;
16
+ bot?: string;
17
+ deviceSerial?: string;
18
+ /** Origin of the web app, sent as `origin` and `referer`. */
19
+ baseUrl?: string;
20
+ /** Where the auth and refresh requests go. */
21
+ authUrl?: string;
22
+ fetch?: typeof fetch;
23
+ /** Defaults to a file in the user cache directory; `false` keeps the token in memory. */
24
+ storage?: TokenStorage | false;
25
+ maxFloodWaitMs?: number;
26
+ };
27
+ export declare class TelegramTokenSource {
28
+ private readonly client;
29
+ private readonly bot;
30
+ private readonly baseUrl;
31
+ private readonly authUrl;
32
+ private readonly fetchImpl;
33
+ private readonly storage;
34
+ private readonly maxFloodWaitMs;
35
+ private deviceSerial;
36
+ private deviceSerialKnown;
37
+ private jwt;
38
+ private refreshToken;
39
+ private sessionId;
40
+ private expiresAt;
41
+ private restored;
42
+ private pending;
43
+ 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;
52
+ private requestInitData;
53
+ private refresh;
54
+ private post;
55
+ private store;
56
+ disconnect(): Promise<void>;
57
+ }