@keldra/sdk 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,331 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/errors.ts
2
+ var KeldraError = class _KeldraError extends Error {
3
+
4
+
5
+ constructor(code, message, cause) {
6
+ super(message);
7
+ this.name = "KeldraError";
8
+ this.code = code;
9
+ this.cause = cause;
10
+ }
11
+ static http(message, cause) {
12
+ return new _KeldraError("HTTP", `HTTP request failed: ${message}`, cause);
13
+ }
14
+ static api(status, message) {
15
+ return new KeldraApiError(status, message);
16
+ }
17
+ static timeout(relayId, lastStatus) {
18
+ return new KeldraTimeoutError(relayId, lastStatus);
19
+ }
20
+ static invalidTransaction(message) {
21
+ return new _KeldraError(
22
+ "INVALID_TRANSACTION",
23
+ `Invalid transaction: ${message}`
24
+ );
25
+ }
26
+ static config(message) {
27
+ return new _KeldraError("CONFIG", `SDK configuration error: ${message}`);
28
+ }
29
+ };
30
+ var KeldraApiError = class extends KeldraError {
31
+
32
+ constructor(status, message) {
33
+ super("API", `API error (HTTP ${status}): ${message}`);
34
+ this.name = "KeldraApiError";
35
+ this.status = status;
36
+ }
37
+ };
38
+ var KeldraTimeoutError = class extends KeldraError {
39
+
40
+
41
+ constructor(relayId, lastStatus) {
42
+ super(
43
+ "TIMEOUT",
44
+ `Relay ${relayId} timed out (last status: ${lastStatus})`
45
+ );
46
+ this.name = "KeldraTimeoutError";
47
+ this.relayId = relayId;
48
+ this.lastStatus = lastStatus;
49
+ }
50
+ };
51
+
52
+ // src/utils.ts
53
+ function parseHex(hexStr) {
54
+ const cleaned = hexStr.startsWith("0x") ? hexStr.slice(2) : hexStr;
55
+ if (cleaned.length === 0) {
56
+ throw KeldraError.invalidTransaction("empty transaction");
57
+ }
58
+ if (cleaned.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(cleaned)) {
59
+ throw KeldraError.invalidTransaction(`invalid hex: ${hexStr}`);
60
+ }
61
+ const bytes = new Uint8Array(cleaned.length / 2);
62
+ for (let i = 0; i < cleaned.length; i += 2) {
63
+ bytes[i / 2] = parseInt(cleaned.substring(i, i + 2), 16);
64
+ }
65
+ return bytes;
66
+ }
67
+ function toHex(bytes) {
68
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
69
+ }
70
+ function toBase64(bytes) {
71
+ if (typeof Buffer !== "undefined") {
72
+ return Buffer.from(bytes).toString("base64");
73
+ }
74
+ let binary = "";
75
+ for (let i = 0; i < bytes.length; i++) {
76
+ binary += String.fromCharCode(bytes[i]);
77
+ }
78
+ return btoa(binary);
79
+ }
80
+ function extractErrorMessage(body) {
81
+ try {
82
+ const parsed = JSON.parse(body);
83
+ if (typeof _optionalChain([parsed, 'optionalAccess', _ => _.error]) === "string") return parsed.error;
84
+ } catch (e) {
85
+ }
86
+ return body;
87
+ }
88
+ function sleep(ms) {
89
+ return new Promise((resolve) => setTimeout(resolve, ms));
90
+ }
91
+
92
+ // src/http.ts
93
+ var HttpClient = class {
94
+
95
+ constructor(bearerToken) {
96
+ this.baseHeaders = { "Content-Type": "application/json" };
97
+ if (bearerToken) {
98
+ this.baseHeaders["Authorization"] = `Bearer ${bearerToken}`;
99
+ }
100
+ }
101
+ async get(url) {
102
+ return this.request("GET", url);
103
+ }
104
+ async post(url, body) {
105
+ return this.request("POST", url, body);
106
+ }
107
+ async request(method, url, body) {
108
+ let response;
109
+ try {
110
+ response = await fetch(url, {
111
+ method,
112
+ headers: this.baseHeaders,
113
+ body: body ? JSON.stringify(body) : void 0
114
+ });
115
+ } catch (err) {
116
+ throw KeldraError.http(String(err), err);
117
+ }
118
+ if (!response.ok) {
119
+ const text = await response.text().catch(() => "");
120
+ throw KeldraError.api(response.status, extractErrorMessage(text));
121
+ }
122
+ return response.json();
123
+ }
124
+ };
125
+
126
+ // src/padding.ts
127
+ var BUCKETS = [512, 1024, 2048, 4096];
128
+ function padTransaction(rawTx) {
129
+ const originalLen = rawTx.length;
130
+ const totalNeeded = originalLen + 4;
131
+ const bucket = BUCKETS.find((b) => b >= totalNeeded);
132
+ if (!bucket) {
133
+ throw new Error(
134
+ `Transaction too large: ${originalLen} bytes (max ${BUCKETS[BUCKETS.length - 1] - 4} bytes)`
135
+ );
136
+ }
137
+ const padded = new Uint8Array(bucket);
138
+ padded.set(rawTx, 0);
139
+ const paddingLen = bucket - totalNeeded;
140
+ if (paddingLen > 0) {
141
+ const randomBytes = crypto.getRandomValues(new Uint8Array(paddingLen));
142
+ padded.set(randomBytes, originalLen);
143
+ }
144
+ const view = new DataView(padded.buffer, padded.byteOffset);
145
+ view.setUint32(bucket - 4, originalLen, false);
146
+ return padded;
147
+ }
148
+ function unpadTransaction(padded) {
149
+ if (padded.length < 4) return null;
150
+ const view = new DataView(padded.buffer, padded.byteOffset);
151
+ const originalLen = view.getUint32(padded.length - 4, false);
152
+ if (originalLen > padded.length - 4) return null;
153
+ return padded.slice(0, originalLen);
154
+ }
155
+
156
+ // src/types.ts
157
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
158
+ "confirmed",
159
+ "failed"
160
+ ]);
161
+
162
+ // src/client.ts
163
+ var DEFAULT_GATEWAY_URL = "http://localhost:3400";
164
+ var DEFAULT_TIMEOUT_MS = 3e5;
165
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
166
+ var MAX_POLL_INTERVAL_MS = 15e3;
167
+ var KeldraClient = class _KeldraClient {
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+ constructor(config) {
177
+ if (!config.apiKey) {
178
+ throw KeldraError.config("apiKey is required");
179
+ }
180
+ this.http = new HttpClient(config.apiKey);
181
+ this.gatewayUrl = (_nullishCoalesce(config.gatewayUrl, () => ( DEFAULT_GATEWAY_URL))).replace(
182
+ /\/$/,
183
+ ""
184
+ );
185
+ this.defaultDelayProfile = _nullishCoalesce(config.delayProfile, () => ( "balanced"));
186
+ this.confirmationTimeoutMs = _nullishCoalesce(config.timeoutMs, () => ( DEFAULT_TIMEOUT_MS));
187
+ this.pollIntervalMs = _nullishCoalesce(config.pollIntervalMs, () => ( DEFAULT_POLL_INTERVAL_MS));
188
+ this.noisePublicKey = config.noisePublicKey;
189
+ this.noiseKid = config.noiseKid;
190
+ this.encryptFn = config.encryptFn;
191
+ }
192
+ static create(apiKey) {
193
+ return new _KeldraClient({ apiKey });
194
+ }
195
+ static builder() {
196
+ return new KeldraClientBuilder();
197
+ }
198
+ async relay(chain, signedTx) {
199
+ const start = Date.now();
200
+ const response = await this.submit(chain, signedTx);
201
+ const result = await this.waitForConfirmation(response.relay_id);
202
+ return {
203
+ relayId: response.relay_id,
204
+ status: result.status,
205
+ txHash: result.tx_hash,
206
+ durationMs: Date.now() - start,
207
+ error: result.error
208
+ };
209
+ }
210
+ async submit(chain, signedTx) {
211
+ const rawBytes = parseHex(signedTx);
212
+ const padded = padTransaction(rawBytes);
213
+ let request;
214
+ if (this.noisePublicKey && this.noiseKid && this.encryptFn) {
215
+ const encrypted = await this.encryptFn(padded, this.noisePublicKey);
216
+ const b64 = toBase64(encrypted);
217
+ request = {
218
+ chain,
219
+ signed_tx: "",
220
+ options: { delay_profile: this.defaultDelayProfile },
221
+ encrypted_payload: b64,
222
+ noise_kid: this.noiseKid
223
+ };
224
+ } else {
225
+ request = {
226
+ chain,
227
+ signed_tx: `0x${toHex(padded)}`,
228
+ options: { delay_profile: this.defaultDelayProfile }
229
+ };
230
+ }
231
+ return this.http.post(
232
+ `${this.gatewayUrl}/v1/relay`,
233
+ request
234
+ );
235
+ }
236
+ async status(relayId) {
237
+ return this.http.get(
238
+ `${this.gatewayUrl}/v1/relay/${relayId}/status`
239
+ );
240
+ }
241
+ async waitForConfirmation(relayId) {
242
+ const deadline = Date.now() + this.confirmationTimeoutMs;
243
+ let interval = this.pollIntervalMs;
244
+ let lastStatus;
245
+ while (Date.now() < deadline) {
246
+ await sleep(interval);
247
+ try {
248
+ lastStatus = await this.status(relayId);
249
+ if (TERMINAL_STATUSES.has(lastStatus.status)) {
250
+ return lastStatus;
251
+ }
252
+ } catch (e2) {
253
+ }
254
+ interval = Math.min(interval * 2, MAX_POLL_INTERVAL_MS);
255
+ }
256
+ throw KeldraError.timeout(relayId, _nullishCoalesce(_optionalChain([lastStatus, 'optionalAccess', _2 => _2.status]), () => ( "queued")));
257
+ }
258
+ async health() {
259
+ return this.http.get(`${this.gatewayUrl}/v1/health`);
260
+ }
261
+ async chains() {
262
+ return this.http.get(`${this.gatewayUrl}/v1/chains`);
263
+ }
264
+ async limits() {
265
+ return this.http.get(`${this.gatewayUrl}/v1/me/limits`);
266
+ }
267
+ async usage(from, to) {
268
+ const params = new URLSearchParams({ from, to });
269
+ return this.http.get(
270
+ `${this.gatewayUrl}/v1/me/usage?${params.toString()}`
271
+ );
272
+ }
273
+ async fetchNoiseKey() {
274
+ const resp = await this.http.get(
275
+ `${this.gatewayUrl}/v1/noise-key`
276
+ );
277
+ this.noisePublicKey = resp.public_key;
278
+ this.noiseKid = resp.kid;
279
+ }
280
+ get encrypted() {
281
+ return !!(this.noisePublicKey && this.noiseKid && this.encryptFn);
282
+ }
283
+ };
284
+ var KeldraClientBuilder = (_class = class {constructor() { _class.prototype.__init.call(this); }
285
+ __init() {this.config = {}}
286
+ apiKey(key) {
287
+ this.config.apiKey = key;
288
+ return this;
289
+ }
290
+ gatewayUrl(url) {
291
+ this.config.gatewayUrl = url;
292
+ return this;
293
+ }
294
+ delayProfile(profile) {
295
+ this.config.delayProfile = profile;
296
+ return this;
297
+ }
298
+ timeout(ms) {
299
+ this.config.timeoutMs = ms;
300
+ return this;
301
+ }
302
+ pollInterval(ms) {
303
+ this.config.pollIntervalMs = ms;
304
+ return this;
305
+ }
306
+ noisePublicKey(key, kid) {
307
+ this.config.noisePublicKey = key;
308
+ this.config.noiseKid = kid;
309
+ return this;
310
+ }
311
+ withEncryption(fn) {
312
+ this.config.encryptFn = fn;
313
+ return this;
314
+ }
315
+ build() {
316
+ if (!this.config.apiKey) {
317
+ throw KeldraError.config("apiKey is required");
318
+ }
319
+ return new KeldraClient(this.config);
320
+ }
321
+ }, _class);
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+ exports.KeldraApiError = KeldraApiError; exports.KeldraClient = KeldraClient; exports.KeldraClientBuilder = KeldraClientBuilder; exports.KeldraError = KeldraError; exports.KeldraTimeoutError = KeldraTimeoutError; exports.TERMINAL_STATUSES = TERMINAL_STATUSES; exports.padTransaction = padTransaction; exports.unpadTransaction = unpadTransaction;
@@ -0,0 +1,29 @@
1
+ export { K as KeldraClient, a as KeldraClientBuilder } from './client-Bm1hg0EA.cjs';
2
+ import { R as RelayStatus } from './types-CL-VpP9K.cjs';
3
+ export { C as Chain, a as ChainConfig, b as ChainsResponse, D as DelayProfile, E as EncryptFn, H as HealthResponse, c as HealthStats, K as KeldraClientConfig, M as MeLimitsResponse, d as MeUsageResponse, N as NoiseKeyResponse, e as RelayOptions, f as RelayRequest, g as RelayResponse, h as RelayResult, i as RelayStatusResponse, T as TERMINAL_STATUSES, U as UsageDailyRow, j as UsageTotals } from './types-CL-VpP9K.cjs';
4
+
5
+ type KeldraErrorCode = 'HTTP' | 'API' | 'TIMEOUT' | 'INVALID_TRANSACTION' | 'CONFIG';
6
+ declare class KeldraError extends Error {
7
+ readonly code: KeldraErrorCode;
8
+ readonly cause?: unknown;
9
+ constructor(code: KeldraErrorCode, message: string, cause?: unknown);
10
+ static http(message: string, cause?: unknown): KeldraError;
11
+ static api(status: number, message: string): KeldraApiError;
12
+ static timeout(relayId: string, lastStatus: RelayStatus): KeldraTimeoutError;
13
+ static invalidTransaction(message: string): KeldraError;
14
+ static config(message: string): KeldraError;
15
+ }
16
+ declare class KeldraApiError extends KeldraError {
17
+ readonly status: number;
18
+ constructor(status: number, message: string);
19
+ }
20
+ declare class KeldraTimeoutError extends KeldraError {
21
+ readonly relayId: string;
22
+ readonly lastStatus: RelayStatus;
23
+ constructor(relayId: string, lastStatus: RelayStatus);
24
+ }
25
+
26
+ declare function padTransaction(rawTx: Uint8Array): Uint8Array;
27
+ declare function unpadTransaction(padded: Uint8Array): Uint8Array | null;
28
+
29
+ export { KeldraApiError, KeldraError, type KeldraErrorCode, KeldraTimeoutError, RelayStatus, padTransaction, unpadTransaction };
@@ -0,0 +1,29 @@
1
+ export { K as KeldraClient, a as KeldraClientBuilder } from './client-03PUOnb6.js';
2
+ import { R as RelayStatus } from './types-CL-VpP9K.js';
3
+ export { C as Chain, a as ChainConfig, b as ChainsResponse, D as DelayProfile, E as EncryptFn, H as HealthResponse, c as HealthStats, K as KeldraClientConfig, M as MeLimitsResponse, d as MeUsageResponse, N as NoiseKeyResponse, e as RelayOptions, f as RelayRequest, g as RelayResponse, h as RelayResult, i as RelayStatusResponse, T as TERMINAL_STATUSES, U as UsageDailyRow, j as UsageTotals } from './types-CL-VpP9K.js';
4
+
5
+ type KeldraErrorCode = 'HTTP' | 'API' | 'TIMEOUT' | 'INVALID_TRANSACTION' | 'CONFIG';
6
+ declare class KeldraError extends Error {
7
+ readonly code: KeldraErrorCode;
8
+ readonly cause?: unknown;
9
+ constructor(code: KeldraErrorCode, message: string, cause?: unknown);
10
+ static http(message: string, cause?: unknown): KeldraError;
11
+ static api(status: number, message: string): KeldraApiError;
12
+ static timeout(relayId: string, lastStatus: RelayStatus): KeldraTimeoutError;
13
+ static invalidTransaction(message: string): KeldraError;
14
+ static config(message: string): KeldraError;
15
+ }
16
+ declare class KeldraApiError extends KeldraError {
17
+ readonly status: number;
18
+ constructor(status: number, message: string);
19
+ }
20
+ declare class KeldraTimeoutError extends KeldraError {
21
+ readonly relayId: string;
22
+ readonly lastStatus: RelayStatus;
23
+ constructor(relayId: string, lastStatus: RelayStatus);
24
+ }
25
+
26
+ declare function padTransaction(rawTx: Uint8Array): Uint8Array;
27
+ declare function unpadTransaction(padded: Uint8Array): Uint8Array | null;
28
+
29
+ export { KeldraApiError, KeldraError, type KeldraErrorCode, KeldraTimeoutError, RelayStatus, padTransaction, unpadTransaction };