@zendfi/sdk 0.5.8 → 0.6.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/README.md CHANGED
@@ -38,20 +38,20 @@ console.log(payment.payment_url); // Send customer here
38
38
 
39
39
  ## Features
40
40
 
41
- ### 🚀 **Core Payments** (Start Here)
41
+ ### **Core Payments** (Start Here)
42
42
  - **Simple Payments** — QR codes, payment links, instant settlements
43
43
  - **Payment Links** — Reusable checkout pages for social/email
44
44
  - **Webhooks** — Real-time notifications with auto-verification
45
45
  - **Test Mode** — Free devnet testing with no real money
46
46
  - **Type-Safe** — Full TypeScript support with auto-completion
47
47
 
48
- ### 💼 **Scale Up** (When You Grow)
48
+ ### **Scale Up** (When You Grow)
49
49
  - **Subscriptions** — Recurring billing with trials
50
50
  - **Installments** — Buy now, pay later flows
51
51
  - **Invoices** — Professional invoicing with email
52
52
  - **Payment Splits** — Revenue sharing for marketplaces
53
53
 
54
- ### 🤖 **AI-Ready** (Optional Advanced)
54
+ ### **AI-Ready** (Optional Advanced)
55
55
  - **Agent Keys** — Scoped API keys for AI with spending limits
56
56
  - **Session Keys** — Pre-funded wallets for autonomous payments
57
57
  - **Payment Intents** — Two-phase commit for reliable checkout
