@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/.prettierrc +6 -0
- package/dist/index.cjs +962 -0
- package/dist/index.d.cts +611 -0
- package/dist/index.d.mts +611 -0
- package/dist/index.esm.d.ts +611 -0
- package/dist/index.esm.js +941 -0
- package/dist/index.mjs +941 -0
- package/dist/types/index.d.ts +611 -0
- package/package.json +45 -0
- package/src/api.ts +332 -0
- package/src/config.ts +75 -0
- package/src/index.ts +7 -0
- package/src/payment.ts +268 -0
- package/src/sdk.ts +140 -0
- package/src/signer.ts +392 -0
- package/src/solanaSigner.ts +243 -0
- package/src/types.ts +161 -0
- package/src/utils/statusPoller.ts +82 -0
- package/src/utils.ts +101 -0
- package/test.js +76 -0
- package/tsconfig.json +18 -0
- package/vite-env.d.ts +1 -0
- package/vitest.config.js +10 -0
package/dist/index.mjs
ADDED
|
@@ -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 };
|