@owney/sdk 0.7.7-beta.2 → 0.7.7-beta.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/dist/index.cjs +246 -151
- package/dist/index.d.cts +36 -45
- package/dist/index.d.ts +36 -45
- package/dist/index.js +246 -151
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -217,6 +217,32 @@ function resolveChainId(chain) {
|
|
|
217
217
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
218
218
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
219
219
|
}
|
|
220
|
+
function mapPendingAllocations(raw) {
|
|
221
|
+
if (!Array.isArray(raw)) return void 0;
|
|
222
|
+
const pending = [];
|
|
223
|
+
for (const entry of raw) {
|
|
224
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
225
|
+
const e = entry;
|
|
226
|
+
if (e.isPending !== true) continue;
|
|
227
|
+
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
228
|
+
if (typeof e.tokenSymbol !== "string" || e.tokenSymbol === "") continue;
|
|
229
|
+
if (typeof e.balance !== "string") continue;
|
|
230
|
+
let amount;
|
|
231
|
+
try {
|
|
232
|
+
amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
|
|
233
|
+
} catch {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!Number.isFinite(amount) || amount <= 0) continue;
|
|
237
|
+
pending.push({
|
|
238
|
+
chainId: e.chainId,
|
|
239
|
+
asset: e.tokenSymbol,
|
|
240
|
+
amount: String(amount),
|
|
241
|
+
...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return pending.length > 0 ? pending : void 0;
|
|
245
|
+
}
|
|
220
246
|
function mapBalances(raw, _chainId, smartWallet) {
|
|
221
247
|
const portfolio = raw.portfolio;
|
|
222
248
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
@@ -268,12 +294,16 @@ function mapBalances(raw, _chainId, smartWallet) {
|
|
|
268
294
|
// structural read keeps it undefined today and auto-populates later.
|
|
269
295
|
liquidity: p.liquidity
|
|
270
296
|
}));
|
|
297
|
+
const pendingAllocations = mapPendingAllocations(
|
|
298
|
+
portfolio.staleBalances
|
|
299
|
+
);
|
|
271
300
|
return {
|
|
272
301
|
smartWallet,
|
|
273
302
|
totalBalance: String(totalBalance),
|
|
274
303
|
totalBalanceAsset: "usdc",
|
|
275
304
|
tokens,
|
|
276
|
-
positions
|
|
305
|
+
positions,
|
|
306
|
+
...pendingAllocations ? { pendingAllocations } : {}
|
|
277
307
|
};
|
|
278
308
|
}
|
|
279
309
|
function sumTokenValues(tokens) {
|
|
@@ -879,6 +909,12 @@ var ZyfaiAgent = class {
|
|
|
879
909
|
connectedAddress = null;
|
|
880
910
|
connectedChainId = null;
|
|
881
911
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
912
|
+
/**
|
|
913
|
+
* `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
|
|
914
|
+
* against re-running it on every activation/deposit when Zyfai's
|
|
915
|
+
* `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
|
|
916
|
+
*/
|
|
917
|
+
sessionKeyAttempts = /* @__PURE__ */ new Set();
|
|
882
918
|
connectAuthPromise = null;
|
|
883
919
|
rpcUrls;
|
|
884
920
|
publicClients = /* @__PURE__ */ new Map();
|
|
@@ -1336,10 +1372,74 @@ var ZyfaiAgent = class {
|
|
|
1336
1372
|
* recoverable (ensureWalletDeployed tries the other deploy call), but
|
|
1337
1373
|
* failing the deposit outright is not.
|
|
1338
1374
|
*/
|
|
1375
|
+
/**
|
|
1376
|
+
* Zyfai's own answer to "what kind of wallet is this, and can the agent act on
|
|
1377
|
+
* it", read from the positions payload (`GET /data/position`, which
|
|
1378
|
+
* `getPositions` wraps).
|
|
1379
|
+
*
|
|
1380
|
+
* `predeployed` and `hasActiveSessionKey` are both returned at runtime but
|
|
1381
|
+
* declared on neither `Portfolio` nor their docs, hence the cast. Verified
|
|
1382
|
+
* live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
|
|
1383
|
+
* false}` while a pool wallet reports `{true, true}`.
|
|
1384
|
+
*
|
|
1385
|
+
* Preferred over the SDK's private `isPredeployed`, which is cached from the
|
|
1386
|
+
* login response and can go stale mid-session. Returns an empty object on any
|
|
1387
|
+
* failure so every caller degrades to its own fallback.
|
|
1388
|
+
*/
|
|
1389
|
+
async readWalletState(ownerAddress) {
|
|
1390
|
+
try {
|
|
1391
|
+
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1392
|
+
const raw = portfolio;
|
|
1393
|
+
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1394
|
+
predeployed: raw?.predeployed,
|
|
1395
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1396
|
+
});
|
|
1397
|
+
return {
|
|
1398
|
+
predeployed: raw?.predeployed,
|
|
1399
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1400
|
+
};
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
console.warn(
|
|
1403
|
+
"[zyfai] getPositions wallet-state read failed (non-fatal):",
|
|
1404
|
+
error instanceof Error ? error.message : String(error)
|
|
1405
|
+
);
|
|
1406
|
+
return {};
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1339
1409
|
async isPredeployedPoolWallet(ownerAddress, smartWallet) {
|
|
1410
|
+
const { predeployed } = await this.readWalletState(ownerAddress);
|
|
1411
|
+
if (typeof predeployed === "boolean") {
|
|
1412
|
+
debugLog(
|
|
1413
|
+
"zyfai:onboard",
|
|
1414
|
+
`wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1415
|
+
{ source: "getPositions.predeployed", predeployed }
|
|
1416
|
+
);
|
|
1417
|
+
return predeployed;
|
|
1418
|
+
}
|
|
1419
|
+
const internals = this.sdk;
|
|
1420
|
+
if (typeof internals.isPredeployed === "boolean") {
|
|
1421
|
+
debugLog(
|
|
1422
|
+
"zyfai:onboard",
|
|
1423
|
+
`wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1424
|
+
{
|
|
1425
|
+
source: "login response (cached)",
|
|
1426
|
+
predeployed: internals.isPredeployed
|
|
1427
|
+
}
|
|
1428
|
+
);
|
|
1429
|
+
return internals.isPredeployed;
|
|
1430
|
+
}
|
|
1340
1431
|
try {
|
|
1341
1432
|
const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
|
|
1342
|
-
|
|
1433
|
+
const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
|
|
1434
|
+
debugLog(
|
|
1435
|
+
"zyfai:onboard",
|
|
1436
|
+
`wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1437
|
+
{
|
|
1438
|
+
source: "getSmartWalletByEOA fallback",
|
|
1439
|
+
poolRecord: byEoa?.smartWallet
|
|
1440
|
+
}
|
|
1441
|
+
);
|
|
1442
|
+
return isPool;
|
|
1343
1443
|
} catch (error) {
|
|
1344
1444
|
console.warn(
|
|
1345
1445
|
"[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
|
|
@@ -1377,8 +1477,24 @@ var ZyfaiAgent = class {
|
|
|
1377
1477
|
ownerAddress,
|
|
1378
1478
|
chainId
|
|
1379
1479
|
);
|
|
1380
|
-
|
|
1381
|
-
|
|
1480
|
+
debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
|
|
1481
|
+
smartWallet: wallet.address,
|
|
1482
|
+
isDeployed: wallet.isDeployed
|
|
1483
|
+
});
|
|
1484
|
+
if (!wallet.isDeployed) {
|
|
1485
|
+
debugLog(
|
|
1486
|
+
"zyfai:onboard",
|
|
1487
|
+
"Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
|
|
1488
|
+
);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1492
|
+
await this.ensureSessionKey(
|
|
1493
|
+
ownerAddress,
|
|
1494
|
+
chainId,
|
|
1495
|
+
wallet.address,
|
|
1496
|
+
hasActiveSessionKey
|
|
1497
|
+
);
|
|
1382
1498
|
} catch (error) {
|
|
1383
1499
|
console.warn(
|
|
1384
1500
|
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
@@ -1386,20 +1502,51 @@ var ZyfaiAgent = class {
|
|
|
1386
1502
|
);
|
|
1387
1503
|
}
|
|
1388
1504
|
}
|
|
1389
|
-
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1505
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
|
|
1506
|
+
const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
|
|
1507
|
+
if (this.sessionKeyAttempts.has(attemptKey)) {
|
|
1508
|
+
debugLog(
|
|
1509
|
+
"zyfai:onboard",
|
|
1510
|
+
"session key: already attempted this session, skipping",
|
|
1511
|
+
{ chainId }
|
|
1512
|
+
);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1390
1515
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1516
|
+
if (hasActiveSessionKey === true) {
|
|
1517
|
+
debugLog(
|
|
1518
|
+
"zyfai:onboard",
|
|
1519
|
+
"session key: already on record -> nothing to do",
|
|
1520
|
+
{ chainId }
|
|
1521
|
+
);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
if (hasActiveSessionKey === void 0) {
|
|
1525
|
+
const installed = await this.readSmartSessionsInstalled(
|
|
1526
|
+
chainId,
|
|
1527
|
+
smartWallet
|
|
1528
|
+
);
|
|
1529
|
+
debugLog(
|
|
1530
|
+
"zyfai:onboard",
|
|
1531
|
+
`session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
|
|
1532
|
+
{ smartWallet, chainId }
|
|
1533
|
+
);
|
|
1534
|
+
if (installed) return;
|
|
1535
|
+
}
|
|
1536
|
+
this.sessionKeyAttempts.add(attemptKey);
|
|
1399
1537
|
console.warn(
|
|
1400
|
-
`[zyfai]
|
|
1538
|
+
`[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
|
|
1401
1539
|
);
|
|
1540
|
+
debugLog("zyfai:onboard", "session key: calling createSessionKey", {
|
|
1541
|
+
chainId,
|
|
1542
|
+
smartWallet,
|
|
1543
|
+
hasActiveSessionKey
|
|
1544
|
+
});
|
|
1402
1545
|
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1546
|
+
debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
|
|
1547
|
+
chainId,
|
|
1548
|
+
smartWallet
|
|
1549
|
+
});
|
|
1403
1550
|
} catch (error) {
|
|
1404
1551
|
console.warn(
|
|
1405
1552
|
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
@@ -1407,6 +1554,16 @@ var ZyfaiAgent = class {
|
|
|
1407
1554
|
);
|
|
1408
1555
|
}
|
|
1409
1556
|
}
|
|
1557
|
+
/** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
|
|
1558
|
+
async readSmartSessionsInstalled(chainId, smartWallet) {
|
|
1559
|
+
return this.getPublicClient(chainId).readContract({
|
|
1560
|
+
address: smartWallet,
|
|
1561
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1562
|
+
functionName: "isModuleInstalled",
|
|
1563
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1564
|
+
args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1410
1567
|
/**
|
|
1411
1568
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1412
1569
|
* funds into it, and return its address.
|
|
@@ -1427,8 +1584,24 @@ var ZyfaiAgent = class {
|
|
|
1427
1584
|
*/
|
|
1428
1585
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1429
1586
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1587
|
+
debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
|
|
1588
|
+
eoa: ownerAddress,
|
|
1589
|
+
smartWallet: wallet.address,
|
|
1590
|
+
isDeployed: wallet.isDeployed,
|
|
1591
|
+
isOwner: wallet.isOwner
|
|
1592
|
+
});
|
|
1430
1593
|
if (wallet.isDeployed) {
|
|
1431
|
-
|
|
1594
|
+
debugLog(
|
|
1595
|
+
"zyfai:onboard",
|
|
1596
|
+
"Safe already has code -> no deploy needed, checking the session key"
|
|
1597
|
+
);
|
|
1598
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1599
|
+
await this.ensureSessionKey(
|
|
1600
|
+
ownerAddress,
|
|
1601
|
+
chainId,
|
|
1602
|
+
wallet.address,
|
|
1603
|
+
hasActiveSessionKey
|
|
1604
|
+
);
|
|
1432
1605
|
return wallet.address;
|
|
1433
1606
|
}
|
|
1434
1607
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
@@ -1444,14 +1617,27 @@ var ZyfaiAgent = class {
|
|
|
1444
1617
|
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1445
1618
|
);
|
|
1446
1619
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1620
|
+
debugLog(
|
|
1621
|
+
"zyfai:onboard",
|
|
1622
|
+
`Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
|
|
1623
|
+
);
|
|
1447
1624
|
try {
|
|
1448
1625
|
await first();
|
|
1626
|
+
debugLog(
|
|
1627
|
+
"zyfai:onboard",
|
|
1628
|
+
`${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
|
|
1629
|
+
);
|
|
1449
1630
|
} catch (error) {
|
|
1450
1631
|
console.warn(
|
|
1451
1632
|
`[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
|
|
1452
1633
|
error instanceof Error ? error.message : String(error)
|
|
1453
1634
|
);
|
|
1635
|
+
debugLog(
|
|
1636
|
+
"zyfai:onboard",
|
|
1637
|
+
`falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
|
|
1638
|
+
);
|
|
1454
1639
|
await second();
|
|
1640
|
+
debugLog("zyfai:onboard", "fallback deploy path succeeded");
|
|
1455
1641
|
}
|
|
1456
1642
|
const redeployed = await this.sdk.getSmartWalletAddress(
|
|
1457
1643
|
ownerAddress,
|
|
@@ -1912,12 +2098,9 @@ async function postSponsorTransferAuth(input) {
|
|
|
1912
2098
|
{
|
|
1913
2099
|
statusCode: res.status,
|
|
1914
2100
|
responseBody: text.slice(0, 500),
|
|
1915
|
-
//
|
|
1916
|
-
//
|
|
1917
|
-
|
|
1918
|
-
// the relayer was out of gas, also pre-broadcast. Any other 5xx stays
|
|
1919
|
-
// ambiguous: the transfer may already be in flight.
|
|
1920
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2101
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2102
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2103
|
+
safeToFallback: res.status === 503
|
|
1921
2104
|
}
|
|
1922
2105
|
);
|
|
1923
2106
|
}
|
|
@@ -2189,9 +2372,9 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2189
2372
|
};
|
|
2190
2373
|
}
|
|
2191
2374
|
|
|
2192
|
-
// src/lib/sponsored-
|
|
2375
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
2193
2376
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2194
|
-
function
|
|
2377
|
+
function makeSponsoredWethCallback(deps) {
|
|
2195
2378
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2196
2379
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2197
2380
|
return async (smartWallet, chainId, amount) => {
|
|
@@ -2200,7 +2383,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2200
2383
|
if (!token) {
|
|
2201
2384
|
throw new OwneyError(
|
|
2202
2385
|
"CHAIN_UNSUPPORTED",
|
|
2203
|
-
`No sponsored
|
|
2386
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2204
2387
|
);
|
|
2205
2388
|
}
|
|
2206
2389
|
const amountWei = BigInt(amount);
|
|
@@ -2212,14 +2395,14 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2212
2395
|
if (balance < amountWei) {
|
|
2213
2396
|
throw new OwneyError(
|
|
2214
2397
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2215
|
-
|
|
2398
|
+
"Insufficient WETH balance for this deposit.",
|
|
2216
2399
|
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2217
2400
|
);
|
|
2218
2401
|
}
|
|
2219
2402
|
} catch (err) {
|
|
2220
2403
|
if (err instanceof OwneyError) throw err;
|
|
2221
2404
|
console.warn(
|
|
2222
|
-
|
|
2405
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2223
2406
|
err instanceof Error ? err.message : String(err)
|
|
2224
2407
|
);
|
|
2225
2408
|
}
|
|
@@ -2227,7 +2410,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2227
2410
|
if (allowance < amountWei) {
|
|
2228
2411
|
throw new OwneyError(
|
|
2229
2412
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
2230
|
-
|
|
2413
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
2231
2414
|
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
2232
2415
|
);
|
|
2233
2416
|
}
|
|
@@ -2308,8 +2491,6 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
2308
2491
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
2309
2492
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
2310
2493
|
};
|
|
2311
|
-
var PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS = 10;
|
|
2312
|
-
var PERMIT2_ALLOWANCE_POLL_INTERVAL_MS = 1500;
|
|
2313
2494
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
2314
2495
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
2315
2496
|
}
|
|
@@ -2334,7 +2515,7 @@ var OwneySDK = class {
|
|
|
2334
2515
|
routingApiBaseUrl;
|
|
2335
2516
|
referralSource;
|
|
2336
2517
|
cachedSponsoredCallback = null;
|
|
2337
|
-
|
|
2518
|
+
cachedWethSponsoredCallback = null;
|
|
2338
2519
|
initializingAgentsPromise = null;
|
|
2339
2520
|
constructor(config) {
|
|
2340
2521
|
this.apiKey = config.apiKey;
|
|
@@ -2362,7 +2543,7 @@ var OwneySDK = class {
|
|
|
2362
2543
|
}
|
|
2363
2544
|
this.state = { provider, walletAddress, chainId: null };
|
|
2364
2545
|
this.cachedSponsoredCallback = null;
|
|
2365
|
-
this.
|
|
2546
|
+
this.cachedWethSponsoredCallback = null;
|
|
2366
2547
|
}
|
|
2367
2548
|
/**
|
|
2368
2549
|
* Disconnect from all agents and clear connection state.
|
|
@@ -2376,7 +2557,7 @@ var OwneySDK = class {
|
|
|
2376
2557
|
this.disabledAgents.clear();
|
|
2377
2558
|
this.state = null;
|
|
2378
2559
|
this.cachedSponsoredCallback = null;
|
|
2379
|
-
this.
|
|
2560
|
+
this.cachedWethSponsoredCallback = null;
|
|
2380
2561
|
}
|
|
2381
2562
|
isAgentDisabled(agentId) {
|
|
2382
2563
|
return this.disabledAgents.has(agentId);
|
|
@@ -2453,23 +2634,25 @@ var OwneySDK = class {
|
|
|
2453
2634
|
return callback;
|
|
2454
2635
|
}
|
|
2455
2636
|
/**
|
|
2456
|
-
* Lazily builds (and caches) the Permit2 sponsored
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2637
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
2638
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
2639
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
2640
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
2460
2641
|
*/
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2642
|
+
getDefaultWethSponsoredCallback(onApproved) {
|
|
2643
|
+
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
2644
|
+
return this.cachedWethSponsoredCallback;
|
|
2464
2645
|
const provider = this.requireConnectedProvider();
|
|
2465
2646
|
const owner = this.state.walletAddress;
|
|
2466
|
-
const callback =
|
|
2647
|
+
const callback = makeSponsoredWethCallback({
|
|
2467
2648
|
apiKey: this.apiKey,
|
|
2468
2649
|
baseUrl: this.routingApiBaseUrl,
|
|
2469
2650
|
ownerAddress: owner,
|
|
2470
|
-
assetLabel: asset,
|
|
2471
2651
|
onApproved,
|
|
2472
|
-
tokenAddressByChain:
|
|
2652
|
+
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
2653
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2654
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
2655
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
2473
2656
|
getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
|
|
2474
2657
|
chain: VIEM_CHAIN2[cid],
|
|
2475
2658
|
transport: (0, import_viem5.custom)(provider)
|
|
@@ -2480,34 +2663,9 @@ var OwneySDK = class {
|
|
|
2480
2663
|
transport: (0, import_viem5.custom)(provider)
|
|
2481
2664
|
})
|
|
2482
2665
|
});
|
|
2483
|
-
if (!onApproved) this.
|
|
2666
|
+
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
2484
2667
|
return callback;
|
|
2485
2668
|
}
|
|
2486
|
-
/**
|
|
2487
|
-
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
2488
|
-
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
2489
|
-
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
2490
|
-
*
|
|
2491
|
-
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
2492
|
-
* is never blocked by a flaky provider.
|
|
2493
|
-
*/
|
|
2494
|
-
async ownerHasCode() {
|
|
2495
|
-
try {
|
|
2496
|
-
const provider = this.requireConnectedProvider();
|
|
2497
|
-
const code = await provider.request({
|
|
2498
|
-
method: "eth_getCode",
|
|
2499
|
-
params: [this.state.walletAddress, "latest"]
|
|
2500
|
-
});
|
|
2501
|
-
return Boolean(code) && code !== "0x";
|
|
2502
|
-
} catch (error) {
|
|
2503
|
-
debugLog(
|
|
2504
|
-
"deposit",
|
|
2505
|
-
"owner code check failed; assuming EOA",
|
|
2506
|
-
error instanceof Error ? error.message : String(error)
|
|
2507
|
-
);
|
|
2508
|
-
return false;
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
2669
|
getAgent(agentId) {
|
|
2512
2670
|
const agent = this.agents.get(agentId);
|
|
2513
2671
|
if (!agent) {
|
|
@@ -2703,11 +2861,7 @@ var OwneySDK = class {
|
|
|
2703
2861
|
}
|
|
2704
2862
|
const state = this.requireState();
|
|
2705
2863
|
const chainId = this.requireChainId();
|
|
2706
|
-
const
|
|
2707
|
-
const effectiveCallback = depositCallback ?? (asset === "WETH" || ownerIsSmartAccount ? this.getDefaultPermit2Callback(
|
|
2708
|
-
asset === "WETH" ? "WETH" : "USDC",
|
|
2709
|
-
onApproved
|
|
2710
|
-
) : this.getDefaultSponsoredCallback(onApproved));
|
|
2864
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
|
|
2711
2865
|
if (agentId) {
|
|
2712
2866
|
if (this.isAgentDisabled(agentId)) {
|
|
2713
2867
|
throw new OwneyError(
|
|
@@ -2776,16 +2930,13 @@ var OwneySDK = class {
|
|
|
2776
2930
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
2777
2931
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
2778
2932
|
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
2779
|
-
* the one-time (user-paid) Permit2 approval via `approvePermit2()
|
|
2780
|
-
*
|
|
2781
|
-
*
|
|
2782
|
-
*
|
|
2783
|
-
*
|
|
2784
|
-
*
|
|
2785
|
-
*
|
|
2786
|
-
* propagates as-is — the user said no to a transaction, so we must not
|
|
2787
|
-
* turn around and ask them to pay for a different one via the fallback
|
|
2788
|
-
* below.
|
|
2933
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
2934
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
2935
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
2936
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
2937
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
2938
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
2939
|
+
* them to pay for a different one via the fallback below.
|
|
2789
2940
|
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
2790
2941
|
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
2791
2942
|
* and retry once with `undefined`, falling through to a user-paid
|
|
@@ -2797,49 +2948,6 @@ var OwneySDK = class {
|
|
|
2797
2948
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
2798
2949
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
2799
2950
|
*/
|
|
2800
|
-
/**
|
|
2801
|
-
* Polls the owner's Permit2 allowance for `asset` until it covers `amount`
|
|
2802
|
-
* or the attempt budget runs out. Called right after `approvePermit2()`
|
|
2803
|
-
* resolves: the approval tx is mined, but the wallet's own RPC can still
|
|
2804
|
-
* serve a stale (zero) allowance on the very next read (replica lag), which
|
|
2805
|
-
* would otherwise make the immediate retry fail with the same
|
|
2806
|
-
* PERMIT2_APPROVAL_REQUIRED error the user just approved past. Never
|
|
2807
|
-
* throws — if the allowance still hasn't caught up after the budget, the
|
|
2808
|
-
* caller's retry will surface the honest PERMIT2_APPROVAL_REQUIRED error
|
|
2809
|
-
* rather than this helper inventing a different one.
|
|
2810
|
-
*/
|
|
2811
|
-
async waitForPermit2Allowance(asset, amount) {
|
|
2812
|
-
const state = this.requireState();
|
|
2813
|
-
const chainId = this.requireChainId();
|
|
2814
|
-
const provider = this.requireConnectedProvider();
|
|
2815
|
-
const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
|
|
2816
|
-
if (!token) return;
|
|
2817
|
-
const publicClient = (0, import_viem5.createPublicClient)({
|
|
2818
|
-
chain: VIEM_CHAIN2[chainId],
|
|
2819
|
-
transport: (0, import_viem5.custom)(provider)
|
|
2820
|
-
});
|
|
2821
|
-
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS; attempt++) {
|
|
2822
|
-
if (attempt > 0) {
|
|
2823
|
-
await new Promise(
|
|
2824
|
-
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_POLL_INTERVAL_MS)
|
|
2825
|
-
);
|
|
2826
|
-
}
|
|
2827
|
-
try {
|
|
2828
|
-
const allowance = await readPermit2Allowance(
|
|
2829
|
-
publicClient,
|
|
2830
|
-
token,
|
|
2831
|
-
state.walletAddress
|
|
2832
|
-
);
|
|
2833
|
-
if (allowance >= amount) return;
|
|
2834
|
-
} catch (error) {
|
|
2835
|
-
debugLog(
|
|
2836
|
-
"deposit",
|
|
2837
|
-
"Permit2 allowance poll read failed; retrying",
|
|
2838
|
-
error instanceof Error ? error.message : String(error)
|
|
2839
|
-
);
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
2951
|
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
2844
2952
|
const attempt = (callback) => withFailureReporting(
|
|
2845
2953
|
this.apiKey,
|
|
@@ -2857,16 +2965,12 @@ var OwneySDK = class {
|
|
|
2857
2965
|
try {
|
|
2858
2966
|
return await attempt(effectiveCallback);
|
|
2859
2967
|
} catch (error) {
|
|
2860
|
-
if (!approvalAttempted && appCallback === void 0 &&
|
|
2968
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2861
2969
|
approvalAttempted = true;
|
|
2862
2970
|
console.warn(
|
|
2863
|
-
|
|
2864
|
-
);
|
|
2865
|
-
await this.approvePermit2(asset);
|
|
2866
|
-
await this.waitForPermit2Allowance(
|
|
2867
|
-
asset,
|
|
2868
|
-
BigInt(amount)
|
|
2971
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
2869
2972
|
);
|
|
2973
|
+
await this.approvePermit2();
|
|
2870
2974
|
continue;
|
|
2871
2975
|
}
|
|
2872
2976
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3380,32 +3484,23 @@ var OwneySDK = class {
|
|
|
3380
3484
|
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
3381
3485
|
}
|
|
3382
3486
|
/**
|
|
3383
|
-
* One-time, user-paid approval of Permit2 on the
|
|
3384
|
-
* the active chain. Required once per wallet per chain
|
|
3385
|
-
*
|
|
3386
|
-
*
|
|
3387
|
-
*
|
|
3388
|
-
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
3389
|
-
* throws if the transaction reverted.
|
|
3487
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
3488
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
3489
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
3490
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
3491
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
3390
3492
|
* @returns the approval transaction hash.
|
|
3391
3493
|
*/
|
|
3392
3494
|
async approvePermit2(asset = "WETH") {
|
|
3495
|
+
void asset;
|
|
3393
3496
|
const state = this.requireState();
|
|
3394
3497
|
const chainId = this.requireChainId();
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
try {
|
|
3398
|
-
guard = this.getEligibleAgents(chainId, asset)[0];
|
|
3399
|
-
} catch {
|
|
3400
|
-
guard = void 0;
|
|
3401
|
-
}
|
|
3402
|
-
}
|
|
3403
|
-
if (guard) this.validateAssetSupport(guard, chainId, asset);
|
|
3404
|
-
const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3498
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
3499
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3405
3500
|
if (!token) {
|
|
3406
3501
|
throw new OwneyError(
|
|
3407
3502
|
"CHAIN_UNSUPPORTED",
|
|
3408
|
-
`No sponsored
|
|
3503
|
+
`No sponsored WETH on chain ${chainId}`
|
|
3409
3504
|
);
|
|
3410
3505
|
}
|
|
3411
3506
|
const provider = this.requireConnectedProvider();
|