@fystack/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,962 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ var fetch = require('cross-fetch');
4
+ var CryptoJS = require('crypto-js');
5
+ var ethers = require('ethers');
6
+ var web3_js = require('@solana/web3.js');
7
+ var bs58 = require('bs58');
8
+ var buffer = require('buffer');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var fetch__default = /*#__PURE__*/_interopDefault(fetch);
13
+ var CryptoJS__default = /*#__PURE__*/_interopDefault(CryptoJS);
14
+ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
15
+
16
+ exports.Environment = void 0;
17
+ (function(Environment) {
18
+ Environment["Local"] = "local";
19
+ Environment["Sandbox"] = "sandbox";
20
+ Environment["Production"] = "production";
21
+ })(exports.Environment || (exports.Environment = {}));
22
+ const getBaseURL = (env)=>{
23
+ switch(env){
24
+ case "local":
25
+ return 'http://localhost:8150';
26
+ case "sandbox":
27
+ return 'https://apex.void.exchange';
28
+ case "production":
29
+ return 'https://api.fystack.io';
30
+ }
31
+ };
32
+ const createAPI = (env)=>{
33
+ const baseURL = `${getBaseURL(env)}/api/v1`;
34
+ const withBaseURL = (path)=>`${baseURL}${path}`;
35
+ return {
36
+ baseURL,
37
+ endpoints: {
38
+ signTransaction: (walletId)=>withBaseURL(`/web3/transaction/${walletId}/signRaw`),
39
+ getWalletDetail: (walletId)=>walletId ? withBaseURL(`/web3/wallet-detail/${walletId}`) : withBaseURL('/web3/wallet-detail'),
40
+ createWallet: ()=>withBaseURL('/wallets'),
41
+ createCheckout: ()=>withBaseURL('/checkouts'),
42
+ getCheckout: (checkoutId)=>withBaseURL(`/checkouts/${checkoutId}`),
43
+ createCheckoutPayment: (checkoutId)=>withBaseURL(`/checkouts/${checkoutId}/payment`),
44
+ getCheckoutPayment: (checkoutId)=>withBaseURL(`/checkouts/payment/${checkoutId}`),
45
+ // New endpoints for signing and transaction status
46
+ requestSign: (walletId)=>withBaseURL(`/web3/${walletId}/sign`),
47
+ getSignStatus: (walletId, transactionId)=>withBaseURL(`/web3/${walletId}/sign/${transactionId}`),
48
+ getTransactionStatus: (walletId, transactionId)=>withBaseURL(`/web3/transaction/${walletId}/${transactionId}`),
49
+ getWalletCreationStatus: (walletId)=>withBaseURL(`/wallets/creation-status/${walletId}`),
50
+ getWalletAssets: (walletId)=>withBaseURL(`/wallets/${walletId}/assets`),
51
+ getDepositAddress: (walletId, addressType)=>withBaseURL(`/wallets/${walletId}/deposit-address?address_type=${addressType}`)
52
+ }
53
+ };
54
+ };
55
+
56
+ async function computeHMAC(apiSecret, params) {
57
+ const encodedParams = Object.entries(params).map(([key, value])=>`${key}=${value}`).join('&');
58
+ const digest = CryptoJS__default.default.HmacSHA256(encodedParams, apiSecret);
59
+ return digest.toString(CryptoJS__default.default.enc.Hex);
60
+ }
61
+ async function computeHMACForWebhook(apiSecret, event) {
62
+ const eventStr = canonicalizeJSON(event);
63
+ console.log('eventStr', eventStr);
64
+ const digest = CryptoJS__default.default.HmacSHA256(eventStr, apiSecret);
65
+ return digest.toString(CryptoJS__default.default.enc.Hex);
66
+ }
67
+ /**
68
+ * Canonicalizes a TypeScript object by sorting its keys recursively.
69
+ *
70
+ * @param inputObject - The input object to canonicalize.
71
+ * @returns A canonicalized JSON string with sorted keys.
72
+ * @throws Error if the input is not a valid object.
73
+ */ function canonicalizeJSON(inputObject) {
74
+ if (typeof inputObject !== 'object' || inputObject === null) {
75
+ throw new Error('Input must be a non-null object.');
76
+ }
77
+ /**
78
+ * Recursively sorts the keys of an object or processes arrays.
79
+ *
80
+ * @param value - The value to sort (can be an object, array, or primitive).
81
+ * @returns The sorted value.
82
+ */ const sortKeys = (value)=>{
83
+ if (Array.isArray(value)) {
84
+ // Recursively sort each element in the array
85
+ return value.map(sortKeys);
86
+ }
87
+ if (value && typeof value === 'object' && value.constructor === Object) {
88
+ // Sort object keys and recursively sort their values
89
+ return Object.keys(value).sort().reduce((sortedObj, key)=>{
90
+ sortedObj[key] = sortKeys(value[key]);
91
+ return sortedObj;
92
+ }, {});
93
+ }
94
+ // Return primitive values as-is
95
+ return value;
96
+ };
97
+ // Sort the keys recursively
98
+ const sortedObject = sortKeys(inputObject);
99
+ // Convert the sorted object back into a JSON string
100
+ return JSON.stringify(sortedObject);
101
+ }
102
+ /**
103
+ * Validates if a string is a valid UUID v4
104
+ * @param uuid The string to validate
105
+ * @returns boolean indicating if the string is a valid UUID
106
+ */ function isValidUUID(uuid) {
107
+ if (!uuid || typeof uuid !== 'string') {
108
+ return false;
109
+ }
110
+ // UUID v4 pattern:
111
+ // 8-4-4-4-12 where third group starts with 4 and fourth group starts with 8, 9, a, or b
112
+ const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
113
+ return uuidV4Regex.test(uuid);
114
+ }
115
+ /**
116
+ * Validates if a string is a valid UUID and throws an error if not
117
+ * @param uuid The string to validate
118
+ * @param paramName The name of the parameter being validated (for error message)
119
+ * @throws Error if the UUID is invalid
120
+ */ function validateUUID(uuid, paramName) {
121
+ if (!uuid || typeof uuid !== 'string') {
122
+ throw new Error(`${paramName} is required and must be a string`);
123
+ }
124
+ if (!isValidUUID(uuid)) {
125
+ throw new Error(`Invalid ${paramName} format. Must be a valid UUID v4`);
126
+ }
127
+ }
128
+
129
+ exports.WalletAddressType = void 0;
130
+ (function(WalletAddressType) {
131
+ WalletAddressType["Evm"] = "evm";
132
+ WalletAddressType["Sol"] = "sol";
133
+ })(exports.WalletAddressType || (exports.WalletAddressType = {}));
134
+ async function composeAPIHeaders(credentials, httpMethod, apiEndpoint, body = {}) {
135
+ if (credentials.apiSecret == '') {
136
+ // If APISecret is not provided, fallback to cookie mode with no headers
137
+ return {};
138
+ }
139
+ const currentTimestampInSeconds = Math.floor(Date.now() / 1000);
140
+ const url = new URL(apiEndpoint);
141
+ const path = url.pathname // Extract the path
142
+ ;
143
+ const params = {
144
+ method: httpMethod,
145
+ path,
146
+ timestamp: String(currentTimestampInSeconds),
147
+ body: Object.keys(body).length ? JSON.stringify(body) : ''
148
+ };
149
+ const digest = await computeHMAC(credentials.apiSecret, params);
150
+ const headers = {
151
+ 'ACCESS-API-KEY': credentials.apiKey,
152
+ 'ACCESS-TIMESTAMP': String(currentTimestampInSeconds),
153
+ 'ACCESS-SIGN': btoa(digest) // convert to base64
154
+ };
155
+ return headers;
156
+ }
157
+ class APIService {
158
+ async getWalletDetail(addressType = "evm", walletId) {
159
+ const endpoint = this.API.endpoints.getWalletDetail(walletId);
160
+ const headers = await composeAPIHeaders(this.credentials, 'GET', endpoint);
161
+ console.info('headers', headers);
162
+ const resp = await get(endpoint + `?address_type=${addressType}`, headers);
163
+ return transformWalletDetail(resp.data);
164
+ }
165
+ async requestSign(walletId, params) {
166
+ const endpoint = this.API.endpoints.requestSign(walletId);
167
+ const headers = await composeAPIHeaders(this.credentials, 'POST', endpoint, params);
168
+ const response = await post(endpoint, params, headers);
169
+ return response.data;
170
+ }
171
+ async getSignStatus(walletId, transactionId) {
172
+ const endpoint = this.API.endpoints.getSignStatus(walletId, transactionId);
173
+ const headers = await composeAPIHeaders(this.credentials, 'GET', endpoint);
174
+ const response = await get(endpoint, headers);
175
+ return response.data;
176
+ }
177
+ async signTransaction(walletId, body) {
178
+ const startTime = Date.now();
179
+ const endpoint = this.API.endpoints.signTransaction(walletId);
180
+ const headers = await composeAPIHeaders(this.credentials, 'POST', endpoint, body);
181
+ const response = await post(endpoint, body, headers);
182
+ const elapsedTime = Date.now() - startTime;
183
+ console.log(`[WalletSDK] Sign transaction completed in ${elapsedTime}ms`);
184
+ console.log('[WalletSDK] Sign transaction response:', response);
185
+ return response.data;
186
+ }
187
+ async getTransactionStatus(walletId, transactionId) {
188
+ const endpoint = this.API.endpoints.getTransactionStatus(walletId, transactionId);
189
+ const headers = await composeAPIHeaders(this.credentials, 'GET', endpoint);
190
+ const response = await get(endpoint, headers);
191
+ return response.data;
192
+ }
193
+ async createWallet(payload) {
194
+ const endpoint = this.API.endpoints.createWallet();
195
+ const transformedPayload = transformCreateWalletPayload(payload);
196
+ const headers = await composeAPIHeaders(this.credentials, 'POST', endpoint, transformedPayload);
197
+ const resp = await post(endpoint, transformedPayload, headers);
198
+ return resp.data;
199
+ }
200
+ async createCheckout(payload) {
201
+ const endpoint = this.API.endpoints.createCheckout();
202
+ const headers = await composeAPIHeaders(this.credentials, 'POST', endpoint, payload);
203
+ const response = await post(endpoint, payload, headers);
204
+ return response.data;
205
+ }
206
+ async getWalletCreationStatus(walletId) {
207
+ const endpoint = this.API.endpoints.getWalletCreationStatus(walletId);
208
+ const headers = await composeAPIHeaders(this.credentials, 'GET', endpoint);
209
+ const response = await get(endpoint, headers);
210
+ return response.data;
211
+ }
212
+ async getWalletAssets(walletId) {
213
+ const endpoint = this.API.endpoints.getWalletAssets(walletId);
214
+ const headers = await composeAPIHeaders(this.credentials, 'GET', endpoint);
215
+ const response = await get(endpoint, headers);
216
+ return response.data;
217
+ }
218
+ /**
219
+ * Gets deposit address for a wallet by address type
220
+ * @param walletId The wallet ID
221
+ * @param addressType The type of address (evm, sol)
222
+ * @returns Deposit address response
223
+ */ async getDepositAddress(walletId, addressType) {
224
+ const endpoint = this.API.endpoints.getDepositAddress(walletId, addressType);
225
+ const headers = await composeAPIHeaders(this.credentials, 'GET', endpoint);
226
+ const response = await get(endpoint, headers);
227
+ return response.data;
228
+ }
229
+ constructor(credentials, environment){
230
+ this.credentials = credentials;
231
+ this.Webhook = new WebhookService(credentials);
232
+ this.API = createAPI(environment);
233
+ }
234
+ }
235
+ class PaymentService {
236
+ async createCheckout(payload) {
237
+ const endpoint = this.API.endpoints.createCheckout();
238
+ const headers = {
239
+ 'ACCESS-API-KEY': this.apiKey
240
+ };
241
+ const response = await post(endpoint, payload, headers);
242
+ return response.data;
243
+ }
244
+ async getCheckout(checkoutId) {
245
+ const endpoint = this.API.endpoints.getCheckout(checkoutId);
246
+ const response = await get(endpoint);
247
+ return response.data;
248
+ }
249
+ async createCheckoutPayment(checkoutId, payload) {
250
+ const endpoint = this.API.endpoints.createCheckoutPayment(checkoutId);
251
+ const response = await post(endpoint, payload);
252
+ return response.data;
253
+ }
254
+ async getCheckoutPayment(checkoutId) {
255
+ const endpoint = this.API.endpoints.getCheckoutPayment(checkoutId);
256
+ const response = await get(endpoint);
257
+ return response.data;
258
+ }
259
+ constructor(params){
260
+ this.apiKey = params.apiKey;
261
+ this.API = createAPI(params.environment);
262
+ }
263
+ }
264
+ class WebhookService {
265
+ // Implement verify webhook here
266
+ async verifyEvent(event, signature) {
267
+ // Recompute HMAC
268
+ const computedHMAC = await computeHMACForWebhook(this.credentials.apiSecret, event);
269
+ const isValid = signature === computedHMAC;
270
+ return isValid;
271
+ }
272
+ constructor(credentials){
273
+ this.credentials = credentials;
274
+ }
275
+ }
276
+ // Simplified GET request function
277
+ async function get(endpoint, headers = {}) {
278
+ const request = {
279
+ method: 'GET',
280
+ headers: {
281
+ ...headers
282
+ }
283
+ };
284
+ // Only include credentials if we're in cookie mode (no API key in headers)
285
+ if (!headers['ACCESS-API-KEY']) {
286
+ request.credentials = 'include';
287
+ }
288
+ const response = await fetch__default.default(`${endpoint}`, request);
289
+ const data = await response.json();
290
+ if (!response.ok || !data.success) {
291
+ console.error('Status Code', response.status);
292
+ throw new Error(data.message || 'Failed to fetch data');
293
+ }
294
+ return data;
295
+ }
296
+ // Simplified POST request function
297
+ async function post(endpoint, body, headers = {}) {
298
+ const request = {
299
+ method: 'POST',
300
+ headers: {
301
+ 'Content-Type': 'application/json',
302
+ ...headers
303
+ },
304
+ body: JSON.stringify(body)
305
+ };
306
+ // Only include credentials if we're in cookie mode (no API key in headers)
307
+ if (!headers['ACCESS-API-KEY']) {
308
+ request.credentials = 'include';
309
+ }
310
+ const response = await fetch__default.default(`${endpoint}`, request);
311
+ const data = await response.json();
312
+ if (!response.ok || !data.success) {
313
+ console.error('Status Code', response.status);
314
+ throw new Error(data.message || 'Failed to fetch data');
315
+ }
316
+ return data;
317
+ }
318
+ function transformWalletDetail(data) {
319
+ return {
320
+ APIKey: data?.ap_key,
321
+ WalletID: data?.wallet_id,
322
+ Name: data?.name,
323
+ AddressType: data?.address_type,
324
+ Address: data?.address
325
+ };
326
+ }
327
+ function transformCreateWalletPayload(data) {
328
+ return {
329
+ name: data.name,
330
+ wallet_type: data.walletType
331
+ };
332
+ }
333
+ BigInt.prototype.toJSON = function() {
334
+ return this.toString();
335
+ };
336
+
337
+ const DEFAULT_POLLER_OPTIONS = {
338
+ maxAttempts: 10,
339
+ interval: 1000,
340
+ backoffFactor: 1.5,
341
+ maxInterval: 10000,
342
+ timeoutMs: 10 * 60 * 1000 // 10 minutes totla
343
+ };
344
+ class StatusPoller {
345
+ async wait() {
346
+ await new Promise((resolve)=>setTimeout(resolve, this.currentInterval));
347
+ // Implement exponential backoff
348
+ this.currentInterval = Math.min(this.currentInterval * this.options.backoffFactor, this.options.maxInterval);
349
+ }
350
+ shouldContinue() {
351
+ const timeElapsed = Date.now() - this.startTime;
352
+ if (timeElapsed >= this.options.timeoutMs) {
353
+ throw new Error(`Status polling timed out after ${timeElapsed}ms`);
354
+ }
355
+ if (this.attempts >= this.options.maxAttempts) {
356
+ throw new Error(`Maximum polling attempts (${this.options.maxAttempts}) exceeded`);
357
+ }
358
+ return true;
359
+ }
360
+ async poll(pollingFn, successCondition, errorCondition) {
361
+ while(this.shouldContinue()){
362
+ this.attempts++;
363
+ const result = await pollingFn();
364
+ // Check for error condition first
365
+ if (errorCondition) {
366
+ const shouldError = errorCondition(result);
367
+ if (shouldError) {
368
+ throw new Error('Status polling failed');
369
+ }
370
+ }
371
+ // Check for success condition
372
+ if (successCondition(result)) {
373
+ return result;
374
+ }
375
+ // If neither condition is met, wait and continue polling
376
+ await this.wait();
377
+ }
378
+ throw new Error('Polling ended without meeting success condition');
379
+ }
380
+ constructor(options = {}){
381
+ this.options = {
382
+ ...DEFAULT_POLLER_OPTIONS,
383
+ ...options
384
+ };
385
+ this.startTime = Date.now();
386
+ this.attempts = 0;
387
+ this.currentInterval = this.options.interval;
388
+ }
389
+ }
390
+
391
+ class TransactionError extends Error {
392
+ constructor(message, code, transactionId, originalError){
393
+ super(message);
394
+ this.code = code;
395
+ this.transactionId = transactionId;
396
+ this.originalError = originalError;
397
+ this.name = 'TransactionError';
398
+ }
399
+ }
400
+ exports.TxStatus = void 0;
401
+ (function(TxStatus) {
402
+ TxStatus["Pending"] = "pending";
403
+ TxStatus["Completed"] = "completed";
404
+ TxStatus["Confirmed"] = "confirmed";
405
+ TxStatus["Failed"] = "failed";
406
+ TxStatus["PendingApproval"] = "pending_approval";
407
+ TxStatus["Rejected"] = "rejected";
408
+ })(exports.TxStatus || (exports.TxStatus = {}));
409
+ exports.TxApprovalStatus = void 0;
410
+ (function(TxApprovalStatus) {
411
+ TxApprovalStatus["Pending"] = "pending";
412
+ TxApprovalStatus["Approved"] = "approved";
413
+ TxApprovalStatus["Rejected"] = "rejected";
414
+ })(exports.TxApprovalStatus || (exports.TxApprovalStatus = {}));
415
+ exports.WalletType = void 0;
416
+ (function(WalletType) {
417
+ WalletType["Standard"] = "standard";
418
+ WalletType["MPC"] = "mpc";
419
+ })(exports.WalletType || (exports.WalletType = {}));
420
+ exports.WalletCreationStatus = void 0;
421
+ (function(WalletCreationStatus) {
422
+ WalletCreationStatus["Pending"] = "pending";
423
+ WalletCreationStatus["Success"] = "success";
424
+ WalletCreationStatus["Error"] = "error";
425
+ })(exports.WalletCreationStatus || (exports.WalletCreationStatus = {}));
426
+ exports.AddressType = void 0;
427
+ (function(AddressType) {
428
+ AddressType["Evm"] = "evm";
429
+ AddressType["Solana"] = "sol";
430
+ })(exports.AddressType || (exports.AddressType = {}));
431
+
432
+ class FystackSDK {
433
+ log(message) {
434
+ if (this.enableLogging) {
435
+ console.log(`[FystackSDK] ${message}`);
436
+ }
437
+ }
438
+ /**
439
+ * Creates a new wallet
440
+ * @param options Wallet creation options
441
+ * @param waitForCompletion Whether to wait for the wallet creation to complete
442
+ * @returns Promise with wallet ID and status
443
+ */ async createWallet(options, waitForCompletion = true) {
444
+ const { name, walletType = exports.WalletType.Standard } = options;
445
+ const response = await this.apiService.createWallet({
446
+ name,
447
+ walletType
448
+ });
449
+ if (waitForCompletion && response.status === exports.WalletCreationStatus.Pending) {
450
+ return this.waitForWalletCreation(response.wallet_id);
451
+ }
452
+ return response;
453
+ }
454
+ /**
455
+ * Gets the current status of a wallet creation process
456
+ * @param walletId The ID of the wallet being created
457
+ * @returns Promise with wallet creation status details
458
+ */ async getWalletCreationStatus(walletId) {
459
+ const response = await this.apiService.getWalletCreationStatus(walletId);
460
+ return response;
461
+ }
462
+ /**
463
+ * Waits for a wallet to be created and returns the final status
464
+ * @param walletId The ID of the wallet being created
465
+ * @returns Promise with wallet ID and final status
466
+ */ async waitForWalletCreation(walletId) {
467
+ const poller = new StatusPoller();
468
+ // Poll for wallet creation status
469
+ const result = await poller.poll(// Polling function
470
+ async ()=>{
471
+ this.log('Polling wallet creation status...');
472
+ const response = await this.apiService.getWalletCreationStatus(walletId);
473
+ return response;
474
+ }, // Success condition - when status is either success or error
475
+ (result)=>[
476
+ exports.WalletCreationStatus.Success,
477
+ exports.WalletCreationStatus.Error
478
+ ].includes(result.status), // Error condition - no specific error condition needed, as we're polling until final state
479
+ undefined);
480
+ this.log(`Wallet creation completed with status: ${result.status}`);
481
+ return {
482
+ wallet_id: result.wallet_id,
483
+ status: result.status
484
+ };
485
+ }
486
+ /**
487
+ * Gets assets associated with a wallet
488
+ * @param walletId The ID of the wallet
489
+ * @returns Promise with wallet assets
490
+ */ async getWalletAssets(walletId) {
491
+ if (!walletId || walletId.trim() === '') {
492
+ throw new Error('Invalid wallet ID provided');
493
+ }
494
+ const data = await this.apiService.getWalletAssets(walletId);
495
+ return data;
496
+ }
497
+ /**
498
+ * Gets deposit address for a wallet by address type
499
+ * @param walletId The wallet ID
500
+ * @param addressType The type of address (evm, sol)
501
+ * @returns Promise with deposit address information
502
+ */ async getDepositAddress(walletId, addressType) {
503
+ validateUUID(walletId, 'walletId');
504
+ if (!Object.values(exports.AddressType).includes(addressType)) {
505
+ throw new Error(`Invalid address type: ${addressType}. Must be one of: ${Object.values(exports.AddressType).join(', ')}`);
506
+ }
507
+ const depositAddressInfo = await this.apiService.getDepositAddress(walletId, addressType);
508
+ return depositAddressInfo;
509
+ }
510
+ constructor(options){
511
+ const { credentials, environment = exports.Environment.Production, logger = false } = options;
512
+ this.apiService = new APIService(credentials, environment);
513
+ this.enableLogging = logger;
514
+ }
515
+ }
516
+
517
+ class ApexSigner extends ethers.AbstractSigner {
518
+ setWallet(walletId) {
519
+ if (!walletId || walletId.trim() === '') {
520
+ throw new Error('Invalid wallet ID provided');
521
+ }
522
+ // Set walletId in walletDetail with default values for required properties
523
+ this.walletDetail = {
524
+ WalletID: walletId,
525
+ APIKey: '',
526
+ Name: '',
527
+ AddressType: '',
528
+ Address: ''
529
+ };
530
+ // Reset address cache since we're changing wallets
531
+ this.address = '';
532
+ }
533
+ async getAddress() {
534
+ if (this.address) {
535
+ return this.address;
536
+ }
537
+ if (!this.APICredentials.apiKey && !this.walletDetail.WalletID) {
538
+ throw new Error('Wallet detail not found, use setWallet(walletId) to set wallet first!');
539
+ }
540
+ const detail = await this.APIService.getWalletDetail(exports.WalletAddressType.Evm, this.walletDetail?.WalletID);
541
+ this.walletDetail = detail;
542
+ if (detail?.Address) {
543
+ // cache the address
544
+ this.address = detail.Address;
545
+ }
546
+ if (!this.address) {
547
+ throw new Error('Address not found');
548
+ }
549
+ return this.address;
550
+ }
551
+ async getChainId() {
552
+ if (!this.provider) {
553
+ throw new Error('Provider is required for signing operations');
554
+ }
555
+ try {
556
+ const network = await this.provider.getNetwork();
557
+ return Number(network.chainId);
558
+ } catch (error) {
559
+ throw new Error('Failed to get chainId from provider: ' + error);
560
+ }
561
+ }
562
+ connect(provider) {
563
+ return new ApexSigner(this.APICredentials, this.environment, provider);
564
+ }
565
+ async waitForSignature(walletId, transactionId) {
566
+ const poller = new StatusPoller(this.pollerOptions);
567
+ const status = await poller.poll(// Polling function
568
+ ()=>this.APIService.getSignStatus(walletId, transactionId), // Success condition
569
+ (result)=>result.status === exports.TxStatus.Confirmed && result.signature != null, // Error condition
570
+ (result)=>[
571
+ exports.TxStatus.Failed,
572
+ exports.TxStatus.Rejected
573
+ ].includes(result.status));
574
+ if (!status.signature) {
575
+ throw new Error('Signature not found in successful response');
576
+ }
577
+ return status.signature;
578
+ }
579
+ async waitForTransactonStatus(transactionId) {
580
+ const poller = new StatusPoller(this.pollerOptions);
581
+ // Poll for transaction status using signaturePoller
582
+ const result = await poller.poll(// Polling function
583
+ ()=>{
584
+ console.log('Polling status');
585
+ return this.APIService.getTransactionStatus(this.walletDetail.WalletID, transactionId);
586
+ }, // Success condition
587
+ (result)=>(result.status === exports.TxStatus.Confirmed || result.status === exports.TxStatus.Completed) && !!result.hash, // Error condition
588
+ (result)=>{
589
+ if (result.status === exports.TxStatus.Failed) {
590
+ throw new TransactionError(result.failed_reason || 'Transaction failed', 'TRANSACTION_FAILED', result.transaction_id);
591
+ }
592
+ return result.status === exports.TxStatus.Rejected;
593
+ });
594
+ console.log('reulst', result);
595
+ if (!result.hash) {
596
+ throw new TransactionError('Transaction hash not found in successful response', 'TRANSACTION_HASH_MISSING', result.transaction_id);
597
+ }
598
+ return result.hash;
599
+ }
600
+ // Copied and editted from ethers.js -> Wallet -> BaseWallet
601
+ async signTransaction(tx) {
602
+ const startTime = new Date();
603
+ console.log(`[WalletSDK] Transaction started at: ${startTime.toLocaleString()}`);
604
+ // Replace any Addressable or ENS name with an address
605
+ const { to, from } = await ethers.resolveProperties({
606
+ to: tx.to ? ethers.resolveAddress(tx.to, this.provider) : undefined,
607
+ from: tx.from ? ethers.resolveAddress(tx.from, this.provider) : undefined
608
+ });
609
+ if (to != null) {
610
+ tx.to = to;
611
+ }
612
+ if (from != null) {
613
+ tx.from = from;
614
+ }
615
+ if (tx.from != null) {
616
+ ethers.assertArgument(ethers.getAddress(tx.from) === this.address, 'transaction from address mismatch', 'tx.from', tx.from);
617
+ delete tx.from;
618
+ }
619
+ const fromAddress = this.address;
620
+ // Build the transaction
621
+ const btx = ethers.Transaction.from(tx);
622
+ const data = {
623
+ maxFeePerGas: btx.maxFeePerGas,
624
+ maxPriorityFeePerGas: btx.maxPriorityFeePerGas,
625
+ to: btx.to,
626
+ from: fromAddress,
627
+ nonce: btx.nonce,
628
+ gasLimit: btx.gasLimit,
629
+ data: btx.data,
630
+ value: btx.value,
631
+ chainId: btx.chainId,
632
+ accessList: btx.accessList
633
+ };
634
+ // return unseralized as API signTransaction is an asynchoronous action
635
+ const response = await this.APIService.signTransaction(this.walletDetail.WalletID, data);
636
+ const txHash = await this.waitForTransactonStatus(response.transaction_id);
637
+ const endTime = new Date();
638
+ const elapsedTimeMs = endTime.getTime() - startTime.getTime();
639
+ console.log(`[WalletSDK] Transaction completed at: ${endTime.toLocaleString()}`);
640
+ console.log(`[WalletSDK] Transaction took ${elapsedTimeMs}ms (${(elapsedTimeMs / 1000).toFixed(2)}s)`);
641
+ console.log('[WalletSDK] Transaction succeed!');
642
+ return txHash;
643
+ }
644
+ async sendTransaction(tx) {
645
+ const startTime = new Date();
646
+ console.log(`[WalletSDK] sendTransaction started at: ${startTime.toLocaleString()}`);
647
+ if (!this.address) {
648
+ await this.getAddress();
649
+ }
650
+ checkProvider(this, 'sendTransaction');
651
+ // Only populate if gas fees are not set
652
+ const hasGasFees = tx.gasPrice != null || tx.maxFeePerGas != null && tx.maxPriorityFeePerGas != null;
653
+ let populatedTx = tx;
654
+ if (!hasGasFees) {
655
+ const populateStartTime = new Date();
656
+ console.log(`[WalletSDK] populateTransaction started at: ${populateStartTime.toLocaleString()}`);
657
+ populatedTx = await this.populateTransaction(tx);
658
+ const populateEndTime = new Date();
659
+ const populateElapsedMs = populateEndTime.getTime() - populateStartTime.getTime();
660
+ console.log(`[WalletSDK] populateTransaction completed in ${(populateElapsedMs / 1000).toFixed(2)}s`);
661
+ } else {
662
+ console.log(`[WalletSDK] Skipping transaction population as gas fees are already set`);
663
+ }
664
+ delete populatedTx.from;
665
+ // Ensure all properties are properly resolved to their string representations
666
+ const resolvedTx = await ethers.resolveProperties(populatedTx);
667
+ const txObj = ethers.Transaction.from(resolvedTx);
668
+ console.log('txObj', txObj);
669
+ const txHash = await this.signTransaction(txObj);
670
+ // Instead of creating a mock response, get the actual transaction from the provider
671
+ const endTime = new Date();
672
+ const totalElapsedMs = endTime.getTime() - startTime.getTime();
673
+ console.log(`[WalletSDK] sendTransaction completed at: ${endTime.toLocaleString()}`);
674
+ console.log(`[WalletSDK] sendTransaction took ${(totalElapsedMs / 1000).toFixed(2)}s`);
675
+ console.log('[WalletSDK] Transaction sent successfully!');
676
+ const txResponse = {
677
+ blockNumber: 0,
678
+ blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
679
+ hash: txHash,
680
+ index: 0,
681
+ type: 0,
682
+ to: tx.to,
683
+ from: this.address,
684
+ /**
685
+ *
686
+ */ nonce: txObj.nonce,
687
+ /**
688
+ * The maximum amount of gas this transaction is authorized to consume.
689
+ */ gasLimit: txObj.gasLimit,
690
+ /**
691
+ * For legacy transactions, this is the gas price per gas to pay.
692
+ */ gasPrice: txObj.gasPrice ? txObj.gasPrice : BigInt(0),
693
+ /**
694
+ * For [[link-eip-1559]] transactions, this is the maximum priority
695
+ * fee to allow a producer to claim.
696
+ */ maxPriorityFeePerGas: txObj.maxPriorityFeePerGas,
697
+ /**
698
+ * For [[link-eip-1559]] transactions, this is the maximum fee that
699
+ * will be paid.
700
+ */ maxFeePerGas: txObj.maxFeePerGas,
701
+ /**
702
+ * The transaction data.
703
+ */ data: txObj.data,
704
+ /**
705
+ * The transaction value (in wei).
706
+ */ value: txObj.value,
707
+ /**
708
+ * The chain ID this transaction is valid on.
709
+ */ chainId: txObj.chainId,
710
+ signature: ethers.Signature.from('0x' + '0'.repeat(130)),
711
+ /**
712
+ * The transaction access list.
713
+ */ accessList: txObj.accessList
714
+ };
715
+ // Let the provider create the TransactionResponse using the txHash
716
+ return new ethers.TransactionResponse(txResponse, this.provider);
717
+ }
718
+ async signMessage(message) {
719
+ if (!this.provider) {
720
+ throw new Error('Provider is required for signing operations');
721
+ }
722
+ if (!this.address) {
723
+ await this.getAddress();
724
+ }
725
+ if (!this.walletDetail) {
726
+ this.walletDetail = await this.APIService.getWalletDetail();
727
+ }
728
+ const chainId = await this.getChainId();
729
+ const messageStr = typeof message === 'string' ? message : Buffer.from(message).toString('hex');
730
+ const response = await this.APIService.requestSign(this.walletDetail.WalletID, {
731
+ method: 'eth_sign',
732
+ message: messageStr,
733
+ chain_id: chainId
734
+ });
735
+ return this.waitForSignature(this.walletDetail.WalletID, response.transaction_id);
736
+ }
737
+ async signTypedData(domain, types, value) {
738
+ if (!this.provider) {
739
+ throw new Error('Provider is required for signing operations');
740
+ }
741
+ if (!this.address) {
742
+ await this.getAddress();
743
+ }
744
+ if (!this.walletDetail) {
745
+ this.walletDetail = await this.APIService.getWalletDetail();
746
+ }
747
+ const chainId = await this.getChainId();
748
+ const typedData = JSON.stringify({
749
+ domain,
750
+ types,
751
+ message: value
752
+ });
753
+ const response = await this.APIService.requestSign(this.walletDetail.WalletID, {
754
+ method: 'eth_signTypedData_v4',
755
+ message: '',
756
+ chain_id: chainId,
757
+ typed_data: typedData
758
+ });
759
+ return this.waitForSignature(this.walletDetail.WalletID, response.transaction_id);
760
+ }
761
+ constructor(credentials, environment, provider, pollerOptions){
762
+ super(provider);
763
+ this.APICredentials = credentials;
764
+ this.APIService = new APIService(credentials, environment);
765
+ this.environment = environment;
766
+ this.pollerOptions = pollerOptions;
767
+ }
768
+ }
769
+ function checkProvider(signer, operation) {
770
+ if (signer.provider) {
771
+ return signer.provider;
772
+ }
773
+ ethers.assert(false, 'missing provider', 'UNSUPPORTED_OPERATION', {
774
+ operation
775
+ });
776
+ }
777
+
778
+ class SolanaSigner {
779
+ setWallet(walletId) {
780
+ if (!walletId || walletId.trim() === '') {
781
+ throw new Error('Invalid wallet ID provided');
782
+ }
783
+ // Set walletId in walletDetail with default values for required properties
784
+ this.walletDetail = {
785
+ WalletID: walletId,
786
+ APIKey: '',
787
+ Name: '',
788
+ AddressType: '',
789
+ Address: ''
790
+ };
791
+ // Reset address cache since we're changing wallets
792
+ this.address = '';
793
+ }
794
+ async getAddress() {
795
+ if (this.address) {
796
+ return this.address;
797
+ }
798
+ if (!this.APIKey && !this.walletDetail?.WalletID) {
799
+ throw new Error('Wallet detail not found, use setWallet(walletId) to set wallet first!');
800
+ }
801
+ const detail = await this.APIService.getWalletDetail(exports.WalletAddressType.Sol, this.walletDetail?.WalletID);
802
+ this.walletDetail = detail;
803
+ if (detail?.Address) {
804
+ // cache the address
805
+ this.address = detail.Address;
806
+ }
807
+ if (!this.address) {
808
+ throw new Error('Address not found');
809
+ }
810
+ return this.address;
811
+ }
812
+ async waitForTransactionStatus(transactionId) {
813
+ const poller = new StatusPoller(this.pollerOptions);
814
+ // Poll for transaction status using signaturePoller
815
+ const result = await poller.poll(// Polling function
816
+ ()=>{
817
+ console.log('Polling status');
818
+ return this.APIService.getTransactionStatus(this.walletDetail.WalletID, transactionId);
819
+ }, // Success condition
820
+ (result)=>(result.status === exports.TxStatus.Confirmed || result.status === exports.TxStatus.Completed) && !!result.hash, // Error condition
821
+ (result)=>{
822
+ if (result.status === exports.TxStatus.Failed) {
823
+ throw new TransactionError(result.failed_reason || 'Transaction failed', 'TRANSACTION_FAILED', result.transaction_id);
824
+ }
825
+ return result.status === exports.TxStatus.Rejected;
826
+ });
827
+ console.log('result', result);
828
+ if (!result.hash) {
829
+ throw new TransactionError('Transaction hash not found in successful response', 'TRANSACTION_HASH_MISSING', result.transaction_id);
830
+ }
831
+ return result.hash;
832
+ }
833
+ /**
834
+ * Signs a Solana transaction
835
+ * @param transaction Base64 encoded serialized transaction
836
+ * @returns Signature as a base58 encoded string
837
+ */ async signTransaction(transaction) {
838
+ if (!this.address) {
839
+ await this.getAddress();
840
+ }
841
+ const data = {
842
+ data: transaction,
843
+ from: this.address
844
+ };
845
+ // Call the signRaw API similar to ApexSigner
846
+ const response = await this.APIService.signTransaction(this.walletDetail.WalletID, {
847
+ ...data,
848
+ meta: {
849
+ tx_method: 'solana_signTransaction'
850
+ },
851
+ chainId: '1399811149'
852
+ });
853
+ console.log('respnpse', response);
854
+ // Wait for the signature
855
+ return this.waitForTransactionStatus(response.transaction_id);
856
+ }
857
+ /**
858
+ * Signs a Solana message
859
+ * @param message The message to sign (string or Uint8Array)
860
+ * @returns Signature as a base58 encoded string
861
+ */ async signMessage(message) {
862
+ if (!this.address) {
863
+ await this.getAddress();
864
+ }
865
+ const messageStr = typeof message === 'string' ? message : buffer.Buffer.from(message).toString('base64');
866
+ const response = await this.APIService.requestSign(this.walletDetail.WalletID, {
867
+ method: 'solana_signMessage',
868
+ message: messageStr,
869
+ chain_id: 0 // Not used for Solana but required by API
870
+ });
871
+ console.log('Respnse', response);
872
+ return this.waitForTransactionStatus(response.transaction_id);
873
+ }
874
+ /**
875
+ * Signs and sends a Solana transaction
876
+ * @param transaction Base64 encoded serialized transaction
877
+ * @returns Transaction signature
878
+ */ async signAndSendTransaction(transaction) {
879
+ if (!this.address) {
880
+ await this.getAddress();
881
+ }
882
+ const data = {
883
+ transaction,
884
+ from: this.address,
885
+ method: 'solana_signAndSendTransaction'
886
+ };
887
+ const response = await this.APIService.signTransaction(this.walletDetail.WalletID, data);
888
+ const txHash = await this.waitForTransactionStatus(response.transaction_id);
889
+ console.log('transaction succeed!');
890
+ return txHash;
891
+ }
892
+ /**
893
+ * Signs multiple Solana transactions
894
+ * @param transactions Array of base64 encoded serialized transactions
895
+ * @returns Array of signatures as base58 encoded strings
896
+ */ async signAllTransactions(transactions) {
897
+ if (!this.address) {
898
+ await this.getAddress();
899
+ }
900
+ // We need to get the signatures and then incorporate them into the transactions
901
+ const signaturePromises = transactions.map(async (transaction)=>{
902
+ // Get the signature
903
+ const signature = await this.signTransaction(transaction);
904
+ // Here you would need to incorporate the signature into the transaction
905
+ // This is a placeholder - you'll need to implement actual signature incorporation
906
+ // based on your Solana transaction structure
907
+ const signedTransaction = this.incorporateSignatureIntoTransaction(transaction, signature);
908
+ return signedTransaction;
909
+ });
910
+ // Wait for all transactions to be signed in parallel
911
+ const signedTransactions = await Promise.all(signaturePromises);
912
+ return {
913
+ transactions: signedTransactions
914
+ };
915
+ }
916
+ incorporateSignatureIntoTransaction(transaction, signature) {
917
+ // Decode base64 transaction to buffer
918
+ const transactionBuffer = buffer.Buffer.from(transaction, 'base64');
919
+ try {
920
+ // First try with legacy transaction format
921
+ const tx = web3_js.Transaction.from(transactionBuffer);
922
+ // Decode the base58 signature
923
+ const signatureBuffer = bs58__default.default.decode(signature);
924
+ // Add the signature to the transaction
925
+ tx.addSignature(new web3_js.PublicKey(this.address), buffer.Buffer.from(signatureBuffer));
926
+ // Serialize and encode back to base64
927
+ return buffer.Buffer.from(tx.serialize()).toString('base64');
928
+ } catch (error) {
929
+ if (error.message.includes('Versioned messages')) {
930
+ // Deserialize as a versioned transaction
931
+ const versionedTx = web3_js.VersionedTransaction.deserialize(transactionBuffer);
932
+ // Add the signature (convert from base58)
933
+ const signatureBuffer = bs58__default.default.decode(signature);
934
+ versionedTx.signatures[0] = buffer.Buffer.from(signatureBuffer);
935
+ // Serialize and encode back to base64
936
+ return buffer.Buffer.from(versionedTx.serialize()).toString('base64');
937
+ }
938
+ // If it's another type of error, rethrow it
939
+ throw error;
940
+ }
941
+ }
942
+ constructor(credentials, environment, pollerOptions){
943
+ this.APIKey = credentials.apiKey;
944
+ this.APIService = new APIService(credentials, environment);
945
+ this.pollerOptions = pollerOptions;
946
+ }
947
+ }
948
+
949
+ exports.APIService = APIService;
950
+ exports.ApexSigner = ApexSigner;
951
+ exports.DEFAULT_POLLER_OPTIONS = DEFAULT_POLLER_OPTIONS;
952
+ exports.FystackSDK = FystackSDK;
953
+ exports.PaymentService = PaymentService;
954
+ exports.SolanaSigner = SolanaSigner;
955
+ exports.StatusPoller = StatusPoller;
956
+ exports.TransactionError = TransactionError;
957
+ exports.WebhookService = WebhookService;
958
+ exports.createAPI = createAPI;
959
+ exports.get = get;
960
+ exports.post = post;
961
+ exports.transformCreateWalletPayload = transformCreateWalletPayload;
962
+ exports.transformWalletDetail = transformWalletDetail;