@owney/sdk 0.3.2 → 0.3.4
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 +4 -0
- package/dist/index.cjs +137 -2
- package/dist/index.js +137 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -749,3 +749,7 @@ The three convenience subclasses extend `OwneyError` and remain backwards-compat
|
|
|
749
749
|
| `NotConnectedError` | `NOT_CONNECTED` | — |
|
|
750
750
|
| `AgentNotFoundError` | `AGENT_NOT_FOUND` | — |
|
|
751
751
|
| `AgentChainIncompatibleError` | `AGENT_CHAIN_INCOMPATIBLE` | `incompatibleAgents`, `connectedChainId` |
|
|
752
|
+
|
|
753
|
+
## License
|
|
754
|
+
|
|
755
|
+
ELv2
|
package/dist/index.cjs
CHANGED
|
@@ -333,6 +333,72 @@ function mapApyByStrategy(raw) {
|
|
|
333
333
|
};
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
// src/agents/zyfai/zyfai.auth-cache.ts
|
|
337
|
+
var KEY_PREFIX = "owney.zyfai.session";
|
|
338
|
+
var storage = () => {
|
|
339
|
+
if (typeof window === "undefined")
|
|
340
|
+
return null;
|
|
341
|
+
try {
|
|
342
|
+
return window.localStorage;
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
var buildKey = (address, chainId) => `${KEY_PREFIX}:${address.toLowerCase()}:${chainId}`;
|
|
348
|
+
var isJwtExpired = (token) => {
|
|
349
|
+
const parts = token.split(".");
|
|
350
|
+
if (parts.length !== 3)
|
|
351
|
+
return false;
|
|
352
|
+
try {
|
|
353
|
+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
354
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
355
|
+
const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
356
|
+
const claims = JSON.parse(decoded);
|
|
357
|
+
if (typeof claims.exp !== "number")
|
|
358
|
+
return false;
|
|
359
|
+
return Date.now() / 1e3 >= claims.exp - 30;
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var readSession = (address, chainId) => {
|
|
365
|
+
const store = storage();
|
|
366
|
+
if (!store)
|
|
367
|
+
return null;
|
|
368
|
+
const raw = store.getItem(buildKey(address, chainId));
|
|
369
|
+
if (!raw)
|
|
370
|
+
return null;
|
|
371
|
+
try {
|
|
372
|
+
const parsed = JSON.parse(raw);
|
|
373
|
+
if (!parsed.token || isJwtExpired(parsed.token)) {
|
|
374
|
+
store.removeItem(buildKey(address, chainId));
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
return parsed;
|
|
378
|
+
} catch {
|
|
379
|
+
store.removeItem(buildKey(address, chainId));
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
var writeSession = (address, chainId, session) => {
|
|
384
|
+
const store = storage();
|
|
385
|
+
if (!store)
|
|
386
|
+
return;
|
|
387
|
+
try {
|
|
388
|
+
store.setItem(buildKey(address, chainId), JSON.stringify(session));
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
var clearSession = (address, chainId) => {
|
|
393
|
+
const store = storage();
|
|
394
|
+
if (!store)
|
|
395
|
+
return;
|
|
396
|
+
try {
|
|
397
|
+
store.removeItem(buildKey(address, chainId));
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
336
402
|
// src/agents/zyfai/zyfai.agent.ts
|
|
337
403
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
338
404
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
@@ -363,6 +429,49 @@ var ZyfaiAgent = class {
|
|
|
363
429
|
apiKey,
|
|
364
430
|
rpcUrls: rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS
|
|
365
431
|
});
|
|
432
|
+
this.installAuthCache();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Patches `sdk.authenticateUser` so a fresh page can reuse the JWT from a
|
|
436
|
+
* prior session instead of triggering a new SIWE prompt. Runs inside
|
|
437
|
+
* `connectAccount` AFTER wallet setup but BEFORE the real SIWE handshake
|
|
438
|
+
* (Zyfai dist line 1374). At that point `walletClient` + `currentChainId`
|
|
439
|
+
* are populated, so the cache lookup has both keys it needs.
|
|
440
|
+
*
|
|
441
|
+
* No-op on the server (no `window`) — the SDK is constructed during SSR
|
|
442
|
+
* for some apps and the patch only matters in the browser anyway.
|
|
443
|
+
*/
|
|
444
|
+
installAuthCache() {
|
|
445
|
+
if (typeof window === "undefined")
|
|
446
|
+
return;
|
|
447
|
+
const internals = this.sdk;
|
|
448
|
+
const original = internals.authenticateUser.bind(internals);
|
|
449
|
+
internals.authenticateUser = async () => {
|
|
450
|
+
if (internals.authenticatedUserId !== null)
|
|
451
|
+
return;
|
|
452
|
+
const address = internals.walletClient?.account?.address;
|
|
453
|
+
const chainId = internals.currentChainId;
|
|
454
|
+
if (address && chainId) {
|
|
455
|
+
const cached = readSession(address, chainId);
|
|
456
|
+
if (cached) {
|
|
457
|
+
internals.httpClient.setAuthToken(cached.token);
|
|
458
|
+
internals.authenticatedUserId = cached.userId;
|
|
459
|
+
internals.hasActiveSessionKey = cached.hasActiveSessionKey;
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
await original();
|
|
464
|
+
const newAddress = internals.walletClient?.account?.address;
|
|
465
|
+
const newChainId = internals.currentChainId;
|
|
466
|
+
const newToken = internals.httpClient.authToken;
|
|
467
|
+
if (newAddress && newChainId && newToken) {
|
|
468
|
+
writeSession(newAddress, newChainId, {
|
|
469
|
+
token: newToken,
|
|
470
|
+
userId: internals.authenticatedUserId,
|
|
471
|
+
hasActiveSessionKey: internals.hasActiveSessionKey
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
366
475
|
}
|
|
367
476
|
// --- Private helpers ---
|
|
368
477
|
getAddress() {
|
|
@@ -387,7 +496,7 @@ var ZyfaiAgent = class {
|
|
|
387
496
|
return;
|
|
388
497
|
}
|
|
389
498
|
this.connectAuthPromise = (async () => {
|
|
390
|
-
const address = await this.
|
|
499
|
+
const address = await this.connectAccountWithRetry(
|
|
391
500
|
state.provider,
|
|
392
501
|
validChainId
|
|
393
502
|
);
|
|
@@ -400,6 +509,29 @@ var ZyfaiAgent = class {
|
|
|
400
509
|
this.connectAuthPromise = null;
|
|
401
510
|
}
|
|
402
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
|
|
514
|
+
* cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
|
|
515
|
+
* we drop the cache and try again — the second attempt has no cache entry,
|
|
516
|
+
* so it falls through to a real SIWE prompt. Zyfai collapses every 4xx into
|
|
517
|
+
* an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
|
|
518
|
+
* JWT from a misconfigured API key; we retry once either way, and if the
|
|
519
|
+
* second attempt still fails the original error propagates.
|
|
520
|
+
*/
|
|
521
|
+
async connectAccountWithRetry(provider, chainId) {
|
|
522
|
+
try {
|
|
523
|
+
return await this.sdk.connectAccount(provider, chainId);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
526
|
+
if (!message.toLowerCase().includes("unauthorized"))
|
|
527
|
+
throw error;
|
|
528
|
+
const internals = this.sdk;
|
|
529
|
+
const address = internals.walletClient?.account?.address;
|
|
530
|
+
if (address)
|
|
531
|
+
clearSession(address, chainId);
|
|
532
|
+
return await this.sdk.connectAccount(provider, chainId);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
403
535
|
/** Full connect: SIWE auth + Safe deploy check + session key. */
|
|
404
536
|
async connectFull(state, chainId) {
|
|
405
537
|
await this.connectAuth(state, chainId);
|
|
@@ -434,6 +566,9 @@ var ZyfaiAgent = class {
|
|
|
434
566
|
}
|
|
435
567
|
// --- IAgent: Connection lifecycle ---
|
|
436
568
|
async disconnect() {
|
|
569
|
+
if (this.connectedAddress && this.connectedChainId !== null) {
|
|
570
|
+
clearSession(this.connectedAddress, this.connectedChainId);
|
|
571
|
+
}
|
|
437
572
|
await this.sdk.disconnectAccount();
|
|
438
573
|
this.connectedAddress = null;
|
|
439
574
|
this.connectedChainId = null;
|
|
@@ -1050,7 +1185,7 @@ var OwneySDK = class {
|
|
|
1050
1185
|
const token = balance.tokens.find(
|
|
1051
1186
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
1052
1187
|
);
|
|
1053
|
-
return !!token &&
|
|
1188
|
+
return !!token && Number(token.amount) > 0;
|
|
1054
1189
|
} catch {
|
|
1055
1190
|
return false;
|
|
1056
1191
|
}
|
package/dist/index.js
CHANGED
|
@@ -303,6 +303,72 @@ function mapApyByStrategy(raw) {
|
|
|
303
303
|
};
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
// src/agents/zyfai/zyfai.auth-cache.ts
|
|
307
|
+
var KEY_PREFIX = "owney.zyfai.session";
|
|
308
|
+
var storage = () => {
|
|
309
|
+
if (typeof window === "undefined")
|
|
310
|
+
return null;
|
|
311
|
+
try {
|
|
312
|
+
return window.localStorage;
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var buildKey = (address, chainId) => `${KEY_PREFIX}:${address.toLowerCase()}:${chainId}`;
|
|
318
|
+
var isJwtExpired = (token) => {
|
|
319
|
+
const parts = token.split(".");
|
|
320
|
+
if (parts.length !== 3)
|
|
321
|
+
return false;
|
|
322
|
+
try {
|
|
323
|
+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
324
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
325
|
+
const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
326
|
+
const claims = JSON.parse(decoded);
|
|
327
|
+
if (typeof claims.exp !== "number")
|
|
328
|
+
return false;
|
|
329
|
+
return Date.now() / 1e3 >= claims.exp - 30;
|
|
330
|
+
} catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var readSession = (address, chainId) => {
|
|
335
|
+
const store = storage();
|
|
336
|
+
if (!store)
|
|
337
|
+
return null;
|
|
338
|
+
const raw = store.getItem(buildKey(address, chainId));
|
|
339
|
+
if (!raw)
|
|
340
|
+
return null;
|
|
341
|
+
try {
|
|
342
|
+
const parsed = JSON.parse(raw);
|
|
343
|
+
if (!parsed.token || isJwtExpired(parsed.token)) {
|
|
344
|
+
store.removeItem(buildKey(address, chainId));
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
return parsed;
|
|
348
|
+
} catch {
|
|
349
|
+
store.removeItem(buildKey(address, chainId));
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var writeSession = (address, chainId, session) => {
|
|
354
|
+
const store = storage();
|
|
355
|
+
if (!store)
|
|
356
|
+
return;
|
|
357
|
+
try {
|
|
358
|
+
store.setItem(buildKey(address, chainId), JSON.stringify(session));
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
var clearSession = (address, chainId) => {
|
|
363
|
+
const store = storage();
|
|
364
|
+
if (!store)
|
|
365
|
+
return;
|
|
366
|
+
try {
|
|
367
|
+
store.removeItem(buildKey(address, chainId));
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
306
372
|
// src/agents/zyfai/zyfai.agent.ts
|
|
307
373
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
308
374
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
@@ -333,6 +399,49 @@ var ZyfaiAgent = class {
|
|
|
333
399
|
apiKey,
|
|
334
400
|
rpcUrls: rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS
|
|
335
401
|
});
|
|
402
|
+
this.installAuthCache();
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Patches `sdk.authenticateUser` so a fresh page can reuse the JWT from a
|
|
406
|
+
* prior session instead of triggering a new SIWE prompt. Runs inside
|
|
407
|
+
* `connectAccount` AFTER wallet setup but BEFORE the real SIWE handshake
|
|
408
|
+
* (Zyfai dist line 1374). At that point `walletClient` + `currentChainId`
|
|
409
|
+
* are populated, so the cache lookup has both keys it needs.
|
|
410
|
+
*
|
|
411
|
+
* No-op on the server (no `window`) — the SDK is constructed during SSR
|
|
412
|
+
* for some apps and the patch only matters in the browser anyway.
|
|
413
|
+
*/
|
|
414
|
+
installAuthCache() {
|
|
415
|
+
if (typeof window === "undefined")
|
|
416
|
+
return;
|
|
417
|
+
const internals = this.sdk;
|
|
418
|
+
const original = internals.authenticateUser.bind(internals);
|
|
419
|
+
internals.authenticateUser = async () => {
|
|
420
|
+
if (internals.authenticatedUserId !== null)
|
|
421
|
+
return;
|
|
422
|
+
const address = internals.walletClient?.account?.address;
|
|
423
|
+
const chainId = internals.currentChainId;
|
|
424
|
+
if (address && chainId) {
|
|
425
|
+
const cached = readSession(address, chainId);
|
|
426
|
+
if (cached) {
|
|
427
|
+
internals.httpClient.setAuthToken(cached.token);
|
|
428
|
+
internals.authenticatedUserId = cached.userId;
|
|
429
|
+
internals.hasActiveSessionKey = cached.hasActiveSessionKey;
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
await original();
|
|
434
|
+
const newAddress = internals.walletClient?.account?.address;
|
|
435
|
+
const newChainId = internals.currentChainId;
|
|
436
|
+
const newToken = internals.httpClient.authToken;
|
|
437
|
+
if (newAddress && newChainId && newToken) {
|
|
438
|
+
writeSession(newAddress, newChainId, {
|
|
439
|
+
token: newToken,
|
|
440
|
+
userId: internals.authenticatedUserId,
|
|
441
|
+
hasActiveSessionKey: internals.hasActiveSessionKey
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
};
|
|
336
445
|
}
|
|
337
446
|
// --- Private helpers ---
|
|
338
447
|
getAddress() {
|
|
@@ -357,7 +466,7 @@ var ZyfaiAgent = class {
|
|
|
357
466
|
return;
|
|
358
467
|
}
|
|
359
468
|
this.connectAuthPromise = (async () => {
|
|
360
|
-
const address = await this.
|
|
469
|
+
const address = await this.connectAccountWithRetry(
|
|
361
470
|
state.provider,
|
|
362
471
|
validChainId
|
|
363
472
|
);
|
|
@@ -370,6 +479,29 @@ var ZyfaiAgent = class {
|
|
|
370
479
|
this.connectAuthPromise = null;
|
|
371
480
|
}
|
|
372
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
|
|
484
|
+
* cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
|
|
485
|
+
* we drop the cache and try again — the second attempt has no cache entry,
|
|
486
|
+
* so it falls through to a real SIWE prompt. Zyfai collapses every 4xx into
|
|
487
|
+
* an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
|
|
488
|
+
* JWT from a misconfigured API key; we retry once either way, and if the
|
|
489
|
+
* second attempt still fails the original error propagates.
|
|
490
|
+
*/
|
|
491
|
+
async connectAccountWithRetry(provider, chainId) {
|
|
492
|
+
try {
|
|
493
|
+
return await this.sdk.connectAccount(provider, chainId);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
496
|
+
if (!message.toLowerCase().includes("unauthorized"))
|
|
497
|
+
throw error;
|
|
498
|
+
const internals = this.sdk;
|
|
499
|
+
const address = internals.walletClient?.account?.address;
|
|
500
|
+
if (address)
|
|
501
|
+
clearSession(address, chainId);
|
|
502
|
+
return await this.sdk.connectAccount(provider, chainId);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
373
505
|
/** Full connect: SIWE auth + Safe deploy check + session key. */
|
|
374
506
|
async connectFull(state, chainId) {
|
|
375
507
|
await this.connectAuth(state, chainId);
|
|
@@ -404,6 +536,9 @@ var ZyfaiAgent = class {
|
|
|
404
536
|
}
|
|
405
537
|
// --- IAgent: Connection lifecycle ---
|
|
406
538
|
async disconnect() {
|
|
539
|
+
if (this.connectedAddress && this.connectedChainId !== null) {
|
|
540
|
+
clearSession(this.connectedAddress, this.connectedChainId);
|
|
541
|
+
}
|
|
407
542
|
await this.sdk.disconnectAccount();
|
|
408
543
|
this.connectedAddress = null;
|
|
409
544
|
this.connectedChainId = null;
|
|
@@ -1020,7 +1155,7 @@ var OwneySDK = class {
|
|
|
1020
1155
|
const token = balance.tokens.find(
|
|
1021
1156
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
1022
1157
|
);
|
|
1023
|
-
return !!token &&
|
|
1158
|
+
return !!token && Number(token.amount) > 0;
|
|
1024
1159
|
} catch {
|
|
1025
1160
|
return false;
|
|
1026
1161
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owney/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"license": "ELv2",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/index.d.ts",
|