@voidaisdk/bridge-sdk 0.0.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.
@@ -0,0 +1,449 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.VoidAIBridgeClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ class VoidAIBridgeClient {
9
+ constructor(config) {
10
+ this.validatedApiKeyData = null;
11
+ this.accessToken = null;
12
+ this.config = config;
13
+ this.client = axios_1.default.create({
14
+ timeout: 10000,
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ });
19
+ // Start auth immediately. Requests will await this promise.
20
+ // New flow: POST /auth/login to get JWT. Fallback to validate-api-key for backward compatibility.
21
+ this.ready = this.authenticate().then(() => undefined);
22
+ }
23
+ /**
24
+ * Authenticate and set Authorization header for subsequent requests.
25
+ * New flow: POST /api/v1/auth/login
26
+ * Fallback: GET /api/v1/auth/validate-api-key (legacy)
27
+ */
28
+ async authenticate() {
29
+ try {
30
+ const token = await this.login();
31
+ this.setAccessToken(token);
32
+ // Populate validatedApiKeyData from JWT payload (best-effort) so existing getters keep working.
33
+ this.validatedApiKeyData = this.tryDecodeTokenToValidationData(token);
34
+ }
35
+ catch (error) {
36
+ // If login isn't available or fails unexpectedly, fallback to legacy validation
37
+ // (do not break current working functionality).
38
+ try {
39
+ await this.validateApiKey();
40
+ }
41
+ catch (fallbackError) {
42
+ throw error;
43
+ }
44
+ }
45
+ }
46
+ setAccessToken(token) {
47
+ this.accessToken = token;
48
+ this.client.defaults.headers.common['Authorization'] = `Bearer ${token}`;
49
+ }
50
+ /**
51
+ * Get access token (available after successful auth)
52
+ */
53
+ getAccessToken() {
54
+ return this.accessToken;
55
+ }
56
+ getLoginUrl() {
57
+ const baseUrl = this.config.getBaseUrl();
58
+ const cleanBase = baseUrl.replace(/\/$/, '');
59
+ return `${cleanBase}/api/v1/auth/login`;
60
+ }
61
+ /**
62
+ * Get EVM bridge contract address for burn operations.
63
+ */
64
+ getBridgeContractAddress() {
65
+ return this.config.bridgeContractAddress;
66
+ }
67
+ /**
68
+ * Login to obtain JWT access token.
69
+ * Frontend usage: { apiKey }
70
+ * Backend usage: { apiKey, secretKey }
71
+ */
72
+ async login() {
73
+ const loginUrl = this.getLoginUrl();
74
+ const body = {
75
+ apiKey: this.config.apiKey,
76
+ ...(this.config.secretKey ? { secretKey: this.config.secretKey } : {}),
77
+ };
78
+ try {
79
+ const response = await axios_1.default.post(loginUrl, body, {
80
+ headers: { 'Content-Type': 'application/json' },
81
+ timeout: 10000,
82
+ });
83
+ if (response.data && response.data.success && response.data.accessToken) {
84
+ return response.data.accessToken;
85
+ }
86
+ const errorData = response.data;
87
+ throw new Error(errorData?.error?.message || errorData?.message || 'Failed to login');
88
+ }
89
+ catch (error) {
90
+ if (axios_1.default.isAxiosError(error)) {
91
+ // If backend returns 401/403 or structured error, surface message.
92
+ const data = error.response?.data;
93
+ const msg = data?.error?.message || data?.message || error.message || 'Failed to login';
94
+ throw new Error(msg);
95
+ }
96
+ throw error;
97
+ }
98
+ }
99
+ tryDecodeTokenToValidationData(token) {
100
+ try {
101
+ const parts = token.split('.');
102
+ if (parts.length < 2)
103
+ return null;
104
+ const payload = parts[1];
105
+ const json = this.base64UrlDecode(payload);
106
+ const parsed = JSON.parse(json);
107
+ // backend token contains: keyId, name, sub/id, apiKey, etc.
108
+ if (!parsed?.keyId || !parsed?.name)
109
+ return null;
110
+ return {
111
+ keyId: String(parsed.keyId),
112
+ tenantId: String(parsed.sub || parsed.id || ''),
113
+ name: String(parsed.name),
114
+ };
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ base64UrlDecode(input) {
121
+ const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
122
+ const pad = base64.length % 4;
123
+ const padded = pad ? base64 + '='.repeat(4 - pad) : base64;
124
+ // Node (backend) vs Browser
125
+ if (typeof Buffer !== 'undefined') {
126
+ return Buffer.from(padded, 'base64').toString('utf8');
127
+ }
128
+ // @ts-ignore
129
+ return atob(padded);
130
+ }
131
+ /**
132
+ * Validate API key with the backend
133
+ * @throws Error if API key is invalid
134
+ */
135
+ async validateApiKey() {
136
+ try {
137
+ // Use a temporary axios instance for validation since baseUrl might be different
138
+ const validationUrl = this.getValidationUrl();
139
+ const response = await axios_1.default.get(validationUrl, {
140
+ headers: {
141
+ 'api-key': this.config.apiKey,
142
+ },
143
+ timeout: 10000,
144
+ });
145
+ if (response.data.success) {
146
+ this.validatedApiKeyData = response.data.data;
147
+ return response.data.data;
148
+ }
149
+ else {
150
+ const error = new Error(response.data.error.message);
151
+ error.code = response.data.error.code;
152
+ throw error;
153
+ }
154
+ }
155
+ catch (error) {
156
+ // If validation fails, ensure we don't keep stale validated data around.
157
+ this.validatedApiKeyData = null;
158
+ if (axios_1.default.isAxiosError(error)) {
159
+ if (error.response?.status === 401) {
160
+ const errorData = error.response.data;
161
+ const apiError = new Error(errorData.error?.message || 'API key is invalid or inactive');
162
+ apiError.code = errorData.error?.code || 'INVALID_API_KEY';
163
+ throw apiError;
164
+ }
165
+ throw new Error(`Failed to validate API key: ${error.message}`);
166
+ }
167
+ throw error;
168
+ }
169
+ }
170
+ /**
171
+ * Get validation URL - uses baseUrl from config
172
+ */
173
+ getValidationUrl() {
174
+ const baseUrl = this.config.getBaseUrl();
175
+ const cleanBase = baseUrl.replace(/\/$/, '');
176
+ return `${cleanBase}/api/v1/auth/validate-api-key`;
177
+ }
178
+ /**
179
+ * Get validated API key data
180
+ */
181
+ getValidatedApiKeyData() {
182
+ return this.validatedApiKeyData;
183
+ }
184
+ getUrl(path) {
185
+ const baseUrl = this.config.getBaseUrl();
186
+ // Remove trailing slash from base and leading slash from path to avoid doubles
187
+ const cleanBase = baseUrl.replace(/\/$/, '');
188
+ const cleanPath = path.replace(/^\//, '');
189
+ return `${cleanBase}/${cleanPath}`;
190
+ }
191
+ async get(path, params, retryAttempted = false) {
192
+ await this.ready;
193
+ const url = this.getUrl(path);
194
+ try {
195
+ const response = await this.client.get(url, { params });
196
+ return response.data;
197
+ }
198
+ catch (error) {
199
+ // If token is expired or unauthorized, transparently re-authenticate once and retry.
200
+ const status = error?.response?.status ?? error?.statusCode;
201
+ if (status === 401 && !retryAttempted) {
202
+ try {
203
+ await this.authenticate();
204
+ // Retry the original request once with fresh credentials.
205
+ return await this.get(path, params, true);
206
+ }
207
+ catch (retryError) {
208
+ this.handleError(retryError);
209
+ }
210
+ }
211
+ this.handleError(error);
212
+ }
213
+ }
214
+ async post(path, data, retryAttempted = false) {
215
+ await this.ready;
216
+ const url = this.getUrl(path);
217
+ try {
218
+ const response = await this.client.post(url, data);
219
+ return response.data;
220
+ }
221
+ catch (error) {
222
+ // If token is expired or unauthorized, transparently re-authenticate once and retry.
223
+ const status = error?.response?.status ?? error?.statusCode;
224
+ if (status === 401 && !retryAttempted) {
225
+ try {
226
+ await this.authenticate();
227
+ // Retry the original request once with fresh credentials.
228
+ return await this.post(path, data, true);
229
+ }
230
+ catch (retryError) {
231
+ this.handleError(retryError);
232
+ }
233
+ }
234
+ this.handleError(error);
235
+ }
236
+ }
237
+ async delete(path, params, retryAttempted = false) {
238
+ await this.ready;
239
+ const url = this.getUrl(path);
240
+ try {
241
+ const response = await this.client.delete(url, { params });
242
+ return response.data;
243
+ }
244
+ catch (error) {
245
+ const status = error?.response?.status ?? error?.statusCode;
246
+ if (status === 401 && !retryAttempted) {
247
+ try {
248
+ await this.authenticate();
249
+ return await this.delete(path, params, true);
250
+ }
251
+ catch (retryError) {
252
+ this.handleError(retryError);
253
+ }
254
+ }
255
+ this.handleError(error);
256
+ }
257
+ }
258
+ async patch(path, data, retryAttempted = false) {
259
+ await this.ready;
260
+ const url = this.getUrl(path);
261
+ try {
262
+ const response = await this.client.patch(url, data);
263
+ return response.data;
264
+ }
265
+ catch (error) {
266
+ const status = error?.response?.status ?? error?.statusCode;
267
+ if (status === 401 && !retryAttempted) {
268
+ try {
269
+ await this.authenticate();
270
+ return await this.patch(path, data, true);
271
+ }
272
+ catch (retryError) {
273
+ this.handleError(retryError);
274
+ }
275
+ }
276
+ this.handleError(error);
277
+ }
278
+ }
279
+ /**
280
+ * Bridge swap operation
281
+ */
282
+ async bridgeSwap(payload) {
283
+ return this.post('api/v1/bridge/swap', payload);
284
+ }
285
+ /**
286
+ * Route transaction operation (SWAP / BRIDGE / CCIP)
287
+ */
288
+ async routeTransaction(payload) {
289
+ // Backend expects amount as a string, but our types use number.
290
+ // Normalize here so callers can continue passing numbers.
291
+ const normalizedPayload = {
292
+ ...payload,
293
+ amount: String(payload.amount),
294
+ };
295
+ return this.post('api/v1/bridge/route-transaction', normalizedPayload);
296
+ }
297
+ /**
298
+ * Cancel a CCIP route transaction
299
+ */
300
+ async cancelCcip(params) {
301
+ const queryParams = {
302
+ operationType: params.operationType,
303
+ fromToken: params.fromToken,
304
+ toToken: params.toToken,
305
+ uuid: params.uuid,
306
+ };
307
+ return this.delete('api/v1/bridge/cancel-ccip', queryParams);
308
+ }
309
+ /**
310
+ * Cancel a router swap transaction
311
+ */
312
+ async cancelRouterSwap(params) {
313
+ const queryParams = {
314
+ uuid: params.uuid,
315
+ };
316
+ return this.delete('api/v1/bridge/cancel-router-swap', queryParams);
317
+ }
318
+ /**
319
+ * Cancel a bridge swap transaction
320
+ */
321
+ async cancelBridgeSwap(params) {
322
+ return this.patch('api/v1/bridge/swap/cancel', params);
323
+ }
324
+ /**
325
+ * Get recent API transactions (tenant-level)
326
+ *
327
+ * Maps to:
328
+ * POST /api/v1/bridge/api-transactions?page={page}&limit={limit}
329
+ */
330
+ async getApiTransactions(page = 1, limit = 20) {
331
+ const path = `api/v1/bridge/api-transactions?page=${encodeURIComponent(String(page))}&limit=${encodeURIComponent(String(limit))}`;
332
+ return this.post(path, undefined);
333
+ }
334
+ /**
335
+ * Manually validate a bridge burn transaction (EVM burn completion)
336
+ */
337
+ async validateBurn(params) {
338
+ return this.post('api/v1/bridge/validate-burn', params);
339
+ }
340
+ /**
341
+ * Manually confirm a CCIP transaction
342
+ */
343
+ async confirmCcipTransaction(params) {
344
+ const path = `api/v1/bridge/transactions/${encodeURIComponent(params.transactionId)}/tx`;
345
+ const body = {
346
+ txnHash: params.txnHash,
347
+ operationType: params.operationType,
348
+ txnStatus: params.txnStatus,
349
+ };
350
+ return this.patch(path, body);
351
+ }
352
+ /**
353
+ * Get list of supported chains
354
+ * @param options - Optional query parameters
355
+ * @param options.isActive - Filter by active status
356
+ * @param options.isCcipSupported - Filter by CCIP support
357
+ * @param options.chainId - Filter by specific chain ID
358
+ */
359
+ async getSupportedChains(options) {
360
+ const params = {};
361
+ if (options?.isActive !== undefined) {
362
+ params.is_active = String(options.isActive);
363
+ }
364
+ if (options?.isCcipSupported !== undefined) {
365
+ params.is_ccip_supported = String(options.isCcipSupported);
366
+ }
367
+ if (options?.chainId !== undefined) {
368
+ params.chain_id = options.chainId;
369
+ }
370
+ const response = await this.get('api/v1/chain/chains', params);
371
+ if (!response.success || !response.data) {
372
+ throw new Error(response.message || 'Failed to fetch chains');
373
+ }
374
+ return response.data.chains;
375
+ }
376
+ /**
377
+ * Get list of supported assets
378
+ */
379
+ async getAssetList(chainId) {
380
+ const response = await this.get('api/v1/asset/assets');
381
+ if (!response.success || !response.data) {
382
+ throw new Error(response.message || 'Failed to fetch assets');
383
+ }
384
+ let assets = response.data.assets;
385
+ // Filter by chainId if provided
386
+ if (chainId !== undefined) {
387
+ assets = assets.filter(asset => asset.chainId === chainId);
388
+ }
389
+ return assets;
390
+ }
391
+ /**
392
+ * Get bridge fee estimate
393
+ * @param params - Fee estimation parameters
394
+ */
395
+ async getBridgeFeeEstimate(params) {
396
+ const queryParams = {
397
+ fromToken: params.fromToken,
398
+ toToken: params.toToken,
399
+ amount: String(params.amount),
400
+ };
401
+ return this.get('api/v1/bridge/call-fee', queryParams);
402
+ }
403
+ /**
404
+ * Get router swap fee estimate
405
+ * @param params - Fee estimation parameters
406
+ */
407
+ async getRouterSwapFeeEstimate(params) {
408
+ const queryParams = {
409
+ originAssetId: String(params.originAssetId),
410
+ destinationAssetId: String(params.destinationAssetId),
411
+ amount: String(params.amount),
412
+ operationType: params.operationType,
413
+ toAddress: params.toAddress,
414
+ };
415
+ return this.get('api/v1/router-swap/call-fee', queryParams);
416
+ }
417
+ /**
418
+ * Normalize and rethrow errors with backend message (if available) so
419
+ * integrators can surface meaningful reasons to their users.
420
+ */
421
+ handleError(error) {
422
+ if (axios_1.default.isAxiosError(error)) {
423
+ const status = error.response?.status;
424
+ const data = error.response?.data;
425
+ // Try to extract a human friendly message from common backend shapes
426
+ let backendMessage = data?.error?.message ||
427
+ data?.message ||
428
+ (typeof data?.error === 'string' ? data.error : undefined) ||
429
+ (data?.success === false && data?.message ? data.message : undefined);
430
+ // For auth failures, avoid leaking raw backend JSON and give a clearer hint.
431
+ if (status === 401 &&
432
+ (backendMessage === 'Unauthorized' ||
433
+ data?.statusCode === 401 ||
434
+ data?.message === 'Unauthorized')) {
435
+ backendMessage =
436
+ 'Session expired or unauthorized. Please verify your API key/secret and try again.';
437
+ }
438
+ const message = backendMessage ||
439
+ error.message ||
440
+ (status ? `Request failed with status ${status}` : 'Request failed');
441
+ throw new Error(message);
442
+ }
443
+ else {
444
+ const message = error?.message || 'Unexpected error';
445
+ throw new Error(message);
446
+ }
447
+ }
448
+ }
449
+ exports.VoidAIBridgeClient = VoidAIBridgeClient;
@@ -0,0 +1,19 @@
1
+ import { BridgeSDKOptions, BridgeSDKServerOptions, Environment } from './types';
2
+ /** Internal: options accepted by BridgeConfig (frontend or server) */
3
+ type BridgeConfigOptions = BridgeSDKOptions | BridgeSDKServerOptions;
4
+ export declare class BridgeConfig {
5
+ readonly apiKey: string;
6
+ readonly secretKey?: string;
7
+ readonly environment: Environment;
8
+ readonly baseUrl: string;
9
+ /**
10
+ * EVM bridge contract address used for burn operations
11
+ * (wTAO/wALPHA -> TAO/ALPHA). Network-specific values can be
12
+ * configured here per environment.
13
+ */
14
+ readonly bridgeContractAddress: string;
15
+ private static readonly DEFAULTS;
16
+ constructor(options: BridgeConfigOptions);
17
+ getBaseUrl(): string;
18
+ }
19
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BridgeConfig = void 0;
4
+ class BridgeConfig {
5
+ constructor(options) {
6
+ if (!options.apiKey) {
7
+ throw new Error('BridgeSDK: apiKey is required');
8
+ }
9
+ this.apiKey = options.apiKey;
10
+ this.secretKey = 'secretKey' in options ? options.secretKey : undefined;
11
+ this.environment = options.environment || 'mainnet';
12
+ // Ensure environment is valid, default to mainnet if invalid
13
+ const validEnvironments = ['devnet', 'testnet', 'mainnet'];
14
+ if (!validEnvironments.includes(this.environment)) {
15
+ this.environment = 'mainnet';
16
+ }
17
+ const defaults = BridgeConfig.DEFAULTS[this.environment];
18
+ // Base URL & bridge contract address are derived from environment;
19
+ // consumers should not override these at runtime.
20
+ this.baseUrl = defaults.baseUrl;
21
+ this.bridgeContractAddress = defaults.bridgeContractAddress;
22
+ }
23
+ getBaseUrl() {
24
+ return this.baseUrl;
25
+ }
26
+ }
27
+ exports.BridgeConfig = BridgeConfig;
28
+ BridgeConfig.DEFAULTS = {
29
+ devnet: {
30
+ baseUrl: 'https://api-sdk-dev.voidai.envistudios.com/',
31
+ bridgeContractAddress: '0x6266ce15aC4f32F096Ff91881dd887a0F4bBa569',
32
+ },
33
+ testnet: {
34
+ baseUrl: 'https://api-sdk-stage.voidai.envistudios.com/',
35
+ bridgeContractAddress: '0x4aA4396BfD6F268b427077079800F420dF947b63',
36
+ },
37
+ mainnet: {
38
+ baseUrl: 'https://api-sdk.voidai.envistudios.com/',
39
+ bridgeContractAddress: '0x604e8Ef901C0E69E79463D997ba7D0724A909b84',
40
+ },
41
+ };
@@ -0,0 +1,70 @@
1
+ import { VoidAIBridgeClient } from '../api/client';
2
+ import { BridgeSwapRequest, BridgeSwapResponse, RouteSwapRequest, RouteBridgeRequest, RouteCcipRequest, RouteTransactionResponse, BridgeFeeEstimateRequest, BridgeFeeEstimateResponse, RouterSwapFeeEstimateRequest, RouterSwapFeeEstimateResponse, CancelCcipRequest, CancelRouterSwapRequest, CancelBridgeSwapRequest, CancelOperationResponse, ApiTransactionsResponse, ValidateBurnRequest, ValidateBurnResponse, CcipTxConfirmationRequest, CcipTxConfirmationResponse } from '../types';
3
+ /**
4
+ * Bridge API Methods
5
+ * Provides read-only API methods for bridge operations
6
+ */
7
+ export declare class Bridge {
8
+ private apiClient;
9
+ constructor(apiClient: VoidAIBridgeClient);
10
+ /**
11
+ * Get transaction status
12
+ */
13
+ getTransactionStatus(txId: string): Promise<string>;
14
+ /**
15
+ * Get transaction history for an address
16
+ */
17
+ getHistory(address: string): Promise<any[]>;
18
+ /**
19
+ * Initiate a bridge operation (formerly swap).
20
+ */
21
+ bridge(payload: BridgeSwapRequest): Promise<BridgeSwapResponse>;
22
+ /**
23
+ * Alias for backward compatibility.
24
+ */
25
+ swap(payload: BridgeSwapRequest): Promise<BridgeSwapResponse>;
26
+ /**
27
+ * Route a SWAP operation through the unified route-transaction API.
28
+ */
29
+ routeSwap(payload: RouteSwapRequest): Promise<RouteTransactionResponse>;
30
+ /**
31
+ * Route a BRIDGE operation through the unified route-transaction API.
32
+ */
33
+ routeBridge(payload: RouteBridgeRequest): Promise<RouteTransactionResponse>;
34
+ /**
35
+ * Route a CCIP operation through the unified route-transaction API.
36
+ */
37
+ routeCcip(payload: RouteCcipRequest): Promise<RouteTransactionResponse>;
38
+ /**
39
+ * Get bridge fee estimate
40
+ */
41
+ getBridgeFeeEstimate(params: BridgeFeeEstimateRequest): Promise<BridgeFeeEstimateResponse>;
42
+ /**
43
+ * Get router swap fee estimate
44
+ */
45
+ getRouterSwapFeeEstimate(params: RouterSwapFeeEstimateRequest): Promise<RouterSwapFeeEstimateResponse>;
46
+ /**
47
+ * Cancel a CCIP route transaction
48
+ */
49
+ cancelCcipRoute(params: CancelCcipRequest): Promise<CancelOperationResponse>;
50
+ /**
51
+ * Cancel a router swap transaction
52
+ */
53
+ cancelRouterSwap(params: CancelRouterSwapRequest): Promise<CancelOperationResponse>;
54
+ /**
55
+ * Cancel a bridge swap transaction
56
+ */
57
+ cancelBridgeSwap(params: CancelBridgeSwapRequest): Promise<CancelOperationResponse>;
58
+ /**
59
+ * Get recent API transactions for the current tenant
60
+ */
61
+ getRecentTransactions(page?: number, limit?: number): Promise<ApiTransactionsResponse>;
62
+ /**
63
+ * Manually validate a bridge burn transaction
64
+ */
65
+ validateBurnTransaction(params: ValidateBurnRequest): Promise<ValidateBurnResponse>;
66
+ /**
67
+ * Manually confirm a CCIP transaction
68
+ */
69
+ confirmCcipTransaction(params: CcipTxConfirmationRequest): Promise<CcipTxConfirmationResponse>;
70
+ }