@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.
- package/LICENSE +21 -0
- package/README.md +105 -2
- package/build/api/accounts.d.ts +111 -0
- package/build/api/accounts.js +1 -0
- package/build/api/analytics.d.ts +67 -0
- package/build/api/analytics.js +1 -0
- package/build/api/earn.d.ts +113 -0
- package/build/api/earn.js +1 -0
- package/build/api/exchange.d.ts +51 -0
- package/build/api/exchange.js +1 -0
- package/build/api/limit-orders.d.ts +62 -0
- package/build/api/limit-orders.js +1 -0
- package/build/api/transactions.d.ts +85 -0
- package/build/api/transactions.js +32 -0
- package/build/bin/login.d.ts +1 -0
- package/build/bin/login.js +26 -0
- package/build/browser.d.ts +19 -0
- package/build/browser.js +21 -0
- package/build/client.d.ts +138 -0
- package/build/client.js +294 -0
- package/build/index.d.ts +10 -0
- package/build/index.js +5 -0
- package/build/telegram/auth.d.ts +57 -0
- package/build/telegram/auth.js +164 -0
- package/build/telegram/file-storage.d.ts +3 -0
- package/build/telegram/file-storage.js +33 -0
- package/build/transport.d.ts +40 -0
- package/build/transport.js +177 -0
- package/package.json +81 -3
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { BROWSER_HEADERS } from "../browser.js";
|
|
2
|
+
import { defaultTokenPath, fileTokenStorage } from "./file-storage.js";
|
|
3
|
+
import { Api, TelegramClient, errors } from "teleproto";
|
|
4
|
+
import { StringSession } from "teleproto/sessions/index.js";
|
|
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
|
+
const MAX_FLOOD_WAIT_MS = 6e4;
|
|
10
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
var AuthEndpointError = class extends Error {
|
|
12
|
+
status;
|
|
13
|
+
constructor(message, status) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.status = status;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var TelegramTokenSource = class {
|
|
19
|
+
client;
|
|
20
|
+
bot;
|
|
21
|
+
baseUrl;
|
|
22
|
+
authUrl;
|
|
23
|
+
fetchImpl;
|
|
24
|
+
storage;
|
|
25
|
+
maxFloodWaitMs;
|
|
26
|
+
deviceSerial;
|
|
27
|
+
deviceSerialKnown;
|
|
28
|
+
jwt = null;
|
|
29
|
+
refreshToken = null;
|
|
30
|
+
sessionId = null;
|
|
31
|
+
expiresAt = 0;
|
|
32
|
+
restored = false;
|
|
33
|
+
pending = null;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.client = new TelegramClient(new StringSession(options.session), options.apiId, options.apiHash, { connectionRetries: 3 });
|
|
36
|
+
this.bot = options.bot ?? "walt";
|
|
37
|
+
this.deviceSerial = options.deviceSerial ?? `weba:${crypto.randomUUID()}`;
|
|
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));
|
|
43
|
+
this.maxFloodWaitMs = options.maxFloodWaitMs ?? MAX_FLOOD_WAIT_MS;
|
|
44
|
+
}
|
|
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() {
|
|
85
|
+
const initData = await this.requestInitData();
|
|
86
|
+
const serial = this.deviceSerialKnown ? { device_serial: this.deviceSerial } : { device_serial_fallback: this.deviceSerial };
|
|
87
|
+
const token = await this.store(await this.post("/alectryon/public-api/auth", {
|
|
88
|
+
web_view_init_data_raw: initData,
|
|
89
|
+
ep: "menu",
|
|
90
|
+
device_serial_multi_domain_backup_supported: true,
|
|
91
|
+
...serial
|
|
92
|
+
}));
|
|
93
|
+
this.deviceSerialKnown = true;
|
|
94
|
+
return token;
|
|
95
|
+
}
|
|
96
|
+
async requestInitData(retryAfterFlood = true) {
|
|
97
|
+
if (!this.client.connected) await this.client.connect();
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = await this.client.invoke(new Api.messages.RequestWebView({
|
|
101
|
+
peer: this.bot,
|
|
102
|
+
bot: this.bot,
|
|
103
|
+
platform: "web",
|
|
104
|
+
fromBotMenu: true
|
|
105
|
+
}));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (!retryAfterFlood || !(error instanceof errors.FloodWaitError)) throw error;
|
|
108
|
+
const waitMs = error.seconds * 1e3;
|
|
109
|
+
if (waitMs > this.maxFloodWaitMs) throw new Error(`telegram rate limited for ${error.seconds}s, above maxFloodWaitMs of ${this.maxFloodWaitMs}`);
|
|
110
|
+
await sleep(waitMs);
|
|
111
|
+
return this.requestInitData(false);
|
|
112
|
+
}
|
|
113
|
+
const initData = new URLSearchParams(new URL(result.url).hash.slice(1)).get("tgWebAppData");
|
|
114
|
+
if (!initData) throw new Error("no tgWebAppData in web view url");
|
|
115
|
+
return initData;
|
|
116
|
+
}
|
|
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
|
+
async disconnect() {
|
|
161
|
+
await this.client.disconnect();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
export { TelegramTokenSource };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
// source/telegram/file-storage.ts
|
|
6
|
+
const defaultTokenPath = (session) => {
|
|
7
|
+
const cacheHome = process.env["XDG_CACHE_HOME"] || join(homedir(), ".cache");
|
|
8
|
+
const account = createHash("sha256").update(session).digest("hex").slice(0, 16);
|
|
9
|
+
return join(cacheHome, "walt", `${account}.json`);
|
|
10
|
+
};
|
|
11
|
+
const fileTokenStorage = (path) => ({
|
|
12
|
+
load: async () => {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
save: async (state) => {
|
|
20
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
21
|
+
try {
|
|
22
|
+
await mkdir(dirname(path), {
|
|
23
|
+
recursive: true,
|
|
24
|
+
mode: 448
|
|
25
|
+
});
|
|
26
|
+
await writeFile(temp, JSON.stringify(state), { mode: 384 });
|
|
27
|
+
await rename(temp, path);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (!(error instanceof Error && "code" in error)) throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
export { defaultTokenPath, fileTokenStorage };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare class WaltAuthError extends Error {}
|
|
2
|
+
export declare class WaltRequestError extends Error {
|
|
3
|
+
/** HTTP status, or `0` when the request never got a response. */
|
|
4
|
+
readonly status: number;
|
|
5
|
+
/** From a `retry-after` given in seconds. */
|
|
6
|
+
readonly retryAfterMs: number | null;
|
|
7
|
+
/** The response body, parsed when it is JSON, such as `{ code: 'market_order_max_limit_error', detail }`. */
|
|
8
|
+
readonly body: unknown;
|
|
9
|
+
constructor(message: string, status: number, retryAfterMs: number | null, body: unknown, options?: ErrorOptions);
|
|
10
|
+
}
|
|
11
|
+
export declare class Transport {
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
private readonly fetchImpl;
|
|
14
|
+
private readonly tokenProvider;
|
|
15
|
+
private readonly traceId;
|
|
16
|
+
private readonly baggage;
|
|
17
|
+
private readonly retries;
|
|
18
|
+
private readonly retryDelayMs;
|
|
19
|
+
private readonly maxConcurrent;
|
|
20
|
+
private token;
|
|
21
|
+
private pending;
|
|
22
|
+
private active;
|
|
23
|
+
private readonly waiting;
|
|
24
|
+
constructor(options: {
|
|
25
|
+
tokenProvider: (rejected?: string) => Promise<string>;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
fetch: typeof fetch;
|
|
28
|
+
retries: number;
|
|
29
|
+
retryDelayMs: number;
|
|
30
|
+
maxConcurrent: number;
|
|
31
|
+
});
|
|
32
|
+
get<T>(path: string): Promise<T>;
|
|
33
|
+
post<T>(path: string, body: unknown, retries?: number): Promise<T>;
|
|
34
|
+
private ensureToken;
|
|
35
|
+
private mintToken;
|
|
36
|
+
private acquire;
|
|
37
|
+
private release;
|
|
38
|
+
private send;
|
|
39
|
+
private request;
|
|
40
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { BROWSER_HEADERS, SENTRY_PUBLIC_KEY, SENTRY_RELEASE } from "./browser.js";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
// source/transport.ts
|
|
4
|
+
var WaltAuthError = class extends Error {};
|
|
5
|
+
var WaltRequestError = class extends Error {
|
|
6
|
+
/** HTTP status, or `0` when the request never got a response. */
|
|
7
|
+
status;
|
|
8
|
+
/** From a `retry-after` given in seconds. */
|
|
9
|
+
retryAfterMs;
|
|
10
|
+
/** The response body, parsed when it is JSON, such as `{ code: 'market_order_max_limit_error', detail }`. */
|
|
11
|
+
body;
|
|
12
|
+
constructor(message, status, retryAfterMs, body, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.retryAfterMs = retryAfterMs;
|
|
16
|
+
this.body = body;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
20
|
+
0,
|
|
21
|
+
429,
|
|
22
|
+
502,
|
|
23
|
+
503,
|
|
24
|
+
504
|
|
25
|
+
]);
|
|
26
|
+
const MAX_RETRY_DELAY_MS = 3e4;
|
|
27
|
+
const hex = (bytes) => randomBytes(bytes).toString("hex");
|
|
28
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
const parseBody = (text) => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
} catch {
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const retryAfterOf = (response) => {
|
|
37
|
+
const header = response.headers.get("retry-after");
|
|
38
|
+
return header !== null && /^\d+$/.test(header.trim()) ? Number(header.trim()) * 1e3 : null;
|
|
39
|
+
};
|
|
40
|
+
const telegramUserIdOf = (token) => {
|
|
41
|
+
try {
|
|
42
|
+
const payload = token.split(".")[1] ?? "";
|
|
43
|
+
const id = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).web_view_init_data?.user?.id;
|
|
44
|
+
return typeof id === "number" ? id : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var Transport = class {
|
|
50
|
+
baseUrl;
|
|
51
|
+
fetchImpl;
|
|
52
|
+
tokenProvider;
|
|
53
|
+
traceId = hex(16);
|
|
54
|
+
baggage;
|
|
55
|
+
retries;
|
|
56
|
+
retryDelayMs;
|
|
57
|
+
maxConcurrent;
|
|
58
|
+
token = null;
|
|
59
|
+
pending = null;
|
|
60
|
+
active = 0;
|
|
61
|
+
waiting = [];
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.tokenProvider = options.tokenProvider;
|
|
64
|
+
this.baseUrl = options.baseUrl;
|
|
65
|
+
this.fetchImpl = options.fetch;
|
|
66
|
+
this.retries = options.retries;
|
|
67
|
+
this.retryDelayMs = options.retryDelayMs;
|
|
68
|
+
this.maxConcurrent = options.maxConcurrent;
|
|
69
|
+
this.baggage = [
|
|
70
|
+
"sentry-environment=production",
|
|
71
|
+
`sentry-release=${SENTRY_RELEASE}`,
|
|
72
|
+
`sentry-public_key=${SENTRY_PUBLIC_KEY}`,
|
|
73
|
+
`sentry-trace_id=${this.traceId}`,
|
|
74
|
+
"sentry-transaction=%2Fwv",
|
|
75
|
+
"sentry-sampled=false",
|
|
76
|
+
`sentry-sample_rand=${Math.random()}`,
|
|
77
|
+
"sentry-sample_rate=0.01"
|
|
78
|
+
].join(",");
|
|
79
|
+
}
|
|
80
|
+
get(path) {
|
|
81
|
+
return this.request(path, void 0, this.retries);
|
|
82
|
+
}
|
|
83
|
+
post(path, body, retries = this.retries) {
|
|
84
|
+
return this.request(path, body, retries);
|
|
85
|
+
}
|
|
86
|
+
async ensureToken(rejected) {
|
|
87
|
+
if (this.token) return this.token;
|
|
88
|
+
this.pending ??= this.mintToken(rejected);
|
|
89
|
+
try {
|
|
90
|
+
return await this.pending;
|
|
91
|
+
} finally {
|
|
92
|
+
this.pending = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async mintToken(rejected) {
|
|
96
|
+
const token = await this.tokenProvider(rejected);
|
|
97
|
+
this.token = token;
|
|
98
|
+
return token;
|
|
99
|
+
}
|
|
100
|
+
async acquire() {
|
|
101
|
+
if (this.active < this.maxConcurrent) {
|
|
102
|
+
this.active++;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
this.waiting.push(() => {
|
|
107
|
+
this.active++;
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
release() {
|
|
113
|
+
this.active--;
|
|
114
|
+
this.waiting.shift()?.();
|
|
115
|
+
}
|
|
116
|
+
async send(path, body, rejected) {
|
|
117
|
+
const token = await this.ensureToken(rejected);
|
|
118
|
+
const method = body === void 0 ? "GET" : "POST";
|
|
119
|
+
const jwt = token.replace(/^Bearer\s+/i, "");
|
|
120
|
+
const userId = telegramUserIdOf(jwt);
|
|
121
|
+
const headers = {
|
|
122
|
+
...BROWSER_HEADERS,
|
|
123
|
+
accept: "application/json",
|
|
124
|
+
authorization: path.startsWith("/emporos/") ? `Bearer ${jwt}` : jwt,
|
|
125
|
+
referer: `${this.baseUrl}/main?ep=menu${userId === null ? "" : `&tgWebAppUserId=${userId}`}`,
|
|
126
|
+
baggage: this.baggage,
|
|
127
|
+
"sentry-trace": `${this.traceId}-${hex(8)}-0`,
|
|
128
|
+
traceparent: `00-${hex(16)}-${hex(8)}-01`
|
|
129
|
+
};
|
|
130
|
+
if (method === "POST") {
|
|
131
|
+
headers["content-type"] = "application/json";
|
|
132
|
+
headers["origin"] = this.baseUrl;
|
|
133
|
+
}
|
|
134
|
+
let response;
|
|
135
|
+
try {
|
|
136
|
+
response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
137
|
+
method,
|
|
138
|
+
headers,
|
|
139
|
+
body: method === "POST" ? JSON.stringify(body) : null
|
|
140
|
+
});
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
throw new WaltRequestError(`${method} ${path} failed (no response)`, 0, null, null, { cause });
|
|
143
|
+
}
|
|
144
|
+
if (response.status === 401) {
|
|
145
|
+
if (rejected !== void 0) throw new WaltAuthError("token rejected after refresh");
|
|
146
|
+
if (this.token === token) this.token = null;
|
|
147
|
+
return this.send(path, body, token);
|
|
148
|
+
}
|
|
149
|
+
let text;
|
|
150
|
+
try {
|
|
151
|
+
text = await response.text();
|
|
152
|
+
} catch (cause) {
|
|
153
|
+
throw new WaltRequestError(`${method} ${path} failed (body not received)`, 0, null, null, { cause });
|
|
154
|
+
}
|
|
155
|
+
if (!response.ok) throw new WaltRequestError(`${method} ${path} failed (${response.status})`, response.status, retryAfterOf(response), parseBody(text));
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(text);
|
|
158
|
+
} catch (cause) {
|
|
159
|
+
throw new WaltRequestError(`${method} ${path} returned a body that is not JSON (${response.status})`, response.status, null, text, { cause });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async request(path, body, retries) {
|
|
163
|
+
await this.acquire();
|
|
164
|
+
try {
|
|
165
|
+
for (let attempt = 0;; attempt++) try {
|
|
166
|
+
return await this.send(path, body);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (!(error instanceof WaltRequestError && RETRYABLE_STATUS.has(error.status)) || attempt >= retries) throw error;
|
|
169
|
+
const backoff = Math.min(MAX_RETRY_DELAY_MS, this.retryDelayMs * 2 ** attempt);
|
|
170
|
+
await sleep(error.retryAfterMs ?? backoff / 2 + Math.random() * (backoff / 2));
|
|
171
|
+
}
|
|
172
|
+
} finally {
|
|
173
|
+
this.release();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
export { Transport, WaltAuthError, WaltRequestError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,84 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2bad/walt",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Client for the Walt (ex. Wallet) API in Telegram to track a crypto portfolio and exchange between assets",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"telegram",
|
|
7
|
+
"walt",
|
|
8
|
+
"wallet",
|
|
9
|
+
"crypto",
|
|
10
|
+
"portfolio"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/2BAD/walt#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/2BAD/walt/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/2BAD/walt.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "2BAD (https://github.com/2BAD)",
|
|
22
|
+
"contributors": [
|
|
23
|
+
{
|
|
24
|
+
"name": "Jason Hyde",
|
|
25
|
+
"email": "github@2bad.me",
|
|
26
|
+
"url": "https://github.com/mrhyde"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./build/index.d.ts",
|
|
34
|
+
"default": "./build/index.js"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"telegram-login": "./build/bin/login.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"build"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"teleproto": "1.232.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@2bad/axiom": "2.1.0",
|
|
49
|
+
"@2bad/tsconfig": "4.0.0",
|
|
50
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
51
|
+
"@types/node": "26.6.6",
|
|
52
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
53
|
+
"npm-run-all2": "9.0.4",
|
|
54
|
+
"oxfmt": "0.75.0",
|
|
55
|
+
"oxlint": "1.90.0",
|
|
56
|
+
"publint": "0.3.25",
|
|
57
|
+
"tsdown": "0.23.4",
|
|
58
|
+
"typescript": "7.0.2",
|
|
59
|
+
"vitest": "5.0.5"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=26"
|
|
63
|
+
},
|
|
64
|
+
"volta": {
|
|
65
|
+
"node": "26.13.0",
|
|
66
|
+
"pnpm": "12.16.1"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsdown",
|
|
70
|
+
"check": "run-p check:**",
|
|
71
|
+
"check:code": "oxlint ./source",
|
|
72
|
+
"check:format": "oxfmt --check ./source ./examples",
|
|
73
|
+
"check:types": "tsc --noEmit",
|
|
74
|
+
"fix": "run-p fix:**",
|
|
75
|
+
"fix:code": "oxlint --fix ./source",
|
|
76
|
+
"fix:format": "oxfmt ./source ./examples",
|
|
77
|
+
"telegram:login": "node --env-file-if-exists=.env source/bin/login.ts",
|
|
78
|
+
"example": "run-s build && node --env-file-if-exists=.env examples/portfolio.ts",
|
|
79
|
+
"preversion": "run-s check test build",
|
|
80
|
+
"test": "vitest run",
|
|
81
|
+
"test:integration": "vitest run integration --coverage",
|
|
82
|
+
"test:unit": "vitest run unit --coverage"
|
|
83
|
+
}
|
|
6
84
|
}
|