@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.js
CHANGED
|
@@ -184,6 +184,32 @@ function resolveChainId(chain) {
|
|
|
184
184
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
185
185
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
186
186
|
}
|
|
187
|
+
function mapPendingAllocations(raw) {
|
|
188
|
+
if (!Array.isArray(raw)) return void 0;
|
|
189
|
+
const pending = [];
|
|
190
|
+
for (const entry of raw) {
|
|
191
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
192
|
+
const e = entry;
|
|
193
|
+
if (e.isPending !== true) continue;
|
|
194
|
+
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
195
|
+
if (typeof e.tokenSymbol !== "string" || e.tokenSymbol === "") continue;
|
|
196
|
+
if (typeof e.balance !== "string") continue;
|
|
197
|
+
let amount;
|
|
198
|
+
try {
|
|
199
|
+
amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
|
|
200
|
+
} catch {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!Number.isFinite(amount) || amount <= 0) continue;
|
|
204
|
+
pending.push({
|
|
205
|
+
chainId: e.chainId,
|
|
206
|
+
asset: e.tokenSymbol,
|
|
207
|
+
amount: String(amount),
|
|
208
|
+
...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return pending.length > 0 ? pending : void 0;
|
|
212
|
+
}
|
|
187
213
|
function mapBalances(raw, _chainId, smartWallet) {
|
|
188
214
|
const portfolio = raw.portfolio;
|
|
189
215
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
@@ -235,12 +261,16 @@ function mapBalances(raw, _chainId, smartWallet) {
|
|
|
235
261
|
// structural read keeps it undefined today and auto-populates later.
|
|
236
262
|
liquidity: p.liquidity
|
|
237
263
|
}));
|
|
264
|
+
const pendingAllocations = mapPendingAllocations(
|
|
265
|
+
portfolio.staleBalances
|
|
266
|
+
);
|
|
238
267
|
return {
|
|
239
268
|
smartWallet,
|
|
240
269
|
totalBalance: String(totalBalance),
|
|
241
270
|
totalBalanceAsset: "usdc",
|
|
242
271
|
tokens,
|
|
243
|
-
positions
|
|
272
|
+
positions,
|
|
273
|
+
...pendingAllocations ? { pendingAllocations } : {}
|
|
244
274
|
};
|
|
245
275
|
}
|
|
246
276
|
function sumTokenValues(tokens) {
|
|
@@ -846,6 +876,12 @@ var ZyfaiAgent = class {
|
|
|
846
876
|
connectedAddress = null;
|
|
847
877
|
connectedChainId = null;
|
|
848
878
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
879
|
+
/**
|
|
880
|
+
* `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
|
|
881
|
+
* against re-running it on every activation/deposit when Zyfai's
|
|
882
|
+
* `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
|
|
883
|
+
*/
|
|
884
|
+
sessionKeyAttempts = /* @__PURE__ */ new Set();
|
|
849
885
|
connectAuthPromise = null;
|
|
850
886
|
rpcUrls;
|
|
851
887
|
publicClients = /* @__PURE__ */ new Map();
|
|
@@ -1303,10 +1339,74 @@ var ZyfaiAgent = class {
|
|
|
1303
1339
|
* recoverable (ensureWalletDeployed tries the other deploy call), but
|
|
1304
1340
|
* failing the deposit outright is not.
|
|
1305
1341
|
*/
|
|
1342
|
+
/**
|
|
1343
|
+
* Zyfai's own answer to "what kind of wallet is this, and can the agent act on
|
|
1344
|
+
* it", read from the positions payload (`GET /data/position`, which
|
|
1345
|
+
* `getPositions` wraps).
|
|
1346
|
+
*
|
|
1347
|
+
* `predeployed` and `hasActiveSessionKey` are both returned at runtime but
|
|
1348
|
+
* declared on neither `Portfolio` nor their docs, hence the cast. Verified
|
|
1349
|
+
* live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
|
|
1350
|
+
* false}` while a pool wallet reports `{true, true}`.
|
|
1351
|
+
*
|
|
1352
|
+
* Preferred over the SDK's private `isPredeployed`, which is cached from the
|
|
1353
|
+
* login response and can go stale mid-session. Returns an empty object on any
|
|
1354
|
+
* failure so every caller degrades to its own fallback.
|
|
1355
|
+
*/
|
|
1356
|
+
async readWalletState(ownerAddress) {
|
|
1357
|
+
try {
|
|
1358
|
+
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1359
|
+
const raw = portfolio;
|
|
1360
|
+
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1361
|
+
predeployed: raw?.predeployed,
|
|
1362
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1363
|
+
});
|
|
1364
|
+
return {
|
|
1365
|
+
predeployed: raw?.predeployed,
|
|
1366
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1367
|
+
};
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
console.warn(
|
|
1370
|
+
"[zyfai] getPositions wallet-state read failed (non-fatal):",
|
|
1371
|
+
error instanceof Error ? error.message : String(error)
|
|
1372
|
+
);
|
|
1373
|
+
return {};
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1306
1376
|
async isPredeployedPoolWallet(ownerAddress, smartWallet) {
|
|
1377
|
+
const { predeployed } = await this.readWalletState(ownerAddress);
|
|
1378
|
+
if (typeof predeployed === "boolean") {
|
|
1379
|
+
debugLog(
|
|
1380
|
+
"zyfai:onboard",
|
|
1381
|
+
`wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1382
|
+
{ source: "getPositions.predeployed", predeployed }
|
|
1383
|
+
);
|
|
1384
|
+
return predeployed;
|
|
1385
|
+
}
|
|
1386
|
+
const internals = this.sdk;
|
|
1387
|
+
if (typeof internals.isPredeployed === "boolean") {
|
|
1388
|
+
debugLog(
|
|
1389
|
+
"zyfai:onboard",
|
|
1390
|
+
`wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1391
|
+
{
|
|
1392
|
+
source: "login response (cached)",
|
|
1393
|
+
predeployed: internals.isPredeployed
|
|
1394
|
+
}
|
|
1395
|
+
);
|
|
1396
|
+
return internals.isPredeployed;
|
|
1397
|
+
}
|
|
1307
1398
|
try {
|
|
1308
1399
|
const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
|
|
1309
|
-
|
|
1400
|
+
const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
|
|
1401
|
+
debugLog(
|
|
1402
|
+
"zyfai:onboard",
|
|
1403
|
+
`wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1404
|
+
{
|
|
1405
|
+
source: "getSmartWalletByEOA fallback",
|
|
1406
|
+
poolRecord: byEoa?.smartWallet
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1409
|
+
return isPool;
|
|
1310
1410
|
} catch (error) {
|
|
1311
1411
|
console.warn(
|
|
1312
1412
|
"[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
|
|
@@ -1344,8 +1444,24 @@ var ZyfaiAgent = class {
|
|
|
1344
1444
|
ownerAddress,
|
|
1345
1445
|
chainId
|
|
1346
1446
|
);
|
|
1347
|
-
|
|
1348
|
-
|
|
1447
|
+
debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
|
|
1448
|
+
smartWallet: wallet.address,
|
|
1449
|
+
isDeployed: wallet.isDeployed
|
|
1450
|
+
});
|
|
1451
|
+
if (!wallet.isDeployed) {
|
|
1452
|
+
debugLog(
|
|
1453
|
+
"zyfai:onboard",
|
|
1454
|
+
"Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
|
|
1455
|
+
);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1459
|
+
await this.ensureSessionKey(
|
|
1460
|
+
ownerAddress,
|
|
1461
|
+
chainId,
|
|
1462
|
+
wallet.address,
|
|
1463
|
+
hasActiveSessionKey
|
|
1464
|
+
);
|
|
1349
1465
|
} catch (error) {
|
|
1350
1466
|
console.warn(
|
|
1351
1467
|
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
@@ -1353,20 +1469,51 @@ var ZyfaiAgent = class {
|
|
|
1353
1469
|
);
|
|
1354
1470
|
}
|
|
1355
1471
|
}
|
|
1356
|
-
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1472
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
|
|
1473
|
+
const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
|
|
1474
|
+
if (this.sessionKeyAttempts.has(attemptKey)) {
|
|
1475
|
+
debugLog(
|
|
1476
|
+
"zyfai:onboard",
|
|
1477
|
+
"session key: already attempted this session, skipping",
|
|
1478
|
+
{ chainId }
|
|
1479
|
+
);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1357
1482
|
try {
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1483
|
+
if (hasActiveSessionKey === true) {
|
|
1484
|
+
debugLog(
|
|
1485
|
+
"zyfai:onboard",
|
|
1486
|
+
"session key: already on record -> nothing to do",
|
|
1487
|
+
{ chainId }
|
|
1488
|
+
);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (hasActiveSessionKey === void 0) {
|
|
1492
|
+
const installed = await this.readSmartSessionsInstalled(
|
|
1493
|
+
chainId,
|
|
1494
|
+
smartWallet
|
|
1495
|
+
);
|
|
1496
|
+
debugLog(
|
|
1497
|
+
"zyfai:onboard",
|
|
1498
|
+
`session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
|
|
1499
|
+
{ smartWallet, chainId }
|
|
1500
|
+
);
|
|
1501
|
+
if (installed) return;
|
|
1502
|
+
}
|
|
1503
|
+
this.sessionKeyAttempts.add(attemptKey);
|
|
1366
1504
|
console.warn(
|
|
1367
|
-
`[zyfai]
|
|
1505
|
+
`[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
|
|
1368
1506
|
);
|
|
1507
|
+
debugLog("zyfai:onboard", "session key: calling createSessionKey", {
|
|
1508
|
+
chainId,
|
|
1509
|
+
smartWallet,
|
|
1510
|
+
hasActiveSessionKey
|
|
1511
|
+
});
|
|
1369
1512
|
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1513
|
+
debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
|
|
1514
|
+
chainId,
|
|
1515
|
+
smartWallet
|
|
1516
|
+
});
|
|
1370
1517
|
} catch (error) {
|
|
1371
1518
|
console.warn(
|
|
1372
1519
|
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
@@ -1374,6 +1521,16 @@ var ZyfaiAgent = class {
|
|
|
1374
1521
|
);
|
|
1375
1522
|
}
|
|
1376
1523
|
}
|
|
1524
|
+
/** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
|
|
1525
|
+
async readSmartSessionsInstalled(chainId, smartWallet) {
|
|
1526
|
+
return this.getPublicClient(chainId).readContract({
|
|
1527
|
+
address: smartWallet,
|
|
1528
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1529
|
+
functionName: "isModuleInstalled",
|
|
1530
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1531
|
+
args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1377
1534
|
/**
|
|
1378
1535
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1379
1536
|
* funds into it, and return its address.
|
|
@@ -1394,8 +1551,24 @@ var ZyfaiAgent = class {
|
|
|
1394
1551
|
*/
|
|
1395
1552
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1396
1553
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1554
|
+
debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
|
|
1555
|
+
eoa: ownerAddress,
|
|
1556
|
+
smartWallet: wallet.address,
|
|
1557
|
+
isDeployed: wallet.isDeployed,
|
|
1558
|
+
isOwner: wallet.isOwner
|
|
1559
|
+
});
|
|
1397
1560
|
if (wallet.isDeployed) {
|
|
1398
|
-
|
|
1561
|
+
debugLog(
|
|
1562
|
+
"zyfai:onboard",
|
|
1563
|
+
"Safe already has code -> no deploy needed, checking the session key"
|
|
1564
|
+
);
|
|
1565
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1566
|
+
await this.ensureSessionKey(
|
|
1567
|
+
ownerAddress,
|
|
1568
|
+
chainId,
|
|
1569
|
+
wallet.address,
|
|
1570
|
+
hasActiveSessionKey
|
|
1571
|
+
);
|
|
1399
1572
|
return wallet.address;
|
|
1400
1573
|
}
|
|
1401
1574
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
@@ -1411,14 +1584,27 @@ var ZyfaiAgent = class {
|
|
|
1411
1584
|
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1412
1585
|
);
|
|
1413
1586
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1587
|
+
debugLog(
|
|
1588
|
+
"zyfai:onboard",
|
|
1589
|
+
`Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
|
|
1590
|
+
);
|
|
1414
1591
|
try {
|
|
1415
1592
|
await first();
|
|
1593
|
+
debugLog(
|
|
1594
|
+
"zyfai:onboard",
|
|
1595
|
+
`${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
|
|
1596
|
+
);
|
|
1416
1597
|
} catch (error) {
|
|
1417
1598
|
console.warn(
|
|
1418
1599
|
`[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
|
|
1419
1600
|
error instanceof Error ? error.message : String(error)
|
|
1420
1601
|
);
|
|
1602
|
+
debugLog(
|
|
1603
|
+
"zyfai:onboard",
|
|
1604
|
+
`falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
|
|
1605
|
+
);
|
|
1421
1606
|
await second();
|
|
1607
|
+
debugLog("zyfai:onboard", "fallback deploy path succeeded");
|
|
1422
1608
|
}
|
|
1423
1609
|
const redeployed = await this.sdk.getSmartWalletAddress(
|
|
1424
1610
|
ownerAddress,
|
|
@@ -1883,12 +2069,9 @@ async function postSponsorTransferAuth(input) {
|
|
|
1883
2069
|
{
|
|
1884
2070
|
statusCode: res.status,
|
|
1885
2071
|
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
|
|
2072
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2073
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2074
|
+
safeToFallback: res.status === 503
|
|
1892
2075
|
}
|
|
1893
2076
|
);
|
|
1894
2077
|
}
|
|
@@ -2160,9 +2343,9 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2160
2343
|
};
|
|
2161
2344
|
}
|
|
2162
2345
|
|
|
2163
|
-
// src/lib/sponsored-
|
|
2346
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
2164
2347
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2165
|
-
function
|
|
2348
|
+
function makeSponsoredWethCallback(deps) {
|
|
2166
2349
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2167
2350
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2168
2351
|
return async (smartWallet, chainId, amount) => {
|
|
@@ -2171,7 +2354,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2171
2354
|
if (!token) {
|
|
2172
2355
|
throw new OwneyError(
|
|
2173
2356
|
"CHAIN_UNSUPPORTED",
|
|
2174
|
-
`No sponsored
|
|
2357
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2175
2358
|
);
|
|
2176
2359
|
}
|
|
2177
2360
|
const amountWei = BigInt(amount);
|
|
@@ -2183,14 +2366,14 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2183
2366
|
if (balance < amountWei) {
|
|
2184
2367
|
throw new OwneyError(
|
|
2185
2368
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2186
|
-
|
|
2369
|
+
"Insufficient WETH balance for this deposit.",
|
|
2187
2370
|
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2188
2371
|
);
|
|
2189
2372
|
}
|
|
2190
2373
|
} catch (err) {
|
|
2191
2374
|
if (err instanceof OwneyError) throw err;
|
|
2192
2375
|
console.warn(
|
|
2193
|
-
|
|
2376
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2194
2377
|
err instanceof Error ? err.message : String(err)
|
|
2195
2378
|
);
|
|
2196
2379
|
}
|
|
@@ -2198,7 +2381,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2198
2381
|
if (allowance < amountWei) {
|
|
2199
2382
|
throw new OwneyError(
|
|
2200
2383
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
2201
|
-
|
|
2384
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
2202
2385
|
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
2203
2386
|
);
|
|
2204
2387
|
}
|
|
@@ -2279,8 +2462,6 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
2279
2462
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
2280
2463
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
2281
2464
|
};
|
|
2282
|
-
var PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS = 10;
|
|
2283
|
-
var PERMIT2_ALLOWANCE_POLL_INTERVAL_MS = 1500;
|
|
2284
2465
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
2285
2466
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
2286
2467
|
}
|
|
@@ -2305,7 +2486,7 @@ var OwneySDK = class {
|
|
|
2305
2486
|
routingApiBaseUrl;
|
|
2306
2487
|
referralSource;
|
|
2307
2488
|
cachedSponsoredCallback = null;
|
|
2308
|
-
|
|
2489
|
+
cachedWethSponsoredCallback = null;
|
|
2309
2490
|
initializingAgentsPromise = null;
|
|
2310
2491
|
constructor(config) {
|
|
2311
2492
|
this.apiKey = config.apiKey;
|
|
@@ -2333,7 +2514,7 @@ var OwneySDK = class {
|
|
|
2333
2514
|
}
|
|
2334
2515
|
this.state = { provider, walletAddress, chainId: null };
|
|
2335
2516
|
this.cachedSponsoredCallback = null;
|
|
2336
|
-
this.
|
|
2517
|
+
this.cachedWethSponsoredCallback = null;
|
|
2337
2518
|
}
|
|
2338
2519
|
/**
|
|
2339
2520
|
* Disconnect from all agents and clear connection state.
|
|
@@ -2347,7 +2528,7 @@ var OwneySDK = class {
|
|
|
2347
2528
|
this.disabledAgents.clear();
|
|
2348
2529
|
this.state = null;
|
|
2349
2530
|
this.cachedSponsoredCallback = null;
|
|
2350
|
-
this.
|
|
2531
|
+
this.cachedWethSponsoredCallback = null;
|
|
2351
2532
|
}
|
|
2352
2533
|
isAgentDisabled(agentId) {
|
|
2353
2534
|
return this.disabledAgents.has(agentId);
|
|
@@ -2424,23 +2605,25 @@ var OwneySDK = class {
|
|
|
2424
2605
|
return callback;
|
|
2425
2606
|
}
|
|
2426
2607
|
/**
|
|
2427
|
-
* Lazily builds (and caches) the Permit2 sponsored
|
|
2428
|
-
*
|
|
2429
|
-
*
|
|
2430
|
-
*
|
|
2608
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
2609
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
2610
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
2611
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
2431
2612
|
*/
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2613
|
+
getDefaultWethSponsoredCallback(onApproved) {
|
|
2614
|
+
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
2615
|
+
return this.cachedWethSponsoredCallback;
|
|
2435
2616
|
const provider = this.requireConnectedProvider();
|
|
2436
2617
|
const owner = this.state.walletAddress;
|
|
2437
|
-
const callback =
|
|
2618
|
+
const callback = makeSponsoredWethCallback({
|
|
2438
2619
|
apiKey: this.apiKey,
|
|
2439
2620
|
baseUrl: this.routingApiBaseUrl,
|
|
2440
2621
|
ownerAddress: owner,
|
|
2441
|
-
assetLabel: asset,
|
|
2442
2622
|
onApproved,
|
|
2443
|
-
tokenAddressByChain:
|
|
2623
|
+
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
2624
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2625
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
2626
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
2444
2627
|
getPublicClient: (cid) => createPublicClient2({
|
|
2445
2628
|
chain: VIEM_CHAIN2[cid],
|
|
2446
2629
|
transport: custom(provider)
|
|
@@ -2451,34 +2634,9 @@ var OwneySDK = class {
|
|
|
2451
2634
|
transport: custom(provider)
|
|
2452
2635
|
})
|
|
2453
2636
|
});
|
|
2454
|
-
if (!onApproved) this.
|
|
2637
|
+
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
2455
2638
|
return callback;
|
|
2456
2639
|
}
|
|
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
2640
|
getAgent(agentId) {
|
|
2483
2641
|
const agent = this.agents.get(agentId);
|
|
2484
2642
|
if (!agent) {
|
|
@@ -2674,11 +2832,7 @@ var OwneySDK = class {
|
|
|
2674
2832
|
}
|
|
2675
2833
|
const state = this.requireState();
|
|
2676
2834
|
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));
|
|
2835
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
|
|
2682
2836
|
if (agentId) {
|
|
2683
2837
|
if (this.isAgentDisabled(agentId)) {
|
|
2684
2838
|
throw new OwneyError(
|
|
@@ -2747,16 +2901,13 @@ var OwneySDK = class {
|
|
|
2747
2901
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
2748
2902
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
2749
2903
|
* 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.
|
|
2904
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
2905
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
2906
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
2907
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
2908
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
2909
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
2910
|
+
* them to pay for a different one via the fallback below.
|
|
2760
2911
|
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
2761
2912
|
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
2762
2913
|
* and retry once with `undefined`, falling through to a user-paid
|
|
@@ -2768,49 +2919,6 @@ var OwneySDK = class {
|
|
|
2768
2919
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
2769
2920
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
2770
2921
|
*/
|
|
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
2922
|
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
2815
2923
|
const attempt = (callback) => withFailureReporting(
|
|
2816
2924
|
this.apiKey,
|
|
@@ -2828,16 +2936,12 @@ var OwneySDK = class {
|
|
|
2828
2936
|
try {
|
|
2829
2937
|
return await attempt(effectiveCallback);
|
|
2830
2938
|
} catch (error) {
|
|
2831
|
-
if (!approvalAttempted && appCallback === void 0 &&
|
|
2939
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2832
2940
|
approvalAttempted = true;
|
|
2833
2941
|
console.warn(
|
|
2834
|
-
|
|
2835
|
-
);
|
|
2836
|
-
await this.approvePermit2(asset);
|
|
2837
|
-
await this.waitForPermit2Allowance(
|
|
2838
|
-
asset,
|
|
2839
|
-
BigInt(amount)
|
|
2942
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
2840
2943
|
);
|
|
2944
|
+
await this.approvePermit2();
|
|
2841
2945
|
continue;
|
|
2842
2946
|
}
|
|
2843
2947
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3351,32 +3455,23 @@ var OwneySDK = class {
|
|
|
3351
3455
|
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
3352
3456
|
}
|
|
3353
3457
|
/**
|
|
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.
|
|
3458
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
3459
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
3460
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
3461
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
3462
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
3361
3463
|
* @returns the approval transaction hash.
|
|
3362
3464
|
*/
|
|
3363
3465
|
async approvePermit2(asset = "WETH") {
|
|
3466
|
+
void asset;
|
|
3364
3467
|
const state = this.requireState();
|
|
3365
3468
|
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];
|
|
3469
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
3470
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3376
3471
|
if (!token) {
|
|
3377
3472
|
throw new OwneyError(
|
|
3378
3473
|
"CHAIN_UNSUPPORTED",
|
|
3379
|
-
`No sponsored
|
|
3474
|
+
`No sponsored WETH on chain ${chainId}`
|
|
3380
3475
|
);
|
|
3381
3476
|
}
|
|
3382
3477
|
const provider = this.requireConnectedProvider();
|