@owney/sdk 0.7.7-beta.2 → 0.7.7-beta.3
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/dist/index.cjs +215 -150
- package/dist/index.d.cts +19 -45
- package/dist/index.d.ts +19 -45
- package/dist/index.js +215 -150
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -392,7 +392,7 @@ declare class OwneySDK {
|
|
|
392
392
|
private routingApiBaseUrl?;
|
|
393
393
|
private referralSource?;
|
|
394
394
|
private cachedSponsoredCallback;
|
|
395
|
-
private
|
|
395
|
+
private cachedWethSponsoredCallback;
|
|
396
396
|
private initializingAgentsPromise;
|
|
397
397
|
constructor(config: OwneySDKConfig);
|
|
398
398
|
/**
|
|
@@ -428,21 +428,12 @@ declare class OwneySDK {
|
|
|
428
428
|
*/
|
|
429
429
|
private getDefaultSponsoredCallback;
|
|
430
430
|
/**
|
|
431
|
-
* Lazily builds (and caches) the Permit2 sponsored
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
431
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
432
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
433
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
434
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
435
435
|
*/
|
|
436
|
-
private
|
|
437
|
-
/**
|
|
438
|
-
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
439
|
-
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
440
|
-
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
441
|
-
*
|
|
442
|
-
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
443
|
-
* is never blocked by a flaky provider.
|
|
444
|
-
*/
|
|
445
|
-
private ownerHasCode;
|
|
436
|
+
private getDefaultWethSponsoredCallback;
|
|
446
437
|
private getAgent;
|
|
447
438
|
private getActiveAgents;
|
|
448
439
|
/**
|
|
@@ -492,16 +483,13 @@ declare class OwneySDK {
|
|
|
492
483
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
493
484
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
494
485
|
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
495
|
-
* the one-time (user-paid) Permit2 approval via `approvePermit2()
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
* propagates as-is — the user said no to a transaction, so we must not
|
|
503
|
-
* turn around and ask them to pay for a different one via the fallback
|
|
504
|
-
* below.
|
|
486
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
487
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
488
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
489
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
490
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
491
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
492
|
+
* them to pay for a different one via the fallback below.
|
|
505
493
|
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
506
494
|
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
507
495
|
* and retry once with `undefined`, falling through to a user-paid
|
|
@@ -513,18 +501,6 @@ declare class OwneySDK {
|
|
|
513
501
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
514
502
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
515
503
|
*/
|
|
516
|
-
/**
|
|
517
|
-
* Polls the owner's Permit2 allowance for `asset` until it covers `amount`
|
|
518
|
-
* or the attempt budget runs out. Called right after `approvePermit2()`
|
|
519
|
-
* resolves: the approval tx is mined, but the wallet's own RPC can still
|
|
520
|
-
* serve a stale (zero) allowance on the very next read (replica lag), which
|
|
521
|
-
* would otherwise make the immediate retry fail with the same
|
|
522
|
-
* PERMIT2_APPROVAL_REQUIRED error the user just approved past. Never
|
|
523
|
-
* throws — if the allowance still hasn't caught up after the budget, the
|
|
524
|
-
* caller's retry will surface the honest PERMIT2_APPROVAL_REQUIRED error
|
|
525
|
-
* rather than this helper inventing a different one.
|
|
526
|
-
*/
|
|
527
|
-
private waitForPermit2Allowance;
|
|
528
504
|
private depositWithFallback;
|
|
529
505
|
private getMinDepositAmount;
|
|
530
506
|
private splitDepositAmount;
|
|
@@ -602,16 +578,14 @@ declare class OwneySDK {
|
|
|
602
578
|
*/
|
|
603
579
|
ensureAutoSelectProtocols(asset: "USDC" | "WETH", agentId?: AgentId): Promise<boolean>;
|
|
604
580
|
/**
|
|
605
|
-
* One-time, user-paid approval of Permit2 on the
|
|
606
|
-
* the active chain. Required once per wallet per chain
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
611
|
-
* throws if the transaction reverted.
|
|
581
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
582
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
583
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
584
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
585
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
612
586
|
* @returns the approval transaction hash.
|
|
613
587
|
*/
|
|
614
|
-
approvePermit2(asset?: "WETH"
|
|
588
|
+
approvePermit2(asset?: "WETH"): Promise<`0x${string}`>;
|
|
615
589
|
/**
|
|
616
590
|
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|
|
617
591
|
* @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
|
package/dist/index.js
CHANGED
|
@@ -846,6 +846,12 @@ var ZyfaiAgent = class {
|
|
|
846
846
|
connectedAddress = null;
|
|
847
847
|
connectedChainId = null;
|
|
848
848
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
849
|
+
/**
|
|
850
|
+
* `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
|
|
851
|
+
* against re-running it on every activation/deposit when Zyfai's
|
|
852
|
+
* `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
|
|
853
|
+
*/
|
|
854
|
+
sessionKeyAttempts = /* @__PURE__ */ new Set();
|
|
849
855
|
connectAuthPromise = null;
|
|
850
856
|
rpcUrls;
|
|
851
857
|
publicClients = /* @__PURE__ */ new Map();
|
|
@@ -1303,10 +1309,74 @@ var ZyfaiAgent = class {
|
|
|
1303
1309
|
* recoverable (ensureWalletDeployed tries the other deploy call), but
|
|
1304
1310
|
* failing the deposit outright is not.
|
|
1305
1311
|
*/
|
|
1312
|
+
/**
|
|
1313
|
+
* Zyfai's own answer to "what kind of wallet is this, and can the agent act on
|
|
1314
|
+
* it", read from the positions payload (`GET /data/position`, which
|
|
1315
|
+
* `getPositions` wraps).
|
|
1316
|
+
*
|
|
1317
|
+
* `predeployed` and `hasActiveSessionKey` are both returned at runtime but
|
|
1318
|
+
* declared on neither `Portfolio` nor their docs, hence the cast. Verified
|
|
1319
|
+
* live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
|
|
1320
|
+
* false}` while a pool wallet reports `{true, true}`.
|
|
1321
|
+
*
|
|
1322
|
+
* Preferred over the SDK's private `isPredeployed`, which is cached from the
|
|
1323
|
+
* login response and can go stale mid-session. Returns an empty object on any
|
|
1324
|
+
* failure so every caller degrades to its own fallback.
|
|
1325
|
+
*/
|
|
1326
|
+
async readWalletState(ownerAddress) {
|
|
1327
|
+
try {
|
|
1328
|
+
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1329
|
+
const raw = portfolio;
|
|
1330
|
+
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1331
|
+
predeployed: raw?.predeployed,
|
|
1332
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1333
|
+
});
|
|
1334
|
+
return {
|
|
1335
|
+
predeployed: raw?.predeployed,
|
|
1336
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1337
|
+
};
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.warn(
|
|
1340
|
+
"[zyfai] getPositions wallet-state read failed (non-fatal):",
|
|
1341
|
+
error instanceof Error ? error.message : String(error)
|
|
1342
|
+
);
|
|
1343
|
+
return {};
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1306
1346
|
async isPredeployedPoolWallet(ownerAddress, smartWallet) {
|
|
1347
|
+
const { predeployed } = await this.readWalletState(ownerAddress);
|
|
1348
|
+
if (typeof predeployed === "boolean") {
|
|
1349
|
+
debugLog(
|
|
1350
|
+
"zyfai:onboard",
|
|
1351
|
+
`wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1352
|
+
{ source: "getPositions.predeployed", predeployed }
|
|
1353
|
+
);
|
|
1354
|
+
return predeployed;
|
|
1355
|
+
}
|
|
1356
|
+
const internals = this.sdk;
|
|
1357
|
+
if (typeof internals.isPredeployed === "boolean") {
|
|
1358
|
+
debugLog(
|
|
1359
|
+
"zyfai:onboard",
|
|
1360
|
+
`wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1361
|
+
{
|
|
1362
|
+
source: "login response (cached)",
|
|
1363
|
+
predeployed: internals.isPredeployed
|
|
1364
|
+
}
|
|
1365
|
+
);
|
|
1366
|
+
return internals.isPredeployed;
|
|
1367
|
+
}
|
|
1307
1368
|
try {
|
|
1308
1369
|
const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
|
|
1309
|
-
|
|
1370
|
+
const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
|
|
1371
|
+
debugLog(
|
|
1372
|
+
"zyfai:onboard",
|
|
1373
|
+
`wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1374
|
+
{
|
|
1375
|
+
source: "getSmartWalletByEOA fallback",
|
|
1376
|
+
poolRecord: byEoa?.smartWallet
|
|
1377
|
+
}
|
|
1378
|
+
);
|
|
1379
|
+
return isPool;
|
|
1310
1380
|
} catch (error) {
|
|
1311
1381
|
console.warn(
|
|
1312
1382
|
"[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
|
|
@@ -1344,8 +1414,24 @@ var ZyfaiAgent = class {
|
|
|
1344
1414
|
ownerAddress,
|
|
1345
1415
|
chainId
|
|
1346
1416
|
);
|
|
1347
|
-
|
|
1348
|
-
|
|
1417
|
+
debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
|
|
1418
|
+
smartWallet: wallet.address,
|
|
1419
|
+
isDeployed: wallet.isDeployed
|
|
1420
|
+
});
|
|
1421
|
+
if (!wallet.isDeployed) {
|
|
1422
|
+
debugLog(
|
|
1423
|
+
"zyfai:onboard",
|
|
1424
|
+
"Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
|
|
1425
|
+
);
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1429
|
+
await this.ensureSessionKey(
|
|
1430
|
+
ownerAddress,
|
|
1431
|
+
chainId,
|
|
1432
|
+
wallet.address,
|
|
1433
|
+
hasActiveSessionKey
|
|
1434
|
+
);
|
|
1349
1435
|
} catch (error) {
|
|
1350
1436
|
console.warn(
|
|
1351
1437
|
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
@@ -1353,20 +1439,51 @@ var ZyfaiAgent = class {
|
|
|
1353
1439
|
);
|
|
1354
1440
|
}
|
|
1355
1441
|
}
|
|
1356
|
-
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1442
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
|
|
1443
|
+
const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
|
|
1444
|
+
if (this.sessionKeyAttempts.has(attemptKey)) {
|
|
1445
|
+
debugLog(
|
|
1446
|
+
"zyfai:onboard",
|
|
1447
|
+
"session key: already attempted this session, skipping",
|
|
1448
|
+
{ chainId }
|
|
1449
|
+
);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1357
1452
|
try {
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1453
|
+
if (hasActiveSessionKey === true) {
|
|
1454
|
+
debugLog(
|
|
1455
|
+
"zyfai:onboard",
|
|
1456
|
+
"session key: already on record -> nothing to do",
|
|
1457
|
+
{ chainId }
|
|
1458
|
+
);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (hasActiveSessionKey === void 0) {
|
|
1462
|
+
const installed = await this.readSmartSessionsInstalled(
|
|
1463
|
+
chainId,
|
|
1464
|
+
smartWallet
|
|
1465
|
+
);
|
|
1466
|
+
debugLog(
|
|
1467
|
+
"zyfai:onboard",
|
|
1468
|
+
`session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
|
|
1469
|
+
{ smartWallet, chainId }
|
|
1470
|
+
);
|
|
1471
|
+
if (installed) return;
|
|
1472
|
+
}
|
|
1473
|
+
this.sessionKeyAttempts.add(attemptKey);
|
|
1366
1474
|
console.warn(
|
|
1367
|
-
`[zyfai]
|
|
1475
|
+
`[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
|
|
1368
1476
|
);
|
|
1477
|
+
debugLog("zyfai:onboard", "session key: calling createSessionKey", {
|
|
1478
|
+
chainId,
|
|
1479
|
+
smartWallet,
|
|
1480
|
+
hasActiveSessionKey
|
|
1481
|
+
});
|
|
1369
1482
|
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1483
|
+
debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
|
|
1484
|
+
chainId,
|
|
1485
|
+
smartWallet
|
|
1486
|
+
});
|
|
1370
1487
|
} catch (error) {
|
|
1371
1488
|
console.warn(
|
|
1372
1489
|
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
@@ -1374,6 +1491,16 @@ var ZyfaiAgent = class {
|
|
|
1374
1491
|
);
|
|
1375
1492
|
}
|
|
1376
1493
|
}
|
|
1494
|
+
/** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
|
|
1495
|
+
async readSmartSessionsInstalled(chainId, smartWallet) {
|
|
1496
|
+
return this.getPublicClient(chainId).readContract({
|
|
1497
|
+
address: smartWallet,
|
|
1498
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1499
|
+
functionName: "isModuleInstalled",
|
|
1500
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1501
|
+
args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1377
1504
|
/**
|
|
1378
1505
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1379
1506
|
* funds into it, and return its address.
|
|
@@ -1394,8 +1521,24 @@ var ZyfaiAgent = class {
|
|
|
1394
1521
|
*/
|
|
1395
1522
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1396
1523
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1524
|
+
debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
|
|
1525
|
+
eoa: ownerAddress,
|
|
1526
|
+
smartWallet: wallet.address,
|
|
1527
|
+
isDeployed: wallet.isDeployed,
|
|
1528
|
+
isOwner: wallet.isOwner
|
|
1529
|
+
});
|
|
1397
1530
|
if (wallet.isDeployed) {
|
|
1398
|
-
|
|
1531
|
+
debugLog(
|
|
1532
|
+
"zyfai:onboard",
|
|
1533
|
+
"Safe already has code -> no deploy needed, checking the session key"
|
|
1534
|
+
);
|
|
1535
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1536
|
+
await this.ensureSessionKey(
|
|
1537
|
+
ownerAddress,
|
|
1538
|
+
chainId,
|
|
1539
|
+
wallet.address,
|
|
1540
|
+
hasActiveSessionKey
|
|
1541
|
+
);
|
|
1399
1542
|
return wallet.address;
|
|
1400
1543
|
}
|
|
1401
1544
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
@@ -1411,14 +1554,27 @@ var ZyfaiAgent = class {
|
|
|
1411
1554
|
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1412
1555
|
);
|
|
1413
1556
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1557
|
+
debugLog(
|
|
1558
|
+
"zyfai:onboard",
|
|
1559
|
+
`Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
|
|
1560
|
+
);
|
|
1414
1561
|
try {
|
|
1415
1562
|
await first();
|
|
1563
|
+
debugLog(
|
|
1564
|
+
"zyfai:onboard",
|
|
1565
|
+
`${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
|
|
1566
|
+
);
|
|
1416
1567
|
} catch (error) {
|
|
1417
1568
|
console.warn(
|
|
1418
1569
|
`[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
|
|
1419
1570
|
error instanceof Error ? error.message : String(error)
|
|
1420
1571
|
);
|
|
1572
|
+
debugLog(
|
|
1573
|
+
"zyfai:onboard",
|
|
1574
|
+
`falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
|
|
1575
|
+
);
|
|
1421
1576
|
await second();
|
|
1577
|
+
debugLog("zyfai:onboard", "fallback deploy path succeeded");
|
|
1422
1578
|
}
|
|
1423
1579
|
const redeployed = await this.sdk.getSmartWalletAddress(
|
|
1424
1580
|
ownerAddress,
|
|
@@ -1883,12 +2039,9 @@ async function postSponsorTransferAuth(input) {
|
|
|
1883
2039
|
{
|
|
1884
2040
|
statusCode: res.status,
|
|
1885
2041
|
responseBody: text.slice(0, 500),
|
|
1886
|
-
//
|
|
1887
|
-
//
|
|
1888
|
-
|
|
1889
|
-
// the relayer was out of gas, also pre-broadcast. Any other 5xx stays
|
|
1890
|
-
// ambiguous: the transfer may already be in flight.
|
|
1891
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2042
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2043
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2044
|
+
safeToFallback: res.status === 503
|
|
1892
2045
|
}
|
|
1893
2046
|
);
|
|
1894
2047
|
}
|
|
@@ -2160,9 +2313,9 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2160
2313
|
};
|
|
2161
2314
|
}
|
|
2162
2315
|
|
|
2163
|
-
// src/lib/sponsored-
|
|
2316
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
2164
2317
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2165
|
-
function
|
|
2318
|
+
function makeSponsoredWethCallback(deps) {
|
|
2166
2319
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2167
2320
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2168
2321
|
return async (smartWallet, chainId, amount) => {
|
|
@@ -2171,7 +2324,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2171
2324
|
if (!token) {
|
|
2172
2325
|
throw new OwneyError(
|
|
2173
2326
|
"CHAIN_UNSUPPORTED",
|
|
2174
|
-
`No sponsored
|
|
2327
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2175
2328
|
);
|
|
2176
2329
|
}
|
|
2177
2330
|
const amountWei = BigInt(amount);
|
|
@@ -2183,14 +2336,14 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2183
2336
|
if (balance < amountWei) {
|
|
2184
2337
|
throw new OwneyError(
|
|
2185
2338
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2186
|
-
|
|
2339
|
+
"Insufficient WETH balance for this deposit.",
|
|
2187
2340
|
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2188
2341
|
);
|
|
2189
2342
|
}
|
|
2190
2343
|
} catch (err) {
|
|
2191
2344
|
if (err instanceof OwneyError) throw err;
|
|
2192
2345
|
console.warn(
|
|
2193
|
-
|
|
2346
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2194
2347
|
err instanceof Error ? err.message : String(err)
|
|
2195
2348
|
);
|
|
2196
2349
|
}
|
|
@@ -2198,7 +2351,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2198
2351
|
if (allowance < amountWei) {
|
|
2199
2352
|
throw new OwneyError(
|
|
2200
2353
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
2201
|
-
|
|
2354
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
2202
2355
|
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
2203
2356
|
);
|
|
2204
2357
|
}
|
|
@@ -2279,8 +2432,6 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
2279
2432
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
2280
2433
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
2281
2434
|
};
|
|
2282
|
-
var PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS = 10;
|
|
2283
|
-
var PERMIT2_ALLOWANCE_POLL_INTERVAL_MS = 1500;
|
|
2284
2435
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
2285
2436
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
2286
2437
|
}
|
|
@@ -2305,7 +2456,7 @@ var OwneySDK = class {
|
|
|
2305
2456
|
routingApiBaseUrl;
|
|
2306
2457
|
referralSource;
|
|
2307
2458
|
cachedSponsoredCallback = null;
|
|
2308
|
-
|
|
2459
|
+
cachedWethSponsoredCallback = null;
|
|
2309
2460
|
initializingAgentsPromise = null;
|
|
2310
2461
|
constructor(config) {
|
|
2311
2462
|
this.apiKey = config.apiKey;
|
|
@@ -2333,7 +2484,7 @@ var OwneySDK = class {
|
|
|
2333
2484
|
}
|
|
2334
2485
|
this.state = { provider, walletAddress, chainId: null };
|
|
2335
2486
|
this.cachedSponsoredCallback = null;
|
|
2336
|
-
this.
|
|
2487
|
+
this.cachedWethSponsoredCallback = null;
|
|
2337
2488
|
}
|
|
2338
2489
|
/**
|
|
2339
2490
|
* Disconnect from all agents and clear connection state.
|
|
@@ -2347,7 +2498,7 @@ var OwneySDK = class {
|
|
|
2347
2498
|
this.disabledAgents.clear();
|
|
2348
2499
|
this.state = null;
|
|
2349
2500
|
this.cachedSponsoredCallback = null;
|
|
2350
|
-
this.
|
|
2501
|
+
this.cachedWethSponsoredCallback = null;
|
|
2351
2502
|
}
|
|
2352
2503
|
isAgentDisabled(agentId) {
|
|
2353
2504
|
return this.disabledAgents.has(agentId);
|
|
@@ -2424,23 +2575,25 @@ var OwneySDK = class {
|
|
|
2424
2575
|
return callback;
|
|
2425
2576
|
}
|
|
2426
2577
|
/**
|
|
2427
|
-
* Lazily builds (and caches) the Permit2 sponsored
|
|
2428
|
-
*
|
|
2429
|
-
*
|
|
2430
|
-
*
|
|
2578
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
2579
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
2580
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
2581
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
2431
2582
|
*/
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2583
|
+
getDefaultWethSponsoredCallback(onApproved) {
|
|
2584
|
+
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
2585
|
+
return this.cachedWethSponsoredCallback;
|
|
2435
2586
|
const provider = this.requireConnectedProvider();
|
|
2436
2587
|
const owner = this.state.walletAddress;
|
|
2437
|
-
const callback =
|
|
2588
|
+
const callback = makeSponsoredWethCallback({
|
|
2438
2589
|
apiKey: this.apiKey,
|
|
2439
2590
|
baseUrl: this.routingApiBaseUrl,
|
|
2440
2591
|
ownerAddress: owner,
|
|
2441
|
-
assetLabel: asset,
|
|
2442
2592
|
onApproved,
|
|
2443
|
-
tokenAddressByChain:
|
|
2593
|
+
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
2594
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2595
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
2596
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
2444
2597
|
getPublicClient: (cid) => createPublicClient2({
|
|
2445
2598
|
chain: VIEM_CHAIN2[cid],
|
|
2446
2599
|
transport: custom(provider)
|
|
@@ -2451,34 +2604,9 @@ var OwneySDK = class {
|
|
|
2451
2604
|
transport: custom(provider)
|
|
2452
2605
|
})
|
|
2453
2606
|
});
|
|
2454
|
-
if (!onApproved) this.
|
|
2607
|
+
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
2455
2608
|
return callback;
|
|
2456
2609
|
}
|
|
2457
|
-
/**
|
|
2458
|
-
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
2459
|
-
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
2460
|
-
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
2461
|
-
*
|
|
2462
|
-
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
2463
|
-
* is never blocked by a flaky provider.
|
|
2464
|
-
*/
|
|
2465
|
-
async ownerHasCode() {
|
|
2466
|
-
try {
|
|
2467
|
-
const provider = this.requireConnectedProvider();
|
|
2468
|
-
const code = await provider.request({
|
|
2469
|
-
method: "eth_getCode",
|
|
2470
|
-
params: [this.state.walletAddress, "latest"]
|
|
2471
|
-
});
|
|
2472
|
-
return Boolean(code) && code !== "0x";
|
|
2473
|
-
} catch (error) {
|
|
2474
|
-
debugLog(
|
|
2475
|
-
"deposit",
|
|
2476
|
-
"owner code check failed; assuming EOA",
|
|
2477
|
-
error instanceof Error ? error.message : String(error)
|
|
2478
|
-
);
|
|
2479
|
-
return false;
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
2610
|
getAgent(agentId) {
|
|
2483
2611
|
const agent = this.agents.get(agentId);
|
|
2484
2612
|
if (!agent) {
|
|
@@ -2674,11 +2802,7 @@ var OwneySDK = class {
|
|
|
2674
2802
|
}
|
|
2675
2803
|
const state = this.requireState();
|
|
2676
2804
|
const chainId = this.requireChainId();
|
|
2677
|
-
const
|
|
2678
|
-
const effectiveCallback = depositCallback ?? (asset === "WETH" || ownerIsSmartAccount ? this.getDefaultPermit2Callback(
|
|
2679
|
-
asset === "WETH" ? "WETH" : "USDC",
|
|
2680
|
-
onApproved
|
|
2681
|
-
) : this.getDefaultSponsoredCallback(onApproved));
|
|
2805
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
|
|
2682
2806
|
if (agentId) {
|
|
2683
2807
|
if (this.isAgentDisabled(agentId)) {
|
|
2684
2808
|
throw new OwneyError(
|
|
@@ -2747,16 +2871,13 @@ var OwneySDK = class {
|
|
|
2747
2871
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
2748
2872
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
2749
2873
|
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
2750
|
-
* the one-time (user-paid) Permit2 approval via `approvePermit2()
|
|
2751
|
-
*
|
|
2752
|
-
*
|
|
2753
|
-
*
|
|
2754
|
-
*
|
|
2755
|
-
*
|
|
2756
|
-
*
|
|
2757
|
-
* propagates as-is — the user said no to a transaction, so we must not
|
|
2758
|
-
* turn around and ask them to pay for a different one via the fallback
|
|
2759
|
-
* below.
|
|
2874
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
2875
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
2876
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
2877
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
2878
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
2879
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
2880
|
+
* them to pay for a different one via the fallback below.
|
|
2760
2881
|
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
2761
2882
|
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
2762
2883
|
* and retry once with `undefined`, falling through to a user-paid
|
|
@@ -2768,49 +2889,6 @@ var OwneySDK = class {
|
|
|
2768
2889
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
2769
2890
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
2770
2891
|
*/
|
|
2771
|
-
/**
|
|
2772
|
-
* Polls the owner's Permit2 allowance for `asset` until it covers `amount`
|
|
2773
|
-
* or the attempt budget runs out. Called right after `approvePermit2()`
|
|
2774
|
-
* resolves: the approval tx is mined, but the wallet's own RPC can still
|
|
2775
|
-
* serve a stale (zero) allowance on the very next read (replica lag), which
|
|
2776
|
-
* would otherwise make the immediate retry fail with the same
|
|
2777
|
-
* PERMIT2_APPROVAL_REQUIRED error the user just approved past. Never
|
|
2778
|
-
* throws — if the allowance still hasn't caught up after the budget, the
|
|
2779
|
-
* caller's retry will surface the honest PERMIT2_APPROVAL_REQUIRED error
|
|
2780
|
-
* rather than this helper inventing a different one.
|
|
2781
|
-
*/
|
|
2782
|
-
async waitForPermit2Allowance(asset, amount) {
|
|
2783
|
-
const state = this.requireState();
|
|
2784
|
-
const chainId = this.requireChainId();
|
|
2785
|
-
const provider = this.requireConnectedProvider();
|
|
2786
|
-
const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
|
|
2787
|
-
if (!token) return;
|
|
2788
|
-
const publicClient = createPublicClient2({
|
|
2789
|
-
chain: VIEM_CHAIN2[chainId],
|
|
2790
|
-
transport: custom(provider)
|
|
2791
|
-
});
|
|
2792
|
-
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS; attempt++) {
|
|
2793
|
-
if (attempt > 0) {
|
|
2794
|
-
await new Promise(
|
|
2795
|
-
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_POLL_INTERVAL_MS)
|
|
2796
|
-
);
|
|
2797
|
-
}
|
|
2798
|
-
try {
|
|
2799
|
-
const allowance = await readPermit2Allowance(
|
|
2800
|
-
publicClient,
|
|
2801
|
-
token,
|
|
2802
|
-
state.walletAddress
|
|
2803
|
-
);
|
|
2804
|
-
if (allowance >= amount) return;
|
|
2805
|
-
} catch (error) {
|
|
2806
|
-
debugLog(
|
|
2807
|
-
"deposit",
|
|
2808
|
-
"Permit2 allowance poll read failed; retrying",
|
|
2809
|
-
error instanceof Error ? error.message : String(error)
|
|
2810
|
-
);
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
2892
|
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
2815
2893
|
const attempt = (callback) => withFailureReporting(
|
|
2816
2894
|
this.apiKey,
|
|
@@ -2828,16 +2906,12 @@ var OwneySDK = class {
|
|
|
2828
2906
|
try {
|
|
2829
2907
|
return await attempt(effectiveCallback);
|
|
2830
2908
|
} catch (error) {
|
|
2831
|
-
if (!approvalAttempted && appCallback === void 0 &&
|
|
2909
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2832
2910
|
approvalAttempted = true;
|
|
2833
2911
|
console.warn(
|
|
2834
|
-
|
|
2835
|
-
);
|
|
2836
|
-
await this.approvePermit2(asset);
|
|
2837
|
-
await this.waitForPermit2Allowance(
|
|
2838
|
-
asset,
|
|
2839
|
-
BigInt(amount)
|
|
2912
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
2840
2913
|
);
|
|
2914
|
+
await this.approvePermit2();
|
|
2841
2915
|
continue;
|
|
2842
2916
|
}
|
|
2843
2917
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3351,32 +3425,23 @@ var OwneySDK = class {
|
|
|
3351
3425
|
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
3352
3426
|
}
|
|
3353
3427
|
/**
|
|
3354
|
-
* One-time, user-paid approval of Permit2 on the
|
|
3355
|
-
* the active chain. Required once per wallet per chain
|
|
3356
|
-
*
|
|
3357
|
-
*
|
|
3358
|
-
*
|
|
3359
|
-
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
3360
|
-
* throws if the transaction reverted.
|
|
3428
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
3429
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
3430
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
3431
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
3432
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
3361
3433
|
* @returns the approval transaction hash.
|
|
3362
3434
|
*/
|
|
3363
3435
|
async approvePermit2(asset = "WETH") {
|
|
3436
|
+
void asset;
|
|
3364
3437
|
const state = this.requireState();
|
|
3365
3438
|
const chainId = this.requireChainId();
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
try {
|
|
3369
|
-
guard = this.getEligibleAgents(chainId, asset)[0];
|
|
3370
|
-
} catch {
|
|
3371
|
-
guard = void 0;
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
if (guard) this.validateAssetSupport(guard, chainId, asset);
|
|
3375
|
-
const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3439
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
3440
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3376
3441
|
if (!token) {
|
|
3377
3442
|
throw new OwneyError(
|
|
3378
3443
|
"CHAIN_UNSUPPORTED",
|
|
3379
|
-
`No sponsored
|
|
3444
|
+
`No sponsored WETH on chain ${chainId}`
|
|
3380
3445
|
);
|
|
3381
3446
|
}
|
|
3382
3447
|
const provider = this.requireConnectedProvider();
|