@zendfi/sdk 0.8.4 → 1.0.1
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 +45 -437
- package/dist/chunk-DAJL2Q36.mjs +907 -0
- package/dist/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/helpers/index.d.mts +402 -0
- package/dist/helpers/index.d.ts +402 -0
- package/dist/helpers/index.js +944 -0
- package/dist/helpers/index.mjs +17 -0
- package/dist/index.d.mts +468 -3121
- package/dist/index.d.ts +468 -3121
- package/dist/index.js +421 -4020
- package/dist/index.mjs +1188 -5267
- package/dist/nextjs.d.mts +1 -1
- package/dist/nextjs.d.ts +1 -1
- package/dist/webhook-handler-61UWBtDI.d.mts +339 -0
- package/dist/webhook-handler-61UWBtDI.d.ts +339 -0
- package/package.json +19 -15
- package/README.md.old +0 -326
- package/dist/cache-T5YPC7OK.mjs +0 -9
- package/dist/chunk-5O5NAX65.mjs +0 -366
- package/dist/webhook-handler-CdtQHVU5.d.mts +0 -1130
- package/dist/webhook-handler-CdtQHVU5.d.ts +0 -1130
package/dist/index.js
CHANGED
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
8
|
var __export = (target, all) => {
|
|
12
9
|
for (var name in all)
|
|
13
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,417 +27,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
27
|
));
|
|
31
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
29
|
|
|
33
|
-
// src/helpers/cache.ts
|
|
34
|
-
var cache_exports = {};
|
|
35
|
-
__export(cache_exports, {
|
|
36
|
-
QuickCaches: () => QuickCaches,
|
|
37
|
-
SessionKeyCache: () => SessionKeyCache
|
|
38
|
-
});
|
|
39
|
-
var SessionKeyCache, QuickCaches;
|
|
40
|
-
var init_cache = __esm({
|
|
41
|
-
"src/helpers/cache.ts"() {
|
|
42
|
-
"use strict";
|
|
43
|
-
SessionKeyCache = class {
|
|
44
|
-
memoryCache = /* @__PURE__ */ new Map();
|
|
45
|
-
config;
|
|
46
|
-
refreshTimers = /* @__PURE__ */ new Map();
|
|
47
|
-
constructor(config = {}) {
|
|
48
|
-
this.config = {
|
|
49
|
-
storage: config.storage || "memory",
|
|
50
|
-
ttl: config.ttl || 30 * 60 * 1e3,
|
|
51
|
-
// 30 minutes default
|
|
52
|
-
autoRefresh: config.autoRefresh || false,
|
|
53
|
-
namespace: config.namespace || "zendfi_cache",
|
|
54
|
-
debug: config.debug || false
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Get cached keypair or decrypt and cache
|
|
59
|
-
*/
|
|
60
|
-
async getCached(sessionKeyId, decryptFn, options) {
|
|
61
|
-
this.log(`getCached: ${sessionKeyId}`);
|
|
62
|
-
const memoryCached = this.memoryCache.get(sessionKeyId);
|
|
63
|
-
if (memoryCached && Date.now() < memoryCached.expiry) {
|
|
64
|
-
this.log(`Memory cache HIT: ${sessionKeyId}`);
|
|
65
|
-
return memoryCached.keypair;
|
|
66
|
-
}
|
|
67
|
-
if (this.config.storage !== "memory") {
|
|
68
|
-
const persistentCached = await this.getFromStorage(sessionKeyId);
|
|
69
|
-
if (persistentCached && Date.now() < persistentCached.expiry) {
|
|
70
|
-
if (options?.deviceFingerprint && persistentCached.deviceFingerprint) {
|
|
71
|
-
if (options.deviceFingerprint !== persistentCached.deviceFingerprint) {
|
|
72
|
-
this.log(`Device fingerprint mismatch for ${sessionKeyId}`);
|
|
73
|
-
await this.invalidate(sessionKeyId);
|
|
74
|
-
return await this.decryptAndCache(sessionKeyId, decryptFn, options);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
this.log(`Persistent cache HIT: ${sessionKeyId}`);
|
|
78
|
-
this.memoryCache.set(sessionKeyId, persistentCached);
|
|
79
|
-
return persistentCached.keypair;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
this.log(`Cache MISS: ${sessionKeyId}`);
|
|
83
|
-
return await this.decryptAndCache(sessionKeyId, decryptFn, options);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Decrypt keypair and cache it
|
|
87
|
-
*/
|
|
88
|
-
async decryptAndCache(sessionKeyId, decryptFn, options) {
|
|
89
|
-
const keypair = await decryptFn();
|
|
90
|
-
const expiry = Date.now() + this.config.ttl;
|
|
91
|
-
const cached = {
|
|
92
|
-
keypair,
|
|
93
|
-
expiry,
|
|
94
|
-
sessionKeyId,
|
|
95
|
-
deviceFingerprint: options?.deviceFingerprint
|
|
96
|
-
};
|
|
97
|
-
this.memoryCache.set(sessionKeyId, cached);
|
|
98
|
-
if (this.config.storage !== "memory") {
|
|
99
|
-
await this.setInStorage(sessionKeyId, cached);
|
|
100
|
-
}
|
|
101
|
-
if (this.config.autoRefresh) {
|
|
102
|
-
this.setupAutoRefresh(sessionKeyId, decryptFn, options);
|
|
103
|
-
}
|
|
104
|
-
this.log(`Cached: ${sessionKeyId}, expires in ${this.config.ttl}ms`);
|
|
105
|
-
return keypair;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Invalidate cached keypair
|
|
109
|
-
*/
|
|
110
|
-
async invalidate(sessionKeyId) {
|
|
111
|
-
this.log(`Invalidating: ${sessionKeyId}`);
|
|
112
|
-
this.memoryCache.delete(sessionKeyId);
|
|
113
|
-
const timer = this.refreshTimers.get(sessionKeyId);
|
|
114
|
-
if (timer) {
|
|
115
|
-
clearTimeout(timer);
|
|
116
|
-
this.refreshTimers.delete(sessionKeyId);
|
|
117
|
-
}
|
|
118
|
-
if (this.config.storage !== "memory") {
|
|
119
|
-
await this.removeFromStorage(sessionKeyId);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Clear all cached keypairs
|
|
124
|
-
*/
|
|
125
|
-
async clear() {
|
|
126
|
-
this.log("Clearing all cache");
|
|
127
|
-
this.memoryCache.clear();
|
|
128
|
-
for (const timer of this.refreshTimers.values()) {
|
|
129
|
-
clearTimeout(timer);
|
|
130
|
-
}
|
|
131
|
-
this.refreshTimers.clear();
|
|
132
|
-
if (this.config.storage !== "memory") {
|
|
133
|
-
await this.clearStorage();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Get cache statistics
|
|
138
|
-
*/
|
|
139
|
-
getStats() {
|
|
140
|
-
const entries = Array.from(this.memoryCache.entries()).map(([id, cached]) => ({
|
|
141
|
-
sessionKeyId: id,
|
|
142
|
-
expiresIn: Math.max(0, cached.expiry - Date.now())
|
|
143
|
-
}));
|
|
144
|
-
return { size: this.memoryCache.size, entries };
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Check if a session key is cached and valid
|
|
148
|
-
*/
|
|
149
|
-
isCached(sessionKeyId) {
|
|
150
|
-
const cached = this.memoryCache.get(sessionKeyId);
|
|
151
|
-
return cached ? Date.now() < cached.expiry : false;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Update TTL for a cached session key
|
|
155
|
-
*/
|
|
156
|
-
async extendTTL(sessionKeyId, additionalMs) {
|
|
157
|
-
const cached = this.memoryCache.get(sessionKeyId);
|
|
158
|
-
if (!cached) return false;
|
|
159
|
-
cached.expiry += additionalMs;
|
|
160
|
-
this.memoryCache.set(sessionKeyId, cached);
|
|
161
|
-
if (this.config.storage !== "memory") {
|
|
162
|
-
await this.setInStorage(sessionKeyId, cached);
|
|
163
|
-
}
|
|
164
|
-
this.log(`Extended TTL for ${sessionKeyId} by ${additionalMs}ms`);
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
// ============================================
|
|
168
|
-
// Storage Backend Implementations
|
|
169
|
-
// ============================================
|
|
170
|
-
async getFromStorage(sessionKeyId) {
|
|
171
|
-
try {
|
|
172
|
-
const key = this.getStorageKey(sessionKeyId);
|
|
173
|
-
if (this.config.storage === "localStorage") {
|
|
174
|
-
const data = localStorage.getItem(key);
|
|
175
|
-
if (!data) return null;
|
|
176
|
-
const parsed = JSON.parse(data);
|
|
177
|
-
return {
|
|
178
|
-
...parsed,
|
|
179
|
-
keypair: this.deserializeKeypair(parsed.keypair)
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (this.config.storage === "indexedDB") {
|
|
183
|
-
return await this.getFromIndexedDB(key);
|
|
184
|
-
}
|
|
185
|
-
if (typeof this.config.storage === "object") {
|
|
186
|
-
const data = await this.config.storage.get(key);
|
|
187
|
-
if (!data) return null;
|
|
188
|
-
const parsed = JSON.parse(data);
|
|
189
|
-
return {
|
|
190
|
-
...parsed,
|
|
191
|
-
keypair: this.deserializeKeypair(parsed.keypair)
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
} catch (error) {
|
|
195
|
-
this.log(`Error reading from storage: ${error}`);
|
|
196
|
-
}
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
async setInStorage(sessionKeyId, cached) {
|
|
200
|
-
try {
|
|
201
|
-
const key = this.getStorageKey(sessionKeyId);
|
|
202
|
-
const serialized = {
|
|
203
|
-
...cached,
|
|
204
|
-
keypair: this.serializeKeypair(cached.keypair)
|
|
205
|
-
};
|
|
206
|
-
if (this.config.storage === "localStorage") {
|
|
207
|
-
localStorage.setItem(key, JSON.stringify(serialized));
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (this.config.storage === "indexedDB") {
|
|
211
|
-
await this.setInIndexedDB(key, serialized);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (typeof this.config.storage === "object") {
|
|
215
|
-
await this.config.storage.set(key, JSON.stringify(serialized));
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
this.log(`Error writing to storage: ${error}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
async removeFromStorage(sessionKeyId) {
|
|
222
|
-
try {
|
|
223
|
-
const key = this.getStorageKey(sessionKeyId);
|
|
224
|
-
if (this.config.storage === "localStorage") {
|
|
225
|
-
localStorage.removeItem(key);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
if (this.config.storage === "indexedDB") {
|
|
229
|
-
await this.removeFromIndexedDB(key);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (typeof this.config.storage === "object") {
|
|
233
|
-
await this.config.storage.remove(key);
|
|
234
|
-
}
|
|
235
|
-
} catch (error) {
|
|
236
|
-
this.log(`Error removing from storage: ${error}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
async clearStorage() {
|
|
240
|
-
try {
|
|
241
|
-
if (this.config.storage === "localStorage") {
|
|
242
|
-
const keysToRemove = [];
|
|
243
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
244
|
-
const key = localStorage.key(i);
|
|
245
|
-
if (key?.startsWith(this.config.namespace)) {
|
|
246
|
-
keysToRemove.push(key);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (this.config.storage === "indexedDB") {
|
|
253
|
-
await this.clearIndexedDB();
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (typeof this.config.storage === "object") {
|
|
257
|
-
await this.config.storage.clear();
|
|
258
|
-
}
|
|
259
|
-
} catch (error) {
|
|
260
|
-
this.log(`Error clearing storage: ${error}`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// ============================================
|
|
264
|
-
// IndexedDB Helpers
|
|
265
|
-
// ============================================
|
|
266
|
-
async getFromIndexedDB(key) {
|
|
267
|
-
return new Promise((resolve) => {
|
|
268
|
-
const request = indexedDB.open(this.config.namespace, 1);
|
|
269
|
-
request.onerror = () => resolve(null);
|
|
270
|
-
request.onupgradeneeded = (event) => {
|
|
271
|
-
const db = event.target.result;
|
|
272
|
-
if (!db.objectStoreNames.contains("cache")) {
|
|
273
|
-
db.createObjectStore("cache");
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
request.onsuccess = (event) => {
|
|
277
|
-
const db = event.target.result;
|
|
278
|
-
const transaction = db.transaction(["cache"], "readonly");
|
|
279
|
-
const store = transaction.objectStore("cache");
|
|
280
|
-
const getRequest = store.get(key);
|
|
281
|
-
getRequest.onsuccess = () => {
|
|
282
|
-
resolve(getRequest.result || null);
|
|
283
|
-
};
|
|
284
|
-
getRequest.onerror = () => resolve(null);
|
|
285
|
-
};
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
async setInIndexedDB(key, value) {
|
|
289
|
-
return new Promise((resolve, reject) => {
|
|
290
|
-
const request = indexedDB.open(this.config.namespace, 1);
|
|
291
|
-
request.onerror = () => reject(new Error("IndexedDB error"));
|
|
292
|
-
request.onupgradeneeded = (event) => {
|
|
293
|
-
const db = event.target.result;
|
|
294
|
-
if (!db.objectStoreNames.contains("cache")) {
|
|
295
|
-
db.createObjectStore("cache");
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
request.onsuccess = (event) => {
|
|
299
|
-
const db = event.target.result;
|
|
300
|
-
const transaction = db.transaction(["cache"], "readwrite");
|
|
301
|
-
const store = transaction.objectStore("cache");
|
|
302
|
-
store.put(value, key);
|
|
303
|
-
transaction.oncomplete = () => resolve();
|
|
304
|
-
transaction.onerror = () => reject(new Error("IndexedDB transaction error"));
|
|
305
|
-
};
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async removeFromIndexedDB(key) {
|
|
309
|
-
return new Promise((resolve) => {
|
|
310
|
-
const request = indexedDB.open(this.config.namespace, 1);
|
|
311
|
-
request.onsuccess = (event) => {
|
|
312
|
-
const db = event.target.result;
|
|
313
|
-
const transaction = db.transaction(["cache"], "readwrite");
|
|
314
|
-
const store = transaction.objectStore("cache");
|
|
315
|
-
store.delete(key);
|
|
316
|
-
transaction.oncomplete = () => resolve();
|
|
317
|
-
};
|
|
318
|
-
request.onerror = () => resolve();
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
async clearIndexedDB() {
|
|
322
|
-
return new Promise((resolve) => {
|
|
323
|
-
const request = indexedDB.open(this.config.namespace, 1);
|
|
324
|
-
request.onsuccess = (event) => {
|
|
325
|
-
const db = event.target.result;
|
|
326
|
-
const transaction = db.transaction(["cache"], "readwrite");
|
|
327
|
-
const store = transaction.objectStore("cache");
|
|
328
|
-
store.clear();
|
|
329
|
-
transaction.oncomplete = () => resolve();
|
|
330
|
-
};
|
|
331
|
-
request.onerror = () => resolve();
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
// ============================================
|
|
335
|
-
// Serialization
|
|
336
|
-
// ============================================
|
|
337
|
-
serializeKeypair(keypair) {
|
|
338
|
-
if (keypair && typeof keypair === "object" && "secretKey" in keypair) {
|
|
339
|
-
return {
|
|
340
|
-
type: "solana",
|
|
341
|
-
secretKey: Array.from(keypair.secretKey)
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
if (keypair instanceof Uint8Array) {
|
|
345
|
-
return {
|
|
346
|
-
type: "uint8array",
|
|
347
|
-
data: Array.from(keypair)
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
return keypair;
|
|
351
|
-
}
|
|
352
|
-
deserializeKeypair(data) {
|
|
353
|
-
if (!data || typeof data !== "object") return data;
|
|
354
|
-
if (data.type === "solana" && data.secretKey) {
|
|
355
|
-
return new Uint8Array(data.secretKey);
|
|
356
|
-
}
|
|
357
|
-
if (data.type === "uint8array" && data.data) {
|
|
358
|
-
return new Uint8Array(data.data);
|
|
359
|
-
}
|
|
360
|
-
return data;
|
|
361
|
-
}
|
|
362
|
-
// ============================================
|
|
363
|
-
// Auto-Refresh
|
|
364
|
-
// ============================================
|
|
365
|
-
setupAutoRefresh(sessionKeyId, decryptFn, options) {
|
|
366
|
-
const existingTimer = this.refreshTimers.get(sessionKeyId);
|
|
367
|
-
if (existingTimer) {
|
|
368
|
-
clearTimeout(existingTimer);
|
|
369
|
-
}
|
|
370
|
-
const refreshIn = Math.max(0, this.config.ttl - 5 * 60 * 1e3);
|
|
371
|
-
const timer = setTimeout(async () => {
|
|
372
|
-
this.log(`Auto-refreshing: ${sessionKeyId}`);
|
|
373
|
-
try {
|
|
374
|
-
await this.decryptAndCache(sessionKeyId, decryptFn, options);
|
|
375
|
-
} catch (error) {
|
|
376
|
-
this.log(`Auto-refresh failed: ${error}`);
|
|
377
|
-
}
|
|
378
|
-
}, refreshIn);
|
|
379
|
-
this.refreshTimers.set(sessionKeyId, timer);
|
|
380
|
-
}
|
|
381
|
-
// ============================================
|
|
382
|
-
// Utilities
|
|
383
|
-
// ============================================
|
|
384
|
-
getStorageKey(sessionKeyId) {
|
|
385
|
-
return `${this.config.namespace}:${sessionKeyId}`;
|
|
386
|
-
}
|
|
387
|
-
log(message) {
|
|
388
|
-
if (this.config.debug) {
|
|
389
|
-
console.log(`[SessionKeyCache] ${message}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
QuickCaches = {
|
|
394
|
-
/** Memory-only cache (30 minutes) */
|
|
395
|
-
memory: () => new SessionKeyCache({ storage: "memory", ttl: 30 * 60 * 1e3 }),
|
|
396
|
-
/** Persistent cache (1 hour, survives reload) */
|
|
397
|
-
persistent: () => new SessionKeyCache({ storage: "localStorage", ttl: 60 * 60 * 1e3 }),
|
|
398
|
-
/** Long-term cache (24 hours, IndexedDB) */
|
|
399
|
-
longTerm: () => new SessionKeyCache({ storage: "indexedDB", ttl: 24 * 60 * 60 * 1e3, autoRefresh: true }),
|
|
400
|
-
/** Secure cache (5 minutes, memory-only) */
|
|
401
|
-
secure: () => new SessionKeyCache({ storage: "memory", ttl: 5 * 60 * 1e3 })
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
|
|
406
30
|
// src/index.ts
|
|
407
31
|
var index_exports = {};
|
|
408
32
|
__export(index_exports, {
|
|
409
|
-
AgentAPI: () => AgentAPI,
|
|
410
|
-
AnthropicAdapter: () => AnthropicAdapter,
|
|
411
33
|
ApiError: () => ApiError,
|
|
412
34
|
AuthenticationError: () => AuthenticationError2,
|
|
413
|
-
AutonomyAPI: () => AutonomyAPI,
|
|
414
35
|
ConfigLoader: () => ConfigLoader,
|
|
415
36
|
DevTools: () => DevTools,
|
|
416
|
-
DeviceBoundSessionKey: () => DeviceBoundSessionKey,
|
|
417
|
-
DeviceFingerprintGenerator: () => DeviceFingerprintGenerator,
|
|
418
37
|
ERROR_CODES: () => ERROR_CODES,
|
|
419
|
-
ErrorRecovery: () => ErrorRecovery,
|
|
420
|
-
GeminiAdapter: () => GeminiAdapter,
|
|
421
38
|
InterceptorManager: () => InterceptorManager,
|
|
422
|
-
LitCryptoSigner: () => LitCryptoSigner,
|
|
423
39
|
NetworkError: () => NetworkError2,
|
|
424
|
-
OpenAIAdapter: () => OpenAIAdapter,
|
|
425
|
-
PINRateLimiter: () => PINRateLimiter,
|
|
426
|
-
PINValidator: () => PINValidator,
|
|
427
40
|
PaymentError: () => PaymentError,
|
|
428
|
-
PaymentIntentParser: () => PaymentIntentParser,
|
|
429
|
-
PaymentIntentsAPI: () => PaymentIntentsAPI,
|
|
430
41
|
PerformanceMonitor: () => PerformanceMonitor,
|
|
431
|
-
PricingAPI: () => PricingAPI,
|
|
432
|
-
QuickCaches: () => QuickCaches,
|
|
433
42
|
RateLimitError: () => RateLimitError2,
|
|
434
43
|
RateLimiter: () => RateLimiter,
|
|
435
|
-
RecoveryQRGenerator: () => RecoveryQRGenerator,
|
|
436
|
-
RetryStrategy: () => RetryStrategy,
|
|
437
|
-
SPENDING_LIMIT_ACTION_CID: () => SPENDING_LIMIT_ACTION_CID,
|
|
438
|
-
SecureStorage: () => SecureStorage,
|
|
439
|
-
SessionKeyCache: () => SessionKeyCache,
|
|
440
|
-
SessionKeyCrypto: () => SessionKeyCrypto,
|
|
441
|
-
SessionKeyLifecycle: () => SessionKeyLifecycle,
|
|
442
|
-
SessionKeysAPI: () => SessionKeysAPI,
|
|
443
|
-
SmartPaymentsAPI: () => SmartPaymentsAPI,
|
|
444
44
|
TransactionMonitor: () => TransactionMonitor,
|
|
445
45
|
TransactionPoller: () => TransactionPoller,
|
|
446
46
|
ValidationError: () => ValidationError2,
|
|
@@ -449,25 +49,17 @@ __export(index_exports, {
|
|
|
449
49
|
ZendFiClient: () => ZendFiClient,
|
|
450
50
|
ZendFiEmbeddedCheckout: () => ZendFiEmbeddedCheckout,
|
|
451
51
|
ZendFiError: () => ZendFiError2,
|
|
452
|
-
asAgentKeyId: () => asAgentKeyId,
|
|
453
|
-
asEscrowId: () => asEscrowId,
|
|
454
52
|
asInstallmentPlanId: () => asInstallmentPlanId,
|
|
455
|
-
asIntentId: () => asIntentId,
|
|
456
53
|
asInvoiceId: () => asInvoiceId,
|
|
457
54
|
asMerchantId: () => asMerchantId,
|
|
458
55
|
asPaymentId: () => asPaymentId,
|
|
459
56
|
asPaymentLinkCode: () => asPaymentLinkCode,
|
|
460
|
-
asSessionId: () => asSessionId,
|
|
461
57
|
asSubscriptionId: () => asSubscriptionId,
|
|
462
58
|
createWalletHook: () => createWalletHook,
|
|
463
59
|
createZendFiError: () => createZendFiError,
|
|
464
|
-
decodeSignatureFromLit: () => decodeSignatureFromLit,
|
|
465
|
-
encodeTransactionForLit: () => encodeTransactionForLit,
|
|
466
60
|
generateIdempotencyKey: () => generateIdempotencyKey,
|
|
467
61
|
isZendFiError: () => isZendFiError,
|
|
468
62
|
processWebhook: () => processWebhook,
|
|
469
|
-
requiresLitSigning: () => requiresLitSigning,
|
|
470
|
-
setupQuickSessionKey: () => setupQuickSessionKey,
|
|
471
63
|
sleep: () => sleep,
|
|
472
64
|
verifyExpressWebhook: () => verifyExpressWebhook,
|
|
473
65
|
verifyNextWebhook: () => verifyNextWebhook,
|
|
@@ -482,15 +74,11 @@ var import_crypto = require("crypto");
|
|
|
482
74
|
|
|
483
75
|
// src/types.ts
|
|
484
76
|
var asPaymentId = (id) => id;
|
|
485
|
-
var asSessionId = (id) => id;
|
|
486
|
-
var asAgentKeyId = (id) => id;
|
|
487
77
|
var asMerchantId = (id) => id;
|
|
488
78
|
var asInvoiceId = (id) => id;
|
|
489
79
|
var asSubscriptionId = (id) => id;
|
|
490
|
-
var asEscrowId = (id) => id;
|
|
491
80
|
var asInstallmentPlanId = (id) => id;
|
|
492
81
|
var asPaymentLinkCode = (id) => id;
|
|
493
|
-
var asIntentId = (id) => id;
|
|
494
82
|
|
|
495
83
|
// src/utils.ts
|
|
496
84
|
var ConfigLoader = class {
|
|
@@ -931,2257 +519,133 @@ function createInterceptors() {
|
|
|
931
519
|
};
|
|
932
520
|
}
|
|
933
521
|
|
|
934
|
-
// src/
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
522
|
+
// src/client.ts
|
|
523
|
+
var ZendFiClient = class {
|
|
524
|
+
config;
|
|
525
|
+
interceptors;
|
|
526
|
+
constructor(options) {
|
|
527
|
+
this.config = ConfigLoader.load(options);
|
|
528
|
+
ConfigLoader.validateApiKey(this.config.apiKey);
|
|
529
|
+
this.interceptors = createInterceptors();
|
|
530
|
+
if (this.config.environment === "development" || this.config.debug) {
|
|
531
|
+
console.log(
|
|
532
|
+
`\u2713 ZendFi SDK initialized in ${this.config.mode} mode (${this.config.mode === "test" ? "devnet" : "mainnet"})`
|
|
533
|
+
);
|
|
534
|
+
if (this.config.debug) {
|
|
535
|
+
console.log("[ZendFi] Debug mode enabled");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
944
538
|
}
|
|
945
|
-
// ============================================
|
|
946
|
-
// Agent API Keys
|
|
947
|
-
// ============================================
|
|
948
539
|
/**
|
|
949
|
-
* Create a new
|
|
950
|
-
*
|
|
951
|
-
* Agent keys (prefixed with `zai_`) have limited permissions compared to
|
|
952
|
-
* merchant keys. This enables safe delegation to AI agents.
|
|
953
|
-
*
|
|
954
|
-
* @param request - Agent key configuration
|
|
955
|
-
* @returns The created agent key (full_key only returned on creation!)
|
|
956
|
-
*
|
|
957
|
-
* @example
|
|
958
|
-
* ```typescript
|
|
959
|
-
* const agentKey = await zendfi.agent.createKey({
|
|
960
|
-
* name: 'Shopping Assistant',
|
|
961
|
-
* agent_id: 'shopping-assistant-v1',
|
|
962
|
-
* scopes: ['create_payments'],
|
|
963
|
-
* rate_limit_per_hour: 500,
|
|
964
|
-
* });
|
|
965
|
-
*
|
|
966
|
-
* // IMPORTANT: Save the full_key now - it won't be shown again!
|
|
967
|
-
* console.log(agentKey.full_key); // => "zai_test_abc123..."
|
|
968
|
-
* ```
|
|
540
|
+
* Create a new payment
|
|
969
541
|
*/
|
|
970
|
-
async
|
|
971
|
-
return this.request("POST", "/api/v1/
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
scopes: request.scopes || ["create_payments"],
|
|
976
|
-
rate_limit_per_hour: request.rate_limit_per_hour || 1e3,
|
|
977
|
-
metadata: request.metadata
|
|
542
|
+
async createPayment(request) {
|
|
543
|
+
return this.request("POST", "/api/v1/payments", {
|
|
544
|
+
...request,
|
|
545
|
+
currency: request.currency || "USD",
|
|
546
|
+
token: request.token || "USDC"
|
|
978
547
|
});
|
|
979
548
|
}
|
|
980
549
|
/**
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
* @returns Array of agent API keys (without full_key for security)
|
|
984
|
-
*
|
|
985
|
-
* @example
|
|
986
|
-
* ```typescript
|
|
987
|
-
* const keys = await zendfi.agent.listKeys();
|
|
988
|
-
* keys.forEach(key => {
|
|
989
|
-
* console.log(`${key.name}: ${key.key_prefix}*** (${key.scopes.join(', ')})`);
|
|
990
|
-
* });
|
|
991
|
-
* ```
|
|
550
|
+
* Get payment by ID
|
|
992
551
|
*/
|
|
993
|
-
async
|
|
994
|
-
|
|
995
|
-
"GET",
|
|
996
|
-
"/api/v1/agent-keys"
|
|
997
|
-
);
|
|
998
|
-
return normalizeArrayResponse(response, "keys");
|
|
552
|
+
async getPayment(paymentId) {
|
|
553
|
+
return this.request("GET", `/api/v1/payments/${paymentId}`);
|
|
999
554
|
}
|
|
1000
555
|
/**
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
* Once revoked, the key cannot be used for any API calls.
|
|
1004
|
-
* This action is irreversible.
|
|
1005
|
-
*
|
|
1006
|
-
* @param keyId - UUID of the agent key to revoke
|
|
1007
|
-
*
|
|
1008
|
-
* @example
|
|
1009
|
-
* ```typescript
|
|
1010
|
-
* await zendfi.agent.revokeKey('ak_123...');
|
|
1011
|
-
* console.log('Agent key revoked');
|
|
1012
|
-
* ```
|
|
556
|
+
* Create a subscription plan
|
|
1013
557
|
*/
|
|
1014
|
-
async
|
|
1015
|
-
|
|
558
|
+
async createSubscriptionPlan(request) {
|
|
559
|
+
return this.request("POST", "/api/v1/subscriptions/plans", {
|
|
560
|
+
...request,
|
|
561
|
+
currency: request.currency || "USD",
|
|
562
|
+
interval_count: request.interval_count || 1,
|
|
563
|
+
trial_days: request.trial_days || 0
|
|
564
|
+
});
|
|
1016
565
|
}
|
|
1017
|
-
// ============================================
|
|
1018
|
-
// Agent Sessions
|
|
1019
|
-
// ============================================
|
|
1020
566
|
/**
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
* Sessions provide time-bounded authorization for agents to make payments
|
|
1024
|
-
* on behalf of users, with configurable spending limits.
|
|
1025
|
-
*
|
|
1026
|
-
* @param request - Session configuration
|
|
1027
|
-
* @returns The created session with token
|
|
1028
|
-
*
|
|
1029
|
-
* @example
|
|
1030
|
-
* ```typescript
|
|
1031
|
-
* const session = await zendfi.agent.createSession({
|
|
1032
|
-
* agent_id: 'shopping-assistant-v1',
|
|
1033
|
-
* agent_name: 'Shopping Assistant',
|
|
1034
|
-
* user_wallet: 'Hx7B...abc',
|
|
1035
|
-
* limits: {
|
|
1036
|
-
* max_per_transaction: 100,
|
|
1037
|
-
* max_per_day: 500,
|
|
1038
|
-
* require_approval_above: 50,
|
|
1039
|
-
* },
|
|
1040
|
-
* duration_hours: 24,
|
|
1041
|
-
* });
|
|
1042
|
-
*
|
|
1043
|
-
* // Use session_token for subsequent API calls
|
|
1044
|
-
* console.log(session.session_token); // => "zai_session_..."
|
|
1045
|
-
* ```
|
|
567
|
+
* Get subscription plan by ID
|
|
1046
568
|
*/
|
|
1047
|
-
async
|
|
1048
|
-
return this.request("
|
|
1049
|
-
agent_id: request.agent_id,
|
|
1050
|
-
agent_name: request.agent_name,
|
|
1051
|
-
user_wallet: request.user_wallet,
|
|
1052
|
-
limits: request.limits || {
|
|
1053
|
-
max_per_transaction: 1e3,
|
|
1054
|
-
max_per_day: 5e3,
|
|
1055
|
-
max_per_week: 2e4,
|
|
1056
|
-
max_per_month: 5e4,
|
|
1057
|
-
require_approval_above: 500
|
|
1058
|
-
},
|
|
1059
|
-
allowed_merchants: request.allowed_merchants,
|
|
1060
|
-
duration_hours: request.duration_hours || 24,
|
|
1061
|
-
mint_pkp: request.mint_pkp,
|
|
1062
|
-
metadata: request.metadata
|
|
1063
|
-
});
|
|
569
|
+
async getSubscriptionPlan(planId) {
|
|
570
|
+
return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
|
|
1064
571
|
}
|
|
1065
572
|
/**
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
* @returns Array of agent sessions (both active and expired)
|
|
1069
|
-
*
|
|
1070
|
-
* @example
|
|
1071
|
-
* ```typescript
|
|
1072
|
-
* const sessions = await zendfi.agent.listSessions();
|
|
1073
|
-
* const activeSessions = sessions.filter(s => s.is_active);
|
|
1074
|
-
* console.log(`${activeSessions.length} active sessions`);
|
|
1075
|
-
* ```
|
|
573
|
+
* Create a subscription
|
|
1076
574
|
*/
|
|
1077
|
-
async
|
|
1078
|
-
|
|
1079
|
-
"GET",
|
|
1080
|
-
"/api/v1/ai/sessions"
|
|
1081
|
-
);
|
|
1082
|
-
return normalizeArrayResponse(response, "sessions");
|
|
575
|
+
async createSubscription(request) {
|
|
576
|
+
return this.request("POST", "/api/v1/subscriptions", request);
|
|
1083
577
|
}
|
|
1084
578
|
/**
|
|
1085
|
-
* Get
|
|
1086
|
-
*
|
|
1087
|
-
* @param sessionId - UUID of the session
|
|
1088
|
-
* @returns The session details with remaining limits
|
|
1089
|
-
*
|
|
1090
|
-
* @example
|
|
1091
|
-
* ```typescript
|
|
1092
|
-
* const session = await zendfi.agent.getSession('sess_123...');
|
|
1093
|
-
* console.log(`Remaining today: $${session.remaining_today}`);
|
|
1094
|
-
* console.log(`Expires: ${session.expires_at}`);
|
|
1095
|
-
* ```
|
|
579
|
+
* Get subscription by ID
|
|
1096
580
|
*/
|
|
1097
|
-
async
|
|
1098
|
-
return this.request("GET", `/api/v1/
|
|
581
|
+
async getSubscription(subscriptionId) {
|
|
582
|
+
return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
|
|
1099
583
|
}
|
|
1100
584
|
/**
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
* Immediately invalidates the session, preventing any further payments.
|
|
1104
|
-
* This action is irreversible.
|
|
1105
|
-
*
|
|
1106
|
-
* @param sessionId - UUID of the session to revoke
|
|
1107
|
-
*
|
|
1108
|
-
* @example
|
|
1109
|
-
* ```typescript
|
|
1110
|
-
* await zendfi.agent.revokeSession('sess_123...');
|
|
1111
|
-
* console.log('Session revoked - agent can no longer make payments');
|
|
1112
|
-
* ```
|
|
585
|
+
* Cancel a subscription
|
|
1113
586
|
*/
|
|
1114
|
-
async
|
|
1115
|
-
|
|
587
|
+
async cancelSubscription(subscriptionId) {
|
|
588
|
+
return this.request(
|
|
589
|
+
"POST",
|
|
590
|
+
`/api/v1/subscriptions/${subscriptionId}/cancel`
|
|
591
|
+
);
|
|
1116
592
|
}
|
|
1117
|
-
// ============================================
|
|
1118
|
-
// Agent Payments
|
|
1119
|
-
// ============================================
|
|
1120
593
|
/**
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
* This is the primary method for AI agents to make payments. It uses the
|
|
1124
|
-
* session token to enforce spending limits and automatically routes the
|
|
1125
|
-
* payment through the optimal path.
|
|
1126
|
-
*
|
|
1127
|
-
* The session token comes from `createSession()` and enforces:
|
|
1128
|
-
* - Per-transaction limits
|
|
1129
|
-
* - Daily limits
|
|
1130
|
-
* - Weekly limits
|
|
1131
|
-
* - Monthly limits
|
|
1132
|
-
*
|
|
1133
|
-
* @param request - Payment request with session token
|
|
1134
|
-
* @returns Payment result with status and receipt
|
|
1135
|
-
*
|
|
1136
|
-
* @example
|
|
1137
|
-
* ```typescript
|
|
1138
|
-
* // Create a session first
|
|
1139
|
-
* const session = await zendfi.agent.createSession({
|
|
1140
|
-
* agent_id: 'shopping-bot',
|
|
1141
|
-
* user_wallet: 'Hx7B...abc',
|
|
1142
|
-
* limits: {
|
|
1143
|
-
* max_per_transaction: 50,
|
|
1144
|
-
* max_per_day: 200,
|
|
1145
|
-
* },
|
|
1146
|
-
* duration_hours: 24,
|
|
1147
|
-
* });
|
|
1148
|
-
*
|
|
1149
|
-
* // Make payments within the session limits
|
|
1150
|
-
* const payment = await zendfi.agent.pay({
|
|
1151
|
-
* session_token: session.session_token,
|
|
1152
|
-
* amount: 29.99,
|
|
1153
|
-
* description: 'Premium widget',
|
|
1154
|
-
* auto_gasless: true,
|
|
1155
|
-
* });
|
|
1156
|
-
*
|
|
1157
|
-
* if (payment.requires_signature) {
|
|
1158
|
-
* // Device-bound: user must sign
|
|
1159
|
-
* console.log('Sign and submit to:', payment.submit_url);
|
|
1160
|
-
* } else {
|
|
1161
|
-
* // Auto-signed: payment complete
|
|
1162
|
-
* console.log('Payment confirmed:', payment.transaction_signature);
|
|
1163
|
-
* }
|
|
1164
|
-
* ```
|
|
594
|
+
* Create a payment link (shareable checkout URL)
|
|
1165
595
|
*/
|
|
1166
|
-
async
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
// Session already has agent_id bound
|
|
1171
|
-
user_wallet: "",
|
|
1172
|
-
// Will be extracted from session
|
|
1173
|
-
amount_usd: request.amount,
|
|
1174
|
-
merchant_id: request.recipient_merchant_id,
|
|
596
|
+
async createPaymentLink(request) {
|
|
597
|
+
const response = await this.request("POST", "/api/v1/payment-links", {
|
|
598
|
+
...request,
|
|
599
|
+
currency: request.currency || "USD",
|
|
1175
600
|
token: request.token || "USDC",
|
|
1176
|
-
|
|
1177
|
-
description: request.description,
|
|
1178
|
-
metadata: request.metadata
|
|
601
|
+
onramp: request.onramp || false
|
|
1179
602
|
});
|
|
603
|
+
return {
|
|
604
|
+
...response,
|
|
605
|
+
url: response.hosted_page_url
|
|
606
|
+
};
|
|
1180
607
|
}
|
|
1181
|
-
// ============================================
|
|
1182
|
-
// Agent Analytics
|
|
1183
|
-
// ============================================
|
|
1184
608
|
/**
|
|
1185
|
-
* Get
|
|
1186
|
-
*
|
|
1187
|
-
* @returns Comprehensive analytics including payments, success rate, and PPP savings
|
|
1188
|
-
*
|
|
1189
|
-
* @example
|
|
1190
|
-
* ```typescript
|
|
1191
|
-
* const analytics = await zendfi.agent.getAnalytics();
|
|
1192
|
-
* console.log(`Total volume: $${analytics.total_volume_usd}`);
|
|
1193
|
-
* console.log(`Success rate: ${(analytics.success_rate * 100).toFixed(1)}%`);
|
|
1194
|
-
* console.log(`PPP savings: $${analytics.ppp_savings_usd}`);
|
|
1195
|
-
* ```
|
|
609
|
+
* Get payment link by link code
|
|
1196
610
|
*/
|
|
1197
|
-
async
|
|
1198
|
-
|
|
611
|
+
async getPaymentLink(linkCode) {
|
|
612
|
+
const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
|
|
613
|
+
return {
|
|
614
|
+
...response,
|
|
615
|
+
url: response.hosted_page_url
|
|
616
|
+
};
|
|
1199
617
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
618
|
+
/**
|
|
619
|
+
* List all payment links for the authenticated merchant
|
|
620
|
+
*/
|
|
621
|
+
async listPaymentLinks() {
|
|
622
|
+
const response = await this.request("GET", "/api/v1/payment-links");
|
|
623
|
+
return response.map((link) => ({
|
|
624
|
+
...link,
|
|
625
|
+
url: link.hosted_page_url
|
|
626
|
+
}));
|
|
1206
627
|
}
|
|
1207
628
|
/**
|
|
1208
|
-
* Create
|
|
1209
|
-
*
|
|
1210
|
-
* This is step 1 of the two-phase payment flow. The intent reserves
|
|
1211
|
-
* the payment amount and provides a client_secret for confirmation.
|
|
1212
|
-
*
|
|
1213
|
-
* @param request - Payment intent configuration
|
|
1214
|
-
* @returns The created payment intent with client_secret
|
|
1215
|
-
*
|
|
1216
|
-
* @example
|
|
1217
|
-
* ```typescript
|
|
1218
|
-
* const intent = await zendfi.intents.create({
|
|
1219
|
-
* amount: 49.99,
|
|
1220
|
-
* description: 'Pro Plan - Monthly',
|
|
1221
|
-
* capture_method: 'automatic', // or 'manual' for auth-only
|
|
1222
|
-
* expires_in_seconds: 3600, // 1 hour
|
|
1223
|
-
* });
|
|
1224
|
-
*
|
|
1225
|
-
* // Store intent.id and pass intent.client_secret to frontend
|
|
1226
|
-
* console.log(`Intent created: ${intent.id}`);
|
|
1227
|
-
* console.log(`Status: ${intent.status}`); // "requires_payment"
|
|
1228
|
-
* ```
|
|
629
|
+
* Create an installment plan
|
|
630
|
+
* Split a purchase into multiple scheduled payments
|
|
1229
631
|
*/
|
|
1230
|
-
async
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
});
|
|
632
|
+
async createInstallmentPlan(request) {
|
|
633
|
+
const response = await this.request(
|
|
634
|
+
"POST",
|
|
635
|
+
"/api/v1/installment-plans",
|
|
636
|
+
request
|
|
637
|
+
);
|
|
638
|
+
return {
|
|
639
|
+
id: response.plan_id,
|
|
640
|
+
plan_id: response.plan_id,
|
|
641
|
+
status: response.status
|
|
642
|
+
};
|
|
1242
643
|
}
|
|
1243
644
|
/**
|
|
1244
|
-
* Get
|
|
1245
|
-
*
|
|
1246
|
-
* @param intentId - UUID of the payment intent
|
|
1247
|
-
* @returns The payment intent details
|
|
1248
|
-
*
|
|
1249
|
-
* @example
|
|
1250
|
-
* ```typescript
|
|
1251
|
-
* const intent = await zendfi.intents.get('pi_123...');
|
|
1252
|
-
* console.log(`Status: ${intent.status}`);
|
|
1253
|
-
* if (intent.payment_id) {
|
|
1254
|
-
* console.log(`Payment: ${intent.payment_id}`);
|
|
1255
|
-
* }
|
|
1256
|
-
* ```
|
|
645
|
+
* Get installment plan by ID
|
|
1257
646
|
*/
|
|
1258
|
-
async
|
|
1259
|
-
return this.request("GET", `/api/v1/
|
|
1260
|
-
}
|
|
1261
|
-
/**
|
|
1262
|
-
* List payment intents
|
|
1263
|
-
*
|
|
1264
|
-
* @param options - Filter and pagination options
|
|
1265
|
-
* @returns Array of payment intents
|
|
1266
|
-
*
|
|
1267
|
-
* @example
|
|
1268
|
-
* ```typescript
|
|
1269
|
-
* // Get recent pending intents
|
|
1270
|
-
* const intents = await zendfi.intents.list({
|
|
1271
|
-
* status: 'requires_payment',
|
|
1272
|
-
* limit: 20,
|
|
1273
|
-
* });
|
|
1274
|
-
* ```
|
|
1275
|
-
*/
|
|
1276
|
-
async list(options) {
|
|
1277
|
-
const params = new URLSearchParams();
|
|
1278
|
-
if (options?.status) params.append("status", options.status);
|
|
1279
|
-
if (options?.limit) params.append("limit", options.limit.toString());
|
|
1280
|
-
if (options?.offset) params.append("offset", options.offset.toString());
|
|
1281
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1282
|
-
const response = await this.request(
|
|
1283
|
-
"GET",
|
|
1284
|
-
`/api/v1/payment-intents${query}`
|
|
1285
|
-
);
|
|
1286
|
-
return Array.isArray(response) ? response : response.intents;
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* Confirm a payment intent
|
|
1290
|
-
*
|
|
1291
|
-
* This is step 2 of the two-phase payment flow. Confirmation triggers
|
|
1292
|
-
* the actual payment using the customer's wallet.
|
|
1293
|
-
*
|
|
1294
|
-
* @param intentId - UUID of the payment intent
|
|
1295
|
-
* @param request - Confirmation details including customer wallet
|
|
1296
|
-
* @returns The confirmed payment intent with payment_id
|
|
1297
|
-
*
|
|
1298
|
-
* @example
|
|
1299
|
-
* ```typescript
|
|
1300
|
-
* const confirmed = await zendfi.intents.confirm('pi_123...', {
|
|
1301
|
-
* client_secret: 'pi_secret_abc...',
|
|
1302
|
-
* customer_wallet: 'Hx7B...abc',
|
|
1303
|
-
* auto_gasless: true,
|
|
1304
|
-
* });
|
|
1305
|
-
*
|
|
1306
|
-
* if (confirmed.status === 'succeeded') {
|
|
1307
|
-
* console.log(`Payment complete: ${confirmed.payment_id}`);
|
|
1308
|
-
* }
|
|
1309
|
-
* ```
|
|
1310
|
-
*/
|
|
1311
|
-
async confirm(intentId, request) {
|
|
1312
|
-
return this.request("POST", `/api/v1/payment-intents/${intentId}/confirm`, {
|
|
1313
|
-
client_secret: request.client_secret,
|
|
1314
|
-
customer_wallet: request.customer_wallet,
|
|
1315
|
-
payment_type: request.payment_type,
|
|
1316
|
-
auto_gasless: request.auto_gasless,
|
|
1317
|
-
metadata: request.metadata,
|
|
1318
|
-
session_token: request.session_token
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
/**
|
|
1322
|
-
* Cancel a payment intent
|
|
1323
|
-
*
|
|
1324
|
-
* Canceling releases any hold on the payment amount. Cannot cancel
|
|
1325
|
-
* intents that are already processing or succeeded.
|
|
1326
|
-
*
|
|
1327
|
-
* @param intentId - UUID of the payment intent
|
|
1328
|
-
* @returns The canceled payment intent
|
|
1329
|
-
*
|
|
1330
|
-
* @example
|
|
1331
|
-
* ```typescript
|
|
1332
|
-
* const canceled = await zendfi.intents.cancel('pi_123...');
|
|
1333
|
-
* console.log(`Status: ${canceled.status}`); // "canceled"
|
|
1334
|
-
* ```
|
|
1335
|
-
*/
|
|
1336
|
-
async cancel(intentId) {
|
|
1337
|
-
return this.request("POST", `/api/v1/payment-intents/${intentId}/cancel`);
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* Get events for a payment intent
|
|
1341
|
-
*
|
|
1342
|
-
* Events track the full lifecycle of the intent, including creation,
|
|
1343
|
-
* confirmation attempts, and status changes.
|
|
1344
|
-
*
|
|
1345
|
-
* @param intentId - UUID of the payment intent
|
|
1346
|
-
* @returns Array of events in chronological order
|
|
1347
|
-
*
|
|
1348
|
-
* @example
|
|
1349
|
-
* ```typescript
|
|
1350
|
-
* const events = await zendfi.intents.getEvents('pi_123...');
|
|
1351
|
-
* events.forEach(event => {
|
|
1352
|
-
* console.log(`${event.created_at}: ${event.event_type}`);
|
|
1353
|
-
* });
|
|
1354
|
-
* ```
|
|
1355
|
-
*/
|
|
1356
|
-
async getEvents(intentId) {
|
|
1357
|
-
const response = await this.request(
|
|
1358
|
-
"GET",
|
|
1359
|
-
`/api/v1/payment-intents/${intentId}/events`
|
|
1360
|
-
);
|
|
1361
|
-
return Array.isArray(response) ? response : response.events;
|
|
1362
|
-
}
|
|
1363
|
-
};
|
|
1364
|
-
|
|
1365
|
-
// src/aip/pricing.ts
|
|
1366
|
-
var PricingAPI = class {
|
|
1367
|
-
constructor(request) {
|
|
1368
|
-
this.request = request;
|
|
1369
|
-
}
|
|
1370
|
-
/**
|
|
1371
|
-
* Get PPP factor for a specific country
|
|
1372
|
-
*
|
|
1373
|
-
* Returns the purchasing power parity adjustment factor for the given
|
|
1374
|
-
* country code. Use this to calculate localized pricing.
|
|
1375
|
-
*
|
|
1376
|
-
* @param countryCode - ISO 3166-1 alpha-2 country code (e.g., "BR", "IN", "NG")
|
|
1377
|
-
* @returns PPP factor and suggested adjustment
|
|
1378
|
-
*
|
|
1379
|
-
* @example
|
|
1380
|
-
* ```typescript
|
|
1381
|
-
* const factor = await zendfi.pricing.getPPPFactor('BR');
|
|
1382
|
-
* // {
|
|
1383
|
-
* // country_code: 'BR',
|
|
1384
|
-
* // country_name: 'Brazil',
|
|
1385
|
-
* // ppp_factor: 0.35,
|
|
1386
|
-
* // currency_code: 'BRL',
|
|
1387
|
-
* // adjustment_percentage: 35.0
|
|
1388
|
-
* // }
|
|
1389
|
-
*
|
|
1390
|
-
* // Calculate localized price
|
|
1391
|
-
* const usdPrice = 100;
|
|
1392
|
-
* const localPrice = usdPrice * (1 - factor.adjustment_percentage / 100);
|
|
1393
|
-
* console.log(`$${localPrice} for Brazilian customers`);
|
|
1394
|
-
* ```
|
|
1395
|
-
*/
|
|
1396
|
-
async getPPPFactor(countryCode) {
|
|
1397
|
-
return this.request("POST", "/api/v1/ai/pricing/ppp-factor", {
|
|
1398
|
-
country_code: countryCode.toUpperCase()
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
/**
|
|
1402
|
-
* List all available PPP factors
|
|
1403
|
-
*
|
|
1404
|
-
* Returns PPP factors for all supported countries. Useful for building
|
|
1405
|
-
* pricing tables or pre-computing regional prices.
|
|
1406
|
-
*
|
|
1407
|
-
* @returns Array of PPP factors for all supported countries
|
|
1408
|
-
*
|
|
1409
|
-
* @example
|
|
1410
|
-
* ```typescript
|
|
1411
|
-
* const factors = await zendfi.pricing.listFactors();
|
|
1412
|
-
*
|
|
1413
|
-
* // Create pricing tiers
|
|
1414
|
-
* const tiers = factors.map(f => ({
|
|
1415
|
-
* country: f.country_name,
|
|
1416
|
-
* price: (100 * f.ppp_factor).toFixed(2),
|
|
1417
|
-
* }));
|
|
1418
|
-
*
|
|
1419
|
-
* console.table(tiers);
|
|
1420
|
-
* ```
|
|
1421
|
-
*/
|
|
1422
|
-
async listFactors() {
|
|
1423
|
-
const response = await this.request(
|
|
1424
|
-
"GET",
|
|
1425
|
-
"/api/v1/ai/pricing/ppp-factors"
|
|
1426
|
-
);
|
|
1427
|
-
return Array.isArray(response) ? response : response.factors;
|
|
1428
|
-
}
|
|
1429
|
-
/**
|
|
1430
|
-
* Get AI-powered pricing suggestion
|
|
1431
|
-
*
|
|
1432
|
-
* Returns an intelligent pricing recommendation based on the user's
|
|
1433
|
-
* location, wallet history, and your pricing configuration.
|
|
1434
|
-
*
|
|
1435
|
-
* @param request - Pricing suggestion request with user context
|
|
1436
|
-
* @returns AI-generated pricing suggestion with reasoning
|
|
1437
|
-
*
|
|
1438
|
-
* @example
|
|
1439
|
-
* ```typescript
|
|
1440
|
-
* const suggestion = await zendfi.pricing.getSuggestion({
|
|
1441
|
-
* agent_id: 'shopping-assistant',
|
|
1442
|
-
* base_price: 99.99,
|
|
1443
|
-
* user_profile: {
|
|
1444
|
-
* location_country: 'BR',
|
|
1445
|
-
* context: 'first-time',
|
|
1446
|
-
* },
|
|
1447
|
-
* ppp_config: {
|
|
1448
|
-
* enabled: true,
|
|
1449
|
-
* max_discount_percent: 50,
|
|
1450
|
-
* floor_price: 29.99,
|
|
1451
|
-
* },
|
|
1452
|
-
* });
|
|
1453
|
-
*
|
|
1454
|
-
* console.log(`Suggested: $${suggestion.suggested_amount}`);
|
|
1455
|
-
* console.log(`Reason: ${suggestion.reasoning}`);
|
|
1456
|
-
* // => "Price adjusted for Brazilian purchasing power (35% PPP discount)
|
|
1457
|
-
* // plus 10% first-time customer discount"
|
|
1458
|
-
* ```
|
|
1459
|
-
*/
|
|
1460
|
-
async getSuggestion(request) {
|
|
1461
|
-
return this.request("POST", "/api/v1/ai/pricing/suggest", {
|
|
1462
|
-
agent_id: request.agent_id,
|
|
1463
|
-
product_id: request.product_id,
|
|
1464
|
-
base_price: request.base_price,
|
|
1465
|
-
currency: request.currency || "USD",
|
|
1466
|
-
user_profile: request.user_profile,
|
|
1467
|
-
ppp_config: request.ppp_config
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
/**
|
|
1471
|
-
* Calculate localized price for a given base price and country
|
|
1472
|
-
*
|
|
1473
|
-
* Convenience method that combines getPPPFactor with price calculation.
|
|
1474
|
-
*
|
|
1475
|
-
* @param basePrice - Original price in USD
|
|
1476
|
-
* @param countryCode - ISO 3166-1 alpha-2 country code
|
|
1477
|
-
* @returns Object with original and adjusted prices
|
|
1478
|
-
*
|
|
1479
|
-
* @example
|
|
1480
|
-
* ```typescript
|
|
1481
|
-
* const result = await zendfi.pricing.calculateLocalPrice(100, 'IN');
|
|
1482
|
-
* console.log(`Original: $${result.original}`);
|
|
1483
|
-
* console.log(`Local: $${result.adjusted}`);
|
|
1484
|
-
* console.log(`Savings: $${result.savings} (${result.discount_percentage}%)`);
|
|
1485
|
-
* ```
|
|
1486
|
-
*/
|
|
1487
|
-
async calculateLocalPrice(basePrice, countryCode) {
|
|
1488
|
-
const factor = await this.getPPPFactor(countryCode);
|
|
1489
|
-
const adjusted = Number((basePrice * factor.ppp_factor).toFixed(2));
|
|
1490
|
-
const savings = Number((basePrice - adjusted).toFixed(2));
|
|
1491
|
-
return {
|
|
1492
|
-
original: basePrice,
|
|
1493
|
-
adjusted,
|
|
1494
|
-
savings,
|
|
1495
|
-
discount_percentage: factor.adjustment_percentage,
|
|
1496
|
-
country: factor.country_name,
|
|
1497
|
-
ppp_factor: factor.ppp_factor
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
};
|
|
1501
|
-
|
|
1502
|
-
// src/aip/autonomy.ts
|
|
1503
|
-
var AutonomyAPI = class {
|
|
1504
|
-
constructor(request) {
|
|
1505
|
-
this.request = request;
|
|
1506
|
-
}
|
|
1507
|
-
/**
|
|
1508
|
-
* Enable autonomous signing for a session key
|
|
1509
|
-
*
|
|
1510
|
-
* This grants an AI agent the ability to sign transactions on behalf of
|
|
1511
|
-
* the user, up to the specified spending limit and duration.
|
|
1512
|
-
*
|
|
1513
|
-
* **Prerequisites:**
|
|
1514
|
-
* 1. Create a device-bound session key first
|
|
1515
|
-
* 2. Generate a delegation signature (see `createDelegationMessage`)
|
|
1516
|
-
* 3. Optionally encrypt keypair with Lit Protocol for true autonomy
|
|
1517
|
-
*
|
|
1518
|
-
* @param sessionKeyId - UUID of the session key
|
|
1519
|
-
* @param request - Autonomy configuration including delegation signature
|
|
1520
|
-
* @returns The created autonomous delegate
|
|
1521
|
-
*
|
|
1522
|
-
* @example
|
|
1523
|
-
* ```typescript
|
|
1524
|
-
* // The user must sign this exact message format
|
|
1525
|
-
* const message = zendfi.autonomy.createDelegationMessage(
|
|
1526
|
-
* sessionKeyId, 100, '2024-12-10T00:00:00Z'
|
|
1527
|
-
* );
|
|
1528
|
-
*
|
|
1529
|
-
* // Have user sign with their session key
|
|
1530
|
-
* const signature = await signWithSessionKey(message, pin);
|
|
1531
|
-
*
|
|
1532
|
-
* // Enable autonomous mode
|
|
1533
|
-
* const delegate = await zendfi.autonomy.enable(sessionKeyId, {
|
|
1534
|
-
* max_amount_usd: 100,
|
|
1535
|
-
* duration_hours: 24,
|
|
1536
|
-
* delegation_signature: signature,
|
|
1537
|
-
* });
|
|
1538
|
-
*
|
|
1539
|
-
* console.log(`Delegate ID: ${delegate.delegate_id}`);
|
|
1540
|
-
* console.log(`Expires: ${delegate.expires_at}`);
|
|
1541
|
-
* ```
|
|
1542
|
-
*/
|
|
1543
|
-
async enable(sessionKeyId, request) {
|
|
1544
|
-
return this.request(
|
|
1545
|
-
"POST",
|
|
1546
|
-
`/api/v1/ai/session-keys/${sessionKeyId}/enable-autonomy`,
|
|
1547
|
-
{
|
|
1548
|
-
max_amount_usd: request.max_amount_usd,
|
|
1549
|
-
duration_hours: request.duration_hours,
|
|
1550
|
-
delegation_signature: request.delegation_signature,
|
|
1551
|
-
expires_at: request.expires_at,
|
|
1552
|
-
lit_encrypted_keypair: request.lit_encrypted_keypair,
|
|
1553
|
-
lit_data_hash: request.lit_data_hash,
|
|
1554
|
-
metadata: request.metadata
|
|
1555
|
-
}
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* Revoke autonomous mode for a session key
|
|
1560
|
-
*
|
|
1561
|
-
* Immediately invalidates the autonomous delegate, preventing any further
|
|
1562
|
-
* automatic payments. The session key itself remains valid for manual use.
|
|
1563
|
-
*
|
|
1564
|
-
* @param sessionKeyId - UUID of the session key
|
|
1565
|
-
* @param reason - Optional reason for revocation (logged for audit)
|
|
1566
|
-
*
|
|
1567
|
-
* @example
|
|
1568
|
-
* ```typescript
|
|
1569
|
-
* await zendfi.autonomy.revoke('sk_123...', 'User requested revocation');
|
|
1570
|
-
* console.log('Autonomous mode disabled');
|
|
1571
|
-
* ```
|
|
1572
|
-
*/
|
|
1573
|
-
async revoke(sessionKeyId, reason) {
|
|
1574
|
-
const request = { reason };
|
|
1575
|
-
await this.request(
|
|
1576
|
-
"POST",
|
|
1577
|
-
`/api/v1/ai/session-keys/${sessionKeyId}/revoke-autonomy`,
|
|
1578
|
-
request
|
|
1579
|
-
);
|
|
1580
|
-
}
|
|
1581
|
-
/**
|
|
1582
|
-
* Get autonomy status for a session key
|
|
1583
|
-
*
|
|
1584
|
-
* Returns whether autonomous mode is enabled and details about the
|
|
1585
|
-
* active delegate including remaining spending allowance.
|
|
1586
|
-
*
|
|
1587
|
-
* @param sessionKeyId - UUID of the session key
|
|
1588
|
-
* @returns Autonomy status with delegate details
|
|
1589
|
-
*
|
|
1590
|
-
* @example
|
|
1591
|
-
* ```typescript
|
|
1592
|
-
* const status = await zendfi.autonomy.getStatus('sk_123...');
|
|
1593
|
-
*
|
|
1594
|
-
* if (status.autonomous_mode_enabled && status.delegate) {
|
|
1595
|
-
* console.log(`Remaining: $${status.delegate.remaining_usd}`);
|
|
1596
|
-
* console.log(`Expires: ${status.delegate.expires_at}`);
|
|
1597
|
-
* } else {
|
|
1598
|
-
* console.log('Autonomous mode not enabled');
|
|
1599
|
-
* }
|
|
1600
|
-
* ```
|
|
1601
|
-
*/
|
|
1602
|
-
async getStatus(sessionKeyId) {
|
|
1603
|
-
return this.request(
|
|
1604
|
-
"GET",
|
|
1605
|
-
`/api/v1/ai/session-keys/${sessionKeyId}/autonomy-status`
|
|
1606
|
-
);
|
|
1607
|
-
}
|
|
1608
|
-
/**
|
|
1609
|
-
* Create the delegation message that needs to be signed
|
|
1610
|
-
*
|
|
1611
|
-
* This generates the exact message format required for the delegation
|
|
1612
|
-
* signature. The user must sign this message with their session key.
|
|
1613
|
-
*
|
|
1614
|
-
* **Message format:**
|
|
1615
|
-
* ```
|
|
1616
|
-
* I authorize autonomous delegate for session {id} to spend up to ${amount} until {expiry}
|
|
1617
|
-
* ```
|
|
1618
|
-
*
|
|
1619
|
-
* @param sessionKeyId - UUID of the session key
|
|
1620
|
-
* @param maxAmountUsd - Maximum spending amount in USD
|
|
1621
|
-
* @param expiresAt - ISO 8601 expiration timestamp
|
|
1622
|
-
* @returns The message to be signed
|
|
1623
|
-
*
|
|
1624
|
-
* @example
|
|
1625
|
-
* ```typescript
|
|
1626
|
-
* const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
1627
|
-
* const message = zendfi.autonomy.createDelegationMessage(
|
|
1628
|
-
* 'sk_123...',
|
|
1629
|
-
* 100,
|
|
1630
|
-
* expiresAt
|
|
1631
|
-
* );
|
|
1632
|
-
* // => "I authorize autonomous delegate for session sk_123... to spend up to $100 until 2024-12-06T..."
|
|
1633
|
-
*
|
|
1634
|
-
* // Sign with nacl.sign.detached() or similar
|
|
1635
|
-
* const signature = signMessage(message, keypair);
|
|
1636
|
-
* ```
|
|
1637
|
-
*/
|
|
1638
|
-
createDelegationMessage(sessionKeyId, maxAmountUsd, expiresAt) {
|
|
1639
|
-
return `I authorize autonomous delegate for session ${sessionKeyId} to spend up to $${maxAmountUsd} until ${expiresAt}`;
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* Validate delegation signature parameters
|
|
1643
|
-
*
|
|
1644
|
-
* Helper method to check if autonomy parameters are valid before
|
|
1645
|
-
* making the API call.
|
|
1646
|
-
*
|
|
1647
|
-
* @param request - The enable autonomy request to validate
|
|
1648
|
-
* @throws Error if validation fails
|
|
1649
|
-
*
|
|
1650
|
-
* @example
|
|
1651
|
-
* ```typescript
|
|
1652
|
-
* try {
|
|
1653
|
-
* zendfi.autonomy.validateRequest(request);
|
|
1654
|
-
* const delegate = await zendfi.autonomy.enable(sessionKeyId, request);
|
|
1655
|
-
* } catch (error) {
|
|
1656
|
-
* console.error('Invalid request:', error.message);
|
|
1657
|
-
* }
|
|
1658
|
-
* ```
|
|
1659
|
-
*/
|
|
1660
|
-
validateRequest(request) {
|
|
1661
|
-
if (request.max_amount_usd <= 0) {
|
|
1662
|
-
throw new Error("max_amount_usd must be positive");
|
|
1663
|
-
}
|
|
1664
|
-
if (request.duration_hours < 1 || request.duration_hours > 168) {
|
|
1665
|
-
throw new Error("duration_hours must be between 1 and 168 (7 days)");
|
|
1666
|
-
}
|
|
1667
|
-
if (!request.delegation_signature || request.delegation_signature.length === 0) {
|
|
1668
|
-
throw new Error("delegation_signature is required");
|
|
1669
|
-
}
|
|
1670
|
-
const base64Regex = /^[A-Za-z0-9+/]+=*$/;
|
|
1671
|
-
if (!base64Regex.test(request.delegation_signature)) {
|
|
1672
|
-
throw new Error("delegation_signature must be base64 encoded");
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
/**
|
|
1676
|
-
* Get spending attestations for a delegate (audit trail)
|
|
1677
|
-
*
|
|
1678
|
-
* Returns all cryptographically signed attestations ZendFi created for
|
|
1679
|
-
* this delegate. Each attestation contains:
|
|
1680
|
-
* - The spending state at the time of payment
|
|
1681
|
-
* - ZendFi's Ed25519 signature
|
|
1682
|
-
* - Timestamp and nonce (for replay protection)
|
|
1683
|
-
*
|
|
1684
|
-
* These attestations can be independently verified using ZendFi's public key
|
|
1685
|
-
* to confirm spending limit enforcement was applied correctly.
|
|
1686
|
-
*
|
|
1687
|
-
* @param delegateId - UUID of the autonomous delegate
|
|
1688
|
-
* @returns Attestation audit response with all signed attestations
|
|
1689
|
-
*
|
|
1690
|
-
* @example
|
|
1691
|
-
* ```typescript
|
|
1692
|
-
* const audit = await zendfi.autonomy.getAttestations('delegate_123...');
|
|
1693
|
-
*
|
|
1694
|
-
* console.log(`Found ${audit.attestation_count} attestations`);
|
|
1695
|
-
* console.log(`ZendFi public key: ${audit.zendfi_attestation_public_key}`);
|
|
1696
|
-
*
|
|
1697
|
-
* // Verify each attestation independently
|
|
1698
|
-
* for (const signed of audit.attestations) {
|
|
1699
|
-
* console.log(`Payment ${signed.attestation.payment_id}:`);
|
|
1700
|
-
* console.log(` Requested: $${signed.attestation.requested_usd}`);
|
|
1701
|
-
* console.log(` Remaining after: $${signed.attestation.remaining_after_usd}`);
|
|
1702
|
-
* // Verify signature with nacl.sign.detached.verify()
|
|
1703
|
-
* }
|
|
1704
|
-
* ```
|
|
1705
|
-
*/
|
|
1706
|
-
async getAttestations(delegateId) {
|
|
1707
|
-
return this.request(
|
|
1708
|
-
"GET",
|
|
1709
|
-
`/api/v1/ai/delegates/${delegateId}/attestations`
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
};
|
|
1713
|
-
|
|
1714
|
-
// src/aip/smart-payments.ts
|
|
1715
|
-
var SmartPaymentsAPI = class {
|
|
1716
|
-
constructor(request) {
|
|
1717
|
-
this.request = request;
|
|
1718
|
-
}
|
|
1719
|
-
/**
|
|
1720
|
-
* Execute an AI-powered smart payment
|
|
1721
|
-
*
|
|
1722
|
-
* Smart payments analyze the context and automatically apply optimizations:
|
|
1723
|
-
* - **PPP Pricing**: Auto-adjusts based on customer location
|
|
1724
|
-
* - **Gasless**: Detects when user needs gas subsidization
|
|
1725
|
-
* - **Instant Settlement**: Optional immediate merchant payout
|
|
1726
|
-
* - **Escrow**: Optional fund holding for service delivery
|
|
1727
|
-
*
|
|
1728
|
-
* @param request - Smart payment request configuration
|
|
1729
|
-
* @returns Payment result with status and receipt
|
|
1730
|
-
*
|
|
1731
|
-
* @example
|
|
1732
|
-
* ```typescript
|
|
1733
|
-
* // Basic smart payment
|
|
1734
|
-
* const result = await zendfi.payments.smart({
|
|
1735
|
-
* agent_id: 'my-agent',
|
|
1736
|
-
* user_wallet: 'Hx7B...abc',
|
|
1737
|
-
* amount_usd: 99.99,
|
|
1738
|
-
* description: 'Annual Pro Plan',
|
|
1739
|
-
* });
|
|
1740
|
-
*
|
|
1741
|
-
* // With all options
|
|
1742
|
-
* const result = await zendfi.payments.smart({
|
|
1743
|
-
* agent_id: 'my-agent',
|
|
1744
|
-
* session_token: 'zai_session_...', // For limit enforcement
|
|
1745
|
-
* user_wallet: 'Hx7B...abc',
|
|
1746
|
-
* amount_usd: 99.99,
|
|
1747
|
-
* token: 'USDC',
|
|
1748
|
-
* auto_detect_gasless: true,
|
|
1749
|
-
* instant_settlement: true,
|
|
1750
|
-
* enable_escrow: false,
|
|
1751
|
-
* description: 'Annual Pro Plan',
|
|
1752
|
-
* product_details: {
|
|
1753
|
-
* name: 'Pro Plan',
|
|
1754
|
-
* sku: 'PRO-ANNUAL',
|
|
1755
|
-
* },
|
|
1756
|
-
* metadata: {
|
|
1757
|
-
* user_id: 'usr_123',
|
|
1758
|
-
* },
|
|
1759
|
-
* });
|
|
1760
|
-
*
|
|
1761
|
-
* if (result.requires_signature) {
|
|
1762
|
-
* // Device-bound flow: need user to sign
|
|
1763
|
-
* console.log('Please sign:', result.unsigned_transaction);
|
|
1764
|
-
* console.log('Submit to:', result.submit_url);
|
|
1765
|
-
* } else {
|
|
1766
|
-
* // Auto-signed (custodial or autonomous delegate)
|
|
1767
|
-
* console.log('Payment complete:', result.transaction_signature);
|
|
1768
|
-
* }
|
|
1769
|
-
* ```
|
|
1770
|
-
*/
|
|
1771
|
-
async execute(request) {
|
|
1772
|
-
return this.request("POST", "/api/v1/ai/smart-payment", {
|
|
1773
|
-
session_token: request.session_token,
|
|
1774
|
-
agent_id: request.agent_id,
|
|
1775
|
-
user_wallet: request.user_wallet,
|
|
1776
|
-
amount_usd: request.amount_usd,
|
|
1777
|
-
merchant_id: request.merchant_id,
|
|
1778
|
-
token: request.token || "USDC",
|
|
1779
|
-
auto_detect_gasless: request.auto_detect_gasless,
|
|
1780
|
-
instant_settlement: request.instant_settlement,
|
|
1781
|
-
enable_escrow: request.enable_escrow,
|
|
1782
|
-
description: request.description,
|
|
1783
|
-
product_details: request.product_details,
|
|
1784
|
-
metadata: request.metadata
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
/**
|
|
1788
|
-
* Submit a signed transaction from device-bound flow
|
|
1789
|
-
*
|
|
1790
|
-
* When a smart payment returns `requires_signature: true`, the client
|
|
1791
|
-
* must sign the transaction and submit it here.
|
|
1792
|
-
*
|
|
1793
|
-
* @param paymentId - UUID of the payment
|
|
1794
|
-
* @param signedTransaction - Base64 encoded signed transaction
|
|
1795
|
-
* @returns Updated payment response
|
|
1796
|
-
*
|
|
1797
|
-
* @example
|
|
1798
|
-
* ```typescript
|
|
1799
|
-
* // After user signs the transaction
|
|
1800
|
-
* const result = await zendfi.payments.submitSigned(
|
|
1801
|
-
* payment.payment_id,
|
|
1802
|
-
* signedTransaction
|
|
1803
|
-
* );
|
|
1804
|
-
*
|
|
1805
|
-
* console.log(`Confirmed in ${result.confirmed_in_ms}ms`);
|
|
1806
|
-
* ```
|
|
1807
|
-
*/
|
|
1808
|
-
async submitSigned(paymentId, signedTransaction) {
|
|
1809
|
-
return this.request(
|
|
1810
|
-
"POST",
|
|
1811
|
-
`/api/v1/ai/payments/${paymentId}/submit-signed`,
|
|
1812
|
-
{
|
|
1813
|
-
signed_transaction: signedTransaction
|
|
1814
|
-
}
|
|
1815
|
-
);
|
|
1816
|
-
}
|
|
1817
|
-
};
|
|
1818
|
-
|
|
1819
|
-
// src/device-bound-crypto.ts
|
|
1820
|
-
var import_web3 = require("@solana/web3.js");
|
|
1821
|
-
var crypto2 = __toESM(require("crypto"));
|
|
1822
|
-
var DeviceFingerprintGenerator = class {
|
|
1823
|
-
/**
|
|
1824
|
-
* Generate a unique device fingerprint
|
|
1825
|
-
* Combines multiple browser attributes for uniqueness
|
|
1826
|
-
*/
|
|
1827
|
-
static async generate() {
|
|
1828
|
-
const components = {};
|
|
1829
|
-
try {
|
|
1830
|
-
components.canvas = await this.getCanvasFingerprint();
|
|
1831
|
-
components.webgl = await this.getWebGLFingerprint();
|
|
1832
|
-
components.audio = await this.getAudioFingerprint();
|
|
1833
|
-
if (typeof screen !== "undefined") {
|
|
1834
|
-
components.screen = `${screen.width}x${screen.height}x${screen.colorDepth}`;
|
|
1835
|
-
} else {
|
|
1836
|
-
components.screen = "unknown-ssr";
|
|
1837
|
-
}
|
|
1838
|
-
components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1839
|
-
if (typeof navigator !== "undefined") {
|
|
1840
|
-
components.languages = navigator.languages?.join(",") || navigator.language;
|
|
1841
|
-
components.platform = navigator.platform;
|
|
1842
|
-
components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
|
|
1843
|
-
} else {
|
|
1844
|
-
components.languages = "unknown-ssr";
|
|
1845
|
-
components.platform = "unknown-ssr";
|
|
1846
|
-
components.hardwareConcurrency = "unknown-ssr";
|
|
1847
|
-
}
|
|
1848
|
-
const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|");
|
|
1849
|
-
const fingerprint = await this.sha256(combined);
|
|
1850
|
-
return {
|
|
1851
|
-
fingerprint,
|
|
1852
|
-
generatedAt: Date.now(),
|
|
1853
|
-
components
|
|
1854
|
-
};
|
|
1855
|
-
} catch (error) {
|
|
1856
|
-
console.warn("Device fingerprinting failed, using fallback", error);
|
|
1857
|
-
return this.generateFallbackFingerprint();
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
/**
|
|
1861
|
-
* Graceful fallback fingerprint generation
|
|
1862
|
-
* Works in headless browsers, SSR, and restricted environments
|
|
1863
|
-
*/
|
|
1864
|
-
static async generateFallbackFingerprint() {
|
|
1865
|
-
const components = {};
|
|
1866
|
-
try {
|
|
1867
|
-
if (typeof navigator !== "undefined") {
|
|
1868
|
-
components.platform = navigator.platform || "unknown";
|
|
1869
|
-
components.languages = navigator.languages?.join(",") || navigator.language || "unknown";
|
|
1870
|
-
components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
|
|
1871
|
-
}
|
|
1872
|
-
if (typeof screen !== "undefined") {
|
|
1873
|
-
components.screen = `${screen.width || 0}x${screen.height || 0}x${screen.colorDepth || 0}`;
|
|
1874
|
-
}
|
|
1875
|
-
if (typeof Intl !== "undefined") {
|
|
1876
|
-
try {
|
|
1877
|
-
components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1878
|
-
} catch {
|
|
1879
|
-
components.timezone = "unknown";
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
} catch {
|
|
1883
|
-
components.platform = "fallback";
|
|
1884
|
-
}
|
|
1885
|
-
let randomEntropy = "";
|
|
1886
|
-
try {
|
|
1887
|
-
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
|
|
1888
|
-
const arr = new Uint8Array(16);
|
|
1889
|
-
window.crypto.getRandomValues(arr);
|
|
1890
|
-
randomEntropy = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1891
|
-
} else if (typeof crypto2 !== "undefined" && crypto2.randomBytes) {
|
|
1892
|
-
randomEntropy = crypto2.randomBytes(16).toString("hex");
|
|
1893
|
-
}
|
|
1894
|
-
} catch {
|
|
1895
|
-
randomEntropy = Date.now().toString(36) + Math.random().toString(36);
|
|
1896
|
-
}
|
|
1897
|
-
const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|") + "|entropy:" + randomEntropy;
|
|
1898
|
-
const fingerprint = await this.sha256(combined);
|
|
1899
|
-
return {
|
|
1900
|
-
fingerprint,
|
|
1901
|
-
generatedAt: Date.now(),
|
|
1902
|
-
components
|
|
1903
|
-
};
|
|
1904
|
-
}
|
|
1905
|
-
static async getCanvasFingerprint() {
|
|
1906
|
-
if (typeof document === "undefined") return "no-canvas-ssr";
|
|
1907
|
-
const canvas = document.createElement("canvas");
|
|
1908
|
-
const ctx = canvas.getContext("2d");
|
|
1909
|
-
if (!ctx) return "no-canvas";
|
|
1910
|
-
canvas.width = 200;
|
|
1911
|
-
canvas.height = 50;
|
|
1912
|
-
ctx.textBaseline = "top";
|
|
1913
|
-
ctx.font = '14px "Arial"';
|
|
1914
|
-
ctx.fillStyle = "#f60";
|
|
1915
|
-
ctx.fillRect(0, 0, 100, 50);
|
|
1916
|
-
ctx.fillStyle = "#069";
|
|
1917
|
-
ctx.fillText("ZendFi \u{1F510}", 2, 2);
|
|
1918
|
-
ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
|
|
1919
|
-
ctx.fillText("Device-Bound", 4, 17);
|
|
1920
|
-
return canvas.toDataURL();
|
|
1921
|
-
}
|
|
1922
|
-
static async getWebGLFingerprint() {
|
|
1923
|
-
if (typeof document === "undefined") return "no-webgl-ssr";
|
|
1924
|
-
const canvas = document.createElement("canvas");
|
|
1925
|
-
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
1926
|
-
if (!gl) return "no-webgl";
|
|
1927
|
-
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
1928
|
-
if (!debugInfo) return "no-debug-info";
|
|
1929
|
-
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
|
|
1930
|
-
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
1931
|
-
return `${vendor}|${renderer}`;
|
|
1932
|
-
}
|
|
1933
|
-
static async getAudioFingerprint() {
|
|
1934
|
-
try {
|
|
1935
|
-
if (typeof window === "undefined") return "no-audio-ssr";
|
|
1936
|
-
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
1937
|
-
if (!AudioContext) return "no-audio";
|
|
1938
|
-
const context = new AudioContext();
|
|
1939
|
-
const oscillator = context.createOscillator();
|
|
1940
|
-
const analyser = context.createAnalyser();
|
|
1941
|
-
const gainNode = context.createGain();
|
|
1942
|
-
const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
|
|
1943
|
-
gainNode.gain.value = 0;
|
|
1944
|
-
oscillator.connect(analyser);
|
|
1945
|
-
analyser.connect(scriptProcessor);
|
|
1946
|
-
scriptProcessor.connect(gainNode);
|
|
1947
|
-
gainNode.connect(context.destination);
|
|
1948
|
-
oscillator.start(0);
|
|
1949
|
-
return new Promise((resolve) => {
|
|
1950
|
-
scriptProcessor.onaudioprocess = (event) => {
|
|
1951
|
-
const output = event.inputBuffer.getChannelData(0);
|
|
1952
|
-
const hash = Array.from(output.slice(0, 30)).reduce((acc, val) => acc + Math.abs(val), 0);
|
|
1953
|
-
oscillator.stop();
|
|
1954
|
-
scriptProcessor.disconnect();
|
|
1955
|
-
context.close();
|
|
1956
|
-
resolve(hash.toString());
|
|
1957
|
-
};
|
|
1958
|
-
});
|
|
1959
|
-
} catch (error) {
|
|
1960
|
-
return "audio-error";
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
static async sha256(data) {
|
|
1964
|
-
if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
|
|
1965
|
-
const encoder = new TextEncoder();
|
|
1966
|
-
const dataBuffer = encoder.encode(data);
|
|
1967
|
-
const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
|
|
1968
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1969
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1970
|
-
} else {
|
|
1971
|
-
return crypto2.createHash("sha256").update(data).digest("hex");
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
};
|
|
1975
|
-
var SessionKeyCrypto = class {
|
|
1976
|
-
/**
|
|
1977
|
-
* Encrypt a Solana keypair with PIN + device fingerprint
|
|
1978
|
-
* Uses Argon2id for key derivation and AES-256-GCM for encryption
|
|
1979
|
-
*/
|
|
1980
|
-
static async encrypt(keypair, pin, deviceFingerprint) {
|
|
1981
|
-
if (!/^\d{6}$/.test(pin)) {
|
|
1982
|
-
throw new Error("PIN must be exactly 6 numeric digits");
|
|
1983
|
-
}
|
|
1984
|
-
const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
|
|
1985
|
-
const nonce = this.generateNonce();
|
|
1986
|
-
const secretKey = keypair.secretKey;
|
|
1987
|
-
const encryptedData = await this.aesEncrypt(secretKey, encryptionKey, nonce);
|
|
1988
|
-
return {
|
|
1989
|
-
encryptedData: Buffer.from(encryptedData).toString("base64"),
|
|
1990
|
-
nonce: Buffer.from(nonce).toString("base64"),
|
|
1991
|
-
publicKey: keypair.publicKey.toBase58(),
|
|
1992
|
-
deviceFingerprint,
|
|
1993
|
-
version: "argon2id-aes256gcm-v1"
|
|
1994
|
-
};
|
|
1995
|
-
}
|
|
1996
|
-
/**
|
|
1997
|
-
* Decrypt an encrypted session key with PIN + device fingerprint
|
|
1998
|
-
*/
|
|
1999
|
-
static async decrypt(encrypted, pin, deviceFingerprint) {
|
|
2000
|
-
if (!/^\d{6}$/.test(pin)) {
|
|
2001
|
-
throw new Error("PIN must be exactly 6 numeric digits");
|
|
2002
|
-
}
|
|
2003
|
-
if (encrypted.deviceFingerprint !== deviceFingerprint) {
|
|
2004
|
-
throw new Error("Device fingerprint mismatch - wrong device or security threat");
|
|
2005
|
-
}
|
|
2006
|
-
const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
|
|
2007
|
-
const encryptedData = Buffer.from(encrypted.encryptedData, "base64");
|
|
2008
|
-
const nonce = Buffer.from(encrypted.nonce, "base64");
|
|
2009
|
-
try {
|
|
2010
|
-
const secretKey = await this.aesDecrypt(encryptedData, encryptionKey, nonce);
|
|
2011
|
-
return import_web3.Keypair.fromSecretKey(secretKey);
|
|
2012
|
-
} catch (error) {
|
|
2013
|
-
throw new Error("Decryption failed - wrong PIN or corrupted data");
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
/**
|
|
2017
|
-
* Derive encryption key from PIN + device fingerprint using Argon2id
|
|
2018
|
-
*
|
|
2019
|
-
* Argon2id parameters (OWASP recommended):
|
|
2020
|
-
* - Memory: 64MB (65536 KB)
|
|
2021
|
-
* - Iterations: 3
|
|
2022
|
-
* - Parallelism: 4
|
|
2023
|
-
* - Salt: device fingerprint
|
|
2024
|
-
*/
|
|
2025
|
-
static async deriveKey(pin, deviceFingerprint) {
|
|
2026
|
-
if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
|
|
2027
|
-
const encoder = new TextEncoder();
|
|
2028
|
-
const keyMaterial = await window.crypto.subtle.importKey(
|
|
2029
|
-
"raw",
|
|
2030
|
-
encoder.encode(pin),
|
|
2031
|
-
{ name: "PBKDF2" },
|
|
2032
|
-
false,
|
|
2033
|
-
["deriveBits"]
|
|
2034
|
-
);
|
|
2035
|
-
const derivedBits = await window.crypto.subtle.deriveBits(
|
|
2036
|
-
{
|
|
2037
|
-
name: "PBKDF2",
|
|
2038
|
-
salt: encoder.encode(deviceFingerprint),
|
|
2039
|
-
iterations: 1e5,
|
|
2040
|
-
// High iteration count for security
|
|
2041
|
-
hash: "SHA-256"
|
|
2042
|
-
},
|
|
2043
|
-
keyMaterial,
|
|
2044
|
-
256
|
|
2045
|
-
// 256 bits = 32 bytes for AES-256
|
|
2046
|
-
);
|
|
2047
|
-
return new Uint8Array(derivedBits);
|
|
2048
|
-
} else {
|
|
2049
|
-
const salt = crypto2.createHash("sha256").update(deviceFingerprint).digest();
|
|
2050
|
-
return crypto2.pbkdf2Sync(pin, salt, 1e5, 32, "sha256");
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
/**
|
|
2054
|
-
* Generate random nonce for AES-GCM (12 bytes)
|
|
2055
|
-
*/
|
|
2056
|
-
static generateNonce() {
|
|
2057
|
-
if (typeof window !== "undefined" && window.crypto) {
|
|
2058
|
-
return window.crypto.getRandomValues(new Uint8Array(12));
|
|
2059
|
-
} else {
|
|
2060
|
-
return crypto2.randomBytes(12);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Encrypt with AES-256-GCM
|
|
2065
|
-
*/
|
|
2066
|
-
static async aesEncrypt(plaintext, key, nonce) {
|
|
2067
|
-
if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
|
|
2068
|
-
const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
|
|
2069
|
-
const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
|
|
2070
|
-
const plaintextBuffer = plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength);
|
|
2071
|
-
const cryptoKey = await window.crypto.subtle.importKey(
|
|
2072
|
-
"raw",
|
|
2073
|
-
keyBuffer,
|
|
2074
|
-
{ name: "AES-GCM" },
|
|
2075
|
-
false,
|
|
2076
|
-
["encrypt"]
|
|
2077
|
-
);
|
|
2078
|
-
const encrypted = await window.crypto.subtle.encrypt(
|
|
2079
|
-
{
|
|
2080
|
-
name: "AES-GCM",
|
|
2081
|
-
iv: nonceBuffer
|
|
2082
|
-
},
|
|
2083
|
-
cryptoKey,
|
|
2084
|
-
plaintextBuffer
|
|
2085
|
-
);
|
|
2086
|
-
return new Uint8Array(encrypted);
|
|
2087
|
-
} else {
|
|
2088
|
-
const cipher = crypto2.createCipheriv("aes-256-gcm", key, nonce);
|
|
2089
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
2090
|
-
const authTag = cipher.getAuthTag();
|
|
2091
|
-
return new Uint8Array(Buffer.concat([encrypted, authTag]));
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
/**
|
|
2095
|
-
* Decrypt with AES-256-GCM
|
|
2096
|
-
*/
|
|
2097
|
-
static async aesDecrypt(ciphertext, key, nonce) {
|
|
2098
|
-
if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
|
|
2099
|
-
const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
|
|
2100
|
-
const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
|
|
2101
|
-
const ciphertextBuffer = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
|
|
2102
|
-
const cryptoKey = await window.crypto.subtle.importKey(
|
|
2103
|
-
"raw",
|
|
2104
|
-
keyBuffer,
|
|
2105
|
-
{ name: "AES-GCM" },
|
|
2106
|
-
false,
|
|
2107
|
-
["decrypt"]
|
|
2108
|
-
);
|
|
2109
|
-
const decrypted = await window.crypto.subtle.decrypt(
|
|
2110
|
-
{
|
|
2111
|
-
name: "AES-GCM",
|
|
2112
|
-
iv: nonceBuffer
|
|
2113
|
-
},
|
|
2114
|
-
cryptoKey,
|
|
2115
|
-
ciphertextBuffer
|
|
2116
|
-
);
|
|
2117
|
-
return new Uint8Array(decrypted);
|
|
2118
|
-
} else {
|
|
2119
|
-
const authTag = ciphertext.slice(-16);
|
|
2120
|
-
const encrypted = ciphertext.slice(0, -16);
|
|
2121
|
-
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
|
|
2122
|
-
decipher.setAuthTag(authTag);
|
|
2123
|
-
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
2124
|
-
return new Uint8Array(decrypted);
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
};
|
|
2128
|
-
var RecoveryQRGenerator = class {
|
|
2129
|
-
/**
|
|
2130
|
-
* Generate recovery QR data
|
|
2131
|
-
* This allows users to recover their session key on a new device
|
|
2132
|
-
*/
|
|
2133
|
-
static generate(encrypted) {
|
|
2134
|
-
return {
|
|
2135
|
-
encryptedSessionKey: encrypted.encryptedData,
|
|
2136
|
-
nonce: encrypted.nonce,
|
|
2137
|
-
publicKey: encrypted.publicKey,
|
|
2138
|
-
version: "v1",
|
|
2139
|
-
createdAt: Date.now()
|
|
2140
|
-
};
|
|
2141
|
-
}
|
|
2142
|
-
/**
|
|
2143
|
-
* Encode recovery QR as JSON string
|
|
2144
|
-
*/
|
|
2145
|
-
static encode(recoveryQR) {
|
|
2146
|
-
return JSON.stringify(recoveryQR);
|
|
2147
|
-
}
|
|
2148
|
-
/**
|
|
2149
|
-
* Decode recovery QR from JSON string
|
|
2150
|
-
*/
|
|
2151
|
-
static decode(qrData) {
|
|
2152
|
-
try {
|
|
2153
|
-
const parsed = JSON.parse(qrData);
|
|
2154
|
-
if (!parsed.encryptedSessionKey || !parsed.nonce || !parsed.publicKey) {
|
|
2155
|
-
throw new Error("Invalid recovery QR data");
|
|
2156
|
-
}
|
|
2157
|
-
return parsed;
|
|
2158
|
-
} catch (error) {
|
|
2159
|
-
throw new Error("Failed to decode recovery QR");
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
/**
|
|
2163
|
-
* Re-encrypt session key for new device
|
|
2164
|
-
*/
|
|
2165
|
-
static async reEncryptForNewDevice(recoveryQR, oldPin, oldDeviceFingerprint, newPin, newDeviceFingerprint) {
|
|
2166
|
-
const oldEncrypted = {
|
|
2167
|
-
encryptedData: recoveryQR.encryptedSessionKey,
|
|
2168
|
-
nonce: recoveryQR.nonce,
|
|
2169
|
-
publicKey: recoveryQR.publicKey,
|
|
2170
|
-
deviceFingerprint: oldDeviceFingerprint,
|
|
2171
|
-
version: "argon2id-aes256gcm-v1"
|
|
2172
|
-
};
|
|
2173
|
-
const keypair = await SessionKeyCrypto.decrypt(oldEncrypted, oldPin, oldDeviceFingerprint);
|
|
2174
|
-
return await SessionKeyCrypto.encrypt(keypair, newPin, newDeviceFingerprint);
|
|
2175
|
-
}
|
|
2176
|
-
};
|
|
2177
|
-
var DeviceBoundSessionKey = class _DeviceBoundSessionKey {
|
|
2178
|
-
encrypted = null;
|
|
2179
|
-
deviceFingerprint = null;
|
|
2180
|
-
sessionKeyId = null;
|
|
2181
|
-
recoveryQR = null;
|
|
2182
|
-
originalKeypair = null;
|
|
2183
|
-
// Store for Lit encryption
|
|
2184
|
-
// Auto-signing cache: decrypted keypair stored in memory
|
|
2185
|
-
// Enables instant signing without re-entering PIN for subsequent payments
|
|
2186
|
-
cachedKeypair = null;
|
|
2187
|
-
cacheExpiry = null;
|
|
2188
|
-
// Timestamp when cache expires
|
|
2189
|
-
DEFAULT_CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
2190
|
-
// 30 minutes
|
|
2191
|
-
/**
|
|
2192
|
-
* Create a new device-bound session key
|
|
2193
|
-
*/
|
|
2194
|
-
static async create(options) {
|
|
2195
|
-
const deviceFingerprint = await DeviceFingerprintGenerator.generate();
|
|
2196
|
-
const keypair = import_web3.Keypair.generate();
|
|
2197
|
-
const encrypted = await SessionKeyCrypto.encrypt(
|
|
2198
|
-
keypair,
|
|
2199
|
-
options.pin,
|
|
2200
|
-
deviceFingerprint.fingerprint
|
|
2201
|
-
);
|
|
2202
|
-
const instance = new _DeviceBoundSessionKey();
|
|
2203
|
-
instance.encrypted = encrypted;
|
|
2204
|
-
instance.deviceFingerprint = deviceFingerprint;
|
|
2205
|
-
instance.originalKeypair = keypair;
|
|
2206
|
-
if (options.generateRecoveryQR) {
|
|
2207
|
-
instance.recoveryQR = RecoveryQRGenerator.generate(encrypted);
|
|
2208
|
-
}
|
|
2209
|
-
return instance;
|
|
2210
|
-
}
|
|
2211
|
-
/**
|
|
2212
|
-
* Get the original keypair (only available immediately after creation)
|
|
2213
|
-
* Used for Lit Protocol encryption during session creation
|
|
2214
|
-
* @internal
|
|
2215
|
-
*/
|
|
2216
|
-
getKeypair() {
|
|
2217
|
-
if (!this.originalKeypair) {
|
|
2218
|
-
throw new Error("Keypair not available - only accessible during session creation");
|
|
2219
|
-
}
|
|
2220
|
-
return this.originalKeypair;
|
|
2221
|
-
}
|
|
2222
|
-
/**
|
|
2223
|
-
* Get encrypted data for backend storage
|
|
2224
|
-
*/
|
|
2225
|
-
getEncryptedData() {
|
|
2226
|
-
if (!this.encrypted) {
|
|
2227
|
-
throw new Error("Session key not created yet");
|
|
2228
|
-
}
|
|
2229
|
-
return this.encrypted;
|
|
2230
|
-
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Get device fingerprint
|
|
2233
|
-
*/
|
|
2234
|
-
getDeviceFingerprint() {
|
|
2235
|
-
if (!this.deviceFingerprint) {
|
|
2236
|
-
throw new Error("Device fingerprint not generated yet");
|
|
2237
|
-
}
|
|
2238
|
-
return this.deviceFingerprint.fingerprint;
|
|
2239
|
-
}
|
|
2240
|
-
/**
|
|
2241
|
-
* Get public key
|
|
2242
|
-
*/
|
|
2243
|
-
getPublicKey() {
|
|
2244
|
-
if (!this.encrypted) {
|
|
2245
|
-
throw new Error("Session key not created yet");
|
|
2246
|
-
}
|
|
2247
|
-
return this.encrypted.publicKey;
|
|
2248
|
-
}
|
|
2249
|
-
/**
|
|
2250
|
-
* Get recovery QR data (if generated)
|
|
2251
|
-
*/
|
|
2252
|
-
getRecoveryQR() {
|
|
2253
|
-
return this.recoveryQR;
|
|
2254
|
-
}
|
|
2255
|
-
/**
|
|
2256
|
-
* Decrypt and sign a transaction
|
|
2257
|
-
*
|
|
2258
|
-
* @param transaction - The transaction to sign
|
|
2259
|
-
* @param pin - User's PIN (required only if keypair not cached)
|
|
2260
|
-
* @param cacheKeypair - Whether to cache the decrypted keypair for future use (default: true)
|
|
2261
|
-
* @param cacheTTL - Cache time-to-live in milliseconds (default: 30 minutes)
|
|
2262
|
-
*
|
|
2263
|
-
* @example
|
|
2264
|
-
* ```typescript
|
|
2265
|
-
* // First payment: requires PIN, caches keypair
|
|
2266
|
-
* await sessionKey.signTransaction(tx1, '123456', true);
|
|
2267
|
-
*
|
|
2268
|
-
* // Subsequent payments: uses cached keypair, no PIN needed!
|
|
2269
|
-
* await sessionKey.signTransaction(tx2, '', false); // PIN ignored if cached
|
|
2270
|
-
*
|
|
2271
|
-
* // Clear cache when done
|
|
2272
|
-
* sessionKey.clearCache();
|
|
2273
|
-
* ```
|
|
2274
|
-
*/
|
|
2275
|
-
async signTransaction(transaction, pin = "", cacheKeypair = true, cacheTTL) {
|
|
2276
|
-
if (!this.encrypted || !this.deviceFingerprint) {
|
|
2277
|
-
throw new Error("Session key not initialized");
|
|
2278
|
-
}
|
|
2279
|
-
let keypair;
|
|
2280
|
-
if (this.isCached()) {
|
|
2281
|
-
keypair = this.cachedKeypair;
|
|
2282
|
-
} else {
|
|
2283
|
-
if (!pin) {
|
|
2284
|
-
throw new Error("PIN required: no cached keypair available");
|
|
2285
|
-
}
|
|
2286
|
-
keypair = await SessionKeyCrypto.decrypt(
|
|
2287
|
-
this.encrypted,
|
|
2288
|
-
pin,
|
|
2289
|
-
this.deviceFingerprint.fingerprint
|
|
2290
|
-
);
|
|
2291
|
-
if (cacheKeypair) {
|
|
2292
|
-
const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
|
|
2293
|
-
this.cacheKeypair(keypair, ttl);
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
transaction.partialSign(keypair);
|
|
2297
|
-
return transaction;
|
|
2298
|
-
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Check if keypair is cached and valid
|
|
2301
|
-
*/
|
|
2302
|
-
isCached() {
|
|
2303
|
-
if (!this.cachedKeypair || !this.cacheExpiry) {
|
|
2304
|
-
return false;
|
|
2305
|
-
}
|
|
2306
|
-
const now = Date.now();
|
|
2307
|
-
if (now > this.cacheExpiry) {
|
|
2308
|
-
this.clearCache();
|
|
2309
|
-
return false;
|
|
2310
|
-
}
|
|
2311
|
-
return true;
|
|
2312
|
-
}
|
|
2313
|
-
/**
|
|
2314
|
-
* Manually cache a keypair
|
|
2315
|
-
* Called internally after PIN decryption
|
|
2316
|
-
*/
|
|
2317
|
-
cacheKeypair(keypair, ttl) {
|
|
2318
|
-
this.cachedKeypair = keypair;
|
|
2319
|
-
this.cacheExpiry = Date.now() + ttl;
|
|
2320
|
-
}
|
|
2321
|
-
/**
|
|
2322
|
-
* Clear cached keypair
|
|
2323
|
-
* Should be called when user logs out or session ends
|
|
2324
|
-
*
|
|
2325
|
-
* @example
|
|
2326
|
-
* ```typescript
|
|
2327
|
-
* // Clear cache on logout
|
|
2328
|
-
* sessionKey.clearCache();
|
|
2329
|
-
*
|
|
2330
|
-
* // Or clear automatically on tab close
|
|
2331
|
-
* window.addEventListener('beforeunload', () => {
|
|
2332
|
-
* sessionKey.clearCache();
|
|
2333
|
-
* });
|
|
2334
|
-
* ```
|
|
2335
|
-
*/
|
|
2336
|
-
clearCache() {
|
|
2337
|
-
this.cachedKeypair = null;
|
|
2338
|
-
this.cacheExpiry = null;
|
|
2339
|
-
}
|
|
2340
|
-
/**
|
|
2341
|
-
* Decrypt and cache keypair without signing a transaction
|
|
2342
|
-
* Useful for pre-warming the cache before user makes payments
|
|
2343
|
-
*
|
|
2344
|
-
* @example
|
|
2345
|
-
* ```typescript
|
|
2346
|
-
* // After session key creation, decrypt and cache
|
|
2347
|
-
* await sessionKey.unlockWithPin('123456');
|
|
2348
|
-
*
|
|
2349
|
-
* // Now all subsequent payments are instant (no PIN)
|
|
2350
|
-
* await sessionKey.signTransaction(tx1, '', false); // Instant!
|
|
2351
|
-
* await sessionKey.signTransaction(tx2, '', false); // Instant!
|
|
2352
|
-
* ```
|
|
2353
|
-
*/
|
|
2354
|
-
async unlockWithPin(pin, cacheTTL) {
|
|
2355
|
-
if (!this.encrypted || !this.deviceFingerprint) {
|
|
2356
|
-
throw new Error("Session key not initialized");
|
|
2357
|
-
}
|
|
2358
|
-
const keypair = await SessionKeyCrypto.decrypt(
|
|
2359
|
-
this.encrypted,
|
|
2360
|
-
pin,
|
|
2361
|
-
this.deviceFingerprint.fingerprint
|
|
2362
|
-
);
|
|
2363
|
-
const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
|
|
2364
|
-
this.cacheKeypair(keypair, ttl);
|
|
2365
|
-
}
|
|
2366
|
-
/**
|
|
2367
|
-
* Get time remaining until cache expires (in milliseconds)
|
|
2368
|
-
* Returns 0 if not cached
|
|
2369
|
-
*/
|
|
2370
|
-
getCacheTimeRemaining() {
|
|
2371
|
-
if (!this.isCached() || !this.cacheExpiry) {
|
|
2372
|
-
return 0;
|
|
2373
|
-
}
|
|
2374
|
-
const remaining = this.cacheExpiry - Date.now();
|
|
2375
|
-
return Math.max(0, remaining);
|
|
2376
|
-
}
|
|
2377
|
-
/**
|
|
2378
|
-
* Extend cache expiry time
|
|
2379
|
-
* Useful to keep session active during user activity
|
|
2380
|
-
*
|
|
2381
|
-
* @example
|
|
2382
|
-
* ```typescript
|
|
2383
|
-
* // Extend cache by 15 minutes on each payment
|
|
2384
|
-
* await sessionKey.signTransaction(tx, '');
|
|
2385
|
-
* sessionKey.extendCache(15 * 60 * 1000);
|
|
2386
|
-
* ```
|
|
2387
|
-
*/
|
|
2388
|
-
extendCache(additionalTTL) {
|
|
2389
|
-
if (!this.isCached()) {
|
|
2390
|
-
throw new Error("Cannot extend cache: no cached keypair");
|
|
2391
|
-
}
|
|
2392
|
-
this.cacheExpiry += additionalTTL;
|
|
2393
|
-
}
|
|
2394
|
-
/**
|
|
2395
|
-
* Set session key ID after backend creation
|
|
2396
|
-
*/
|
|
2397
|
-
setSessionKeyId(id) {
|
|
2398
|
-
this.sessionKeyId = id;
|
|
2399
|
-
}
|
|
2400
|
-
/**
|
|
2401
|
-
* Get session key ID
|
|
2402
|
-
*/
|
|
2403
|
-
getSessionKeyId() {
|
|
2404
|
-
if (!this.sessionKeyId) {
|
|
2405
|
-
throw new Error("Session key not registered with backend");
|
|
2406
|
-
}
|
|
2407
|
-
return this.sessionKeyId;
|
|
2408
|
-
}
|
|
2409
|
-
};
|
|
2410
|
-
async function encryptKeypairWithLit(keypair, options) {
|
|
2411
|
-
const network = options?.network || "datil";
|
|
2412
|
-
const debug = options?.debug || false;
|
|
2413
|
-
if (debug) {
|
|
2414
|
-
console.log("[Lit] Encrypting keypair with Lit Protocol...");
|
|
2415
|
-
}
|
|
2416
|
-
try {
|
|
2417
|
-
const { LitNodeClient } = await import("@lit-protocol/lit-node-client");
|
|
2418
|
-
const litClient = new LitNodeClient({
|
|
2419
|
-
litNetwork: network,
|
|
2420
|
-
debug
|
|
2421
|
-
});
|
|
2422
|
-
await litClient.connect();
|
|
2423
|
-
if (debug) {
|
|
2424
|
-
console.log(`[Lit] Connected to ${network}`);
|
|
2425
|
-
}
|
|
2426
|
-
try {
|
|
2427
|
-
const evmContractConditions = [
|
|
2428
|
-
{
|
|
2429
|
-
contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
2430
|
-
// USDC on Ethereum
|
|
2431
|
-
functionName: "balanceOf",
|
|
2432
|
-
functionParams: [":userAddress"],
|
|
2433
|
-
functionAbi: {
|
|
2434
|
-
constant: true,
|
|
2435
|
-
inputs: [{ name: "account", type: "address" }],
|
|
2436
|
-
name: "balanceOf",
|
|
2437
|
-
outputs: [{ name: "", type: "uint256" }],
|
|
2438
|
-
stateMutability: "view",
|
|
2439
|
-
type: "function"
|
|
2440
|
-
},
|
|
2441
|
-
chain: "ethereum",
|
|
2442
|
-
returnValueTest: {
|
|
2443
|
-
key: "",
|
|
2444
|
-
comparator: ">=",
|
|
2445
|
-
value: "0"
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
];
|
|
2449
|
-
const dataToEncrypt = keypair.secretKey;
|
|
2450
|
-
if (debug) {
|
|
2451
|
-
console.log("[Lit] Encrypting keypair bytes with evmContractConditions...");
|
|
2452
|
-
}
|
|
2453
|
-
const encryptResult = await litClient.encrypt({
|
|
2454
|
-
evmContractConditions,
|
|
2455
|
-
dataToEncrypt
|
|
2456
|
-
});
|
|
2457
|
-
if (debug) {
|
|
2458
|
-
console.log("[Lit] \u2705 Encryption successful");
|
|
2459
|
-
}
|
|
2460
|
-
return {
|
|
2461
|
-
ciphertext: encryptResult.ciphertext,
|
|
2462
|
-
dataHash: encryptResult.dataToEncryptHash
|
|
2463
|
-
};
|
|
2464
|
-
} finally {
|
|
2465
|
-
await litClient.disconnect();
|
|
2466
|
-
}
|
|
2467
|
-
} catch (error) {
|
|
2468
|
-
if (debug) {
|
|
2469
|
-
console.error("[Lit] \u274C Encryption failed:", error);
|
|
2470
|
-
}
|
|
2471
|
-
throw new Error(
|
|
2472
|
-
`Lit Protocol encryption failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2473
|
-
);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
// src/aip/session-keys.ts
|
|
2478
|
-
var import_web32 = require("@solana/web3.js");
|
|
2479
|
-
var SessionKeysAPI = class {
|
|
2480
|
-
sessionKeys = /* @__PURE__ */ new Map();
|
|
2481
|
-
sessionMetadata = /* @__PURE__ */ new Map();
|
|
2482
|
-
requestFn;
|
|
2483
|
-
debugMode = false;
|
|
2484
|
-
constructor(request) {
|
|
2485
|
-
this.requestFn = request;
|
|
2486
|
-
}
|
|
2487
|
-
/**
|
|
2488
|
-
* Enable debug logging
|
|
2489
|
-
*/
|
|
2490
|
-
debug(...args) {
|
|
2491
|
-
if (this.debugMode && typeof console !== "undefined") {
|
|
2492
|
-
console.log("[ZendFi SessionKeys]", ...args);
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
/**
|
|
2496
|
-
* Create a new device-bound session key
|
|
2497
|
-
*
|
|
2498
|
-
* The keypair is generated client-side and encrypted with your PIN.
|
|
2499
|
-
* The backend NEVER sees your private key.
|
|
2500
|
-
*
|
|
2501
|
-
* @param options - Session key configuration
|
|
2502
|
-
* @returns Created session key info with optional recovery QR
|
|
2503
|
-
*
|
|
2504
|
-
* @example
|
|
2505
|
-
* ```typescript
|
|
2506
|
-
* const result = await zendfi.sessionKeys.create({
|
|
2507
|
-
* userWallet: '7xKNH...',
|
|
2508
|
-
* agentId: 'shopping-assistant-v1',
|
|
2509
|
-
* agentName: 'AI Shopping Assistant',
|
|
2510
|
-
* limitUSDC: 100,
|
|
2511
|
-
* durationDays: 7,
|
|
2512
|
-
* pin: '123456',
|
|
2513
|
-
* generateRecoveryQR: true,
|
|
2514
|
-
* });
|
|
2515
|
-
*
|
|
2516
|
-
* console.log(`Session key: ${result.sessionKeyId}`);
|
|
2517
|
-
* console.log(`Recovery QR: ${result.recoveryQR}`);
|
|
2518
|
-
* ```
|
|
2519
|
-
*/
|
|
2520
|
-
async create(options) {
|
|
2521
|
-
if (!options.pin || options.pin.length < 4) {
|
|
2522
|
-
throw new Error("PIN must be at least 4 characters");
|
|
2523
|
-
}
|
|
2524
|
-
const sessionKey = await DeviceBoundSessionKey.create({
|
|
2525
|
-
pin: options.pin,
|
|
2526
|
-
limitUSDC: options.limitUSDC,
|
|
2527
|
-
durationDays: options.durationDays,
|
|
2528
|
-
userWallet: options.userWallet,
|
|
2529
|
-
generateRecoveryQR: options.generateRecoveryQR
|
|
2530
|
-
});
|
|
2531
|
-
const encrypted = sessionKey.getEncryptedData();
|
|
2532
|
-
let recoveryQR;
|
|
2533
|
-
if (options.generateRecoveryQR) {
|
|
2534
|
-
const qr = RecoveryQRGenerator.generate(encrypted);
|
|
2535
|
-
recoveryQR = RecoveryQRGenerator.encode(qr);
|
|
2536
|
-
}
|
|
2537
|
-
let litEncryption;
|
|
2538
|
-
const enableLit = options.enableLitProtocol !== false;
|
|
2539
|
-
if (enableLit) {
|
|
2540
|
-
const maxRetries = 3;
|
|
2541
|
-
let lastError;
|
|
2542
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
2543
|
-
try {
|
|
2544
|
-
this.debug(`Encrypting session key with Lit Protocol (attempt ${attempt}/${maxRetries})...`);
|
|
2545
|
-
litEncryption = await encryptKeypairWithLit(sessionKey.getKeypair(), {
|
|
2546
|
-
network: options.litNetwork || "datil",
|
|
2547
|
-
debug: this.debugMode
|
|
2548
|
-
});
|
|
2549
|
-
this.debug("Lit Protocol encryption successful - autonomous signing enabled");
|
|
2550
|
-
break;
|
|
2551
|
-
} catch (error) {
|
|
2552
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2553
|
-
if (attempt < maxRetries) {
|
|
2554
|
-
const delayMs = 500 * Math.pow(2, attempt - 1);
|
|
2555
|
-
this.debug(`Lit encryption attempt ${attempt} failed, retrying in ${delayMs}ms...`);
|
|
2556
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2557
|
-
} else {
|
|
2558
|
-
const errorMsg = lastError.message;
|
|
2559
|
-
this.debug("Lit encryption failed after 3 attempts, continuing without autonomous mode:", errorMsg);
|
|
2560
|
-
console.warn("[ZendFi SDK] Lit Protocol encryption failed after retries:", errorMsg);
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
} else {
|
|
2565
|
-
this.debug("\u2139\uFE0F Lit Protocol disabled - autonomous signing not available");
|
|
2566
|
-
}
|
|
2567
|
-
const request = {
|
|
2568
|
-
user_wallet: options.userWallet,
|
|
2569
|
-
agent_id: options.agentId,
|
|
2570
|
-
agent_name: options.agentName,
|
|
2571
|
-
limit_usdc: options.limitUSDC,
|
|
2572
|
-
duration_days: options.durationDays,
|
|
2573
|
-
encrypted_session_key: encrypted.encryptedData,
|
|
2574
|
-
nonce: encrypted.nonce,
|
|
2575
|
-
session_public_key: encrypted.publicKey,
|
|
2576
|
-
device_fingerprint: sessionKey.getDeviceFingerprint(),
|
|
2577
|
-
recovery_qr_data: recoveryQR,
|
|
2578
|
-
// Include Lit encryption if available
|
|
2579
|
-
lit_encrypted_keypair: litEncryption?.ciphertext,
|
|
2580
|
-
lit_data_hash: litEncryption?.dataHash
|
|
2581
|
-
};
|
|
2582
|
-
const response = await this.requestFn(
|
|
2583
|
-
"POST",
|
|
2584
|
-
"/api/v1/ai/session-keys/device-bound/create",
|
|
2585
|
-
request
|
|
2586
|
-
);
|
|
2587
|
-
sessionKey.setSessionKeyId(response.session_key_id);
|
|
2588
|
-
this.sessionKeys.set(response.session_key_id, sessionKey);
|
|
2589
|
-
this.sessionMetadata.set(response.session_key_id, {
|
|
2590
|
-
agentId: response.agent_id,
|
|
2591
|
-
agentName: response.agent_name
|
|
2592
|
-
});
|
|
2593
|
-
return {
|
|
2594
|
-
sessionKeyId: response.session_key_id,
|
|
2595
|
-
agentId: response.agent_id,
|
|
2596
|
-
agentName: response.agent_name,
|
|
2597
|
-
sessionWallet: response.session_wallet,
|
|
2598
|
-
limitUsdc: response.limit_usdc,
|
|
2599
|
-
expiresAt: response.expires_at,
|
|
2600
|
-
recoveryQR,
|
|
2601
|
-
crossAppCompatible: response.cross_app_compatible
|
|
2602
|
-
};
|
|
2603
|
-
}
|
|
2604
|
-
/**
|
|
2605
|
-
* Load an existing session key from backend
|
|
2606
|
-
*
|
|
2607
|
-
* Fetches the encrypted session key and decrypts it with your PIN.
|
|
2608
|
-
* Use this when resuming a session on the same device.
|
|
2609
|
-
*
|
|
2610
|
-
* @param sessionKeyId - UUID of the session key
|
|
2611
|
-
* @param pin - PIN to decrypt the session key
|
|
2612
|
-
*
|
|
2613
|
-
* @example
|
|
2614
|
-
* ```typescript
|
|
2615
|
-
* // Resume session on same device
|
|
2616
|
-
* await zendfi.sessionKeys.load('uuid-of-session-key', '123456');
|
|
2617
|
-
*
|
|
2618
|
-
* // Now you can make payments
|
|
2619
|
-
* await zendfi.sessionKeys.makePayment({...});
|
|
2620
|
-
* ```
|
|
2621
|
-
*/
|
|
2622
|
-
async load(sessionKeyId, pin) {
|
|
2623
|
-
const deviceInfo = await DeviceFingerprintGenerator.generate();
|
|
2624
|
-
const response = await this.requestFn(
|
|
2625
|
-
"POST",
|
|
2626
|
-
"/api/v1/ai/session-keys/device-bound/get-encrypted",
|
|
2627
|
-
{
|
|
2628
|
-
session_key_id: sessionKeyId,
|
|
2629
|
-
device_fingerprint: deviceInfo.fingerprint
|
|
2630
|
-
}
|
|
2631
|
-
);
|
|
2632
|
-
if (!response.device_fingerprint_valid) {
|
|
2633
|
-
throw new Error(
|
|
2634
|
-
"Device fingerprint mismatch - this session key was created on a different device. Use recover() to migrate."
|
|
2635
|
-
);
|
|
2636
|
-
}
|
|
2637
|
-
const encrypted = {
|
|
2638
|
-
encryptedData: response.encrypted_session_key,
|
|
2639
|
-
nonce: response.nonce,
|
|
2640
|
-
publicKey: "",
|
|
2641
|
-
// Will be populated after decryption
|
|
2642
|
-
deviceFingerprint: deviceInfo.fingerprint,
|
|
2643
|
-
version: "argon2id-aes256gcm-v1"
|
|
2644
|
-
};
|
|
2645
|
-
const keypair = await SessionKeyCrypto.decrypt(encrypted, pin, deviceInfo.fingerprint);
|
|
2646
|
-
encrypted.publicKey = keypair.publicKey.toBase58();
|
|
2647
|
-
const sessionKey = new DeviceBoundSessionKey();
|
|
2648
|
-
sessionKey.encrypted = encrypted;
|
|
2649
|
-
sessionKey.deviceFingerprint = deviceInfo;
|
|
2650
|
-
sessionKey.setSessionKeyId(sessionKeyId);
|
|
2651
|
-
this.sessionKeys.set(sessionKeyId, sessionKey);
|
|
2652
|
-
}
|
|
2653
|
-
/**
|
|
2654
|
-
* Unlock a session key for auto-signing
|
|
2655
|
-
*
|
|
2656
|
-
* After unlocking, payments can be made without entering PIN.
|
|
2657
|
-
* The decrypted keypair is cached in memory with a TTL.
|
|
2658
|
-
*
|
|
2659
|
-
* @param sessionKeyId - UUID of the session key
|
|
2660
|
-
* @param pin - PIN to decrypt the session key
|
|
2661
|
-
* @param cacheTTL - How long to cache (default: 30 minutes)
|
|
2662
|
-
*
|
|
2663
|
-
* @example
|
|
2664
|
-
* ```typescript
|
|
2665
|
-
* // Unlock once
|
|
2666
|
-
* await zendfi.sessionKeys.unlock('uuid', '123456');
|
|
2667
|
-
*
|
|
2668
|
-
* // Make payments instantly (no PIN!)
|
|
2669
|
-
* await zendfi.sessionKeys.makePayment({...}); // Instant!
|
|
2670
|
-
* await zendfi.sessionKeys.makePayment({...}); // Instant!
|
|
2671
|
-
* ```
|
|
2672
|
-
*/
|
|
2673
|
-
async unlock(sessionKeyId, pin, cacheTTL) {
|
|
2674
|
-
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
2675
|
-
if (!sessionKey) {
|
|
2676
|
-
await this.load(sessionKeyId, pin);
|
|
2677
|
-
const loaded = this.sessionKeys.get(sessionKeyId);
|
|
2678
|
-
if (loaded) {
|
|
2679
|
-
await loaded.unlockWithPin(pin, cacheTTL);
|
|
2680
|
-
}
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
await sessionKey.unlockWithPin(pin, cacheTTL);
|
|
2684
|
-
}
|
|
2685
|
-
/**
|
|
2686
|
-
* Make a payment using a session key
|
|
2687
|
-
*
|
|
2688
|
-
* If the session key is unlocked (cached), no PIN is needed.
|
|
2689
|
-
* Otherwise, you must provide the PIN.
|
|
2690
|
-
*
|
|
2691
|
-
* @param options - Payment configuration
|
|
2692
|
-
* @returns Payment result with signature
|
|
2693
|
-
*
|
|
2694
|
-
* @example
|
|
2695
|
-
* ```typescript
|
|
2696
|
-
* // With unlocked session key (no PIN)
|
|
2697
|
-
* const result = await zendfi.sessionKeys.makePayment({
|
|
2698
|
-
* sessionKeyId: 'uuid',
|
|
2699
|
-
* amount: 5.0,
|
|
2700
|
-
* recipient: '8xYZA...',
|
|
2701
|
-
* description: 'Coffee purchase',
|
|
2702
|
-
* });
|
|
2703
|
-
*
|
|
2704
|
-
* // Or with PIN (one-time)
|
|
2705
|
-
* const result = await zendfi.sessionKeys.makePayment({
|
|
2706
|
-
* sessionKeyId: 'uuid',
|
|
2707
|
-
* amount: 5.0,
|
|
2708
|
-
* recipient: '8xYZA...',
|
|
2709
|
-
* pin: '123456',
|
|
2710
|
-
* });
|
|
2711
|
-
* ```
|
|
2712
|
-
*/
|
|
2713
|
-
async makePayment(options) {
|
|
2714
|
-
const sessionKey = this.sessionKeys.get(options.sessionKeyId);
|
|
2715
|
-
if (!sessionKey) {
|
|
2716
|
-
throw new Error(
|
|
2717
|
-
`Session key ${options.sessionKeyId} not loaded. Call create() or load() first.`
|
|
2718
|
-
);
|
|
2719
|
-
}
|
|
2720
|
-
const enableAutoSign = options.enableAutoSign !== false;
|
|
2721
|
-
const needsPin = !sessionKey.isCached();
|
|
2722
|
-
if (needsPin && !options.pin) {
|
|
2723
|
-
throw new Error(
|
|
2724
|
-
"PIN required: session key not unlocked. Provide PIN or call unlock() first."
|
|
2725
|
-
);
|
|
2726
|
-
}
|
|
2727
|
-
const metadata = this.sessionMetadata.get(options.sessionKeyId);
|
|
2728
|
-
if (!metadata) {
|
|
2729
|
-
throw new Error(
|
|
2730
|
-
`Session key metadata not found for ${options.sessionKeyId}. Was it created in this session?`
|
|
2731
|
-
);
|
|
2732
|
-
}
|
|
2733
|
-
const paymentResponse = await this.requestFn("POST", "/api/v1/ai/smart-payment", {
|
|
2734
|
-
agent_id: metadata.agentId,
|
|
2735
|
-
// Required by backend
|
|
2736
|
-
amount_usd: options.amount,
|
|
2737
|
-
user_wallet: sessionKey.getPublicKey(),
|
|
2738
|
-
// Session key's wallet (payer)
|
|
2739
|
-
token: options.token || "USDC",
|
|
2740
|
-
description: options.description,
|
|
2741
|
-
session_key_id: options.sessionKeyId
|
|
2742
|
-
});
|
|
2743
|
-
if (!paymentResponse.requires_signature && paymentResponse.status === "confirmed") {
|
|
2744
|
-
return {
|
|
2745
|
-
paymentId: paymentResponse.payment_id,
|
|
2746
|
-
signature: "",
|
|
2747
|
-
status: paymentResponse.status
|
|
2748
|
-
};
|
|
2749
|
-
}
|
|
2750
|
-
if (!paymentResponse.unsigned_transaction) {
|
|
2751
|
-
throw new Error("Backend did not return unsigned transaction");
|
|
2752
|
-
}
|
|
2753
|
-
const transactionBuffer = Buffer.from(paymentResponse.unsigned_transaction, "base64");
|
|
2754
|
-
const transaction = import_web32.Transaction.from(transactionBuffer);
|
|
2755
|
-
const signedTransaction = await sessionKey.signTransaction(
|
|
2756
|
-
transaction,
|
|
2757
|
-
options.pin || "",
|
|
2758
|
-
enableAutoSign
|
|
2759
|
-
);
|
|
2760
|
-
const submitResponse = await this.requestFn("POST", `/api/v1/ai/payments/${paymentResponse.payment_id}/submit-signed`, {
|
|
2761
|
-
signed_transaction: signedTransaction.serialize().toString("base64")
|
|
2762
|
-
});
|
|
2763
|
-
return {
|
|
2764
|
-
paymentId: paymentResponse.payment_id,
|
|
2765
|
-
signature: submitResponse.signature,
|
|
2766
|
-
status: submitResponse.status
|
|
2767
|
-
};
|
|
2768
|
-
}
|
|
2769
|
-
/**
|
|
2770
|
-
* Get session key status
|
|
2771
|
-
*
|
|
2772
|
-
* @param sessionKeyId - UUID of the session key
|
|
2773
|
-
* @returns Current status including balance and expiry
|
|
2774
|
-
*/
|
|
2775
|
-
async getStatus(sessionKeyId) {
|
|
2776
|
-
const response = await this.requestFn("POST", "/api/v1/ai/session-keys/status", {
|
|
2777
|
-
session_key_id: sessionKeyId
|
|
2778
|
-
});
|
|
2779
|
-
return {
|
|
2780
|
-
sessionKeyId,
|
|
2781
|
-
isActive: response.is_active,
|
|
2782
|
-
isApproved: response.is_approved,
|
|
2783
|
-
limitUsdc: response.limit_usdc,
|
|
2784
|
-
usedAmountUsdc: response.used_amount_usdc,
|
|
2785
|
-
remainingUsdc: response.remaining_usdc,
|
|
2786
|
-
expiresAt: response.expires_at,
|
|
2787
|
-
daysUntilExpiry: response.days_until_expiry
|
|
2788
|
-
};
|
|
2789
|
-
}
|
|
2790
|
-
/**
|
|
2791
|
-
* Revoke a session key
|
|
2792
|
-
*
|
|
2793
|
-
* Permanently deactivates the session key. Cannot be undone.
|
|
2794
|
-
*
|
|
2795
|
-
* @param sessionKeyId - UUID of the session key to revoke
|
|
2796
|
-
*/
|
|
2797
|
-
async revoke(sessionKeyId) {
|
|
2798
|
-
await this.requestFn("POST", "/api/v1/ai/session-keys/revoke", {
|
|
2799
|
-
session_key_id: sessionKeyId
|
|
2800
|
-
});
|
|
2801
|
-
this.sessionKeys.delete(sessionKeyId);
|
|
2802
|
-
}
|
|
2803
|
-
/**
|
|
2804
|
-
* Recover session key on new device
|
|
2805
|
-
*
|
|
2806
|
-
* Use this when moving to a new device with a recovery QR code.
|
|
2807
|
-
*
|
|
2808
|
-
* @param options - Recovery configuration
|
|
2809
|
-
*
|
|
2810
|
-
* @example
|
|
2811
|
-
* ```typescript
|
|
2812
|
-
* await zendfi.sessionKeys.recover({
|
|
2813
|
-
* sessionKeyId: 'uuid',
|
|
2814
|
-
* recoveryQR: '{"encryptedSessionKey":"..."}',
|
|
2815
|
-
* oldPin: '123456',
|
|
2816
|
-
* newPin: '654321',
|
|
2817
|
-
* });
|
|
2818
|
-
* ```
|
|
2819
|
-
*/
|
|
2820
|
-
async recover(options) {
|
|
2821
|
-
const recoveryData = RecoveryQRGenerator.decode(options.recoveryQR);
|
|
2822
|
-
const newDeviceInfo = await DeviceFingerprintGenerator.generate();
|
|
2823
|
-
const newEncrypted = await RecoveryQRGenerator.reEncryptForNewDevice(
|
|
2824
|
-
recoveryData,
|
|
2825
|
-
options.oldPin,
|
|
2826
|
-
"recovery-mode",
|
|
2827
|
-
// Old fingerprint (stored in QR in production)
|
|
2828
|
-
options.newPin,
|
|
2829
|
-
newDeviceInfo.fingerprint
|
|
2830
|
-
);
|
|
2831
|
-
await this.requestFn(
|
|
2832
|
-
"POST",
|
|
2833
|
-
`/api/v1/ai/session-keys/device-bound/${options.sessionKeyId}/recover`,
|
|
2834
|
-
{
|
|
2835
|
-
recovery_qr_data: options.recoveryQR,
|
|
2836
|
-
new_device_fingerprint: newDeviceInfo.fingerprint,
|
|
2837
|
-
new_encrypted_session_key: newEncrypted.encryptedData,
|
|
2838
|
-
new_nonce: newEncrypted.nonce
|
|
2839
|
-
}
|
|
2840
|
-
);
|
|
2841
|
-
await this.load(options.sessionKeyId, options.newPin);
|
|
2842
|
-
}
|
|
2843
|
-
/**
|
|
2844
|
-
* Clear cached keypair for a session key
|
|
2845
|
-
*
|
|
2846
|
-
* Use this on logout or when session ends.
|
|
2847
|
-
*
|
|
2848
|
-
* @param sessionKeyId - UUID of the session key (or all if not specified)
|
|
2849
|
-
*/
|
|
2850
|
-
clearCache(sessionKeyId) {
|
|
2851
|
-
if (sessionKeyId) {
|
|
2852
|
-
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
2853
|
-
if (sessionKey) {
|
|
2854
|
-
sessionKey.clearCache();
|
|
2855
|
-
}
|
|
2856
|
-
} else {
|
|
2857
|
-
for (const sessionKey of this.sessionKeys.values()) {
|
|
2858
|
-
sessionKey.clearCache();
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
}
|
|
2862
|
-
/**
|
|
2863
|
-
* Check if a session key is cached (unlocked)
|
|
2864
|
-
*
|
|
2865
|
-
* @param sessionKeyId - UUID of the session key
|
|
2866
|
-
* @returns True if keypair is cached and auto-signing is enabled
|
|
2867
|
-
*/
|
|
2868
|
-
isCached(sessionKeyId) {
|
|
2869
|
-
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
2870
|
-
return sessionKey?.isCached() || false;
|
|
2871
|
-
}
|
|
2872
|
-
/**
|
|
2873
|
-
* Get time remaining until cache expires
|
|
2874
|
-
*
|
|
2875
|
-
* @param sessionKeyId - UUID of the session key
|
|
2876
|
-
* @returns Milliseconds until cache expires
|
|
2877
|
-
*/
|
|
2878
|
-
getCacheTimeRemaining(sessionKeyId) {
|
|
2879
|
-
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
2880
|
-
return sessionKey?.getCacheTimeRemaining() || 0;
|
|
2881
|
-
}
|
|
2882
|
-
/**
|
|
2883
|
-
* Extend cache expiry time
|
|
2884
|
-
*
|
|
2885
|
-
* Useful to keep session active during user activity.
|
|
2886
|
-
*
|
|
2887
|
-
* @param sessionKeyId - UUID of the session key
|
|
2888
|
-
* @param additionalTTL - Additional time in milliseconds
|
|
2889
|
-
*/
|
|
2890
|
-
extendCache(sessionKeyId, additionalTTL) {
|
|
2891
|
-
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
2892
|
-
if (sessionKey) {
|
|
2893
|
-
sessionKey.extendCache(additionalTTL);
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
/**
|
|
2897
|
-
* Get all loaded session key IDs
|
|
2898
|
-
*
|
|
2899
|
-
* @returns Array of session key UUIDs currently loaded
|
|
2900
|
-
*/
|
|
2901
|
-
getLoadedSessionKeys() {
|
|
2902
|
-
return Array.from(this.sessionKeys.keys());
|
|
2903
|
-
}
|
|
2904
|
-
};
|
|
2905
|
-
|
|
2906
|
-
// src/client.ts
|
|
2907
|
-
var ZendFiClient = class {
|
|
2908
|
-
config;
|
|
2909
|
-
interceptors;
|
|
2910
|
-
// ============================================
|
|
2911
|
-
// Agentic Intent Protocol APIs
|
|
2912
|
-
// ============================================
|
|
2913
|
-
/**
|
|
2914
|
-
* Agent API - Manage agent API keys and sessions
|
|
2915
|
-
*
|
|
2916
|
-
* @example
|
|
2917
|
-
* ```typescript
|
|
2918
|
-
* // Create an agent API key
|
|
2919
|
-
* const agentKey = await zendfi.agent.createKey({
|
|
2920
|
-
* name: 'Shopping Assistant',
|
|
2921
|
-
* agent_id: 'shopping-assistant-v1',
|
|
2922
|
-
* scopes: ['create_payments'],
|
|
2923
|
-
* });
|
|
2924
|
-
*
|
|
2925
|
-
* // Create an agent session
|
|
2926
|
-
* const session = await zendfi.agent.createSession({
|
|
2927
|
-
* agent_id: 'shopping-assistant-v1',
|
|
2928
|
-
* user_wallet: 'Hx7B...abc',
|
|
2929
|
-
* limits: { max_per_day: 500 },
|
|
2930
|
-
* });
|
|
2931
|
-
* ```
|
|
2932
|
-
*/
|
|
2933
|
-
agent;
|
|
2934
|
-
/**
|
|
2935
|
-
* Payment Intents API - Two-phase payment flow
|
|
2936
|
-
*
|
|
2937
|
-
* @example
|
|
2938
|
-
* ```typescript
|
|
2939
|
-
* // Create intent
|
|
2940
|
-
* const intent = await zendfi.intents.create({ amount: 99.99 });
|
|
2941
|
-
*
|
|
2942
|
-
* // Confirm when ready
|
|
2943
|
-
* await zendfi.intents.confirm(intent.id, {
|
|
2944
|
-
* client_secret: intent.client_secret,
|
|
2945
|
-
* customer_wallet: 'Hx7B...abc',
|
|
2946
|
-
* });
|
|
2947
|
-
* ```
|
|
2948
|
-
*/
|
|
2949
|
-
intents;
|
|
2950
|
-
/**
|
|
2951
|
-
* Pricing API - PPP and AI-powered pricing
|
|
2952
|
-
*
|
|
2953
|
-
* @example
|
|
2954
|
-
* ```typescript
|
|
2955
|
-
* // Get PPP factor for Brazil
|
|
2956
|
-
* const factor = await zendfi.pricing.getPPPFactor('BR');
|
|
2957
|
-
* const localPrice = 100 * factor.ppp_factor; // $35 for Brazil
|
|
2958
|
-
*
|
|
2959
|
-
* // Get AI pricing suggestion
|
|
2960
|
-
* const suggestion = await zendfi.pricing.getSuggestion({
|
|
2961
|
-
* agent_id: 'my-agent',
|
|
2962
|
-
* base_price: 100,
|
|
2963
|
-
* user_profile: { location_country: 'BR' },
|
|
2964
|
-
* });
|
|
2965
|
-
* ```
|
|
2966
|
-
*/
|
|
2967
|
-
pricing;
|
|
2968
|
-
/**
|
|
2969
|
-
* Autonomy API - Enable autonomous agent signing
|
|
2970
|
-
*
|
|
2971
|
-
* @example
|
|
2972
|
-
* ```typescript
|
|
2973
|
-
* // Enable autonomous mode for a session key
|
|
2974
|
-
* const delegate = await zendfi.autonomy.enable(sessionKeyId, {
|
|
2975
|
-
* max_amount_usd: 100,
|
|
2976
|
-
* duration_hours: 24,
|
|
2977
|
-
* delegation_signature: signature,
|
|
2978
|
-
* });
|
|
2979
|
-
*
|
|
2980
|
-
* // Check status
|
|
2981
|
-
* const status = await zendfi.autonomy.getStatus(sessionKeyId);
|
|
2982
|
-
* ```
|
|
2983
|
-
*/
|
|
2984
|
-
autonomy;
|
|
2985
|
-
/**
|
|
2986
|
-
* Smart Payments API - AI-powered payment routing
|
|
2987
|
-
*
|
|
2988
|
-
* Create intelligent payments that automatically:
|
|
2989
|
-
* - Apply PPP discounts based on user location
|
|
2990
|
-
* - Use agent sessions when available
|
|
2991
|
-
* - Route to optimal payment paths
|
|
2992
|
-
*
|
|
2993
|
-
* @example
|
|
2994
|
-
* ```typescript
|
|
2995
|
-
* const payment = await zendfi.smart.create({
|
|
2996
|
-
* amount_usd: 99.99,
|
|
2997
|
-
* wallet_address: 'Hx7B...abc',
|
|
2998
|
-
* merchant_id: 'merch_123',
|
|
2999
|
-
* country_code: 'BR', // Apply PPP
|
|
3000
|
-
* enable_ppp: true,
|
|
3001
|
-
* });
|
|
3002
|
-
*
|
|
3003
|
-
* console.log(`Original: $${payment.original_amount_usd}`);
|
|
3004
|
-
* console.log(`Final: $${payment.final_amount_usd}`);
|
|
3005
|
-
* // Original: $99.99
|
|
3006
|
-
* // Final: $64.99 (35% PPP discount applied)
|
|
3007
|
-
* ```
|
|
3008
|
-
*/
|
|
3009
|
-
smart;
|
|
3010
|
-
/**
|
|
3011
|
-
* Session Keys API - On-chain funded session keys with PKP identity
|
|
3012
|
-
*
|
|
3013
|
-
* Session keys are pre-funded wallets with spending limits that enable
|
|
3014
|
-
* AI agents to make autonomous payments without user signatures.
|
|
3015
|
-
*
|
|
3016
|
-
* The flow:
|
|
3017
|
-
* 1. Create a session key (user approves spending limit)
|
|
3018
|
-
* 2. User signs the approval transaction (one-time)
|
|
3019
|
-
* 3. Agent can now make payments up to the limit
|
|
3020
|
-
*
|
|
3021
|
-
* @example
|
|
3022
|
-
* ```typescript
|
|
3023
|
-
* // Create session key
|
|
3024
|
-
* const key = await zendfi.sessionKeys.create({
|
|
3025
|
-
* agent_id: 'shopping-bot',
|
|
3026
|
-
* user_wallet: 'Hx7B...abc',
|
|
3027
|
-
* max_amount: 100,
|
|
3028
|
-
* expiry_hours: 24,
|
|
3029
|
-
* });
|
|
3030
|
-
*
|
|
3031
|
-
* // User signs the approval
|
|
3032
|
-
* await zendfi.sessionKeys.submitApproval(key.session_key_id, {
|
|
3033
|
-
* signed_transaction: signedTx,
|
|
3034
|
-
* });
|
|
3035
|
-
*
|
|
3036
|
-
* // Check status
|
|
3037
|
-
* const status = await zendfi.sessionKeys.getStatus(key.session_key_id);
|
|
3038
|
-
* console.log(`Remaining: $${status.remaining_amount}`);
|
|
3039
|
-
* ```
|
|
3040
|
-
*/
|
|
3041
|
-
sessionKeys;
|
|
3042
|
-
constructor(options) {
|
|
3043
|
-
this.config = ConfigLoader.load(options);
|
|
3044
|
-
ConfigLoader.validateApiKey(this.config.apiKey);
|
|
3045
|
-
this.interceptors = createInterceptors();
|
|
3046
|
-
const boundRequest = this.request.bind(this);
|
|
3047
|
-
this.agent = new AgentAPI(boundRequest);
|
|
3048
|
-
this.intents = new PaymentIntentsAPI(boundRequest);
|
|
3049
|
-
this.pricing = new PricingAPI(boundRequest);
|
|
3050
|
-
this.autonomy = new AutonomyAPI(boundRequest);
|
|
3051
|
-
this.smart = new SmartPaymentsAPI(boundRequest);
|
|
3052
|
-
this.sessionKeys = new SessionKeysAPI(boundRequest);
|
|
3053
|
-
if (this.config.environment === "development" || this.config.debug) {
|
|
3054
|
-
console.log(
|
|
3055
|
-
`\u2713 ZendFi SDK initialized in ${this.config.mode} mode (${this.config.mode === "test" ? "devnet" : "mainnet"})`
|
|
3056
|
-
);
|
|
3057
|
-
if (this.config.debug) {
|
|
3058
|
-
console.log("[ZendFi] Debug mode enabled");
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
/**
|
|
3063
|
-
* Create a new payment
|
|
3064
|
-
*/
|
|
3065
|
-
async createPayment(request) {
|
|
3066
|
-
return this.request("POST", "/api/v1/payments", {
|
|
3067
|
-
...request,
|
|
3068
|
-
currency: request.currency || "USD",
|
|
3069
|
-
token: request.token || "USDC"
|
|
3070
|
-
});
|
|
3071
|
-
}
|
|
3072
|
-
/**
|
|
3073
|
-
* Get payment by ID
|
|
3074
|
-
*/
|
|
3075
|
-
async getPayment(paymentId) {
|
|
3076
|
-
return this.request("GET", `/api/v1/payments/${paymentId}`);
|
|
3077
|
-
}
|
|
3078
|
-
/**
|
|
3079
|
-
* List all payments with pagination
|
|
3080
|
-
*/
|
|
3081
|
-
async listPayments(request) {
|
|
3082
|
-
const params = new URLSearchParams();
|
|
3083
|
-
if (request?.page) params.append("page", request.page.toString());
|
|
3084
|
-
if (request?.limit) params.append("limit", request.limit.toString());
|
|
3085
|
-
if (request?.status) params.append("status", request.status);
|
|
3086
|
-
if (request?.from_date) params.append("from_date", request.from_date);
|
|
3087
|
-
if (request?.to_date) params.append("to_date", request.to_date);
|
|
3088
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
3089
|
-
return this.request("GET", `/api/v1/payments${query}`);
|
|
3090
|
-
}
|
|
3091
|
-
/**
|
|
3092
|
-
* Create a subscription plan
|
|
3093
|
-
*/
|
|
3094
|
-
async createSubscriptionPlan(request) {
|
|
3095
|
-
return this.request("POST", "/api/v1/subscriptions/plans", {
|
|
3096
|
-
...request,
|
|
3097
|
-
currency: request.currency || "USD",
|
|
3098
|
-
interval_count: request.interval_count || 1,
|
|
3099
|
-
trial_days: request.trial_days || 0
|
|
3100
|
-
});
|
|
3101
|
-
}
|
|
3102
|
-
/**
|
|
3103
|
-
* Get subscription plan by ID
|
|
3104
|
-
*/
|
|
3105
|
-
async getSubscriptionPlan(planId) {
|
|
3106
|
-
return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
|
|
3107
|
-
}
|
|
3108
|
-
/**
|
|
3109
|
-
* Create a subscription
|
|
3110
|
-
*/
|
|
3111
|
-
async createSubscription(request) {
|
|
3112
|
-
return this.request("POST", "/api/v1/subscriptions", request);
|
|
3113
|
-
}
|
|
3114
|
-
/**
|
|
3115
|
-
* Get subscription by ID
|
|
3116
|
-
*/
|
|
3117
|
-
async getSubscription(subscriptionId) {
|
|
3118
|
-
return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
|
|
3119
|
-
}
|
|
3120
|
-
/**
|
|
3121
|
-
* Cancel a subscription
|
|
3122
|
-
*/
|
|
3123
|
-
async cancelSubscription(subscriptionId) {
|
|
3124
|
-
return this.request(
|
|
3125
|
-
"POST",
|
|
3126
|
-
`/api/v1/subscriptions/${subscriptionId}/cancel`
|
|
3127
|
-
);
|
|
3128
|
-
}
|
|
3129
|
-
/**
|
|
3130
|
-
* Create a payment link (shareable checkout URL)
|
|
3131
|
-
*/
|
|
3132
|
-
async createPaymentLink(request) {
|
|
3133
|
-
const response = await this.request("POST", "/api/v1/payment-links", {
|
|
3134
|
-
...request,
|
|
3135
|
-
currency: request.currency || "USD",
|
|
3136
|
-
token: request.token || "USDC",
|
|
3137
|
-
onramp: request.onramp || false
|
|
3138
|
-
});
|
|
3139
|
-
return {
|
|
3140
|
-
...response,
|
|
3141
|
-
url: response.hosted_page_url
|
|
3142
|
-
};
|
|
3143
|
-
}
|
|
3144
|
-
/**
|
|
3145
|
-
* Get payment link by link code
|
|
3146
|
-
*/
|
|
3147
|
-
async getPaymentLink(linkCode) {
|
|
3148
|
-
const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
|
|
3149
|
-
return {
|
|
3150
|
-
...response,
|
|
3151
|
-
url: response.hosted_page_url
|
|
3152
|
-
};
|
|
3153
|
-
}
|
|
3154
|
-
/**
|
|
3155
|
-
* List all payment links for the authenticated merchant
|
|
3156
|
-
*/
|
|
3157
|
-
async listPaymentLinks() {
|
|
3158
|
-
const response = await this.request("GET", "/api/v1/payment-links");
|
|
3159
|
-
return response.map((link) => ({
|
|
3160
|
-
...link,
|
|
3161
|
-
url: link.hosted_page_url
|
|
3162
|
-
}));
|
|
3163
|
-
}
|
|
3164
|
-
/**
|
|
3165
|
-
* Create an installment plan
|
|
3166
|
-
* Split a purchase into multiple scheduled payments
|
|
3167
|
-
*/
|
|
3168
|
-
async createInstallmentPlan(request) {
|
|
3169
|
-
const response = await this.request(
|
|
3170
|
-
"POST",
|
|
3171
|
-
"/api/v1/installment-plans",
|
|
3172
|
-
request
|
|
3173
|
-
);
|
|
3174
|
-
return {
|
|
3175
|
-
id: response.plan_id,
|
|
3176
|
-
plan_id: response.plan_id,
|
|
3177
|
-
status: response.status
|
|
3178
|
-
};
|
|
3179
|
-
}
|
|
3180
|
-
/**
|
|
3181
|
-
* Get installment plan by ID
|
|
3182
|
-
*/
|
|
3183
|
-
async getInstallmentPlan(planId) {
|
|
3184
|
-
return this.request("GET", `/api/v1/installment-plans/${planId}`);
|
|
647
|
+
async getInstallmentPlan(planId) {
|
|
648
|
+
return this.request("GET", `/api/v1/installment-plans/${planId}`);
|
|
3185
649
|
}
|
|
3186
650
|
/**
|
|
3187
651
|
* List all installment plans for merchant
|
|
@@ -3203,63 +667,14 @@ var ZendFiClient = class {
|
|
|
3203
667
|
);
|
|
3204
668
|
}
|
|
3205
669
|
/**
|
|
3206
|
-
* Cancel an installment plan
|
|
3207
|
-
*/
|
|
3208
|
-
async cancelInstallmentPlan(planId) {
|
|
3209
|
-
return this.request(
|
|
3210
|
-
"POST",
|
|
3211
|
-
`/api/v1/installment-plans/${planId}/cancel`
|
|
3212
|
-
);
|
|
3213
|
-
}
|
|
3214
|
-
/**
|
|
3215
|
-
* Create an escrow transaction
|
|
3216
|
-
* Hold funds until conditions are met
|
|
3217
|
-
*/
|
|
3218
|
-
async createEscrow(request) {
|
|
3219
|
-
return this.request("POST", "/api/v1/escrows", {
|
|
3220
|
-
...request,
|
|
3221
|
-
currency: request.currency || "USD",
|
|
3222
|
-
token: request.token || "USDC"
|
|
3223
|
-
});
|
|
3224
|
-
}
|
|
3225
|
-
/**
|
|
3226
|
-
* Get escrow by ID
|
|
3227
|
-
*/
|
|
3228
|
-
async getEscrow(escrowId) {
|
|
3229
|
-
return this.request("GET", `/api/v1/escrows/${escrowId}`);
|
|
3230
|
-
}
|
|
3231
|
-
/**
|
|
3232
|
-
* List all escrows for merchant
|
|
3233
|
-
*/
|
|
3234
|
-
async listEscrows(params) {
|
|
3235
|
-
const query = new URLSearchParams();
|
|
3236
|
-
if (params?.limit) query.append("limit", params.limit.toString());
|
|
3237
|
-
if (params?.offset) query.append("offset", params.offset.toString());
|
|
3238
|
-
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
3239
|
-
return this.request("GET", `/api/v1/escrows${queryString}`);
|
|
3240
|
-
}
|
|
3241
|
-
/**
|
|
3242
|
-
* Approve escrow release to seller
|
|
670
|
+
* Cancel an installment plan
|
|
3243
671
|
*/
|
|
3244
|
-
async
|
|
672
|
+
async cancelInstallmentPlan(planId) {
|
|
3245
673
|
return this.request(
|
|
3246
674
|
"POST",
|
|
3247
|
-
`/api/v1/
|
|
3248
|
-
request
|
|
675
|
+
`/api/v1/installment-plans/${planId}/cancel`
|
|
3249
676
|
);
|
|
3250
677
|
}
|
|
3251
|
-
/**
|
|
3252
|
-
* Refund escrow to buyer
|
|
3253
|
-
*/
|
|
3254
|
-
async refundEscrow(escrowId, request) {
|
|
3255
|
-
return this.request("POST", `/api/v1/escrows/${escrowId}/refund`, request);
|
|
3256
|
-
}
|
|
3257
|
-
/**
|
|
3258
|
-
* Raise a dispute for an escrow
|
|
3259
|
-
*/
|
|
3260
|
-
async disputeEscrow(escrowId, request) {
|
|
3261
|
-
return this.request("POST", `/api/v1/escrows/${escrowId}/dispute`, request);
|
|
3262
|
-
}
|
|
3263
678
|
/**
|
|
3264
679
|
* Create an invoice
|
|
3265
680
|
*/
|
|
@@ -3287,54 +702,6 @@ var ZendFiClient = class {
|
|
|
3287
702
|
async sendInvoice(invoiceId) {
|
|
3288
703
|
return this.request("POST", `/api/v1/invoices/${invoiceId}/send`);
|
|
3289
704
|
}
|
|
3290
|
-
// ============================================
|
|
3291
|
-
// Agentic Intent Protocol - Smart Payments
|
|
3292
|
-
// ============================================
|
|
3293
|
-
/**
|
|
3294
|
-
* Execute an AI-powered smart payment
|
|
3295
|
-
*
|
|
3296
|
-
* Smart payments combine multiple features:
|
|
3297
|
-
* - Automatic PPP pricing adjustments
|
|
3298
|
-
* - Gasless transaction detection
|
|
3299
|
-
* - Instant settlement options
|
|
3300
|
-
* - Escrow integration
|
|
3301
|
-
* - Receipt generation
|
|
3302
|
-
*
|
|
3303
|
-
* @param request - Smart payment configuration
|
|
3304
|
-
* @returns Payment result with status and receipt
|
|
3305
|
-
*
|
|
3306
|
-
* @example
|
|
3307
|
-
* ```typescript
|
|
3308
|
-
* const result = await zendfi.smartPayment({
|
|
3309
|
-
* agent_id: 'shopping-assistant',
|
|
3310
|
-
* user_wallet: 'Hx7B...abc',
|
|
3311
|
-
* amount_usd: 50,
|
|
3312
|
-
* auto_detect_gasless: true,
|
|
3313
|
-
* description: 'Premium subscription',
|
|
3314
|
-
* });
|
|
3315
|
-
*
|
|
3316
|
-
* if (result.requires_signature) {
|
|
3317
|
-
* // Device-bound flow: user needs to sign
|
|
3318
|
-
* console.log('Sign and submit:', result.submit_url);
|
|
3319
|
-
* } else {
|
|
3320
|
-
* // Auto-signed
|
|
3321
|
-
* console.log('Payment complete:', result.transaction_signature);
|
|
3322
|
-
* }
|
|
3323
|
-
* ```
|
|
3324
|
-
*/
|
|
3325
|
-
async smartPayment(request) {
|
|
3326
|
-
return this.smart.execute(request);
|
|
3327
|
-
}
|
|
3328
|
-
/**
|
|
3329
|
-
* Submit a signed transaction for device-bound smart payment
|
|
3330
|
-
*
|
|
3331
|
-
* @param paymentId - UUID of the payment
|
|
3332
|
-
* @param signedTransaction - Base64 encoded signed transaction
|
|
3333
|
-
* @returns Updated payment response
|
|
3334
|
-
*/
|
|
3335
|
-
async submitSignedPayment(paymentId, signedTransaction) {
|
|
3336
|
-
return this.smart.submitSigned(paymentId, signedTransaction);
|
|
3337
|
-
}
|
|
3338
705
|
/**
|
|
3339
706
|
* Verify webhook signature using HMAC-SHA256
|
|
3340
707
|
*
|
|
@@ -3628,6 +995,8 @@ var ZendFiEmbeddedCheckout = class {
|
|
|
3628
995
|
mounted = false;
|
|
3629
996
|
paymentProcessed = false;
|
|
3630
997
|
// Prevent duplicate success callbacks
|
|
998
|
+
// Bank payment state
|
|
999
|
+
bankPaymentState = {};
|
|
3631
1000
|
constructor(config) {
|
|
3632
1001
|
this.config = {
|
|
3633
1002
|
linkCode: config.linkCode || "",
|
|
@@ -3646,7 +1015,8 @@ var ZendFiEmbeddedCheckout = class {
|
|
|
3646
1015
|
paymentMethods: config.paymentMethods || {
|
|
3647
1016
|
walletConnect: true,
|
|
3648
1017
|
qrCode: true,
|
|
3649
|
-
solanaWallet: true
|
|
1018
|
+
solanaWallet: true,
|
|
1019
|
+
bank: true
|
|
3650
1020
|
}
|
|
3651
1021
|
};
|
|
3652
1022
|
if (!this.config.linkCode && !this.config.paymentId) {
|
|
@@ -3909,12 +1279,14 @@ var ZendFiEmbeddedCheckout = class {
|
|
|
3909
1279
|
*/
|
|
3910
1280
|
renderPaymentMethods() {
|
|
3911
1281
|
if (!this.checkoutData) return "";
|
|
1282
|
+
const showBank = this.config.paymentMethods.bank && this.checkoutData.onramp;
|
|
3912
1283
|
return `
|
|
3913
1284
|
<div class="zendfi-payment-methods" style="padding: 24px;">
|
|
3914
1285
|
<h3 style="margin: 0 0 16px 0; font-size: 16px; font-weight: 600; color: #1f2937;">
|
|
3915
1286
|
Payment Methods
|
|
3916
1287
|
</h3>
|
|
3917
1288
|
|
|
1289
|
+
${showBank ? this.renderBankMethod() : ""}
|
|
3918
1290
|
${this.config.paymentMethods.qrCode ? this.renderQRCodeMethod() : ""}
|
|
3919
1291
|
${this.config.paymentMethods.solanaWallet ? this.renderWalletMethod() : ""}
|
|
3920
1292
|
${this.config.paymentMethods.walletConnect ? this.renderWalletConnectMethod() : ""}
|
|
@@ -3981,6 +1353,142 @@ var ZendFiEmbeddedCheckout = class {
|
|
|
3981
1353
|
</div>
|
|
3982
1354
|
`;
|
|
3983
1355
|
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Render bank payment method (PAJ Ramp onramp)
|
|
1358
|
+
*/
|
|
1359
|
+
renderBankMethod() {
|
|
1360
|
+
if (!this.checkoutData) return "";
|
|
1361
|
+
const isEmailStep = !this.bankPaymentState.orderId;
|
|
1362
|
+
const isBankDetailsStep = !!this.bankPaymentState.bankDetails;
|
|
1363
|
+
return `
|
|
1364
|
+
<div class="zendfi-payment-method" style="padding: 16px; border: 2px solid #10b981; border-radius: 12px; margin-bottom: 16px; background: #f0fdf4;">
|
|
1365
|
+
<div style="margin-bottom: 12px;">
|
|
1366
|
+
<h4 style="margin: 0 0 4px 0; font-size: 16px; font-weight: 600; color: #065f46; display: flex; align-items: center;">
|
|
1367
|
+
\u{1F3E6} Pay with Bank Transfer (Nigeria)
|
|
1368
|
+
</h4>
|
|
1369
|
+
<p style="margin: 0; font-size: 12px; color: #047857;">
|
|
1370
|
+
Pay with your Nigerian bank account \u2022 Instant confirmation
|
|
1371
|
+
</p>
|
|
1372
|
+
</div>
|
|
1373
|
+
|
|
1374
|
+
${isEmailStep ? this.renderBankEmailStep() : ""}
|
|
1375
|
+
${isBankDetailsStep ? this.renderBankDetailsStep() : ""}
|
|
1376
|
+
</div>
|
|
1377
|
+
`;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Render bank email/OTP step
|
|
1381
|
+
*/
|
|
1382
|
+
renderBankEmailStep() {
|
|
1383
|
+
return `
|
|
1384
|
+
<div id="zendfi-bank-email-step">
|
|
1385
|
+
<div style="margin-bottom: 12px;">
|
|
1386
|
+
<label style="display: block; font-size: 14px; color: #374151; margin-bottom: 4px; font-weight: 500;">
|
|
1387
|
+
Email Address
|
|
1388
|
+
</label>
|
|
1389
|
+
<input
|
|
1390
|
+
type="email"
|
|
1391
|
+
id="zendfi-bank-email"
|
|
1392
|
+
placeholder="your@email.com"
|
|
1393
|
+
style="width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px;"
|
|
1394
|
+
/>
|
|
1395
|
+
</div>
|
|
1396
|
+
|
|
1397
|
+
<div id="zendfi-bank-otp-container" style="display: none; margin-bottom: 12px;">
|
|
1398
|
+
<label style="display: block; font-size: 14px; color: #374151; margin-bottom: 4px; font-weight: 500;">
|
|
1399
|
+
Verification Code
|
|
1400
|
+
</label>
|
|
1401
|
+
<input
|
|
1402
|
+
type="text"
|
|
1403
|
+
id="zendfi-bank-otp"
|
|
1404
|
+
placeholder="Enter 4-digit code"
|
|
1405
|
+
maxlength="4"
|
|
1406
|
+
style="width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px;"
|
|
1407
|
+
/>
|
|
1408
|
+
<p style="font-size: 12px; color: #6b7280; margin-top: 4px;">
|
|
1409
|
+
Check your email for the verification code
|
|
1410
|
+
</p>
|
|
1411
|
+
</div>
|
|
1412
|
+
|
|
1413
|
+
<button
|
|
1414
|
+
id="zendfi-bank-submit"
|
|
1415
|
+
style="width: 100%; padding: 12px; background: #10b981; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;"
|
|
1416
|
+
onmouseover="this.style.background='#059669';"
|
|
1417
|
+
onmouseout="this.style.background='#10b981';"
|
|
1418
|
+
>
|
|
1419
|
+
Send Verification Code
|
|
1420
|
+
</button>
|
|
1421
|
+
|
|
1422
|
+
<div id="zendfi-bank-error" style="display: none; margin-top: 12px; padding: 12px; background: #fee2e2; border: 1px solid #fecaca; border-radius: 8px; color: #991b1b; font-size: 14px;"></div>
|
|
1423
|
+
</div>
|
|
1424
|
+
`;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Render bank details step
|
|
1428
|
+
*/
|
|
1429
|
+
renderBankDetailsStep() {
|
|
1430
|
+
if (!this.bankPaymentState.bankDetails) return "";
|
|
1431
|
+
const { accountNumber, accountName, bankName, amount } = this.bankPaymentState.bankDetails;
|
|
1432
|
+
return `
|
|
1433
|
+
<div id="zendfi-bank-details-step">
|
|
1434
|
+
<div style="background: white; border: 1px solid #d1d5db; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
1435
|
+
<h5 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: #374151;">
|
|
1436
|
+
\u{1F4CB} Transfer to this account:
|
|
1437
|
+
</h5>
|
|
1438
|
+
|
|
1439
|
+
<div style="margin-bottom: 8px;">
|
|
1440
|
+
<div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Bank Name</div>
|
|
1441
|
+
<div style="font-weight: 600; color: #111827;">${bankName}</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
|
|
1444
|
+
<div style="margin-bottom: 8px;">
|
|
1445
|
+
<div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Account Number</div>
|
|
1446
|
+
<div style="font-weight: 600; color: #111827; font-size: 18px; display: flex; align-items: center; gap: 8px;">
|
|
1447
|
+
<span id="zendfi-bank-account">${accountNumber}</span>
|
|
1448
|
+
<button
|
|
1449
|
+
id="zendfi-copy-account"
|
|
1450
|
+
style="padding: 2px 6px; font-size: 11px; background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer;"
|
|
1451
|
+
>
|
|
1452
|
+
Copy
|
|
1453
|
+
</button>
|
|
1454
|
+
</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
|
|
1457
|
+
<div style="margin-bottom: 8px;">
|
|
1458
|
+
<div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Account Name</div>
|
|
1459
|
+
<div style="font-weight: 600; color: #111827;">${accountName}</div>
|
|
1460
|
+
</div>
|
|
1461
|
+
|
|
1462
|
+
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e5e7eb;">
|
|
1463
|
+
<div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Amount to Send</div>
|
|
1464
|
+
<div style="font-size: 24px; font-weight: 700; color: #8866ff;">${amount.toFixed(2)} NGN</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
</div>
|
|
1467
|
+
|
|
1468
|
+
<div style="background: #fef3c7; border: 1px solid #fcd34d; border-radius: 8px; padding: 12px; margin-bottom: 16px;">
|
|
1469
|
+
<p style="margin: 0; font-size: 12px; color: #92400e;">
|
|
1470
|
+
<strong>Important:</strong> Transfer the exact amount shown above. Payment will auto-complete in 10-30 seconds.
|
|
1471
|
+
</p>
|
|
1472
|
+
</div>
|
|
1473
|
+
|
|
1474
|
+
<div id="zendfi-bank-status" style="text-align: center; padding: 16px;">
|
|
1475
|
+
<div class="zendfi-spinner" style="margin: 0 auto 8px;"></div>
|
|
1476
|
+
<div style="color: #6b7280; font-size: 14px;" id="zendfi-bank-status-text">
|
|
1477
|
+
Waiting for bank transfer... (<span id="zendfi-bank-timer">0:00</span>)
|
|
1478
|
+
</div>
|
|
1479
|
+
<div style="font-size: 12px; color: #9ca3af; margin-top: 4px;">
|
|
1480
|
+
\u23F1\uFE0F Usually takes 10-30 seconds
|
|
1481
|
+
</div>
|
|
1482
|
+
<button
|
|
1483
|
+
id="zendfi-refresh-status"
|
|
1484
|
+
style="margin-top: 12px; padding: 8px 16px; background: white; border: 1px solid #e5e7eb; border-radius: 8px; cursor: pointer; color: #6b7280; font-size: 14px;"
|
|
1485
|
+
>
|
|
1486
|
+
\u{1F504} Refresh Status
|
|
1487
|
+
</button>
|
|
1488
|
+
</div>
|
|
1489
|
+
</div>
|
|
1490
|
+
`;
|
|
1491
|
+
}
|
|
3984
1492
|
/**
|
|
3985
1493
|
* Render footer
|
|
3986
1494
|
*/
|
|
@@ -4053,6 +1561,29 @@ var ZendFiEmbeddedCheckout = class {
|
|
|
4053
1561
|
if (walletConnectBtn) {
|
|
4054
1562
|
walletConnectBtn.addEventListener("click", () => this.handleWalletConnectScan());
|
|
4055
1563
|
}
|
|
1564
|
+
const bankSubmitBtn = document.getElementById("zendfi-bank-submit");
|
|
1565
|
+
if (bankSubmitBtn) {
|
|
1566
|
+
bankSubmitBtn.addEventListener("click", () => this.handleBankSubmit());
|
|
1567
|
+
}
|
|
1568
|
+
const copyAccountBtn = document.getElementById("zendfi-copy-account");
|
|
1569
|
+
if (copyAccountBtn) {
|
|
1570
|
+
copyAccountBtn.addEventListener("click", () => {
|
|
1571
|
+
const accountNum = document.getElementById("zendfi-bank-account")?.textContent || "";
|
|
1572
|
+
navigator.clipboard.writeText(accountNum);
|
|
1573
|
+
copyAccountBtn.textContent = "Copied!";
|
|
1574
|
+
copyAccountBtn.style.background = "#15803D";
|
|
1575
|
+
copyAccountBtn.style.color = "white";
|
|
1576
|
+
setTimeout(() => {
|
|
1577
|
+
copyAccountBtn.textContent = "Copy";
|
|
1578
|
+
copyAccountBtn.style.background = "";
|
|
1579
|
+
copyAccountBtn.style.color = "";
|
|
1580
|
+
}, 2e3);
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
const refreshBtn = document.getElementById("zendfi-refresh-status");
|
|
1584
|
+
if (refreshBtn) {
|
|
1585
|
+
refreshBtn.addEventListener("click", () => this.refreshBankStatus());
|
|
1586
|
+
}
|
|
4056
1587
|
}
|
|
4057
1588
|
/**
|
|
4058
1589
|
* Handle wallet connection and payment
|
|
@@ -4289,6 +1820,158 @@ var ZendFiEmbeddedCheckout = class {
|
|
|
4289
1820
|
getErrorStyles() {
|
|
4290
1821
|
return this.getLoadingStyles();
|
|
4291
1822
|
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Handle bank payment submit (send OTP or verify OTP)
|
|
1825
|
+
*/
|
|
1826
|
+
async handleBankSubmit() {
|
|
1827
|
+
const submitBtn = document.getElementById("zendfi-bank-submit");
|
|
1828
|
+
const errorDiv = document.getElementById("zendfi-bank-error");
|
|
1829
|
+
const emailInput = document.getElementById("zendfi-bank-email");
|
|
1830
|
+
const otpContainer = document.getElementById("zendfi-bank-otp-container");
|
|
1831
|
+
const otpInput = document.getElementById("zendfi-bank-otp");
|
|
1832
|
+
if (!submitBtn || !errorDiv || !emailInput) return;
|
|
1833
|
+
errorDiv.style.display = "none";
|
|
1834
|
+
submitBtn.disabled = true;
|
|
1835
|
+
try {
|
|
1836
|
+
if (otpContainer && otpContainer.style.display !== "none" && otpInput) {
|
|
1837
|
+
const otp = otpInput.value.trim();
|
|
1838
|
+
if (otp.length !== 4) {
|
|
1839
|
+
throw new Error("Please enter the 4-digit verification code");
|
|
1840
|
+
}
|
|
1841
|
+
submitBtn.textContent = "Creating order...";
|
|
1842
|
+
const response = await fetch(`${this.config.apiUrl}/api/v1/onramp/create-order`, {
|
|
1843
|
+
method: "POST",
|
|
1844
|
+
headers: { "Content-Type": "application/json" },
|
|
1845
|
+
body: JSON.stringify({
|
|
1846
|
+
customer_email: this.bankPaymentState.customerEmail,
|
|
1847
|
+
otp,
|
|
1848
|
+
fiat_amount: this.checkoutData.amount_usd,
|
|
1849
|
+
currency: this.checkoutData.currency,
|
|
1850
|
+
payment_link_id: this.checkoutData.payment_link_id
|
|
1851
|
+
})
|
|
1852
|
+
});
|
|
1853
|
+
if (!response.ok) {
|
|
1854
|
+
const error = await response.json();
|
|
1855
|
+
throw new Error(error.error || "Failed to create order");
|
|
1856
|
+
}
|
|
1857
|
+
const orderData = await response.json();
|
|
1858
|
+
this.bankPaymentState.orderId = orderData.order_id;
|
|
1859
|
+
this.bankPaymentState.bankDetails = {
|
|
1860
|
+
accountNumber: orderData.bank_account_number,
|
|
1861
|
+
accountName: orderData.bank_account_name,
|
|
1862
|
+
bankName: orderData.bank_name,
|
|
1863
|
+
amount: orderData.fiat_amount
|
|
1864
|
+
};
|
|
1865
|
+
this.render();
|
|
1866
|
+
this.startBankStatusPolling();
|
|
1867
|
+
} else {
|
|
1868
|
+
const email = emailInput.value.trim();
|
|
1869
|
+
if (!email || !email.includes("@")) {
|
|
1870
|
+
throw new Error("Please enter a valid email address");
|
|
1871
|
+
}
|
|
1872
|
+
this.bankPaymentState.customerEmail = email;
|
|
1873
|
+
submitBtn.textContent = "Sending code...";
|
|
1874
|
+
const response = await fetch(`${this.config.apiUrl}/api/v1/onramp/initiate`, {
|
|
1875
|
+
method: "POST",
|
|
1876
|
+
headers: { "Content-Type": "application/json" },
|
|
1877
|
+
body: JSON.stringify({
|
|
1878
|
+
customer_email: email,
|
|
1879
|
+
fiat_amount: this.checkoutData.amount_usd,
|
|
1880
|
+
payment_link_id: this.checkoutData.payment_link_id
|
|
1881
|
+
})
|
|
1882
|
+
});
|
|
1883
|
+
if (!response.ok) {
|
|
1884
|
+
const error = await response.json();
|
|
1885
|
+
throw new Error(error.error || "Failed to send verification code");
|
|
1886
|
+
}
|
|
1887
|
+
if (otpContainer) {
|
|
1888
|
+
otpContainer.style.display = "block";
|
|
1889
|
+
}
|
|
1890
|
+
emailInput.disabled = true;
|
|
1891
|
+
submitBtn.textContent = "Verify Code";
|
|
1892
|
+
}
|
|
1893
|
+
} catch (error) {
|
|
1894
|
+
errorDiv.textContent = error.message;
|
|
1895
|
+
errorDiv.style.display = "block";
|
|
1896
|
+
} finally {
|
|
1897
|
+
submitBtn.disabled = false;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Start polling bank payment status
|
|
1902
|
+
*/
|
|
1903
|
+
startBankStatusPolling() {
|
|
1904
|
+
if (!this.bankPaymentState.orderId) return;
|
|
1905
|
+
let startTime = Date.now();
|
|
1906
|
+
const timerInterval = setInterval(() => {
|
|
1907
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1e3);
|
|
1908
|
+
const minutes = Math.floor(elapsed / 60);
|
|
1909
|
+
const seconds = elapsed % 60;
|
|
1910
|
+
const timerEl = document.getElementById("zendfi-bank-timer");
|
|
1911
|
+
if (timerEl) {
|
|
1912
|
+
timerEl.textContent = `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
1913
|
+
}
|
|
1914
|
+
}, 1e3);
|
|
1915
|
+
const pollInterval = setInterval(async () => {
|
|
1916
|
+
await this.checkBankPaymentStatus();
|
|
1917
|
+
}, 5e3);
|
|
1918
|
+
this.bankPaymentState.pollingInterval = pollInterval;
|
|
1919
|
+
this.bankPaymentState.timerInterval = timerInterval;
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Check bank payment status
|
|
1923
|
+
*/
|
|
1924
|
+
async checkBankPaymentStatus() {
|
|
1925
|
+
if (!this.bankPaymentState.orderId) return;
|
|
1926
|
+
try {
|
|
1927
|
+
const response = await fetch(
|
|
1928
|
+
`${this.config.apiUrl}/api/v1/onramp/orders/${this.bankPaymentState.orderId}`
|
|
1929
|
+
);
|
|
1930
|
+
if (!response.ok) return;
|
|
1931
|
+
const order = await response.json();
|
|
1932
|
+
if (order.status === "COMPLETED" || order.status === "completed") {
|
|
1933
|
+
this.handleBankPaymentSuccess(order);
|
|
1934
|
+
}
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
console.error("Failed to check bank payment status:", error);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Handle successful bank payment
|
|
1941
|
+
*/
|
|
1942
|
+
handleBankPaymentSuccess(order) {
|
|
1943
|
+
if (this.bankPaymentState.pollingInterval) {
|
|
1944
|
+
clearInterval(this.bankPaymentState.pollingInterval);
|
|
1945
|
+
}
|
|
1946
|
+
if (this.bankPaymentState.timerInterval) {
|
|
1947
|
+
clearInterval(this.bankPaymentState.timerInterval);
|
|
1948
|
+
}
|
|
1949
|
+
this.renderSuccess();
|
|
1950
|
+
if (!this.paymentProcessed) {
|
|
1951
|
+
this.paymentProcessed = true;
|
|
1952
|
+
this.config.onSuccess({
|
|
1953
|
+
paymentId: this.checkoutData.payment_id,
|
|
1954
|
+
transactionSignature: order.paj_order_id,
|
|
1955
|
+
amount: order.token_amount,
|
|
1956
|
+
token: this.checkoutData.token,
|
|
1957
|
+
merchantName: this.checkoutData.merchant_name
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Manually refresh bank payment status
|
|
1963
|
+
*/
|
|
1964
|
+
async refreshBankStatus() {
|
|
1965
|
+
const refreshBtn = document.getElementById("zendfi-refresh-status");
|
|
1966
|
+
if (!refreshBtn) return;
|
|
1967
|
+
refreshBtn.disabled = true;
|
|
1968
|
+
refreshBtn.textContent = "\u{1F504} Checking...";
|
|
1969
|
+
await this.checkBankPaymentStatus();
|
|
1970
|
+
setTimeout(() => {
|
|
1971
|
+
refreshBtn.disabled = false;
|
|
1972
|
+
refreshBtn.textContent = "\u{1F504} Refresh Status";
|
|
1973
|
+
}, 1e3);
|
|
1974
|
+
}
|
|
4292
1975
|
};
|
|
4293
1976
|
if (typeof window !== "undefined") {
|
|
4294
1977
|
window.ZendFiEmbeddedCheckout = ZendFiEmbeddedCheckout;
|
|
@@ -4424,552 +2107,16 @@ async function processWebhook(a, b, c) {
|
|
|
4424
2107
|
checkDuplicate: cfg.checkDuplicate,
|
|
4425
2108
|
markProcessed: cfg.markProcessed
|
|
4426
2109
|
};
|
|
4427
|
-
return await processPayload(payload, handlers, fullConfig);
|
|
4428
|
-
} catch (err) {
|
|
4429
|
-
return {
|
|
4430
|
-
success: false,
|
|
4431
|
-
processed: false,
|
|
4432
|
-
error: err.message,
|
|
4433
|
-
statusCode: 500
|
|
4434
|
-
};
|
|
4435
|
-
}
|
|
4436
|
-
}
|
|
4437
|
-
|
|
4438
|
-
// src/lit-crypto-signer.ts
|
|
4439
|
-
var SPENDING_LIMIT_ACTION_CID = "QmXXunoMeNhXhnr4onzBuvnMzDqH8rf1qdM94RKXayypX3";
|
|
4440
|
-
var LitCryptoSigner = class {
|
|
4441
|
-
config;
|
|
4442
|
-
litNodeClient = null;
|
|
4443
|
-
connected = false;
|
|
4444
|
-
constructor(config = {}) {
|
|
4445
|
-
this.config = {
|
|
4446
|
-
network: config.network || "datil-dev",
|
|
4447
|
-
apiEndpoint: config.apiEndpoint || "https://api.zendfi.tech",
|
|
4448
|
-
apiKey: config.apiKey || "",
|
|
4449
|
-
debug: config.debug || false
|
|
4450
|
-
};
|
|
4451
|
-
}
|
|
4452
|
-
async connect() {
|
|
4453
|
-
if (this.connected && this.litNodeClient) {
|
|
4454
|
-
return;
|
|
4455
|
-
}
|
|
4456
|
-
this.log("Connecting to Lit Protocol network:", this.config.network);
|
|
4457
|
-
const { LitNodeClient } = await import("@lit-protocol/lit-node-client");
|
|
4458
|
-
this.litNodeClient = new LitNodeClient({
|
|
4459
|
-
litNetwork: this.config.network,
|
|
4460
|
-
debug: this.config.debug
|
|
4461
|
-
});
|
|
4462
|
-
await this.litNodeClient.connect();
|
|
4463
|
-
this.connected = true;
|
|
4464
|
-
this.log("Connected to Lit Protocol");
|
|
4465
|
-
}
|
|
4466
|
-
async disconnect() {
|
|
4467
|
-
if (this.litNodeClient) {
|
|
4468
|
-
await this.litNodeClient.disconnect();
|
|
4469
|
-
this.litNodeClient = null;
|
|
4470
|
-
this.connected = false;
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
async signPayment(params) {
|
|
4474
|
-
if (!this.connected || !this.litNodeClient) {
|
|
4475
|
-
throw new Error("Not connected to Lit Protocol. Call connect() first.");
|
|
4476
|
-
}
|
|
4477
|
-
this.log("Signing payment with Lit Protocol");
|
|
4478
|
-
this.log(" Session:", params.sessionId);
|
|
4479
|
-
this.log(" Amount: $" + params.amountUsd);
|
|
4480
|
-
try {
|
|
4481
|
-
let sessionSigs = params.sessionSigs;
|
|
4482
|
-
if (!sessionSigs) {
|
|
4483
|
-
sessionSigs = await this.getSessionSigs(params.pkpPublicKey);
|
|
4484
|
-
}
|
|
4485
|
-
const result = await this.litNodeClient.executeJs({
|
|
4486
|
-
ipfsId: SPENDING_LIMIT_ACTION_CID,
|
|
4487
|
-
sessionSigs,
|
|
4488
|
-
jsParams: {
|
|
4489
|
-
sessionId: params.sessionId,
|
|
4490
|
-
requestedAmountUsd: params.amountUsd,
|
|
4491
|
-
merchantId: params.merchantId,
|
|
4492
|
-
transactionToSign: params.transactionToSign,
|
|
4493
|
-
apiEndpoint: this.config.apiEndpoint,
|
|
4494
|
-
apiKey: this.config.apiKey,
|
|
4495
|
-
pkpPublicKey: params.pkpPublicKey
|
|
4496
|
-
}
|
|
4497
|
-
});
|
|
4498
|
-
this.log("Lit Action result:", result);
|
|
4499
|
-
const response = JSON.parse(result.response);
|
|
4500
|
-
return {
|
|
4501
|
-
success: response.success,
|
|
4502
|
-
signature: response.signature,
|
|
4503
|
-
publicKey: response.publicKey,
|
|
4504
|
-
recid: response.recid,
|
|
4505
|
-
sessionId: response.session_id,
|
|
4506
|
-
amountUsd: response.amount_usd,
|
|
4507
|
-
remainingBudget: response.remaining_budget,
|
|
4508
|
-
cryptoEnforced: response.crypto_enforced ?? true,
|
|
4509
|
-
error: response.error,
|
|
4510
|
-
code: response.code,
|
|
4511
|
-
currentSpent: response.current_spent,
|
|
4512
|
-
limit: response.limit,
|
|
4513
|
-
remaining: response.remaining
|
|
4514
|
-
};
|
|
4515
|
-
} catch (error) {
|
|
4516
|
-
this.log("Lit signing error:", error);
|
|
4517
|
-
return {
|
|
4518
|
-
success: false,
|
|
4519
|
-
cryptoEnforced: true,
|
|
4520
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
4521
|
-
code: "LIT_ERROR"
|
|
4522
|
-
};
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
async getSessionSigs(pkpPublicKey) {
|
|
4526
|
-
this.log("Generating Lit session signatures");
|
|
4527
|
-
if (typeof window !== "undefined" && window.ethereum) {
|
|
4528
|
-
const { ethers } = await import("ethers");
|
|
4529
|
-
const provider = new ethers.BrowserProvider(window.ethereum);
|
|
4530
|
-
const signer = await provider.getSigner();
|
|
4531
|
-
const { LitAbility, LitPKPResource } = await import("@lit-protocol/auth-helpers");
|
|
4532
|
-
const sessionSigs = await this.litNodeClient.getSessionSigs({
|
|
4533
|
-
pkpPublicKey,
|
|
4534
|
-
chain: "ethereum",
|
|
4535
|
-
expiration: new Date(Date.now() + 1e3 * 60 * 10).toISOString(),
|
|
4536
|
-
resourceAbilityRequests: [
|
|
4537
|
-
{
|
|
4538
|
-
resource: new LitPKPResource("*"),
|
|
4539
|
-
ability: LitAbility.PKPSigning
|
|
4540
|
-
}
|
|
4541
|
-
],
|
|
4542
|
-
authNeededCallback: async (params) => {
|
|
4543
|
-
const message = params.message;
|
|
4544
|
-
const signature = await signer.signMessage(message);
|
|
4545
|
-
return {
|
|
4546
|
-
sig: signature,
|
|
4547
|
-
derivedVia: "web3.eth.personal.sign",
|
|
4548
|
-
signedMessage: message,
|
|
4549
|
-
address: await signer.getAddress()
|
|
4550
|
-
};
|
|
4551
|
-
}
|
|
4552
|
-
});
|
|
4553
|
-
return sessionSigs;
|
|
4554
|
-
}
|
|
4555
|
-
throw new Error(
|
|
4556
|
-
"No wallet available. In browser, ensure MetaMask or similar is connected. In Node.js, pass pre-generated sessionSigs to signPayment()."
|
|
4557
|
-
);
|
|
4558
|
-
}
|
|
4559
|
-
log(...args) {
|
|
4560
|
-
if (this.config.debug) {
|
|
4561
|
-
console.log("[LitCryptoSigner]", ...args);
|
|
4562
|
-
}
|
|
4563
|
-
}
|
|
4564
|
-
};
|
|
4565
|
-
function requiresLitSigning(session) {
|
|
4566
|
-
return session.crypto_enforced === true || session.mint_pkp === true;
|
|
4567
|
-
}
|
|
4568
|
-
function encodeTransactionForLit(transaction) {
|
|
4569
|
-
const bytes = transaction instanceof Uint8Array ? transaction : new Uint8Array(transaction);
|
|
4570
|
-
return btoa(String.fromCharCode(...bytes));
|
|
4571
|
-
}
|
|
4572
|
-
function decodeSignatureFromLit(result) {
|
|
4573
|
-
if (!result.success || !result.signature) {
|
|
4574
|
-
return null;
|
|
4575
|
-
}
|
|
4576
|
-
const hex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
|
|
4577
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
4578
|
-
for (let i = 0; i < hex.length; i += 2) {
|
|
4579
|
-
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
|
4580
|
-
}
|
|
4581
|
-
return bytes;
|
|
4582
|
-
}
|
|
4583
|
-
|
|
4584
|
-
// src/helpers/index.ts
|
|
4585
|
-
init_cache();
|
|
4586
|
-
|
|
4587
|
-
// src/helpers/ai.ts
|
|
4588
|
-
var PaymentIntentParser = class {
|
|
4589
|
-
/**
|
|
4590
|
-
* Parse natural language into structured intent
|
|
4591
|
-
*/
|
|
4592
|
-
static parse(text) {
|
|
4593
|
-
if (!text || typeof text !== "string") return null;
|
|
4594
|
-
const lowerText = text.toLowerCase().trim();
|
|
4595
|
-
let action = "chat_only";
|
|
4596
|
-
let confidence = 0;
|
|
4597
|
-
if (this.containsPaymentKeywords(lowerText)) {
|
|
4598
|
-
action = "payment";
|
|
4599
|
-
confidence = 0.7;
|
|
4600
|
-
const amount = this.extractAmount(lowerText);
|
|
4601
|
-
const description = this.extractDescription(lowerText);
|
|
4602
|
-
if (amount && description) {
|
|
4603
|
-
confidence = 0.9;
|
|
4604
|
-
return { action, amount, description, confidence, rawText: text };
|
|
4605
|
-
}
|
|
4606
|
-
if (amount) {
|
|
4607
|
-
confidence = 0.8;
|
|
4608
|
-
return { action, amount, confidence, rawText: text };
|
|
4609
|
-
}
|
|
4610
|
-
}
|
|
4611
|
-
if (this.containsSessionKeywords(lowerText)) {
|
|
4612
|
-
action = "create_session";
|
|
4613
|
-
confidence = 0.8;
|
|
4614
|
-
const amount = this.extractAmount(lowerText);
|
|
4615
|
-
return { action, amount, confidence, rawText: text, description: "Session key budget" };
|
|
4616
|
-
}
|
|
4617
|
-
if (this.containsStatusKeywords(lowerText)) {
|
|
4618
|
-
action = lowerText.includes("session") || lowerText.includes("key") ? "check_status" : "check_balance";
|
|
4619
|
-
confidence = 0.9;
|
|
4620
|
-
return { action, confidence, rawText: text };
|
|
4621
|
-
}
|
|
4622
|
-
if (this.containsTopUpKeywords(lowerText)) {
|
|
4623
|
-
action = "topup";
|
|
4624
|
-
confidence = 0.8;
|
|
4625
|
-
const amount = this.extractAmount(lowerText);
|
|
4626
|
-
return { action, amount, confidence, rawText: text };
|
|
4627
|
-
}
|
|
4628
|
-
if (this.containsRevokeKeywords(lowerText)) {
|
|
4629
|
-
action = "revoke";
|
|
4630
|
-
confidence = 0.9;
|
|
4631
|
-
return { action, confidence, rawText: text };
|
|
4632
|
-
}
|
|
4633
|
-
if (this.containsAutonomyKeywords(lowerText)) {
|
|
4634
|
-
action = "enable_autonomy";
|
|
4635
|
-
confidence = 0.8;
|
|
4636
|
-
const amount = this.extractAmount(lowerText);
|
|
4637
|
-
return { action, amount, confidence, rawText: text, description: "Autonomous delegate limit" };
|
|
4638
|
-
}
|
|
4639
|
-
return null;
|
|
4640
|
-
}
|
|
4641
|
-
/**
|
|
4642
|
-
* Generate system prompt for AI models
|
|
4643
|
-
*/
|
|
4644
|
-
static generateSystemPrompt(capabilities = {}) {
|
|
4645
|
-
const enabledFeatures = Object.entries({
|
|
4646
|
-
createPayment: capabilities.createPayment !== false,
|
|
4647
|
-
createSessionKey: capabilities.createSessionKey !== false,
|
|
4648
|
-
checkBalance: capabilities.checkBalance !== false,
|
|
4649
|
-
checkStatus: capabilities.checkStatus !== false,
|
|
4650
|
-
topUpSession: capabilities.topUpSession !== false,
|
|
4651
|
-
revokeSession: capabilities.revokeSession !== false,
|
|
4652
|
-
enableAutonomy: capabilities.enableAutonomy !== false
|
|
4653
|
-
}).filter(([_, enabled]) => enabled).map(([feature]) => feature);
|
|
4654
|
-
return `You are a ZendFi payment assistant. You can help users with crypto payments on Solana.
|
|
4655
|
-
|
|
4656
|
-
Available Actions:
|
|
4657
|
-
${enabledFeatures.includes("createPayment") ? '- Make payments (e.g., "Buy coffee for $5", "Send $20 to merchant")' : ""}
|
|
4658
|
-
${enabledFeatures.includes("createSessionKey") ? '- Create session keys (e.g., "Create a session key with $100 budget")' : ""}
|
|
4659
|
-
${enabledFeatures.includes("checkBalance") ? `- Check balances (e.g., "What's my balance?", "How much do I have?")` : ""}
|
|
4660
|
-
${enabledFeatures.includes("checkStatus") ? '- Check session status (e.g., "Session key status", "How much is left?")' : ""}
|
|
4661
|
-
${enabledFeatures.includes("topUpSession") ? '- Top up session keys (e.g., "Add $50 to session key", "Top up with $100")' : ""}
|
|
4662
|
-
${enabledFeatures.includes("revokeSession") ? '- Revoke session keys (e.g., "Revoke session key", "Cancel my session")' : ""}
|
|
4663
|
-
${enabledFeatures.includes("enableAutonomy") ? '- Enable autonomous mode (e.g., "Enable auto-pay with $25 limit")' : ""}
|
|
4664
|
-
|
|
4665
|
-
Response Format:
|
|
4666
|
-
Always respond with valid JSON:
|
|
4667
|
-
{
|
|
4668
|
-
"action": "payment" | "create_session" | "check_balance" | "check_status" | "topup" | "revoke" | "enable_autonomy" | "chat_only",
|
|
4669
|
-
"amount_usd": <number if applicable>,
|
|
4670
|
-
"description": "<description>",
|
|
4671
|
-
"message": "<friendly response to user>"
|
|
4672
|
-
}
|
|
4673
|
-
|
|
4674
|
-
Examples:
|
|
4675
|
-
User: "Buy coffee for $5"
|
|
4676
|
-
You: {"action": "payment", "amount_usd": 5, "description": "coffee", "message": "Sure! Processing your $5 coffee payment..."}
|
|
4677
|
-
|
|
4678
|
-
User: "Create a session key with $100"
|
|
4679
|
-
You: {"action": "create_session", "amount_usd": 100, "message": "I'll create a session key with a $100 budget..."}
|
|
4680
|
-
|
|
4681
|
-
User: "What's my balance?"
|
|
4682
|
-
You: {"action": "check_balance", "message": "Let me check your balance..."}
|
|
4683
|
-
|
|
4684
|
-
Be helpful, concise, and always respond in valid JSON format.`;
|
|
4685
|
-
}
|
|
4686
|
-
// ============================================
|
|
4687
|
-
// Keyword Detection
|
|
4688
|
-
// ============================================
|
|
4689
|
-
static containsPaymentKeywords(text) {
|
|
4690
|
-
const keywords = [
|
|
4691
|
-
"buy",
|
|
4692
|
-
"purchase",
|
|
4693
|
-
"pay",
|
|
4694
|
-
"send",
|
|
4695
|
-
"transfer",
|
|
4696
|
-
"payment",
|
|
4697
|
-
"order",
|
|
4698
|
-
"checkout",
|
|
4699
|
-
"subscribe",
|
|
4700
|
-
"donate",
|
|
4701
|
-
"tip"
|
|
4702
|
-
];
|
|
4703
|
-
return keywords.some((kw) => text.includes(kw));
|
|
4704
|
-
}
|
|
4705
|
-
static containsSessionKeywords(text) {
|
|
4706
|
-
const keywords = [
|
|
4707
|
-
"create session",
|
|
4708
|
-
"new session",
|
|
4709
|
-
"session key",
|
|
4710
|
-
"setup session",
|
|
4711
|
-
"generate session",
|
|
4712
|
-
"make session",
|
|
4713
|
-
"start session"
|
|
4714
|
-
];
|
|
4715
|
-
return keywords.some((kw) => text.includes(kw));
|
|
4716
|
-
}
|
|
4717
|
-
static containsStatusKeywords(text) {
|
|
4718
|
-
const keywords = [
|
|
4719
|
-
"status",
|
|
4720
|
-
"balance",
|
|
4721
|
-
"remaining",
|
|
4722
|
-
"left",
|
|
4723
|
-
"how much",
|
|
4724
|
-
"check",
|
|
4725
|
-
"show",
|
|
4726
|
-
"view",
|
|
4727
|
-
"display"
|
|
4728
|
-
];
|
|
4729
|
-
return keywords.some((kw) => text.includes(kw));
|
|
4730
|
-
}
|
|
4731
|
-
static containsTopUpKeywords(text) {
|
|
4732
|
-
const keywords = [
|
|
4733
|
-
"top up",
|
|
4734
|
-
"topup",
|
|
4735
|
-
"add funds",
|
|
4736
|
-
"add money",
|
|
4737
|
-
"fund",
|
|
4738
|
-
"increase",
|
|
4739
|
-
"reload",
|
|
4740
|
-
"refill"
|
|
4741
|
-
];
|
|
4742
|
-
return keywords.some((kw) => text.includes(kw));
|
|
4743
|
-
}
|
|
4744
|
-
static containsRevokeKeywords(text) {
|
|
4745
|
-
const keywords = [
|
|
4746
|
-
"revoke",
|
|
4747
|
-
"cancel",
|
|
4748
|
-
"disable",
|
|
4749
|
-
"remove",
|
|
4750
|
-
"delete",
|
|
4751
|
-
"stop",
|
|
4752
|
-
"end",
|
|
4753
|
-
"terminate"
|
|
4754
|
-
];
|
|
4755
|
-
return keywords.some((kw) => text.includes(kw));
|
|
4756
|
-
}
|
|
4757
|
-
static containsAutonomyKeywords(text) {
|
|
4758
|
-
const keywords = [
|
|
4759
|
-
"autonomous",
|
|
4760
|
-
"auto",
|
|
4761
|
-
"automatic",
|
|
4762
|
-
"delegate",
|
|
4763
|
-
"enable auto",
|
|
4764
|
-
"auto-sign",
|
|
4765
|
-
"auto sign",
|
|
4766
|
-
"auto-pay",
|
|
4767
|
-
"auto pay"
|
|
4768
|
-
];
|
|
4769
|
-
return keywords.some((kw) => text.includes(kw));
|
|
4770
|
-
}
|
|
4771
|
-
// ============================================
|
|
4772
|
-
// Amount Extraction
|
|
4773
|
-
// ============================================
|
|
4774
|
-
static extractAmount(text) {
|
|
4775
|
-
const dollarMatch = text.match(/\$\s*(\d+(?:\.\d{1,2})?)/);
|
|
4776
|
-
if (dollarMatch) {
|
|
4777
|
-
return parseFloat(dollarMatch[1]);
|
|
4778
|
-
}
|
|
4779
|
-
const usdMatch = text.match(/(\d+(?:\.\d{1,2})?)\s*(?:usd|usdc|dollars?)/i);
|
|
4780
|
-
if (usdMatch) {
|
|
4781
|
-
return parseFloat(usdMatch[1]);
|
|
4782
|
-
}
|
|
4783
|
-
const numberMatch = text.match(/(\d+(?:\.\d{1,2})?)/);
|
|
4784
|
-
if (numberMatch) {
|
|
4785
|
-
const num = parseFloat(numberMatch[1]);
|
|
4786
|
-
if (num > 0 && num < 1e5) {
|
|
4787
|
-
return num;
|
|
4788
|
-
}
|
|
4789
|
-
}
|
|
4790
|
-
return void 0;
|
|
4791
|
-
}
|
|
4792
|
-
// ============================================
|
|
4793
|
-
// Description Extraction
|
|
4794
|
-
// ============================================
|
|
4795
|
-
static extractDescription(text) {
|
|
4796
|
-
const lowerText = text.toLowerCase();
|
|
4797
|
-
const items = {
|
|
4798
|
-
"coffee": ["coffee", "espresso", "latte", "cappuccino"],
|
|
4799
|
-
"food": ["food", "meal", "lunch", "dinner", "breakfast"],
|
|
4800
|
-
"drink": ["drink", "beverage", "soda", "juice"],
|
|
4801
|
-
"book": ["book", "ebook", "magazine"],
|
|
4802
|
-
"subscription": ["subscription", "membership", "plan"],
|
|
4803
|
-
"tip": ["tip", "gratuity"],
|
|
4804
|
-
"donation": ["donate", "donation", "contribute"],
|
|
4805
|
-
"game": ["game", "gaming"],
|
|
4806
|
-
"music": ["music", "song", "album"],
|
|
4807
|
-
"video": ["video", "movie", "film"]
|
|
4808
|
-
};
|
|
4809
|
-
for (const [item, keywords] of Object.entries(items)) {
|
|
4810
|
-
if (keywords.some((kw) => lowerText.includes(kw))) {
|
|
4811
|
-
return item;
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
const forMatch = text.match(/for\s+(.+?)(?:\s+\$|\s+usd|$)/i);
|
|
4815
|
-
if (forMatch && forMatch[1]) {
|
|
4816
|
-
const desc = forMatch[1].trim();
|
|
4817
|
-
if (desc.length > 0 && desc.length < 50) {
|
|
4818
|
-
return desc;
|
|
4819
|
-
}
|
|
4820
|
-
}
|
|
4821
|
-
return void 0;
|
|
4822
|
-
}
|
|
4823
|
-
};
|
|
4824
|
-
var OpenAIAdapter = class {
|
|
4825
|
-
constructor(apiKey, model = "gpt-4o-mini", capabilities) {
|
|
4826
|
-
this.apiKey = apiKey;
|
|
4827
|
-
this.model = model;
|
|
4828
|
-
this.capabilities = capabilities;
|
|
4829
|
-
}
|
|
4830
|
-
async chat(message, conversationHistory = []) {
|
|
4831
|
-
const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
|
|
4832
|
-
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
4833
|
-
method: "POST",
|
|
4834
|
-
headers: {
|
|
4835
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
4836
|
-
"Content-Type": "application/json"
|
|
4837
|
-
},
|
|
4838
|
-
body: JSON.stringify({
|
|
4839
|
-
model: this.model,
|
|
4840
|
-
messages: [
|
|
4841
|
-
{ role: "system", content: systemPrompt },
|
|
4842
|
-
...conversationHistory,
|
|
4843
|
-
{ role: "user", content: message }
|
|
4844
|
-
],
|
|
4845
|
-
temperature: 0.7,
|
|
4846
|
-
max_tokens: 500
|
|
4847
|
-
})
|
|
4848
|
-
});
|
|
4849
|
-
if (!response.ok) {
|
|
4850
|
-
throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
|
|
4851
|
-
}
|
|
4852
|
-
const data = await response.json();
|
|
4853
|
-
const text = data.choices[0]?.message?.content || "";
|
|
4854
|
-
let intent = null;
|
|
4855
|
-
try {
|
|
4856
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
4857
|
-
if (jsonMatch) {
|
|
4858
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
4859
|
-
intent = {
|
|
4860
|
-
action: parsed.action || "chat_only",
|
|
4861
|
-
amount: parsed.amount_usd,
|
|
4862
|
-
description: parsed.description,
|
|
4863
|
-
confidence: 0.9,
|
|
4864
|
-
rawText: text,
|
|
4865
|
-
metadata: { message: parsed.message }
|
|
4866
|
-
};
|
|
4867
|
-
}
|
|
4868
|
-
} catch {
|
|
4869
|
-
intent = PaymentIntentParser.parse(text);
|
|
4870
|
-
}
|
|
4871
|
-
return { text, intent, raw: data };
|
|
4872
|
-
}
|
|
4873
|
-
};
|
|
4874
|
-
var AnthropicAdapter = class {
|
|
4875
|
-
constructor(apiKey, model = "claude-3-5-sonnet-20241022", capabilities) {
|
|
4876
|
-
this.apiKey = apiKey;
|
|
4877
|
-
this.model = model;
|
|
4878
|
-
this.capabilities = capabilities;
|
|
4879
|
-
}
|
|
4880
|
-
async chat(message, conversationHistory = []) {
|
|
4881
|
-
const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
|
|
4882
|
-
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
4883
|
-
method: "POST",
|
|
4884
|
-
headers: {
|
|
4885
|
-
"x-api-key": this.apiKey,
|
|
4886
|
-
"anthropic-version": "2023-06-01",
|
|
4887
|
-
"Content-Type": "application/json"
|
|
4888
|
-
},
|
|
4889
|
-
body: JSON.stringify({
|
|
4890
|
-
model: this.model,
|
|
4891
|
-
system: systemPrompt,
|
|
4892
|
-
messages: [
|
|
4893
|
-
...conversationHistory.map((msg) => ({
|
|
4894
|
-
role: msg.role === "user" ? "user" : "assistant",
|
|
4895
|
-
content: msg.content
|
|
4896
|
-
})),
|
|
4897
|
-
{ role: "user", content: message }
|
|
4898
|
-
],
|
|
4899
|
-
max_tokens: 500
|
|
4900
|
-
})
|
|
4901
|
-
});
|
|
4902
|
-
if (!response.ok) {
|
|
4903
|
-
throw new Error(`Anthropic API error: ${response.status} ${response.statusText}`);
|
|
4904
|
-
}
|
|
4905
|
-
const data = await response.json();
|
|
4906
|
-
const text = data.content[0]?.text || "";
|
|
4907
|
-
let intent = null;
|
|
4908
|
-
try {
|
|
4909
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
4910
|
-
if (jsonMatch) {
|
|
4911
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
4912
|
-
intent = {
|
|
4913
|
-
action: parsed.action || "chat_only",
|
|
4914
|
-
amount: parsed.amount_usd,
|
|
4915
|
-
description: parsed.description,
|
|
4916
|
-
confidence: 0.9,
|
|
4917
|
-
rawText: text,
|
|
4918
|
-
metadata: { message: parsed.message }
|
|
4919
|
-
};
|
|
4920
|
-
}
|
|
4921
|
-
} catch {
|
|
4922
|
-
intent = PaymentIntentParser.parse(text);
|
|
4923
|
-
}
|
|
4924
|
-
return { text, intent, raw: data };
|
|
4925
|
-
}
|
|
4926
|
-
};
|
|
4927
|
-
var GeminiAdapter = class {
|
|
4928
|
-
constructor(apiKey, model = "gemini-2.0-flash-exp", capabilities) {
|
|
4929
|
-
this.apiKey = apiKey;
|
|
4930
|
-
this.model = model;
|
|
4931
|
-
this.capabilities = capabilities;
|
|
4932
|
-
}
|
|
4933
|
-
async chat(message) {
|
|
4934
|
-
const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
|
|
4935
|
-
const fullPrompt = `${systemPrompt}
|
|
4936
|
-
|
|
4937
|
-
User: ${message}`;
|
|
4938
|
-
const response = await fetch(
|
|
4939
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`,
|
|
4940
|
-
{
|
|
4941
|
-
method: "POST",
|
|
4942
|
-
headers: { "Content-Type": "application/json" },
|
|
4943
|
-
body: JSON.stringify({
|
|
4944
|
-
contents: [{ parts: [{ text: fullPrompt }] }]
|
|
4945
|
-
})
|
|
4946
|
-
}
|
|
4947
|
-
);
|
|
4948
|
-
if (!response.ok) {
|
|
4949
|
-
throw new Error(`Gemini API error: ${response.status} ${response.statusText}`);
|
|
4950
|
-
}
|
|
4951
|
-
const data = await response.json();
|
|
4952
|
-
const text = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
4953
|
-
let intent = null;
|
|
4954
|
-
try {
|
|
4955
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
4956
|
-
if (jsonMatch) {
|
|
4957
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
4958
|
-
intent = {
|
|
4959
|
-
action: parsed.action || "chat_only",
|
|
4960
|
-
amount: parsed.amount_usd,
|
|
4961
|
-
description: parsed.description,
|
|
4962
|
-
confidence: 0.9,
|
|
4963
|
-
rawText: text,
|
|
4964
|
-
metadata: { message: parsed.message }
|
|
4965
|
-
};
|
|
4966
|
-
}
|
|
4967
|
-
} catch {
|
|
4968
|
-
intent = PaymentIntentParser.parse(text);
|
|
4969
|
-
}
|
|
4970
|
-
return { text, intent, raw: data };
|
|
2110
|
+
return await processPayload(payload, handlers, fullConfig);
|
|
2111
|
+
} catch (err) {
|
|
2112
|
+
return {
|
|
2113
|
+
success: false,
|
|
2114
|
+
processed: false,
|
|
2115
|
+
error: err.message,
|
|
2116
|
+
statusCode: 500
|
|
2117
|
+
};
|
|
4971
2118
|
}
|
|
4972
|
-
}
|
|
2119
|
+
}
|
|
4973
2120
|
|
|
4974
2121
|
// src/helpers/wallet.ts
|
|
4975
2122
|
var WalletConnector = class _WalletConnector {
|
|
@@ -5263,323 +2410,6 @@ function createWalletHook() {
|
|
|
5263
2410
|
};
|
|
5264
2411
|
}
|
|
5265
2412
|
|
|
5266
|
-
// src/helpers/security.ts
|
|
5267
|
-
var PINValidator = class {
|
|
5268
|
-
/**
|
|
5269
|
-
* Validate PIN strength and format
|
|
5270
|
-
*/
|
|
5271
|
-
static validate(pin) {
|
|
5272
|
-
const errors = [];
|
|
5273
|
-
const suggestions = [];
|
|
5274
|
-
if (!pin || pin.length < 4) {
|
|
5275
|
-
errors.push("PIN must be at least 4 digits");
|
|
5276
|
-
}
|
|
5277
|
-
if (pin.length > 12) {
|
|
5278
|
-
errors.push("PIN must be at most 12 digits");
|
|
5279
|
-
}
|
|
5280
|
-
if (!/^\d+$/.test(pin)) {
|
|
5281
|
-
errors.push("PIN must contain only digits");
|
|
5282
|
-
}
|
|
5283
|
-
if (this.isWeakPattern(pin)) {
|
|
5284
|
-
errors.push("PIN is too simple");
|
|
5285
|
-
suggestions.push("Avoid sequential numbers (1234) or repeated digits (1111)");
|
|
5286
|
-
}
|
|
5287
|
-
let strength = "strong";
|
|
5288
|
-
if (errors.length > 0) {
|
|
5289
|
-
strength = "weak";
|
|
5290
|
-
} else if (pin.length <= 4 || this.hasRepeatedDigits(pin)) {
|
|
5291
|
-
strength = "weak";
|
|
5292
|
-
suggestions.push("Use at least 6 digits for better security");
|
|
5293
|
-
} else if (pin.length <= 6) {
|
|
5294
|
-
strength = "medium";
|
|
5295
|
-
suggestions.push("Use 8+ digits for maximum security");
|
|
5296
|
-
}
|
|
5297
|
-
return {
|
|
5298
|
-
valid: errors.length === 0,
|
|
5299
|
-
strength,
|
|
5300
|
-
errors,
|
|
5301
|
-
suggestions
|
|
5302
|
-
};
|
|
5303
|
-
}
|
|
5304
|
-
/**
|
|
5305
|
-
* Check PIN strength (0-100 score)
|
|
5306
|
-
*/
|
|
5307
|
-
static strengthScore(pin) {
|
|
5308
|
-
if (!pin) return 0;
|
|
5309
|
-
let score = 0;
|
|
5310
|
-
score += Math.min(pin.length * 10, 50);
|
|
5311
|
-
const uniqueDigits = new Set(pin).size;
|
|
5312
|
-
score += uniqueDigits * 5;
|
|
5313
|
-
if (!this.isSequential(pin)) {
|
|
5314
|
-
score += 20;
|
|
5315
|
-
}
|
|
5316
|
-
if (!this.hasRepeatedDigits(pin)) {
|
|
5317
|
-
score += 10;
|
|
5318
|
-
}
|
|
5319
|
-
return Math.min(score, 100);
|
|
5320
|
-
}
|
|
5321
|
-
/**
|
|
5322
|
-
* Generate a secure random PIN
|
|
5323
|
-
*/
|
|
5324
|
-
static generateSecureRandom(length = 6) {
|
|
5325
|
-
if (length < 4 || length > 12) {
|
|
5326
|
-
throw new Error("PIN length must be between 4 and 12");
|
|
5327
|
-
}
|
|
5328
|
-
const array = new Uint32Array(length);
|
|
5329
|
-
crypto.getRandomValues(array);
|
|
5330
|
-
const pin = Array.from(array).map((num) => num % 10).join("");
|
|
5331
|
-
if (this.isWeakPattern(pin)) {
|
|
5332
|
-
return this.generateSecureRandom(length);
|
|
5333
|
-
}
|
|
5334
|
-
return pin;
|
|
5335
|
-
}
|
|
5336
|
-
/**
|
|
5337
|
-
* Check for weak patterns
|
|
5338
|
-
*/
|
|
5339
|
-
static isWeakPattern(pin) {
|
|
5340
|
-
if (this.isSequential(pin)) return true;
|
|
5341
|
-
if (/^(\d)\1+$/.test(pin)) return true;
|
|
5342
|
-
const commonPatterns = [
|
|
5343
|
-
"1234",
|
|
5344
|
-
"4321",
|
|
5345
|
-
"1111",
|
|
5346
|
-
"2222",
|
|
5347
|
-
"3333",
|
|
5348
|
-
"4444",
|
|
5349
|
-
"5555",
|
|
5350
|
-
"6666",
|
|
5351
|
-
"7777",
|
|
5352
|
-
"8888",
|
|
5353
|
-
"9999",
|
|
5354
|
-
"0000",
|
|
5355
|
-
"1212",
|
|
5356
|
-
"2323",
|
|
5357
|
-
"0123",
|
|
5358
|
-
"3210",
|
|
5359
|
-
"9876",
|
|
5360
|
-
"6789"
|
|
5361
|
-
];
|
|
5362
|
-
return commonPatterns.some((pattern) => pin.includes(pattern));
|
|
5363
|
-
}
|
|
5364
|
-
/**
|
|
5365
|
-
* Check if PIN is sequential
|
|
5366
|
-
*/
|
|
5367
|
-
static isSequential(pin) {
|
|
5368
|
-
for (let i = 1; i < pin.length; i++) {
|
|
5369
|
-
const diff = parseInt(pin[i]) - parseInt(pin[i - 1]);
|
|
5370
|
-
if (Math.abs(diff) !== 1) return false;
|
|
5371
|
-
}
|
|
5372
|
-
return true;
|
|
5373
|
-
}
|
|
5374
|
-
/**
|
|
5375
|
-
* Check if PIN has repeated digits
|
|
5376
|
-
*/
|
|
5377
|
-
static hasRepeatedDigits(pin) {
|
|
5378
|
-
return /(\d)\1{2,}/.test(pin);
|
|
5379
|
-
}
|
|
5380
|
-
/**
|
|
5381
|
-
* Hash PIN for storage (if needed)
|
|
5382
|
-
* Note: For device-bound keys, the PIN is used for key derivation, not storage
|
|
5383
|
-
*/
|
|
5384
|
-
static async hashPIN(pin, salt) {
|
|
5385
|
-
const actualSalt = salt || crypto.getRandomValues(new Uint8Array(16));
|
|
5386
|
-
const encoder = new TextEncoder();
|
|
5387
|
-
const data = encoder.encode(pin + actualSalt);
|
|
5388
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
5389
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
5390
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5391
|
-
}
|
|
5392
|
-
};
|
|
5393
|
-
var PINRateLimiter = class {
|
|
5394
|
-
attempts = /* @__PURE__ */ new Map();
|
|
5395
|
-
locked = /* @__PURE__ */ new Map();
|
|
5396
|
-
maxAttempts;
|
|
5397
|
-
windowMs;
|
|
5398
|
-
lockoutMs;
|
|
5399
|
-
constructor(config = {}) {
|
|
5400
|
-
this.maxAttempts = config.maxAttempts || 3;
|
|
5401
|
-
this.windowMs = config.windowMs || 6e4;
|
|
5402
|
-
this.lockoutMs = config.lockoutMs || 3e5;
|
|
5403
|
-
}
|
|
5404
|
-
/**
|
|
5405
|
-
* Check if attempt is allowed
|
|
5406
|
-
*/
|
|
5407
|
-
async checkAttempt(sessionKeyId) {
|
|
5408
|
-
const now = Date.now();
|
|
5409
|
-
const lockoutUntil = this.locked.get(sessionKeyId);
|
|
5410
|
-
if (lockoutUntil && now < lockoutUntil) {
|
|
5411
|
-
const lockoutSeconds = Math.ceil((lockoutUntil - now) / 1e3);
|
|
5412
|
-
return {
|
|
5413
|
-
allowed: false,
|
|
5414
|
-
remainingAttempts: 0,
|
|
5415
|
-
lockoutSeconds
|
|
5416
|
-
};
|
|
5417
|
-
}
|
|
5418
|
-
if (lockoutUntil && now >= lockoutUntil) {
|
|
5419
|
-
this.locked.delete(sessionKeyId);
|
|
5420
|
-
this.attempts.delete(sessionKeyId);
|
|
5421
|
-
}
|
|
5422
|
-
const recentAttempts = this.getRecentAttempts(sessionKeyId, now);
|
|
5423
|
-
const remainingAttempts = this.maxAttempts - recentAttempts.length;
|
|
5424
|
-
if (remainingAttempts <= 0) {
|
|
5425
|
-
this.locked.set(sessionKeyId, now + this.lockoutMs);
|
|
5426
|
-
return {
|
|
5427
|
-
allowed: false,
|
|
5428
|
-
remainingAttempts: 0,
|
|
5429
|
-
lockoutSeconds: Math.ceil(this.lockoutMs / 1e3)
|
|
5430
|
-
};
|
|
5431
|
-
}
|
|
5432
|
-
return {
|
|
5433
|
-
allowed: true,
|
|
5434
|
-
remainingAttempts
|
|
5435
|
-
};
|
|
5436
|
-
}
|
|
5437
|
-
/**
|
|
5438
|
-
* Record a failed attempt
|
|
5439
|
-
*/
|
|
5440
|
-
recordFailedAttempt(sessionKeyId) {
|
|
5441
|
-
const now = Date.now();
|
|
5442
|
-
const attempts = this.attempts.get(sessionKeyId) || [];
|
|
5443
|
-
attempts.push(now);
|
|
5444
|
-
this.attempts.set(sessionKeyId, attempts);
|
|
5445
|
-
}
|
|
5446
|
-
/**
|
|
5447
|
-
* Record a successful attempt (clears history)
|
|
5448
|
-
*/
|
|
5449
|
-
recordSuccessfulAttempt(sessionKeyId) {
|
|
5450
|
-
this.attempts.delete(sessionKeyId);
|
|
5451
|
-
this.locked.delete(sessionKeyId);
|
|
5452
|
-
}
|
|
5453
|
-
/**
|
|
5454
|
-
* Get recent attempts within window
|
|
5455
|
-
*/
|
|
5456
|
-
getRecentAttempts(sessionKeyId, now) {
|
|
5457
|
-
const attempts = this.attempts.get(sessionKeyId) || [];
|
|
5458
|
-
const cutoff = now - this.windowMs;
|
|
5459
|
-
const recent = attempts.filter((timestamp) => timestamp > cutoff);
|
|
5460
|
-
if (recent.length !== attempts.length) {
|
|
5461
|
-
this.attempts.set(sessionKeyId, recent);
|
|
5462
|
-
}
|
|
5463
|
-
return recent;
|
|
5464
|
-
}
|
|
5465
|
-
/**
|
|
5466
|
-
* Clear all rate limit data
|
|
5467
|
-
*/
|
|
5468
|
-
clear() {
|
|
5469
|
-
this.attempts.clear();
|
|
5470
|
-
this.locked.clear();
|
|
5471
|
-
}
|
|
5472
|
-
/**
|
|
5473
|
-
* Check lockout status
|
|
5474
|
-
*/
|
|
5475
|
-
isLockedOut(sessionKeyId) {
|
|
5476
|
-
const lockoutUntil = this.locked.get(sessionKeyId);
|
|
5477
|
-
return lockoutUntil ? Date.now() < lockoutUntil : false;
|
|
5478
|
-
}
|
|
5479
|
-
/**
|
|
5480
|
-
* Get remaining lockout time
|
|
5481
|
-
*/
|
|
5482
|
-
getRemainingLockoutTime(sessionKeyId) {
|
|
5483
|
-
const lockoutUntil = this.locked.get(sessionKeyId);
|
|
5484
|
-
if (!lockoutUntil) return 0;
|
|
5485
|
-
return Math.max(0, lockoutUntil - Date.now());
|
|
5486
|
-
}
|
|
5487
|
-
};
|
|
5488
|
-
var SecureStorage = class {
|
|
5489
|
-
/**
|
|
5490
|
-
* Store data with encryption (basic)
|
|
5491
|
-
* For production, consider using Web Crypto API with user-derived keys
|
|
5492
|
-
*/
|
|
5493
|
-
static async setEncrypted(key, value, secret) {
|
|
5494
|
-
const encrypted = await this.encrypt(value, secret);
|
|
5495
|
-
localStorage.setItem(key, JSON.stringify(encrypted));
|
|
5496
|
-
}
|
|
5497
|
-
/**
|
|
5498
|
-
* Retrieve encrypted data
|
|
5499
|
-
*/
|
|
5500
|
-
static async getEncrypted(key, secret) {
|
|
5501
|
-
const stored = localStorage.getItem(key);
|
|
5502
|
-
if (!stored) return null;
|
|
5503
|
-
try {
|
|
5504
|
-
const encrypted = JSON.parse(stored);
|
|
5505
|
-
return await this.decrypt(encrypted, secret);
|
|
5506
|
-
} catch {
|
|
5507
|
-
return null;
|
|
5508
|
-
}
|
|
5509
|
-
}
|
|
5510
|
-
/**
|
|
5511
|
-
* Encrypt string with AES-GCM
|
|
5512
|
-
*/
|
|
5513
|
-
static async encrypt(plaintext, secret) {
|
|
5514
|
-
const encoder = new TextEncoder();
|
|
5515
|
-
const data = encoder.encode(plaintext);
|
|
5516
|
-
const key = await this.deriveKey(secret);
|
|
5517
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
5518
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
5519
|
-
{ name: "AES-GCM", iv },
|
|
5520
|
-
key,
|
|
5521
|
-
data
|
|
5522
|
-
);
|
|
5523
|
-
return {
|
|
5524
|
-
ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
|
|
5525
|
-
iv: btoa(String.fromCharCode(...iv))
|
|
5526
|
-
};
|
|
5527
|
-
}
|
|
5528
|
-
/**
|
|
5529
|
-
* Decrypt AES-GCM ciphertext
|
|
5530
|
-
*/
|
|
5531
|
-
static async decrypt(encrypted, secret) {
|
|
5532
|
-
const key = await this.deriveKey(secret);
|
|
5533
|
-
const iv = Uint8Array.from(atob(encrypted.iv), (c) => c.charCodeAt(0));
|
|
5534
|
-
const ciphertext = Uint8Array.from(atob(encrypted.ciphertext), (c) => c.charCodeAt(0));
|
|
5535
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
5536
|
-
{ name: "AES-GCM", iv },
|
|
5537
|
-
key,
|
|
5538
|
-
ciphertext
|
|
5539
|
-
);
|
|
5540
|
-
const decoder = new TextDecoder();
|
|
5541
|
-
return decoder.decode(decrypted);
|
|
5542
|
-
}
|
|
5543
|
-
/**
|
|
5544
|
-
* Derive encryption key from secret
|
|
5545
|
-
*/
|
|
5546
|
-
static async deriveKey(secret) {
|
|
5547
|
-
const encoder = new TextEncoder();
|
|
5548
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
5549
|
-
"raw",
|
|
5550
|
-
encoder.encode(secret),
|
|
5551
|
-
{ name: "PBKDF2" },
|
|
5552
|
-
false,
|
|
5553
|
-
["deriveBits", "deriveKey"]
|
|
5554
|
-
);
|
|
5555
|
-
return await crypto.subtle.deriveKey(
|
|
5556
|
-
{
|
|
5557
|
-
name: "PBKDF2",
|
|
5558
|
-
salt: encoder.encode("zendfi-secure-storage"),
|
|
5559
|
-
iterations: 1e5,
|
|
5560
|
-
hash: "SHA-256"
|
|
5561
|
-
},
|
|
5562
|
-
keyMaterial,
|
|
5563
|
-
{ name: "AES-GCM", length: 256 },
|
|
5564
|
-
false,
|
|
5565
|
-
["encrypt", "decrypt"]
|
|
5566
|
-
);
|
|
5567
|
-
}
|
|
5568
|
-
/**
|
|
5569
|
-
* Clear all secure storage
|
|
5570
|
-
*/
|
|
5571
|
-
static clearAll(namespace = "zendfi") {
|
|
5572
|
-
const keysToRemove = [];
|
|
5573
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
5574
|
-
const key = localStorage.key(i);
|
|
5575
|
-
if (key?.startsWith(namespace)) {
|
|
5576
|
-
keysToRemove.push(key);
|
|
5577
|
-
}
|
|
5578
|
-
}
|
|
5579
|
-
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
5580
|
-
}
|
|
5581
|
-
};
|
|
5582
|
-
|
|
5583
2413
|
// src/helpers/polling.ts
|
|
5584
2414
|
var TransactionPoller = class {
|
|
5585
2415
|
/**
|
|
@@ -5840,403 +2670,6 @@ var TransactionMonitor = class {
|
|
|
5840
2670
|
}
|
|
5841
2671
|
};
|
|
5842
2672
|
|
|
5843
|
-
// src/helpers/recovery.ts
|
|
5844
|
-
var RetryStrategy = class {
|
|
5845
|
-
/**
|
|
5846
|
-
* Execute function with retry logic
|
|
5847
|
-
*/
|
|
5848
|
-
static async withRetry(fn, options = {}) {
|
|
5849
|
-
const {
|
|
5850
|
-
maxAttempts = 3,
|
|
5851
|
-
backoffMs = 1e3,
|
|
5852
|
-
backoffMultiplier = 2,
|
|
5853
|
-
maxBackoffMs = 3e4,
|
|
5854
|
-
shouldRetry = () => true,
|
|
5855
|
-
onRetry
|
|
5856
|
-
} = options;
|
|
5857
|
-
let lastError = null;
|
|
5858
|
-
let currentBackoff = backoffMs;
|
|
5859
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
5860
|
-
try {
|
|
5861
|
-
return await fn();
|
|
5862
|
-
} catch (error) {
|
|
5863
|
-
lastError = error;
|
|
5864
|
-
if (attempt === maxAttempts || !shouldRetry(error, attempt)) {
|
|
5865
|
-
throw error;
|
|
5866
|
-
}
|
|
5867
|
-
const jitter = Math.random() * 0.3 * currentBackoff;
|
|
5868
|
-
const nextDelay = Math.min(currentBackoff + jitter, maxBackoffMs);
|
|
5869
|
-
onRetry?.(error, attempt, nextDelay);
|
|
5870
|
-
await this.sleep(nextDelay);
|
|
5871
|
-
currentBackoff *= backoffMultiplier;
|
|
5872
|
-
}
|
|
5873
|
-
}
|
|
5874
|
-
throw lastError || new Error("Retry failed");
|
|
5875
|
-
}
|
|
5876
|
-
/**
|
|
5877
|
-
* Retry with linear backoff
|
|
5878
|
-
*/
|
|
5879
|
-
static async withLinearRetry(fn, maxAttempts = 3, delayMs = 1e3) {
|
|
5880
|
-
return this.withRetry(fn, {
|
|
5881
|
-
maxAttempts,
|
|
5882
|
-
backoffMs: delayMs,
|
|
5883
|
-
backoffMultiplier: 1
|
|
5884
|
-
// Linear
|
|
5885
|
-
});
|
|
5886
|
-
}
|
|
5887
|
-
/**
|
|
5888
|
-
* Retry with custom backoff function
|
|
5889
|
-
*/
|
|
5890
|
-
static async withCustomBackoff(fn, backoffFn, maxAttempts = 3) {
|
|
5891
|
-
let lastError = null;
|
|
5892
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
5893
|
-
try {
|
|
5894
|
-
return await fn();
|
|
5895
|
-
} catch (error) {
|
|
5896
|
-
lastError = error;
|
|
5897
|
-
if (attempt === maxAttempts) {
|
|
5898
|
-
throw error;
|
|
5899
|
-
}
|
|
5900
|
-
const delay = backoffFn(attempt);
|
|
5901
|
-
await this.sleep(delay);
|
|
5902
|
-
}
|
|
5903
|
-
}
|
|
5904
|
-
throw lastError || new Error("Retry failed");
|
|
5905
|
-
}
|
|
5906
|
-
/**
|
|
5907
|
-
* Check if error is retryable
|
|
5908
|
-
*/
|
|
5909
|
-
static isRetryableError(error) {
|
|
5910
|
-
if (error.name === "NetworkError" || error.message?.includes("network")) {
|
|
5911
|
-
return true;
|
|
5912
|
-
}
|
|
5913
|
-
if (error.name === "TimeoutError" || error.message?.includes("timeout")) {
|
|
5914
|
-
return true;
|
|
5915
|
-
}
|
|
5916
|
-
if (error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED") {
|
|
5917
|
-
return true;
|
|
5918
|
-
}
|
|
5919
|
-
if (error.status >= 500 && error.status < 600) {
|
|
5920
|
-
return true;
|
|
5921
|
-
}
|
|
5922
|
-
if (error.message?.includes("blockhash") || error.message?.includes("recent")) {
|
|
5923
|
-
return true;
|
|
5924
|
-
}
|
|
5925
|
-
return false;
|
|
5926
|
-
}
|
|
5927
|
-
static sleep(ms) {
|
|
5928
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5929
|
-
}
|
|
5930
|
-
};
|
|
5931
|
-
var ErrorRecovery = class {
|
|
5932
|
-
/**
|
|
5933
|
-
* Recover from network errors with retry
|
|
5934
|
-
*/
|
|
5935
|
-
static async recoverFromNetworkError(fn, maxAttempts = 3) {
|
|
5936
|
-
return RetryStrategy.withRetry(fn, {
|
|
5937
|
-
maxAttempts,
|
|
5938
|
-
backoffMs: 2e3,
|
|
5939
|
-
shouldRetry: (error) => {
|
|
5940
|
-
return error.name === "NetworkError" || error.message?.includes("network") || error.message?.includes("fetch");
|
|
5941
|
-
},
|
|
5942
|
-
onRetry: (error, attempt, nextDelay) => {
|
|
5943
|
-
console.warn(
|
|
5944
|
-
`Network error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
|
|
5945
|
-
error.message
|
|
5946
|
-
);
|
|
5947
|
-
}
|
|
5948
|
-
});
|
|
5949
|
-
}
|
|
5950
|
-
/**
|
|
5951
|
-
* Recover from rate limit errors with exponential backoff
|
|
5952
|
-
*/
|
|
5953
|
-
static async recoverFromRateLimit(fn, maxAttempts = 5) {
|
|
5954
|
-
return RetryStrategy.withRetry(fn, {
|
|
5955
|
-
maxAttempts,
|
|
5956
|
-
backoffMs: 5e3,
|
|
5957
|
-
// Start with 5 seconds
|
|
5958
|
-
backoffMultiplier: 2,
|
|
5959
|
-
maxBackoffMs: 6e4,
|
|
5960
|
-
// Cap at 1 minute
|
|
5961
|
-
shouldRetry: (error) => {
|
|
5962
|
-
return error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED";
|
|
5963
|
-
},
|
|
5964
|
-
onRetry: (attempt, nextDelay) => {
|
|
5965
|
-
console.warn(
|
|
5966
|
-
`Rate limited (attempt ${attempt}), waiting ${nextDelay}ms before retry`
|
|
5967
|
-
);
|
|
5968
|
-
}
|
|
5969
|
-
});
|
|
5970
|
-
}
|
|
5971
|
-
/**
|
|
5972
|
-
* Recover from Solana RPC errors (blockhash, etc.)
|
|
5973
|
-
*/
|
|
5974
|
-
static async recoverFromRPCError(fn, maxAttempts = 3) {
|
|
5975
|
-
return RetryStrategy.withRetry(fn, {
|
|
5976
|
-
maxAttempts,
|
|
5977
|
-
backoffMs: 1e3,
|
|
5978
|
-
shouldRetry: (error) => {
|
|
5979
|
-
const message = error.message?.toLowerCase() || "";
|
|
5980
|
-
return message.includes("blockhash") || message.includes("recent") || message.includes("slot") || message.includes("rpc");
|
|
5981
|
-
},
|
|
5982
|
-
onRetry: (error, attempt, nextDelay) => {
|
|
5983
|
-
console.warn(
|
|
5984
|
-
`RPC error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
|
|
5985
|
-
error.message
|
|
5986
|
-
);
|
|
5987
|
-
}
|
|
5988
|
-
});
|
|
5989
|
-
}
|
|
5990
|
-
/**
|
|
5991
|
-
* Recover from timeout errors
|
|
5992
|
-
*/
|
|
5993
|
-
static async recoverFromTimeout(fn, timeoutMs = 3e4, maxAttempts = 2) {
|
|
5994
|
-
return RetryStrategy.withRetry(
|
|
5995
|
-
() => this.withTimeout(fn, timeoutMs),
|
|
5996
|
-
{
|
|
5997
|
-
maxAttempts,
|
|
5998
|
-
backoffMs: 5e3,
|
|
5999
|
-
shouldRetry: (error) => {
|
|
6000
|
-
return error.name === "TimeoutError" || error.message?.includes("timeout");
|
|
6001
|
-
}
|
|
6002
|
-
}
|
|
6003
|
-
);
|
|
6004
|
-
}
|
|
6005
|
-
/**
|
|
6006
|
-
* Add timeout to async function
|
|
6007
|
-
*/
|
|
6008
|
-
static async withTimeout(fn, timeoutMs) {
|
|
6009
|
-
return Promise.race([
|
|
6010
|
-
fn(),
|
|
6011
|
-
new Promise((_, reject) => {
|
|
6012
|
-
setTimeout(() => {
|
|
6013
|
-
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
|
|
6014
|
-
}, timeoutMs);
|
|
6015
|
-
})
|
|
6016
|
-
]);
|
|
6017
|
-
}
|
|
6018
|
-
/**
|
|
6019
|
-
* Circuit breaker pattern for repeated failures
|
|
6020
|
-
*/
|
|
6021
|
-
static createCircuitBreaker(fn, options = {}) {
|
|
6022
|
-
const {
|
|
6023
|
-
failureThreshold = 5,
|
|
6024
|
-
resetTimeoutMs = 6e4,
|
|
6025
|
-
onStateChange
|
|
6026
|
-
} = options;
|
|
6027
|
-
let state = "closed";
|
|
6028
|
-
let failureCount = 0;
|
|
6029
|
-
let lastFailureTime = 0;
|
|
6030
|
-
let resetTimer = null;
|
|
6031
|
-
return async () => {
|
|
6032
|
-
if (state === "open" && Date.now() - lastFailureTime > resetTimeoutMs) {
|
|
6033
|
-
state = "half-open";
|
|
6034
|
-
onStateChange?.("half-open");
|
|
6035
|
-
}
|
|
6036
|
-
if (state === "open") {
|
|
6037
|
-
throw new Error("Circuit breaker is OPEN - too many failures");
|
|
6038
|
-
}
|
|
6039
|
-
try {
|
|
6040
|
-
const result = await fn();
|
|
6041
|
-
if (state === "half-open") {
|
|
6042
|
-
state = "closed";
|
|
6043
|
-
onStateChange?.("closed");
|
|
6044
|
-
}
|
|
6045
|
-
failureCount = 0;
|
|
6046
|
-
return result;
|
|
6047
|
-
} catch (error) {
|
|
6048
|
-
failureCount++;
|
|
6049
|
-
lastFailureTime = Date.now();
|
|
6050
|
-
if (failureCount >= failureThreshold) {
|
|
6051
|
-
state = "open";
|
|
6052
|
-
onStateChange?.("open");
|
|
6053
|
-
if (resetTimer) clearTimeout(resetTimer);
|
|
6054
|
-
resetTimer = setTimeout(() => {
|
|
6055
|
-
state = "half-open";
|
|
6056
|
-
onStateChange?.("half-open");
|
|
6057
|
-
}, resetTimeoutMs);
|
|
6058
|
-
}
|
|
6059
|
-
throw error;
|
|
6060
|
-
}
|
|
6061
|
-
};
|
|
6062
|
-
}
|
|
6063
|
-
/**
|
|
6064
|
-
* Fallback to alternative function on error
|
|
6065
|
-
*/
|
|
6066
|
-
static async withFallback(primaryFn, fallbackFn, shouldFallback = () => true) {
|
|
6067
|
-
try {
|
|
6068
|
-
return await primaryFn();
|
|
6069
|
-
} catch (error) {
|
|
6070
|
-
if (shouldFallback(error)) {
|
|
6071
|
-
console.warn("Primary function failed, using fallback:", error.message);
|
|
6072
|
-
return await fallbackFn();
|
|
6073
|
-
}
|
|
6074
|
-
throw error;
|
|
6075
|
-
}
|
|
6076
|
-
}
|
|
6077
|
-
/**
|
|
6078
|
-
* Graceful degradation - return partial result on error
|
|
6079
|
-
*/
|
|
6080
|
-
static async withGracefulDegradation(fn, defaultValue, onError) {
|
|
6081
|
-
try {
|
|
6082
|
-
return await fn();
|
|
6083
|
-
} catch (error) {
|
|
6084
|
-
onError?.(error);
|
|
6085
|
-
console.warn("Operation failed, returning default value:", error.message);
|
|
6086
|
-
return defaultValue;
|
|
6087
|
-
}
|
|
6088
|
-
}
|
|
6089
|
-
};
|
|
6090
|
-
|
|
6091
|
-
// src/helpers/lifecycle.ts
|
|
6092
|
-
var SessionKeyLifecycle = class {
|
|
6093
|
-
constructor(client, config = {}) {
|
|
6094
|
-
this.client = client;
|
|
6095
|
-
this.config = config;
|
|
6096
|
-
if (config.autoCleanup && typeof window !== "undefined") {
|
|
6097
|
-
window.addEventListener("beforeunload", () => {
|
|
6098
|
-
this.cleanup().catch(console.error);
|
|
6099
|
-
});
|
|
6100
|
-
}
|
|
6101
|
-
}
|
|
6102
|
-
sessionKeyId = null;
|
|
6103
|
-
sessionWallet = null;
|
|
6104
|
-
/**
|
|
6105
|
-
* Create and fund session key in one call
|
|
6106
|
-
* Handles: keypair generation → encryption → backend registration
|
|
6107
|
-
* Note: The SDK now handles all crypto internally
|
|
6108
|
-
*/
|
|
6109
|
-
async createAndFund(config) {
|
|
6110
|
-
const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Create PIN for session key");
|
|
6111
|
-
const response = await this.client.sessionKeys.create({
|
|
6112
|
-
userWallet: config.userWallet,
|
|
6113
|
-
agentId: config.agentId,
|
|
6114
|
-
agentName: config.agentName,
|
|
6115
|
-
limitUSDC: config.limitUsdc,
|
|
6116
|
-
durationDays: config.durationDays || 7,
|
|
6117
|
-
pin,
|
|
6118
|
-
// SDK handles encryption internally
|
|
6119
|
-
generateRecoveryQR: false
|
|
6120
|
-
});
|
|
6121
|
-
this.sessionKeyId = response.sessionKeyId;
|
|
6122
|
-
this.sessionWallet = response.sessionWallet;
|
|
6123
|
-
if (config.onApprovalNeeded) {
|
|
6124
|
-
await config.onApprovalNeeded(`Fund session wallet: ${this.sessionWallet}`);
|
|
6125
|
-
}
|
|
6126
|
-
return {
|
|
6127
|
-
sessionKeyId: this.sessionKeyId,
|
|
6128
|
-
sessionWallet: this.sessionWallet
|
|
6129
|
-
};
|
|
6130
|
-
}
|
|
6131
|
-
/**
|
|
6132
|
-
* Make a payment using the session key
|
|
6133
|
-
* Uses the new SDK's makePayment which handles caching internally
|
|
6134
|
-
*/
|
|
6135
|
-
async pay(amount, description) {
|
|
6136
|
-
if (!this.sessionKeyId || !this.sessionWallet) {
|
|
6137
|
-
throw new Error("No active session key. Call createAndFund() first.");
|
|
6138
|
-
}
|
|
6139
|
-
const pin = this.config.pinProvider ? await this.config.pinProvider() : void 0;
|
|
6140
|
-
const result = await this.client.sessionKeys.makePayment({
|
|
6141
|
-
sessionKeyId: this.sessionKeyId,
|
|
6142
|
-
amount,
|
|
6143
|
-
recipient: this.sessionWallet,
|
|
6144
|
-
description,
|
|
6145
|
-
pin
|
|
6146
|
-
// SDK will only use PIN if needed
|
|
6147
|
-
});
|
|
6148
|
-
return {
|
|
6149
|
-
paymentId: result.paymentId,
|
|
6150
|
-
status: result.status,
|
|
6151
|
-
signature: result.signature
|
|
6152
|
-
};
|
|
6153
|
-
}
|
|
6154
|
-
/**
|
|
6155
|
-
* Check session key status
|
|
6156
|
-
*/
|
|
6157
|
-
async getStatus() {
|
|
6158
|
-
if (!this.sessionKeyId) {
|
|
6159
|
-
throw new Error("No active session key");
|
|
6160
|
-
}
|
|
6161
|
-
return await this.client.sessionKeys.getStatus(this.sessionKeyId);
|
|
6162
|
-
}
|
|
6163
|
-
/**
|
|
6164
|
-
* Top up session key
|
|
6165
|
-
* @deprecated Device-bound session keys are funded directly by the user.
|
|
6166
|
-
* Use the session wallet address to send funds directly.
|
|
6167
|
-
*/
|
|
6168
|
-
async topUp(_amount, _userWallet, _onApprovalNeeded) {
|
|
6169
|
-
if (!this.sessionWallet) {
|
|
6170
|
-
throw new Error("No active session key");
|
|
6171
|
-
}
|
|
6172
|
-
console.warn(
|
|
6173
|
-
`topUp() is deprecated for device-bound session keys. Send funds directly to the session wallet: ${this.sessionWallet}`
|
|
6174
|
-
);
|
|
6175
|
-
}
|
|
6176
|
-
/**
|
|
6177
|
-
* Revoke session key
|
|
6178
|
-
*/
|
|
6179
|
-
async revoke() {
|
|
6180
|
-
if (!this.sessionKeyId) {
|
|
6181
|
-
throw new Error("No active session key");
|
|
6182
|
-
}
|
|
6183
|
-
await this.client.sessionKeys.revoke(this.sessionKeyId);
|
|
6184
|
-
await this.cleanup();
|
|
6185
|
-
}
|
|
6186
|
-
/**
|
|
6187
|
-
* Cleanup (clear cache, reset state)
|
|
6188
|
-
*/
|
|
6189
|
-
async cleanup() {
|
|
6190
|
-
if (this.config.cache && this.sessionKeyId) {
|
|
6191
|
-
await this.config.cache.invalidate(this.sessionKeyId);
|
|
6192
|
-
}
|
|
6193
|
-
if (this.sessionKeyId) {
|
|
6194
|
-
this.client.sessionKeys.clearCache(this.sessionKeyId);
|
|
6195
|
-
}
|
|
6196
|
-
this.sessionKeyId = null;
|
|
6197
|
-
this.sessionWallet = null;
|
|
6198
|
-
}
|
|
6199
|
-
/**
|
|
6200
|
-
* Get current session key ID
|
|
6201
|
-
*/
|
|
6202
|
-
getSessionKeyId() {
|
|
6203
|
-
return this.sessionKeyId;
|
|
6204
|
-
}
|
|
6205
|
-
/**
|
|
6206
|
-
* Check if session is active
|
|
6207
|
-
*/
|
|
6208
|
-
isActive() {
|
|
6209
|
-
return this.sessionKeyId !== null;
|
|
6210
|
-
}
|
|
6211
|
-
// ============================================
|
|
6212
|
-
// Private Helpers
|
|
6213
|
-
// ============================================
|
|
6214
|
-
async promptForPIN(message) {
|
|
6215
|
-
if (typeof window !== "undefined" && window.prompt) {
|
|
6216
|
-
const pin = window.prompt(message);
|
|
6217
|
-
if (!pin) {
|
|
6218
|
-
throw new Error("PIN required");
|
|
6219
|
-
}
|
|
6220
|
-
return pin;
|
|
6221
|
-
}
|
|
6222
|
-
throw new Error("PIN provider not configured and no browser prompt available");
|
|
6223
|
-
}
|
|
6224
|
-
};
|
|
6225
|
-
async function setupQuickSessionKey(client, config) {
|
|
6226
|
-
const { SessionKeyCache: SessionKeyCache2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
|
|
6227
|
-
const lifecycle = new SessionKeyLifecycle(client, {
|
|
6228
|
-
cache: new SessionKeyCache2({ storage: "localStorage", ttl: 36e5 }),
|
|
6229
|
-
autoCleanup: true
|
|
6230
|
-
});
|
|
6231
|
-
await lifecycle.createAndFund({
|
|
6232
|
-
userWallet: config.userWallet,
|
|
6233
|
-
agentId: config.agentId,
|
|
6234
|
-
limitUsdc: config.budgetUsdc,
|
|
6235
|
-
onApprovalNeeded: config.onApproval
|
|
6236
|
-
});
|
|
6237
|
-
return lifecycle;
|
|
6238
|
-
}
|
|
6239
|
-
|
|
6240
2673
|
// src/helpers/dev.ts
|
|
6241
2674
|
var DevTools = class {
|
|
6242
2675
|
static debugEnabled = false;
|
|
@@ -6318,8 +2751,8 @@ var DevTools = class {
|
|
|
6318
2751
|
if (!this.isDevelopment()) {
|
|
6319
2752
|
throw new Error("Test session keys can only be created in development");
|
|
6320
2753
|
}
|
|
6321
|
-
const { Keypair
|
|
6322
|
-
const keypair =
|
|
2754
|
+
const { Keypair } = await this.getSolanaWeb3();
|
|
2755
|
+
const keypair = Keypair.generate();
|
|
6323
2756
|
return {
|
|
6324
2757
|
sessionKeyId: this.generateTestId("sk_test"),
|
|
6325
2758
|
sessionWallet: keypair.publicKey.toString(),
|
|
@@ -6581,41 +3014,17 @@ var PerformanceMonitor = class {
|
|
|
6581
3014
|
};
|
|
6582
3015
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6583
3016
|
0 && (module.exports = {
|
|
6584
|
-
AgentAPI,
|
|
6585
|
-
AnthropicAdapter,
|
|
6586
3017
|
ApiError,
|
|
6587
3018
|
AuthenticationError,
|
|
6588
|
-
AutonomyAPI,
|
|
6589
3019
|
ConfigLoader,
|
|
6590
3020
|
DevTools,
|
|
6591
|
-
DeviceBoundSessionKey,
|
|
6592
|
-
DeviceFingerprintGenerator,
|
|
6593
3021
|
ERROR_CODES,
|
|
6594
|
-
ErrorRecovery,
|
|
6595
|
-
GeminiAdapter,
|
|
6596
3022
|
InterceptorManager,
|
|
6597
|
-
LitCryptoSigner,
|
|
6598
3023
|
NetworkError,
|
|
6599
|
-
OpenAIAdapter,
|
|
6600
|
-
PINRateLimiter,
|
|
6601
|
-
PINValidator,
|
|
6602
3024
|
PaymentError,
|
|
6603
|
-
PaymentIntentParser,
|
|
6604
|
-
PaymentIntentsAPI,
|
|
6605
3025
|
PerformanceMonitor,
|
|
6606
|
-
PricingAPI,
|
|
6607
|
-
QuickCaches,
|
|
6608
3026
|
RateLimitError,
|
|
6609
3027
|
RateLimiter,
|
|
6610
|
-
RecoveryQRGenerator,
|
|
6611
|
-
RetryStrategy,
|
|
6612
|
-
SPENDING_LIMIT_ACTION_CID,
|
|
6613
|
-
SecureStorage,
|
|
6614
|
-
SessionKeyCache,
|
|
6615
|
-
SessionKeyCrypto,
|
|
6616
|
-
SessionKeyLifecycle,
|
|
6617
|
-
SessionKeysAPI,
|
|
6618
|
-
SmartPaymentsAPI,
|
|
6619
3028
|
TransactionMonitor,
|
|
6620
3029
|
TransactionPoller,
|
|
6621
3030
|
ValidationError,
|
|
@@ -6624,25 +3033,17 @@ var PerformanceMonitor = class {
|
|
|
6624
3033
|
ZendFiClient,
|
|
6625
3034
|
ZendFiEmbeddedCheckout,
|
|
6626
3035
|
ZendFiError,
|
|
6627
|
-
asAgentKeyId,
|
|
6628
|
-
asEscrowId,
|
|
6629
3036
|
asInstallmentPlanId,
|
|
6630
|
-
asIntentId,
|
|
6631
3037
|
asInvoiceId,
|
|
6632
3038
|
asMerchantId,
|
|
6633
3039
|
asPaymentId,
|
|
6634
3040
|
asPaymentLinkCode,
|
|
6635
|
-
asSessionId,
|
|
6636
3041
|
asSubscriptionId,
|
|
6637
3042
|
createWalletHook,
|
|
6638
3043
|
createZendFiError,
|
|
6639
|
-
decodeSignatureFromLit,
|
|
6640
|
-
encodeTransactionForLit,
|
|
6641
3044
|
generateIdempotencyKey,
|
|
6642
3045
|
isZendFiError,
|
|
6643
3046
|
processWebhook,
|
|
6644
|
-
requiresLitSigning,
|
|
6645
|
-
setupQuickSessionKey,
|
|
6646
3047
|
sleep,
|
|
6647
3048
|
verifyExpressWebhook,
|
|
6648
3049
|
verifyNextWebhook,
|