@ic-pay/icpay-sdk 1.3.61

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.
Files changed (38) hide show
  1. package/README.md +32 -0
  2. package/dist/declarations/icp-ledger/icp-ledger.did.d.ts +82 -0
  3. package/dist/declarations/icp-ledger/icp-ledger.did.js +76 -0
  4. package/dist/declarations/icpay_canister_backend/icpay_canister_backend.did +193 -0
  5. package/dist/declarations/icpay_canister_backend/icpay_canister_backend.did.d.ts +219 -0
  6. package/dist/declarations/icpay_canister_backend/icpay_canister_backend.did.js +231 -0
  7. package/dist/declarations/icrc-ledger/ledger.did +560 -0
  8. package/dist/declarations/icrc-ledger/ledger.did.d.ts +364 -0
  9. package/dist/declarations/icrc-ledger/ledger.did.js +530 -0
  10. package/dist/errors.d.ts +72 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +162 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/events.d.ts +24 -0
  15. package/dist/events.d.ts.map +1 -0
  16. package/dist/events.js +131 -0
  17. package/dist/events.js.map +1 -0
  18. package/dist/index.d.ts +163 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +1211 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/protected.d.ts +28 -0
  23. package/dist/protected.d.ts.map +1 -0
  24. package/dist/protected.js +336 -0
  25. package/dist/protected.js.map +1 -0
  26. package/dist/types/index.d.ts +482 -0
  27. package/dist/types/index.d.ts.map +1 -0
  28. package/dist/types/index.js +3 -0
  29. package/dist/types/index.js.map +1 -0
  30. package/dist/utils.d.ts +15 -0
  31. package/dist/utils.d.ts.map +1 -0
  32. package/dist/utils.js +42 -0
  33. package/dist/utils.js.map +1 -0
  34. package/dist/wallet.d.ts +83 -0
  35. package/dist/wallet.d.ts.map +1 -0
  36. package/dist/wallet.js +261 -0
  37. package/dist/wallet.js.map +1 -0
  38. package/package.json +58 -0
