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