@@ -226,7 +226,7 @@ const payment = await zendfi.agent.pay({
226
226
 
227
227
  ---
228
228
 
229
- ## 📚 Core API Reference
229
+ ## Core API Reference
230
230
 
231
231
  ### Namespaced APIs
232
232
 
@@ -399,6 +399,111 @@ const suggestion = await zendfi.pricing.getSuggestion({
399
399
  **Supported Countries (27+):**
400
400
  Argentina, Australia, Brazil, Canada, China, Colombia, Egypt, France, Germany, Ghana, Hong Kong, Hungary, India, Indonesia, Israel, Japan, Kenya, Mexico, Nigeria, Philippines, Poland, South Africa, Thailand, Turkey, Ukraine, United Kingdom, Vietnam, and more.
401
401
 
402
+ ---
403
+
404
+ ## Optional Helper Utilities
405
+
406
+ Production-ready utilities to simplify common integration patterns. All helpers are **optional**, **tree-shakeable**, and **zero-config**.
407
+
408
+ ```typescript
409
+ import {
410
+ SessionKeyCache,
411
+ WalletConnector,
412
+ TransactionPoller,
413
+ DevTools
414
+ } from '@zendfi/sdk/helpers';
415
+ ```
416
+
417
+ ### Why Use Helpers?
418
+
419
+ - **Optional**: Import only what you need
420
+ - **Tree-shakeable**: Unused code eliminated by bundlers
421
+ - **Zero config**: Sensible defaults, works out of the box
422
+ - **Pluggable**: Bring your own storage/AI/PIN providers
423
+ - **Production-ready**: Full TypeScript types, error handling
424
+
425
+ ### Available Helpers
426
+
427
+ | Helper | Purpose | Use Case |
428
+ |--------|---------|----------|
429
+ | `SessionKeyCache` | Cache encrypted session keys | Avoid re-prompting for PIN |
430
+ | `WalletConnector` | Detect & connect Solana wallets | Phantom, Solflare, Backpack |
431
+ | `PaymentIntentParser` | Parse natural language to payments | AI chat interfaces |
432
+ | `PINValidator` | Validate PIN strength | Device-bound security |
433
+ | `TransactionPoller` | Poll for confirmations | Wait for on-chain finality |
434
+ | `RetryStrategy` | Exponential backoff retries | Handle network failures |
435
+ | `SessionKeyLifecycle` | High-level session key manager | One-liner setup |
436
+ | `DevTools` | Debug mode & test utilities | Development & testing |
437
+
438
+ ### Session Key Cache
439
+
440
+ Cache encrypted session keys to avoid re-prompting users for their PIN:
441
+
442
+ ```typescript
443
+ import { SessionKeyCache, QuickCaches } from '@zendfi/sdk/helpers';
444
+
445
+ // Use presets
446
+ const cache = QuickCaches.persistent(); // 1 hour localStorage
447
+
448
+ // Use with device-bound session keys
449
+ const keypair = await cache.getCached(
450
+ sessionKeyId,
451
+ async () => {
452
+ const pin = await promptUserForPIN();
453
+ return await decryptKeypair(pin);
454
+ }
455
+ );
456
+ ```
457
+
458
+ ### Wallet Connector
459
+
460
+ Auto-detect and connect to Solana wallets:
461
+
462
+ ```typescript
463
+ import { WalletConnector } from '@zendfi/sdk/helpers';
464
+
465
+ const wallet = await WalletConnector.detectAndConnect();
466
+ console.log(wallet.address);
467
+
468
+ const signedTx = await wallet.signTransaction(transaction);
469
+ ```
470
+
471
+ ### Transaction Polling
472
+
473
+ Wait for confirmations with exponential backoff:
474
+
475
+ ```typescript
476
+ import { TransactionPoller } from '@zendfi/sdk/helpers';
477
+
478
+ const poller = new TransactionPoller({ connection: rpcConnection });
479
+ const result = await poller.waitForConfirmation(signature);
480
+ ```
481
+
482
+ ### Session Key Lifecycle
483
+
484
+ High-level wrapper for complete session key management:
485
+
486
+ ```typescript
487
+ import { SessionKeyLifecycle } from '@zendfi/sdk/helpers';
488
+
489
+ const lifecycle = new SessionKeyLifecycle(zendfi, {
490
+ cache: QuickCaches.persistent(),
491
+ autoCleanup: true,
492
+ });
493
+
494
+ await lifecycle.createAndFund({
495
+ userWallet: userAddress,
496
+ agentId: 'my-agent',
497
+ limitUsdc: 100,
498
+ });
499
+
500
+ await lifecycle.pay(5.00, 'Coffee');
501
+ ```
502
+
503
+ **Full documentation:** See [Helper Utilities Guide](https://docs.zendfi.tech/helpers) for complete API reference and examples.
504
+
505
+ ---
506
+
402
507
  ### Autonomous Delegation
403
508
 
404
509
  Enable agents to make payments without per-transaction approval:
@@ -0,0 +1,9 @@
1
+ import {
2
+ QuickCaches,
3
+ SessionKeyCache
4
+ } from "./chunk-5O5NAX65.mjs";
5
+ import "./chunk-Y6FXYEAI.mjs";
6
+ export {
7
+ QuickCaches,
8
+ SessionKeyCache
9
+ };
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/webhook-handler.ts
9
2
  import { createHmac, timingSafeEqual } from "crypto";
10
3
  var processedWebhooks = /* @__PURE__ */ new Set();
@@ -147,6 +140,5 @@ async function processWebhook(a, b, c) {
147
140
  }
148
141
 
149
142
  export {
150
- __require,
151
143
  processWebhook
152
144
  };
@@ -0,0 +1,366 @@
1
+ // src/helpers/cache.ts
2
+ var SessionKeyCache = class {
3
+ memoryCache = /* @__PURE__ */ new Map();
4
+ config;
5
+ refreshTimers = /* @__PURE__ */ new Map();
6
+ constructor(config = {}) {
7
+ this.config = {
8
+ storage: config.storage || "memory",
9
+ ttl: config.ttl || 30 * 60 * 1e3,
10
+ // 30 minutes default
11
+ autoRefresh: config.autoRefresh || false,
12
+ namespace: config.namespace || "zendfi_cache",
13
+ debug: config.debug || false
14
+ };
15
+ }
16
+ /**
17
+ * Get cached keypair or decrypt and cache
18
+ */
19
+ async getCached(sessionKeyId, decryptFn, options) {
20
+ this.log(`getCached: ${sessionKeyId}`);
21
+ const memoryCached = this.memoryCache.get(sessionKeyId);
22
+ if (memoryCached && Date.now() < memoryCached.expiry) {
23
+ this.log(`Memory cache HIT: ${sessionKeyId}`);
24
+ return memoryCached.keypair;
25
+ }
26
+ if (this.config.storage !== "memory") {
27
+ const persistentCached = await this.getFromStorage(sessionKeyId);
28
+ if (persistentCached && Date.now() < persistentCached.expiry) {
29
+ if (options?.deviceFingerprint && persistentCached.deviceFingerprint) {
30
+ if (options.deviceFingerprint !== persistentCached.deviceFingerprint) {
31
+ this.log(`Device fingerprint mismatch for ${sessionKeyId}`);
32
+ await this.invalidate(sessionKeyId);
33
+ return await this.decryptAndCache(sessionKeyId, decryptFn, options);
34
+ }
35
+ }
36
+ this.log(`Persistent cache HIT: ${sessionKeyId}`);
37
+ this.memoryCache.set(sessionKeyId, persistentCached);
38
+ return persistentCached.keypair;
39
+ }
40
+ }
41
+ this.log(`Cache MISS: ${sessionKeyId}`);
42
+ return await this.decryptAndCache(sessionKeyId, decryptFn, options);
43
+ }
44
+ /**
45
+ * Decrypt keypair and cache it
46
+ */
47
+ async decryptAndCache(sessionKeyId, decryptFn, options) {
48
+ const keypair = await decryptFn();
49
+ const expiry = Date.now() + this.config.ttl;
50
+ const cached = {
51
+ keypair,
52
+ expiry,
53
+ sessionKeyId,
54
+ deviceFingerprint: options?.deviceFingerprint
55
+ };
56
+ this.memoryCache.set(sessionKeyId, cached);
57
+ if (this.config.storage !== "memory") {
58
+ await this.setInStorage(sessionKeyId, cached);
59
+ }
60
+ if (this.config.autoRefresh) {
61
+ this.setupAutoRefresh(sessionKeyId, decryptFn, options);
62
+ }
63
+ this.log(`Cached: ${sessionKeyId}, expires in ${this.config.ttl}ms`);
64
+ return keypair;
65
+ }
66
+ /**
67
+ * Invalidate cached keypair
68
+ */
69
+ async invalidate(sessionKeyId) {
70
+ this.log(`Invalidating: ${sessionKeyId}`);
71
+ this.memoryCache.delete(sessionKeyId);
72
+ const timer = this.refreshTimers.get(sessionKeyId);
73
+ if (timer) {
74
+ clearTimeout(timer);
75
+ this.refreshTimers.delete(sessionKeyId);
76
+ }
77
+ if (this.config.storage !== "memory") {
78
+ await this.removeFromStorage(sessionKeyId);
79
+ }
80
+ }
81
+ /**
82
+ * Clear all cached keypairs
83
+ */
84
+ async clear() {
85
+ this.log("Clearing all cache");
86
+ this.memoryCache.clear();
87
+ for (const timer of this.refreshTimers.values()) {
88
+ clearTimeout(timer);
89
+ }
90
+ this.refreshTimers.clear();
91
+ if (this.config.storage !== "memory") {
92
+ await this.clearStorage();
93
+ }
94
+ }
95
+ /**
96
+ * Get cache statistics
97
+ */
98
+ getStats() {
99
+ const entries = Array.from(this.memoryCache.entries()).map(([id, cached]) => ({
100
+ sessionKeyId: id,
101
+ expiresIn: Math.max(0, cached.expiry - Date.now())
102
+ }));
103
+ return { size: this.memoryCache.size, entries };
104
+ }
105
+ /**
106
+ * Check if a session key is cached and valid
107
+ */
108
+ isCached(sessionKeyId) {
109
+ const cached = this.memoryCache.get(sessionKeyId);
110
+ return cached ? Date.now() < cached.expiry : false;
111
+ }
112
+ /**
113
+ * Update TTL for a cached session key
114
+ */
115
+ async extendTTL(sessionKeyId, additionalMs) {
116
+ const cached = this.memoryCache.get(sessionKeyId);
117
+ if (!cached) return false;
118
+ cached.expiry += additionalMs;
119
+ this.memoryCache.set(sessionKeyId, cached);
120
+ if (this.config.storage !== "memory") {
121
+ await this.setInStorage(sessionKeyId, cached);
122
+ }
123
+ this.log(`Extended TTL for ${sessionKeyId} by ${additionalMs}ms`);
124
+ return true;
125
+ }
126
+ // ============================================
127
+ // Storage Backend Implementations
128
+ // ============================================
129
+ async getFromStorage(sessionKeyId) {
130
+ try {
131
+ const key = this.getStorageKey(sessionKeyId);
132
+ if (this.config.storage === "localStorage") {
133
+ const data = localStorage.getItem(key);
134
+ if (!data) return null;
135
+ const parsed = JSON.parse(data);
136
+ return {
137
+ ...parsed,
138
+ keypair: this.deserializeKeypair(parsed.keypair)
139
+ };
140
+ }
141
+ if (this.config.storage === "indexedDB") {
142
+ return await this.getFromIndexedDB(key);
143
+ }
144
+ if (typeof this.config.storage === "object") {
145
+ const data = await this.config.storage.get(key);
146
+ if (!data) return null;
147
+ const parsed = JSON.parse(data);
148
+ return {
149
+ ...parsed,
150
+ keypair: this.deserializeKeypair(parsed.keypair)
151
+ };
152
+ }
153
+ } catch (error) {
154
+ this.log(`Error reading from storage: ${error}`);
155
+ }
156
+ return null;
157
+ }
158
+ async setInStorage(sessionKeyId, cached) {
159
+ try {
160
+ const key = this.getStorageKey(sessionKeyId);
161
+ const serialized = {
162
+ ...cached,
163
+ keypair: this.serializeKeypair(cached.keypair)
164
+ };
165
+ if (this.config.storage === "localStorage") {
166
+ localStorage.setItem(key, JSON.stringify(serialized));
167
+ return;
168
+ }
169
+ if (this.config.storage === "indexedDB") {
170
+ await this.setInIndexedDB(key, serialized);
171
+ return;
172
+ }
173
+ if (typeof this.config.storage === "object") {
174
+ await this.config.storage.set(key, JSON.stringify(serialized));
175
+ }
176
+ } catch (error) {
177
+ this.log(`Error writing to storage: ${error}`);
178
+ }
179
+ }
180
+ async removeFromStorage(sessionKeyId) {
181
+ try {
182
+ const key = this.getStorageKey(sessionKeyId);
183
+ if (this.config.storage === "localStorage") {
184
+ localStorage.removeItem(key);
185
+ return;
186
+ }
187
+ if (this.config.storage === "indexedDB") {
188
+ await this.removeFromIndexedDB(key);
189
+ return;
190
+ }
191
+ if (typeof this.config.storage === "object") {
192
+ await this.config.storage.remove(key);
193
+ }
194
+ } catch (error) {
195
+ this.log(`Error removing from storage: ${error}`);
196
+ }
197
+ }
198
+ async clearStorage() {
199
+ try {
200
+ if (this.config.storage === "localStorage") {
201
+ const keysToRemove = [];
202
+ for (let i = 0; i < localStorage.length; i++) {
203
+ const key = localStorage.key(i);
204
+ if (key?.startsWith(this.config.namespace)) {
205
+ keysToRemove.push(key);
206
+ }
207
+ }
208
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
209
+ return;
210
+ }
211
+ if (this.config.storage === "indexedDB") {
212
+ await this.clearIndexedDB();
213
+ return;
214
+ }
215
+ if (typeof this.config.storage === "object") {
216
+ await this.config.storage.clear();
217
+ }
218
+ } catch (error) {
219
+ this.log(`Error clearing storage: ${error}`);
220
+ }
221
+ }
222
+ // ============================================
223
+ // IndexedDB Helpers
224
+ // ============================================
225
+ async getFromIndexedDB(key) {
226
+ return new Promise((resolve) => {
227
+ const request = indexedDB.open(this.config.namespace, 1);
228
+ request.onerror = () => resolve(null);
229
+ request.onupgradeneeded = (event) => {
230
+ const db = event.target.result;
231
+ if (!db.objectStoreNames.contains("cache")) {
232
+ db.createObjectStore("cache");
233
+ }
234
+ };
235
+ request.onsuccess = (event) => {
236
+ const db = event.target.result;
237
+ const transaction = db.transaction(["cache"], "readonly");
238
+ const store = transaction.objectStore("cache");
239
+ const getRequest = store.get(key);
240
+ getRequest.onsuccess = () => {
241
+ resolve(getRequest.result || null);
242
+ };
243
+ getRequest.onerror = () => resolve(null);
244
+ };
245
+ });
246
+ }
247
+ async setInIndexedDB(key, value) {
248
+ return new Promise((resolve, reject) => {
249
+ const request = indexedDB.open(this.config.namespace, 1);
250
+ request.onerror = () => reject(new Error("IndexedDB error"));
251
+ request.onupgradeneeded = (event) => {
252
+ const db = event.target.result;
253
+ if (!db.objectStoreNames.contains("cache")) {
254
+ db.createObjectStore("cache");
255
+ }
256
+ };
257
+ request.onsuccess = (event) => {
258
+ const db = event.target.result;
259
+ const transaction = db.transaction(["cache"], "readwrite");
260
+ const store = transaction.objectStore("cache");
261
+ store.put(value, key);
262
+ transaction.oncomplete = () => resolve();
263
+ transaction.onerror = () => reject(new Error("IndexedDB transaction error"));
264
+ };
265
+ });
266
+ }
267
+ async removeFromIndexedDB(key) {
268
+ return new Promise((resolve) => {
269
+ const request = indexedDB.open(this.config.namespace, 1);
270
+ request.onsuccess = (event) => {
271
+ const db = event.target.result;
272
+ const transaction = db.transaction(["cache"], "readwrite");
273
+ const store = transaction.objectStore("cache");
274
+ store.delete(key);
275
+ transaction.oncomplete = () => resolve();
276
+ };
277
+ request.onerror = () => resolve();
278
+ });
279
+ }
280
+ async clearIndexedDB() {
281
+ return new Promise((resolve) => {
282
+ const request = indexedDB.open(this.config.namespace, 1);
283
+ request.onsuccess = (event) => {
284
+ const db = event.target.result;
285
+ const transaction = db.transaction(["cache"], "readwrite");
286
+ const store = transaction.objectStore("cache");
287
+ store.clear();
288
+ transaction.oncomplete = () => resolve();
289
+ };
290
+ request.onerror = () => resolve();
291
+ });
292
+ }
293
+ // ============================================
294
+ // Serialization
295
+ // ============================================
296
+ serializeKeypair(keypair) {
297
+ if (keypair && typeof keypair === "object" && "secretKey" in keypair) {
298
+ return {
299
+ type: "solana",
300
+ secretKey: Array.from(keypair.secretKey)
301
+ };
302
+ }
303
+ if (keypair instanceof Uint8Array) {
304
+ return {
305
+ type: "uint8array",
306
+ data: Array.from(keypair)
307
+ };
308
+ }
309
+ return keypair;
310
+ }
311
+ deserializeKeypair(data) {
312
+ if (!data || typeof data !== "object") return data;
313
+ if (data.type === "solana" && data.secretKey) {
314
+ return new Uint8Array(data.secretKey);
315
+ }
316
+ if (data.type === "uint8array" && data.data) {
317
+ return new Uint8Array(data.data);
318
+ }
319
+ return data;
320
+ }
321
+ // ============================================
322
+ // Auto-Refresh
323
+ // ============================================
324
+ setupAutoRefresh(sessionKeyId, decryptFn, options) {
325
+ const existingTimer = this.refreshTimers.get(sessionKeyId);
326
+ if (existingTimer) {
327
+ clearTimeout(existingTimer);
328
+ }
329
+ const refreshIn = Math.max(0, this.config.ttl - 5 * 60 * 1e3);
330
+ const timer = setTimeout(async () => {
331
+ this.log(`Auto-refreshing: ${sessionKeyId}`);
332
+ try {
333
+ await this.decryptAndCache(sessionKeyId, decryptFn, options);
334
+ } catch (error) {
335
+ this.log(`Auto-refresh failed: ${error}`);
336
+ }
337
+ }, refreshIn);
338
+ this.refreshTimers.set(sessionKeyId, timer);
339
+ }
340
+ // ============================================
341
+ // Utilities
342
+ // ============================================
343
+ getStorageKey(sessionKeyId) {
344
+ return `${this.config.namespace}:${sessionKeyId}`;
345
+ }
346
+ log(message) {
347
+ if (this.config.debug) {
348
+ console.log(`[SessionKeyCache] ${message}`);
349
+ }
350
+ }
351
+ };
352
+ var QuickCaches = {
353
+ /** Memory-only cache (30 minutes) */
354
+ memory: () => new SessionKeyCache({ storage: "memory", ttl: 30 * 60 * 1e3 }),
355
+ /** Persistent cache (1 hour, survives reload) */
356
+ persistent: () => new SessionKeyCache({ storage: "localStorage", ttl: 60 * 60 * 1e3 }),
357
+ /** Long-term cache (24 hours, IndexedDB) */
358
+ longTerm: () => new SessionKeyCache({ storage: "indexedDB", ttl: 24 * 60 * 60 * 1e3, autoRefresh: true }),
359
+ /** Secure cache (5 minutes, memory-only) */
360
+ secure: () => new SessionKeyCache({ storage: "memory", ttl: 5 * 60 * 1e3 })
361
+ };
362
+
363
+ export {
364
+ SessionKeyCache,
365
+ QuickCaches
366
+ };