package/dist/index.js ADDED
@@ -0,0 +1,1211 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.IcpayWallet = exports.IcpayError = exports.Icpay = void 0;
21
+ const errors_1 = require("./errors");
22
+ const events_1 = require("./events");
23
+ const wallet_1 = require("./wallet");
24
+ const axios_1 = __importDefault(require("axios"));
25
+ const agent_1 = require("@dfinity/agent");
26
+ const icpay_canister_backend_did_js_1 = require("./declarations/icpay_canister_backend/icpay_canister_backend.did.js");
27
+ const ledger_did_js_1 = require("./declarations/icrc-ledger/ledger.did.js");
28
+ const principal_1 = require("@dfinity/principal");
29
+ const utils_1 = require("./utils");
30
+ const protected_1 = require("./protected");
31
+ class Icpay {
32
+ constructor(config) {
33
+ this.privateApiClient = null;
34
+ this.connectedWallet = null;
35
+ this.icpayCanisterId = null;
36
+ this.accountInfoCache = null;
37
+ this.verifiedLedgersCache = { data: null, timestamp: 0 };
38
+ this.config = {
39
+ environment: 'production',
40
+ apiUrl: 'https://api.icpay.org',
41
+ debug: false,
42
+ enableEvents: false,
43
+ awaitServerNotification: false,
44
+ ...config
45
+ };
46
+ (0, utils_1.debugLog)(this.config.debug || false, 'constructor', { config: this.config });
47
+ // Validate authentication configuration
48
+ if (!this.config.publishableKey && !this.config.secretKey) {
49
+ throw new Error('Either publishableKey or secretKey must be provided');
50
+ }
51
+ this.icHost = config.icHost || 'https://ic0.app';
52
+ this.connectedWallet = config.connectedWallet || null;
53
+ this.actorProvider = config.actorProvider;
54
+ (0, utils_1.debugLog)(this.config.debug || false, 'constructor', { connectedWallet: this.connectedWallet, actorProvider: this.actorProvider });
55
+ // Initialize wallet with connected wallet if provided
56
+ this.wallet = new wallet_1.IcpayWallet({ connectedWallet: this.connectedWallet });
57
+ // Initialize event center
58
+ this.events = new events_1.IcpayEventCenter();
59
+ (0, utils_1.debugLog)(this.config.debug || false, 'constructor', { connectedWallet: this.connectedWallet });
60
+ // Create public API client (always available)
61
+ this.publicApiClient = axios_1.default.create({
62
+ baseURL: this.config.apiUrl,
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ 'Authorization': `Bearer ${this.config.publishableKey || this.config.secretKey}`
66
+ }
67
+ });
68
+ (0, utils_1.debugLog)(this.config.debug || false, 'publicApiClient created', this.publicApiClient);
69
+ // Create private API client (only if secret key is provided)
70
+ if (this.config.secretKey) {
71
+ const privateHeaders = {
72
+ 'Content-Type': 'application/json',
73
+ 'Authorization': `Bearer ${this.config.secretKey}`
74
+ };
75
+ this.privateApiClient = axios_1.default.create({
76
+ baseURL: this.config.apiUrl,
77
+ headers: privateHeaders
78
+ });
79
+ }
80
+ (0, utils_1.debugLog)(this.config.debug || false, 'privateApiClient created', this.privateApiClient);
81
+ // Initialize protected API
82
+ this.protected = (0, protected_1.createProtectedApi)({
83
+ privateApiClient: this.privateApiClient,
84
+ emitStart: (name, args) => this.emitMethodStart(name, args),
85
+ emitSuccess: (name, result) => this.emitMethodSuccess(name, result),
86
+ emitError: (name, error) => this.emitMethodError(name, error),
87
+ });
88
+ }
89
+ // ===== Event API (no Lit required) =====
90
+ on(type, listener) {
91
+ return this.events.on(type, listener);
92
+ }
93
+ off(type, listener) {
94
+ this.events.off(type, listener);
95
+ }
96
+ emit(type, detail) {
97
+ if (this.config.enableEvents) {
98
+ this.events.emit(type, detail);
99
+ }
100
+ }
101
+ addEventListener(type, listener) {
102
+ this.events.addEventListener(type, listener);
103
+ }
104
+ removeEventListener(type, listener) {
105
+ this.events.removeEventListener(type, listener);
106
+ }
107
+ dispatchEvent(event) {
108
+ return this.events.dispatchEvent(event);
109
+ }
110
+ emitError(error) {
111
+ const err = error instanceof errors_1.IcpayError
112
+ ? error
113
+ : new errors_1.IcpayError({
114
+ code: errors_1.ICPAY_ERROR_CODES.UNKNOWN_ERROR,
115
+ message: (error && (error.message || error.toString())) || 'Unknown error',
116
+ details: error
117
+ });
118
+ if (this.config.enableEvents) {
119
+ this.events.emit('icpay-sdk-error', err);
120
+ }
121
+ }
122
+ emitMethodStart(name, args) {
123
+ if (this.config.enableEvents) {
124
+ this.events.emit('icpay-sdk-method-start', { name, args });
125
+ }
126
+ }
127
+ emitMethodSuccess(name, result) {
128
+ if (this.config.enableEvents) {
129
+ this.events.emit('icpay-sdk-method-success', { name, result });
130
+ }
131
+ }
132
+ emitMethodError(name, error) {
133
+ if (this.config.enableEvents) {
134
+ this.events.emit('icpay-sdk-method-error', { name, error });
135
+ }
136
+ this.emitError(error);
137
+ }
138
+ /**
139
+ * Check if SDK has secret key for private operations
140
+ */
141
+ hasSecretKey() {
142
+ return !!this.config.secretKey && !!this.privateApiClient;
143
+ }
144
+ /**
145
+ * Require secret key for private operations
146
+ */
147
+ requireSecretKey(methodName) {
148
+ if (!this.hasSecretKey()) {
149
+ throw new errors_1.IcpayError({
150
+ code: 'SECRET_KEY_REQUIRED',
151
+ message: `${methodName} requires secret key authentication. Please provide secretKey and accountId in configuration.`
152
+ });
153
+ }
154
+ }
155
+ /**
156
+ * Get account information (public method - limited data)
157
+ */
158
+ async getAccountInfo() {
159
+ this.emitMethodStart('getAccountInfo');
160
+ try {
161
+ const response = await this.publicApiClient.get('/sdk/public/account');
162
+ const account = response.data;
163
+ const result = {
164
+ id: account.id,
165
+ name: account.name,
166
+ isActive: account.isActive,
167
+ isLive: account.isLive,
168
+ accountCanisterId: account.accountCanisterId,
169
+ icpayCanisterId: account.icpayCanisterId,
170
+ };
171
+ this.emitMethodSuccess('getAccountInfo', result);
172
+ return result;
173
+ }
174
+ catch (error) {
175
+ const err = new errors_1.IcpayError({
176
+ code: 'ACCOUNT_INFO_FETCH_FAILED',
177
+ message: 'Failed to fetch account information',
178
+ details: error
179
+ });
180
+ this.emitMethodError('getAccountInfo', err);
181
+ throw err;
182
+ }
183
+ }
184
+ /**
185
+ * Get verified ledgers (public method)
186
+ */
187
+ async getVerifiedLedgers() {
188
+ this.emitMethodStart('getVerifiedLedgers');
189
+ const now = Date.now();
190
+ const cacheAge = 60 * 60 * 1000; // 60 minutes cache
191
+ // Return cached data if it's still fresh
192
+ if (this.verifiedLedgersCache.data && (now - this.verifiedLedgersCache.timestamp) < cacheAge) {
193
+ return this.verifiedLedgersCache.data;
194
+ }
195
+ try {
196
+ const response = await this.publicApiClient.get('/sdk/public/ledgers/verified');
197
+ const ledgers = response.data.map((ledger) => ({
198
+ id: ledger.id,
199
+ name: ledger.name,
200
+ symbol: ledger.symbol,
201
+ canisterId: ledger.canisterId,
202
+ decimals: ledger.decimals,
203
+ logoUrl: ledger.logoUrl,
204
+ verified: ledger.verified,
205
+ fee: ledger.fee,
206
+ currentPrice: ledger.currentPrice ?? null,
207
+ lastPriceUpdate: ledger.lastPriceUpdate ?? null,
208
+ }));
209
+ // Update cache
210
+ this.verifiedLedgersCache = {
211
+ data: ledgers,
212
+ timestamp: now
213
+ };
214
+ this.emitMethodSuccess('getVerifiedLedgers', { count: ledgers.length });
215
+ return ledgers;
216
+ }
217
+ catch (error) {
218
+ const err = new errors_1.IcpayError({
219
+ code: 'VERIFIED_LEDGERS_FETCH_FAILED',
220
+ message: 'Failed to fetch verified ledgers',
221
+ details: error
222
+ });
223
+ this.emitMethodError('getVerifiedLedgers', err);
224
+ throw err;
225
+ }
226
+ }
227
+ /**
228
+ * Get a verified ledger's canister ID by its symbol (public helper)
229
+ */
230
+ async getLedgerCanisterIdBySymbol(symbol) {
231
+ this.emitMethodStart('getLedgerCanisterIdBySymbol', { symbol });
232
+ if (!symbol || typeof symbol !== 'string') {
233
+ throw new errors_1.IcpayError({
234
+ code: 'INVALID_LEDGER_SYMBOL',
235
+ message: 'Symbol must be a non-empty string'
236
+ });
237
+ }
238
+ const ledgers = await this.getVerifiedLedgers();
239
+ const match = ledgers.find(l => l.symbol.toLowerCase() === symbol.toLowerCase());
240
+ if (!match) {
241
+ throw new errors_1.IcpayError({
242
+ code: 'LEDGER_SYMBOL_NOT_FOUND',
243
+ message: `Verified ledger with symbol ${symbol} not found`
244
+ });
245
+ }
246
+ const result = match.canisterId;
247
+ this.emitMethodSuccess('getLedgerCanisterIdBySymbol', { symbol, canisterId: result });
248
+ return result;
249
+ }
250
+ /**
251
+ * Trigger transaction sync from canister (public method)
252
+ *
253
+ * This method attempts to sync a transaction directly from the canister to the API database
254
+ * and returns the result immediately. This is useful when you know a transaction exists
255
+ * in the canister but it's not showing up in the API database yet.
256
+ */
257
+ async triggerTransactionSync(canisterTransactionId) {
258
+ this.emitMethodStart('triggerTransactionSync', { canisterTransactionId });
259
+ try {
260
+ const response = await this.publicApiClient.get(`/sdk/public/transactions/${canisterTransactionId}/sync`);
261
+ this.emitMethodSuccess('triggerTransactionSync', response.data);
262
+ return response.data;
263
+ }
264
+ catch (error) {
265
+ const err = new errors_1.IcpayError({
266
+ code: 'TRANSACTION_SYNC_TRIGGER_FAILED',
267
+ message: 'Failed to trigger transaction sync from canister',
268
+ details: error
269
+ });
270
+ this.emitMethodError('triggerTransactionSync', err);
271
+ throw err;
272
+ }
273
+ }
274
+ /**
275
+ * Fetch and cache account info, including icpayCanisterId (public method)
276
+ */
277
+ async fetchAccountInfo() {
278
+ if (this.accountInfoCache) {
279
+ this.icpayCanisterId = this.accountInfoCache.icpayCanisterId.toString();
280
+ return this.accountInfoCache;
281
+ }
282
+ try {
283
+ // Use public endpoint to get account info
284
+ const response = await this.publicApiClient.get('/sdk/public/account');
285
+ this.accountInfoCache = response.data;
286
+ if (response.data && response.data.icpayCanisterId) {
287
+ this.icpayCanisterId = response.data.icpayCanisterId.toString();
288
+ }
289
+ return this.accountInfoCache;
290
+ }
291
+ catch (error) {
292
+ throw new errors_1.IcpayError({
293
+ code: 'ACCOUNT_INFO_FETCH_FAILED',
294
+ message: 'Failed to fetch account information',
295
+ details: error
296
+ });
297
+ }
298
+ }
299
+ /**
300
+ * Show wallet connection modal
301
+ */
302
+ async showWalletModal() {
303
+ this.emitMethodStart('showWalletModal');
304
+ try {
305
+ const res = await this.wallet.showConnectionModal();
306
+ this.emitMethodSuccess('showWalletModal', res);
307
+ return res;
308
+ }
309
+ catch (error) {
310
+ this.emitMethodError('showWalletModal', error);
311
+ throw error;
312
+ }
313
+ }
314
+ /**
315
+ * Connect to a specific wallet provider
316
+ */
317
+ async connectWallet(providerId) {
318
+ this.emitMethodStart('connectWallet', { providerId });
319
+ try {
320
+ const res = await this.wallet.connectToProvider(providerId);
321
+ this.emitMethodSuccess('connectWallet', res);
322
+ return res;
323
+ }
324
+ catch (error) {
325
+ this.emitMethodError('connectWallet', error);
326
+ throw error;
327
+ }
328
+ }
329
+ /**
330
+ * Get available wallet providers
331
+ */
332
+ getWalletProviders() {
333
+ this.emitMethodStart('getWalletProviders');
334
+ const res = this.wallet.getProviders();
335
+ this.emitMethodSuccess('getWalletProviders', { count: Array.isArray(res) ? res.length : undefined });
336
+ return res;
337
+ }
338
+ /**
339
+ * Check if a wallet provider is available
340
+ */
341
+ isWalletProviderAvailable(providerId) {
342
+ this.emitMethodStart('isWalletProviderAvailable', { providerId });
343
+ const res = this.wallet.isProviderAvailable(providerId);
344
+ this.emitMethodSuccess('isWalletProviderAvailable', { providerId, available: res });
345
+ return res;
346
+ }
347
+ /**
348
+ * Get the connected wallet's account address
349
+ */
350
+ getAccountAddress() {
351
+ this.emitMethodStart('getAccountAddress');
352
+ const res = this.wallet.getAccountAddress();
353
+ this.emitMethodSuccess('getAccountAddress', { accountAddress: res });
354
+ return res;
355
+ }
356
+ /**
357
+ * Get balance for a specific ledger canister
358
+ */
359
+ async getLedgerBalance(ledgerCanisterId) {
360
+ this.emitMethodStart('getLedgerBalance', { ledgerCanisterId });
361
+ try {
362
+ // Extract principal from connected wallet
363
+ let principal = null;
364
+ if (this.connectedWallet) {
365
+ if (this.connectedWallet.owner) {
366
+ principal = this.connectedWallet.owner;
367
+ }
368
+ else if (this.connectedWallet.principal) {
369
+ principal = this.connectedWallet.principal;
370
+ }
371
+ }
372
+ if (!principal) {
373
+ throw new Error('No principal available for balance check');
374
+ }
375
+ // Convert string principal to Principal object
376
+ const principalObj = principal_1.Principal.fromText(principal);
377
+ // Create anonymous actor for balance queries (no signing required)
378
+ const agent = new agent_1.HttpAgent({ host: this.icHost });
379
+ const actor = agent_1.Actor.createActor(ledger_did_js_1.idlFactory, { agent, canisterId: ledgerCanisterId });
380
+ // Get the balance of the user's account
381
+ const result = await actor.icrc1_balance_of({
382
+ owner: principalObj,
383
+ subaccount: []
384
+ });
385
+ const out = BigInt(result);
386
+ this.emitMethodSuccess('getLedgerBalance', { ledgerCanisterId, balance: out.toString() });
387
+ return out;
388
+ }
389
+ catch (error) {
390
+ this.emitMethodError('getLedgerBalance', error);
391
+ throw error;
392
+ }
393
+ }
394
+ /**
395
+ * Create a simple memo with account canister ID as bytes
396
+ * Example: 1 => Uint8Array([1]), 2 => Uint8Array([2])
397
+ */
398
+ createMemoWithAccountCanisterId(accountCanisterId) {
399
+ // Convert number to bytes (simple approach)
400
+ const bytes = [];
401
+ let num = accountCanisterId;
402
+ // Handle 0 case
403
+ if (num === 0) {
404
+ return new Uint8Array([0]);
405
+ }
406
+ // Convert to bytes (little-endian)
407
+ while (num > 0) {
408
+ bytes.push(num & 0xff);
409
+ num = Math.floor(num / 256);
410
+ }
411
+ return new Uint8Array(bytes);
412
+ }
413
+ createPackedMemo(accountCanisterId, intentCode) {
414
+ let memo = (BigInt(accountCanisterId >>> 0) << BigInt(32)) | BigInt(intentCode >>> 0);
415
+ if (memo === BigInt(0))
416
+ return new Uint8Array([0]);
417
+ const out = [];
418
+ while (memo > BigInt(0)) {
419
+ out.push(Number(memo & BigInt(0xff)));
420
+ memo >>= BigInt(8);
421
+ }
422
+ return new Uint8Array(out);
423
+ }
424
+ /**
425
+ * Send funds to a specific canister/ledger (public method)
426
+ * This is now a real transaction
427
+ */
428
+ async sendFunds(request) {
429
+ this.emitMethodStart('sendFunds', { request: { ...request, amount: typeof request.amount === 'string' ? request.amount : String(request.amount) } });
430
+ try {
431
+ (0, utils_1.debugLog)(this.config.debug || false, 'sendFunds start', { request });
432
+ // Fetch account info to get accountCanisterId if not provided
433
+ let accountCanisterId = request.accountCanisterId;
434
+ if (!accountCanisterId) {
435
+ (0, utils_1.debugLog)(this.config.debug || false, 'fetching account info for accountCanisterId');
436
+ const accountInfo = await this.getAccountInfo();
437
+ accountCanisterId = accountInfo.accountCanisterId.toString();
438
+ (0, utils_1.debugLog)(this.config.debug || false, 'accountCanisterId resolved', { accountCanisterId });
439
+ }
440
+ // Always use icpayCanisterId as toPrincipal
441
+ if (!this.icpayCanisterId) {
442
+ await this.fetchAccountInfo();
443
+ }
444
+ const ledgerCanisterId = request.ledgerCanisterId;
445
+ let toPrincipal = this.icpayCanisterId;
446
+ const amount = typeof request.amount === 'string' ? BigInt(request.amount) : BigInt(request.amount);
447
+ const host = this.icHost;
448
+ let memo = undefined;
449
+ // Check balance before sending
450
+ const requiredAmount = amount;
451
+ (0, utils_1.debugLog)(this.config.debug || false, 'checking balance', { ledgerCanisterId, requiredAmount: requiredAmount.toString() });
452
+ // Helper function to make amounts human-readable
453
+ const formatAmount = (amount, decimals = 8, symbol = '') => {
454
+ const divisor = BigInt(10 ** decimals);
455
+ const whole = amount / divisor;
456
+ const fraction = amount % divisor;
457
+ const fractionStr = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
458
+ return `${whole}${fractionStr ? '.' + fractionStr : ''} ${symbol}`.trim();
459
+ };
460
+ // Check if user has sufficient balance based on ledger type
461
+ try {
462
+ // Get the actual balance from the specific ledger (works for all ICRC ledgers including ICP)
463
+ const actualBalance = await this.getLedgerBalance(ledgerCanisterId);
464
+ if (actualBalance < requiredAmount) {
465
+ const requiredFormatted = formatAmount(requiredAmount, 8, 'tokens');
466
+ const availableFormatted = formatAmount(actualBalance, 8, 'tokens');
467
+ throw (0, errors_1.createBalanceError)(requiredFormatted, availableFormatted, {
468
+ required: requiredAmount,
469
+ available: actualBalance,
470
+ ledgerCanisterId
471
+ });
472
+ }
473
+ (0, utils_1.debugLog)(this.config.debug || false, 'balance ok', { actualBalance: actualBalance.toString() });
474
+ }
475
+ catch (balanceError) {
476
+ // If we can't fetch the specific ledger balance, fall back to the old logic
477
+ throw new errors_1.IcpayError({
478
+ code: 'INSUFFICIENT_BALANCE',
479
+ message: 'Insufficient balance',
480
+ details: { required: requiredAmount, available: 0 }
481
+ });
482
+ }
483
+ // 1) Create payment intent via API
484
+ let paymentIntentId = null;
485
+ let paymentIntentCode = null;
486
+ try {
487
+ (0, utils_1.debugLog)(this.config.debug || false, 'creating payment intent');
488
+ // Get the expected sender principal from connected wallet
489
+ const expectedSenderPrincipal = this.connectedWallet?.owner || this.connectedWallet?.principal?.toString();
490
+ if (!expectedSenderPrincipal) {
491
+ throw new errors_1.IcpayError({
492
+ code: errors_1.ICPAY_ERROR_CODES.WALLET_NOT_CONNECTED,
493
+ message: 'Wallet must be connected to create payment intent',
494
+ details: { connectedWallet: this.connectedWallet },
495
+ retryable: false,
496
+ userAction: 'Connect your wallet first'
497
+ });
498
+ }
499
+ const intentResp = await this.publicApiClient.post('/sdk/public/payments/intents', {
500
+ amount: request.amount,
501
+ ledgerCanisterId,
502
+ expectedSenderPrincipal,
503
+ metadata: request.metadata || {},
504
+ });
505
+ paymentIntentId = intentResp.data?.paymentIntent?.id || null;
506
+ paymentIntentCode = intentResp.data?.paymentIntent?.intentCode ?? null;
507
+ (0, utils_1.debugLog)(this.config.debug || false, 'payment intent created', { paymentIntentId, paymentIntentCode, expectedSenderPrincipal });
508
+ // Emit transaction created event
509
+ if (paymentIntentId) {
510
+ this.emit('icpay-sdk-transaction-created', {
511
+ paymentIntentId,
512
+ amount: request.amount,
513
+ ledgerCanisterId,
514
+ expectedSenderPrincipal
515
+ });
516
+ }
517
+ }
518
+ catch (e) {
519
+ // Do not proceed without a payment intent
520
+ // Throw a standardized error so integrators can handle it consistently
521
+ const err = new errors_1.IcpayError({
522
+ code: errors_1.ICPAY_ERROR_CODES.API_ERROR,
523
+ message: 'Failed to create payment intent. Please try again.',
524
+ details: e,
525
+ retryable: true,
526
+ userAction: 'Try again'
527
+ });
528
+ this.emitError(err);
529
+ throw err;
530
+ }
531
+ // Build packed memo if possible
532
+ try {
533
+ const acctIdNum = parseInt(accountCanisterId);
534
+ if (!isNaN(acctIdNum) && paymentIntentCode != null) {
535
+ memo = this.createPackedMemo(acctIdNum, Number(paymentIntentCode));
536
+ (0, utils_1.debugLog)(this.config.debug || false, 'built packed memo', { accountCanisterId: acctIdNum, paymentIntentCode });
537
+ }
538
+ else if (!isNaN(acctIdNum)) {
539
+ memo = this.createMemoWithAccountCanisterId(acctIdNum);
540
+ (0, utils_1.debugLog)(this.config.debug || false, 'built legacy memo', { accountCanisterId: acctIdNum });
541
+ }
542
+ (0, utils_1.debugLog)(this.config.debug || false, 'memo', { memo });
543
+ }
544
+ catch { }
545
+ let transferResult;
546
+ if (ledgerCanisterId === 'ryjl3-tyaaa-aaaaa-aaaba-cai') {
547
+ // ICP Ledger: use ICRC-1 transfer (ICP ledger supports ICRC-1)
548
+ (0, utils_1.debugLog)(this.config.debug || false, 'sending ICRC-1 transfer (ICP)');
549
+ transferResult = await this.sendFundsToLedger(ledgerCanisterId, toPrincipal, amount, memo, host);
550
+ }
551
+ else {
552
+ // ICRC-1 ledgers: use principal directly
553
+ (0, utils_1.debugLog)(this.config.debug || false, 'sending ICRC-1 transfer');
554
+ transferResult = await this.sendFundsToLedger(ledgerCanisterId, toPrincipal, amount, memo, host);
555
+ }
556
+ // Assume transferResult returns a block index or transaction id
557
+ const blockIndex = transferResult?.Ok?.toString() || transferResult?.blockIndex?.toString() || `temp-${Date.now()}`;
558
+ (0, utils_1.debugLog)(this.config.debug || false, 'transfer result', { blockIndex });
559
+ // First, notify the canister about the ledger transaction
560
+ let canisterTransactionId;
561
+ let notifyStatus = null;
562
+ try {
563
+ (0, utils_1.debugLog)(this.config.debug || false, 'notifying canister about ledger tx');
564
+ const notifyRes = await this.notifyLedgerTransaction(this.icpayCanisterId, ledgerCanisterId, BigInt(blockIndex));
565
+ // notify returns { id, status, amount }
566
+ if (typeof notifyRes === 'string') {
567
+ canisterTransactionId = parseInt(notifyRes, 10);
568
+ }
569
+ else {
570
+ canisterTransactionId = parseInt(notifyRes.id, 10);
571
+ notifyStatus = notifyRes;
572
+ }
573
+ (0, utils_1.debugLog)(this.config.debug || false, 'canister notified', { canisterTransactionId });
574
+ }
575
+ catch (notifyError) {
576
+ canisterTransactionId = parseInt(blockIndex, 10);
577
+ (0, utils_1.debugLog)(this.config.debug || false, 'notify failed, using blockIndex as tx id', { canisterTransactionId });
578
+ }
579
+ // Poll for transaction status until completed
580
+ // Use the transaction ID returned by the notification, not the block index
581
+ let status = null;
582
+ if (notifyStatus && notifyStatus.status) {
583
+ status = { status: notifyStatus.status };
584
+ }
585
+ else {
586
+ try {
587
+ (0, utils_1.debugLog)(this.config.debug || false, 'polling transaction status (public)', { canisterTransactionId });
588
+ status = await this.pollTransactionStatus(this.icpayCanisterId, canisterTransactionId, accountCanisterId, Number(blockIndex), 2000, 30);
589
+ (0, utils_1.debugLog)(this.config.debug || false, 'poll done', { status });
590
+ }
591
+ catch (e) {
592
+ status = { status: 'pending' };
593
+ (0, utils_1.debugLog)(this.config.debug || false, 'poll failed, falling back to pending');
594
+ }
595
+ }
596
+ // Extract the status string from the transaction object
597
+ let statusString = 'pending';
598
+ if (status) {
599
+ if (typeof status === 'object' && status.status) {
600
+ // Handle variant status like {Completed: null}
601
+ if (typeof status.status === 'object') {
602
+ const statusKeys = Object.keys(status.status);
603
+ if (statusKeys.length > 0) {
604
+ const rawStatus = statusKeys[0].toLowerCase();
605
+ if (rawStatus === 'completed' || rawStatus === 'failed') {
606
+ statusString = rawStatus;
607
+ }
608
+ }
609
+ }
610
+ else {
611
+ const rawStatus = status.status;
612
+ if (rawStatus === 'completed' || rawStatus === 'failed') {
613
+ statusString = rawStatus;
614
+ }
615
+ }
616
+ }
617
+ }
618
+ // 5) Notify API about completion with intent and transaction id
619
+ // Optionally await based on config.awaitServerNotification
620
+ let publicNotify = undefined;
621
+ const notifyApi = async () => {
622
+ const notifyClient = this.publicApiClient;
623
+ const notifyPath = '/sdk/public/payments/notify';
624
+ const maxNotifyAttempts = 5;
625
+ const notifyDelayMs = 1000;
626
+ for (let attempt = 1; attempt <= maxNotifyAttempts; attempt++) {
627
+ try {
628
+ (0, utils_1.debugLog)(this.config.debug || false, 'notifying API about completion', { attempt, notifyPath, paymentIntentId, canisterTransactionId });
629
+ const resp = await notifyClient.post(notifyPath, {
630
+ paymentIntentId,
631
+ canisterTxId: canisterTransactionId,
632
+ });
633
+ return resp.data;
634
+ }
635
+ catch (e) {
636
+ const status = e?.response?.status;
637
+ const data = e?.response?.data;
638
+ (0, utils_1.debugLog)(this.config.debug || false, 'API notify attempt failed', { attempt, status, data });
639
+ // Proactively trigger a transaction sync if we get not found
640
+ try {
641
+ await this.triggerTransactionSync(canisterTransactionId);
642
+ }
643
+ catch { }
644
+ if (attempt < maxNotifyAttempts) {
645
+ await new Promise(r => setTimeout(r, notifyDelayMs));
646
+ }
647
+ }
648
+ }
649
+ (0, utils_1.debugLog)(this.config.debug || false, 'API notify failed after retries (non-fatal)');
650
+ return undefined;
651
+ };
652
+ if (this.config.awaitServerNotification) {
653
+ publicNotify = await notifyApi();
654
+ }
655
+ else {
656
+ // fire-and-forget
657
+ notifyApi()
658
+ .then((data) => {
659
+ // Optionally emit a success for the async notify step
660
+ this.emitMethodSuccess('sendFunds.notifyApi', { paymentIntentId, canisterTransactionId, data });
661
+ })
662
+ .catch((err) => {
663
+ this.emitMethodError('sendFunds.notifyApi', err);
664
+ });
665
+ }
666
+ const response = {
667
+ transactionId: canisterTransactionId,
668
+ status: statusString,
669
+ amount: amount.toString(),
670
+ recipientCanister: ledgerCanisterId,
671
+ timestamp: new Date(),
672
+ description: 'Fund transfer',
673
+ metadata: request.metadata,
674
+ payment: publicNotify
675
+ };
676
+ (0, utils_1.debugLog)(this.config.debug || false, 'sendFunds done', response);
677
+ if (statusString === 'completed') {
678
+ this.emit('icpay-sdk-transaction-completed', response);
679
+ }
680
+ else if (statusString === 'failed') {
681
+ this.emit('icpay-sdk-transaction-failed', response);
682
+ }
683
+ else {
684
+ this.emit('icpay-sdk-transaction-updated', response);
685
+ }
686
+ this.emitMethodSuccess('sendFunds', response);
687
+ return response;
688
+ }
689
+ catch (error) {
690
+ if (error instanceof errors_1.IcpayError) {
691
+ this.emitMethodError('sendFunds', error);
692
+ throw error;
693
+ }
694
+ const err = new errors_1.IcpayError({
695
+ code: 'TRANSACTION_FAILED',
696
+ message: 'Failed to send funds',
697
+ details: error
698
+ });
699
+ this.emitMethodError('sendFunds', err);
700
+ throw err;
701
+ }
702
+ }
703
+ /**
704
+ * Disconnect from wallet
705
+ */
706
+ async disconnectWallet() {
707
+ return await this.wallet.disconnect();
708
+ }
709
+ /**
710
+ * Check if wallet is connected
711
+ */
712
+ isWalletConnected() {
713
+ return this.wallet.isConnected();
714
+ }
715
+ /**
716
+ * Get the connected wallet provider
717
+ */
718
+ getConnectedWalletProvider() {
719
+ return this.wallet.getConnectedProvider();
720
+ }
721
+ /**
722
+ * Poll for transaction status using anonymous actor (no signature required)
723
+ */
724
+ async pollTransactionStatus(canisterId, transactionId, accountCanisterId, indexReceived, intervalMs = 2000, maxAttempts = 30) {
725
+ this.emitMethodStart('pollTransactionStatus', { canisterId, transactionId, accountCanisterId, indexReceived, intervalMs, maxAttempts });
726
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
727
+ try {
728
+ // Use public-only method
729
+ let status = await this.getTransactionStatusPublic(canisterId, transactionId, indexReceived, accountCanisterId);
730
+ // If we get an array (unexpected), try the alternative method
731
+ if (Array.isArray(status) && status.length > 0) {
732
+ const transaction = status[0];
733
+ if (transaction && typeof transaction === 'object') {
734
+ // Check if we have a valid status
735
+ if (transaction.status) {
736
+ // Check if transaction is completed
737
+ const transactionStatus = transaction.status;
738
+ if (this.isTransactionCompleted(transactionStatus)) {
739
+ return transaction; // Return immediately when completed
740
+ }
741
+ // If not completed, continue polling
742
+ }
743
+ }
744
+ }
745
+ // If we get null or no valid result, try the alternative method
746
+ // No secondary fallback to controller-only methods
747
+ if (status && status.status) {
748
+ if (this.isTransactionCompleted(status.status)) {
749
+ this.emitMethodSuccess('pollTransactionStatus', { attempt, status });
750
+ return status; // Return immediately when completed
751
+ }
752
+ // If not completed, continue polling
753
+ }
754
+ // Check if status is an object with Ok/Err pattern
755
+ if (status && typeof status === 'object' && (status.Ok || status.Err)) {
756
+ this.emitMethodSuccess('pollTransactionStatus', { attempt, status });
757
+ return status; // Return immediately when we find a valid status
758
+ }
759
+ // Wait before next attempt (unless this is the last attempt)
760
+ if (attempt < maxAttempts - 1) {
761
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
762
+ }
763
+ }
764
+ catch (error) {
765
+ if (attempt === maxAttempts - 1) {
766
+ this.emitMethodError('pollTransactionStatus', error);
767
+ }
768
+ // Wait before next attempt
769
+ if (attempt < maxAttempts - 1) {
770
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
771
+ }
772
+ }
773
+ }
774
+ const err = new Error('Transaction status polling timed out');
775
+ this.emitMethodError('pollTransactionStatus', err);
776
+ throw err;
777
+ }
778
+ /**
779
+ * Check if transaction status indicates completion
780
+ */
781
+ isTransactionCompleted(status) {
782
+ if (!status)
783
+ return false;
784
+ // Handle variant status like {Completed: null}
785
+ if (typeof status === 'object') {
786
+ const statusKeys = Object.keys(status);
787
+ if (statusKeys.length > 0) {
788
+ const rawStatus = statusKeys[0].toLowerCase();
789
+ return rawStatus === 'completed';
790
+ }
791
+ }
792
+ // Handle string status
793
+ if (typeof status === 'string') {
794
+ return status.toLowerCase() === 'completed';
795
+ }
796
+ return false;
797
+ }
798
+ /**
799
+ * Notify canister about ledger transaction using anonymous actor (no signature required)
800
+ */
801
+ async notifyLedgerTransaction(canisterId, ledgerCanisterId, blockIndex) {
802
+ this.emitMethodStart('notifyLedgerTransaction', { canisterId, ledgerCanisterId, blockIndex: blockIndex.toString() });
803
+ // Create anonymous actor for canister notifications (no signature required)
804
+ const agent = new agent_1.HttpAgent({ host: this.icHost });
805
+ const actor = agent_1.Actor.createActor(icpay_canister_backend_did_js_1.idlFactory, { agent, canisterId });
806
+ const result = await actor.notify_ledger_transaction({
807
+ ledger_canister_id: ledgerCanisterId,
808
+ block_index: blockIndex
809
+ });
810
+ if (result && result.Ok) {
811
+ this.emitMethodSuccess('notifyLedgerTransaction', { result: result.Ok });
812
+ return result.Ok;
813
+ }
814
+ else if (result && result.Err) {
815
+ const err = new Error(result.Err);
816
+ this.emitMethodError('notifyLedgerTransaction', err);
817
+ throw err;
818
+ }
819
+ else {
820
+ const err = new Error('Unexpected canister notify result');
821
+ this.emitMethodError('notifyLedgerTransaction', err);
822
+ throw err;
823
+ }
824
+ }
825
+ async getTransactionStatusPublic(canisterId, canisterTransactionId, indexReceived, accountCanisterId) {
826
+ this.emitMethodStart('getTransactionStatusPublic', { canisterId, canisterTransactionId, indexReceived, accountCanisterId });
827
+ const agent = new agent_1.HttpAgent({ host: this.icHost });
828
+ const actor = agent_1.Actor.createActor(icpay_canister_backend_did_js_1.idlFactory, { agent, canisterId });
829
+ const acctIdNum = parseInt(accountCanisterId);
830
+ const res = await actor.get_transaction_status_public(acctIdNum, BigInt(canisterTransactionId), [indexReceived]);
831
+ const result = res || { status: 'pending' };
832
+ this.emitMethodSuccess('getTransactionStatusPublic', result);
833
+ return result;
834
+ }
835
+ /**
836
+ * Send funds to a ledger canister using agent-js
837
+ * Now uses host from config
838
+ */
839
+ async sendFundsToLedger(ledgerCanisterId, toPrincipal, amount, memo, host) {
840
+ this.emitMethodStart('sendFundsToLedger', { ledgerCanisterId, toPrincipal, amount: amount.toString(), hasMemo: !!memo });
841
+ let actor;
842
+ if (this.actorProvider) {
843
+ actor = this.actorProvider(ledgerCanisterId, ledger_did_js_1.idlFactory);
844
+ }
845
+ else {
846
+ const err = new Error('actorProvider is required for sending funds');
847
+ this.emitMethodError('sendFundsToLedger', err);
848
+ throw err;
849
+ }
850
+ // ICRC-1 transfer
851
+ const res = await actor.icrc1_transfer({
852
+ to: { owner: principal_1.Principal.fromText(toPrincipal), subaccount: [] },
853
+ amount,
854
+ fee: [], // Always include fee, even if empty
855
+ memo: memo ? [memo] : [],
856
+ from_subaccount: [],
857
+ created_at_time: [],
858
+ });
859
+ this.emitMethodSuccess('sendFundsToLedger', res);
860
+ return res;
861
+ }
862
+ /**
863
+ * Get transaction by ID using get_transactions filter (alternative to get_transaction)
864
+ */
865
+ async getTransactionByFilter(transactionId) {
866
+ this.emitMethodStart('getTransactionByFilter', { transactionId });
867
+ try {
868
+ if (!this.icpayCanisterId) {
869
+ await this.fetchAccountInfo();
870
+ }
871
+ // Create anonymous actor for canister queries
872
+ const agent = new agent_1.HttpAgent({ host: this.icHost });
873
+ const actor = agent_1.Actor.createActor(icpay_canister_backend_did_js_1.idlFactory, { agent, canisterId: this.icpayCanisterId });
874
+ // Convert string transaction ID to Nat
875
+ const transactionIdNat = BigInt(transactionId);
876
+ // Get all transactions and filter by ID
877
+ const result = await actor.get_transactions({
878
+ account_canister_id: [], // Use empty array instead of null
879
+ ledger_canister_id: [], // Use empty array instead of null
880
+ from_timestamp: [], // Use empty array instead of null
881
+ to_timestamp: [], // Use empty array instead of null
882
+ from_id: [], // Use empty array instead of null
883
+ status: [], // Use empty array instead of null
884
+ limit: [], // Use empty array instead of 100 for optional nat32
885
+ offset: [] // Use empty array instead of 0 for optional nat32
886
+ });
887
+ if (result && result.transactions) {
888
+ const transaction = result.transactions.find((tx) => tx.id.toString() === transactionId.toString());
889
+ this.emitMethodSuccess('getTransactionByFilter', { found: !!transaction });
890
+ return transaction;
891
+ }
892
+ this.emitMethodSuccess('getTransactionByFilter', { found: false });
893
+ return null;
894
+ }
895
+ catch (error) {
896
+ this.emitMethodError('getTransactionByFilter', error);
897
+ throw error;
898
+ }
899
+ }
900
+ // ===== NEW ENHANCED SDK FUNCTIONS =====
901
+ /**
902
+ * Get balance for all verified ledgers for the connected wallet (public method)
903
+ */
904
+ async getAllLedgerBalances() {
905
+ this.emitMethodStart('getAllLedgerBalances');
906
+ try {
907
+ if (!this.isWalletConnected()) {
908
+ throw new errors_1.IcpayError({
909
+ code: 'WALLET_NOT_CONNECTED',
910
+ message: 'Wallet must be connected to fetch balances'
911
+ });
912
+ }
913
+ const verifiedLedgers = await this.getVerifiedLedgers();
914
+ const balances = [];
915
+ let totalBalancesUSD = 0;
916
+ for (const ledger of verifiedLedgers) {
917
+ try {
918
+ const rawBalance = await this.getLedgerBalance(ledger.canisterId);
919
+ const formattedBalance = this.formatBalance(rawBalance.toString(), ledger.decimals);
920
+ const balance = {
921
+ ledgerId: ledger.id,
922
+ ledgerName: ledger.name,
923
+ ledgerSymbol: ledger.symbol,
924
+ canisterId: ledger.canisterId,
925
+ balance: rawBalance.toString(),
926
+ formattedBalance,
927
+ decimals: ledger.decimals,
928
+ currentPrice: ledger.currentPrice || undefined,
929
+ lastPriceUpdate: ledger.lastPriceUpdate ? new Date(ledger.lastPriceUpdate) : undefined,
930
+ lastUpdated: new Date()
931
+ };
932
+ balances.push(balance);
933
+ // Calculate USD value if price is available
934
+ if (ledger.currentPrice && rawBalance > 0) {
935
+ const humanReadableBalance = parseFloat(formattedBalance);
936
+ totalBalancesUSD += humanReadableBalance * ledger.currentPrice;
937
+ }
938
+ }
939
+ catch (error) {
940
+ this.emit('icpay-sdk-method-error', {
941
+ name: 'getAllLedgerBalances.getLedgerBalance',
942
+ error,
943
+ ledgerSymbol: ledger.symbol,
944
+ ledgerCanisterId: ledger.canisterId
945
+ });
946
+ // Continue with other ledgers even if one fails
947
+ }
948
+ }
949
+ const result = {
950
+ balances,
951
+ totalBalancesUSD: totalBalancesUSD > 0 ? totalBalancesUSD : undefined,
952
+ lastUpdated: new Date()
953
+ };
954
+ this.emitMethodSuccess('getAllLedgerBalances', { count: balances.length, totalUSD: result.totalBalancesUSD });
955
+ return result;
956
+ }
957
+ catch (error) {
958
+ const err = new errors_1.IcpayError({
959
+ code: 'BALANCES_FETCH_FAILED',
960
+ message: 'Failed to fetch all ledger balances',
961
+ details: error
962
+ });
963
+ this.emitMethodError('getAllLedgerBalances', err);
964
+ throw err;
965
+ }
966
+ }
967
+ /**
968
+ * Get balance for a specific ledger by canister ID (public method)
969
+ */
970
+ async getSingleLedgerBalance(ledgerCanisterId) {
971
+ this.emitMethodStart('getSingleLedgerBalance', { ledgerCanisterId });
972
+ try {
973
+ if (!this.isWalletConnected()) {
974
+ throw new errors_1.IcpayError({
975
+ code: 'WALLET_NOT_CONNECTED',
976
+ message: 'Wallet must be connected to fetch balance'
977
+ });
978
+ }
979
+ // Get ledger info to include price data
980
+ const verifiedLedgers = await this.getVerifiedLedgers();
981
+ const ledger = verifiedLedgers.find(l => l.canisterId === ledgerCanisterId);
982
+ if (!ledger) {
983
+ throw new errors_1.IcpayError({
984
+ code: 'LEDGER_NOT_FOUND',
985
+ message: `Ledger with canister ID ${ledgerCanisterId} not found or not verified`
986
+ });
987
+ }
988
+ const rawBalance = await this.getLedgerBalance(ledgerCanisterId);
989
+ const formattedBalance = this.formatBalance(rawBalance.toString(), ledger.decimals);
990
+ const result = {
991
+ ledgerId: ledger.id,
992
+ ledgerName: ledger.name,
993
+ ledgerSymbol: ledger.symbol,
994
+ canisterId: ledger.canisterId,
995
+ balance: rawBalance.toString(),
996
+ formattedBalance,
997
+ decimals: ledger.decimals,
998
+ currentPrice: ledger.currentPrice || undefined,
999
+ lastPriceUpdate: ledger.lastPriceUpdate ? new Date(ledger.lastPriceUpdate) : undefined,
1000
+ lastUpdated: new Date()
1001
+ };
1002
+ this.emitMethodSuccess('getSingleLedgerBalance', { ledgerCanisterId, balance: result.balance });
1003
+ return result;
1004
+ }
1005
+ catch (error) {
1006
+ const err = new errors_1.IcpayError({
1007
+ code: 'SINGLE_BALANCE_FETCH_FAILED',
1008
+ message: `Failed to fetch balance for ledger ${ledgerCanisterId}`,
1009
+ details: error
1010
+ });
1011
+ this.emitMethodError('getSingleLedgerBalance', err);
1012
+ throw err;
1013
+ }
1014
+ }
1015
+ /**
1016
+ * Calculate token amount from USD price for a specific ledger (public method)
1017
+ */
1018
+ async calculateTokenAmountFromUSD(request) {
1019
+ this.emitMethodStart('calculateTokenAmountFromUSD', { usdAmount: request.usdAmount, ledgerCanisterId: request.ledgerCanisterId, ledgerSymbol: request.ledgerSymbol });
1020
+ try {
1021
+ const { usdAmount, ledgerCanisterId, ledgerSymbol } = request;
1022
+ if (usdAmount <= 0) {
1023
+ throw new errors_1.IcpayError({
1024
+ code: 'INVALID_USD_AMOUNT',
1025
+ message: 'USD amount must be greater than 0'
1026
+ });
1027
+ }
1028
+ // Get ledger info
1029
+ const verifiedLedgers = await this.getVerifiedLedgers();
1030
+ const ledger = verifiedLedgers.find(l => l.canisterId === ledgerCanisterId ||
1031
+ (ledgerSymbol && l.symbol === ledgerSymbol));
1032
+ if (!ledger) {
1033
+ throw new errors_1.IcpayError({
1034
+ code: 'LEDGER_NOT_FOUND',
1035
+ message: `Ledger not found for canister ID ${ledgerCanisterId} or symbol ${ledgerSymbol}`
1036
+ });
1037
+ }
1038
+ if (!ledger.currentPrice || ledger.currentPrice <= 0) {
1039
+ throw new errors_1.IcpayError({
1040
+ code: 'PRICE_NOT_AVAILABLE',
1041
+ message: `Current price not available for ledger ${ledger.symbol}`
1042
+ });
1043
+ }
1044
+ // Calculate token amount
1045
+ const tokenAmountHuman = usdAmount / ledger.currentPrice;
1046
+ // Convert to smallest unit and truncate decimals to get whole number for blockchain
1047
+ const tokenAmountDecimals = Math.floor(tokenAmountHuman * Math.pow(10, ledger.decimals)).toString();
1048
+ const result = {
1049
+ usdAmount,
1050
+ ledgerCanisterId: ledger.canisterId,
1051
+ ledgerSymbol: ledger.symbol,
1052
+ ledgerName: ledger.name,
1053
+ currentPrice: ledger.currentPrice,
1054
+ priceTimestamp: ledger.lastPriceUpdate ? new Date(ledger.lastPriceUpdate) : new Date(),
1055
+ tokenAmountHuman: tokenAmountHuman.toFixed(ledger.decimals),
1056
+ tokenAmountDecimals,
1057
+ decimals: ledger.decimals
1058
+ };
1059
+ this.emitMethodSuccess('calculateTokenAmountFromUSD', { ledgerCanisterId: result.ledgerCanisterId, tokenAmountDecimals });
1060
+ return result;
1061
+ }
1062
+ catch (error) {
1063
+ const err = new errors_1.IcpayError({
1064
+ code: 'PRICE_CALCULATION_FAILED',
1065
+ message: 'Failed to calculate token amount from USD',
1066
+ details: error
1067
+ });
1068
+ this.emitMethodError('calculateTokenAmountFromUSD', err);
1069
+ throw err;
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Get detailed ledger information including price data (public method)
1074
+ */
1075
+ async getLedgerInfo(ledgerCanisterId) {
1076
+ this.emitMethodStart('getLedgerInfo', { ledgerCanisterId });
1077
+ try {
1078
+ const response = await this.publicApiClient.get(`/sdk/public/ledgers/${ledgerCanisterId}`);
1079
+ const ledger = response.data;
1080
+ const result = {
1081
+ id: ledger.id,
1082
+ name: ledger.name,
1083
+ symbol: ledger.symbol,
1084
+ canisterId: ledger.canisterId,
1085
+ standard: ledger.standard,
1086
+ decimals: ledger.decimals,
1087
+ logoUrl: ledger.logoUrl ?? null,
1088
+ verified: ledger.verified,
1089
+ fee: ledger.fee ?? null,
1090
+ network: ledger.network,
1091
+ description: ledger.description ?? null,
1092
+ lastBlockIndex: ledger.lastBlockIndex ?? null,
1093
+ coingeckoId: ledger.coingeckoId ?? null,
1094
+ currentPrice: ledger.currentPrice ?? null,
1095
+ priceFetchMethod: ledger.priceFetchMethod ?? null,
1096
+ lastPriceUpdate: ledger.lastPriceUpdate ?? null,
1097
+ createdAt: ledger.createdAt,
1098
+ updatedAt: ledger.updatedAt,
1099
+ };
1100
+ this.emitMethodSuccess('getLedgerInfo', { ledgerCanisterId });
1101
+ return result;
1102
+ }
1103
+ catch (error) {
1104
+ const err = new errors_1.IcpayError({
1105
+ code: 'LEDGER_INFO_FETCH_FAILED',
1106
+ message: `Failed to fetch ledger info for ${ledgerCanisterId}`,
1107
+ details: error
1108
+ });
1109
+ this.emitMethodError('getLedgerInfo', err);
1110
+ throw err;
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Send funds from USD to a specific ledger (public method)
1115
+ */
1116
+ async sendFundsUsd(request) {
1117
+ this.emitMethodStart('sendFundsUsd', { request });
1118
+ try {
1119
+ // Convert usdAmount to number if it's a string
1120
+ const usdAmount = typeof request.usdAmount === 'string' ? parseFloat(request.usdAmount) : request.usdAmount;
1121
+ const priceCalculationResult = await this.calculateTokenAmountFromUSD({
1122
+ usdAmount: usdAmount,
1123
+ ledgerCanisterId: request.ledgerCanisterId
1124
+ });
1125
+ const createTransactionRequest = {
1126
+ ledgerCanisterId: request.ledgerCanisterId,
1127
+ amount: priceCalculationResult.tokenAmountDecimals,
1128
+ accountCanisterId: request.accountCanisterId,
1129
+ metadata: request.metadata
1130
+ };
1131
+ const res = await this.sendFunds(createTransactionRequest);
1132
+ this.emitMethodSuccess('sendFundsUsd', res);
1133
+ return res;
1134
+ }
1135
+ catch (error) {
1136
+ if (error instanceof errors_1.IcpayError) {
1137
+ this.emitMethodError('sendFundsUsd', error);
1138
+ throw error;
1139
+ }
1140
+ const err = new errors_1.IcpayError({
1141
+ code: 'SEND_FUNDS_USD_FAILED',
1142
+ message: 'Failed to send funds from USD',
1143
+ details: error
1144
+ });
1145
+ this.emitMethodError('sendFundsUsd', err);
1146
+ throw err;
1147
+ }
1148
+ }
1149
+ /**
1150
+ * Get all ledgers with price information (public method)
1151
+ */
1152
+ async getAllLedgersWithPrices() {
1153
+ this.emitMethodStart('getAllLedgersWithPrices');
1154
+ try {
1155
+ const response = await this.publicApiClient.get('/sdk/public/ledgers/all-with-prices');
1156
+ const result = response.data.map((ledger) => ({
1157
+ id: ledger.id,
1158
+ name: ledger.name,
1159
+ symbol: ledger.symbol,
1160
+ canisterId: ledger.canisterId,
1161
+ standard: ledger.standard,
1162
+ decimals: ledger.decimals,
1163
+ logoUrl: ledger.logoUrl ?? null,
1164
+ verified: ledger.verified,
1165
+ fee: ledger.fee ?? null,
1166
+ network: ledger.network,
1167
+ description: ledger.description ?? null,
1168
+ lastBlockIndex: ledger.lastBlockIndex ?? null,
1169
+ coingeckoId: ledger.coingeckoId ?? null,
1170
+ currentPrice: ledger.currentPrice ?? null,
1171
+ priceFetchMethod: ledger.priceFetchMethod ?? null,
1172
+ lastPriceUpdate: ledger.lastPriceUpdate ?? null,
1173
+ createdAt: ledger.createdAt,
1174
+ updatedAt: ledger.updatedAt,
1175
+ }));
1176
+ this.emitMethodSuccess('getAllLedgersWithPrices', { count: result.length });
1177
+ return result;
1178
+ }
1179
+ catch (error) {
1180
+ const err = new errors_1.IcpayError({
1181
+ code: 'LEDGERS_WITH_PRICES_FETCH_FAILED',
1182
+ message: 'Failed to fetch ledgers with price information',
1183
+ details: error
1184
+ });
1185
+ this.emitMethodError('getAllLedgersWithPrices', err);
1186
+ throw err;
1187
+ }
1188
+ }
1189
+ /**
1190
+ * Utility function to format balance from smallest unit to human readable
1191
+ */
1192
+ formatBalance(balance, decimals) {
1193
+ const balanceNum = parseFloat(balance);
1194
+ const divisor = Math.pow(10, decimals);
1195
+ const whole = Math.floor(balanceNum / divisor);
1196
+ const fraction = balanceNum % divisor;
1197
+ const fractionStr = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
1198
+ return `${whole}${fractionStr ? '.' + fractionStr : ''}`;
1199
+ }
1200
+ }
1201
+ exports.Icpay = Icpay;
1202
+ // Export types and classes
1203
+ __exportStar(require("./types"), exports);
1204
+ var errors_2 = require("./errors");
1205
+ Object.defineProperty(exports, "IcpayError", { enumerable: true, get: function () { return errors_2.IcpayError; } });
1206
+ var wallet_2 = require("./wallet");
1207
+ Object.defineProperty(exports, "IcpayWallet", { enumerable: true, get: function () { return wallet_2.IcpayWallet; } });
1208
+ __exportStar(require("./events"), exports);
1209
+ // Default export
1210
+ exports.default = Icpay;
1211
+ //# sourceMappingURL=index.js.map