@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.cjs
CHANGED
|
@@ -879,6 +879,12 @@ var ZyfaiAgent = class {
|
|
|
879
879
|
connectedAddress = null;
|
|
880
880
|
connectedChainId = null;
|
|
881
881
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
882
|
+
/**
|
|
883
|
+
* `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
|
|
884
|
+
* against re-running it on every activation/deposit when Zyfai's
|
|
885
|
+
* `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
|
|
886
|
+
*/
|
|
887
|
+
sessionKeyAttempts = /* @__PURE__ */ new Set();
|
|
882
888
|
connectAuthPromise = null;
|
|
883
889
|
rpcUrls;
|
|
884
890
|
publicClients = /* @__PURE__ */ new Map();
|
|
@@ -1336,10 +1342,74 @@ var ZyfaiAgent = class {
|
|
|
1336
1342
|
* recoverable (ensureWalletDeployed tries the other deploy call), but
|
|
1337
1343
|
* failing the deposit outright is not.
|
|
1338
1344
|
*/
|
|
1345
|
+
/**
|
|
1346
|
+
* Zyfai's own answer to "what kind of wallet is this, and can the agent act on
|
|
1347
|
+
* it", read from the positions payload (`GET /data/position`, which
|
|
1348
|
+
* `getPositions` wraps).
|
|
1349
|
+
*
|
|
1350
|
+
* `predeployed` and `hasActiveSessionKey` are both returned at runtime but
|
|
1351
|
+
* declared on neither `Portfolio` nor their docs, hence the cast. Verified
|
|
1352
|
+
* live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
|
|
1353
|
+
* false}` while a pool wallet reports `{true, true}`.
|
|
1354
|
+
*
|
|
1355
|
+
* Preferred over the SDK's private `isPredeployed`, which is cached from the
|
|
1356
|
+
* login response and can go stale mid-session. Returns an empty object on any
|
|
1357
|
+
* failure so every caller degrades to its own fallback.
|
|
1358
|
+
*/
|
|
1359
|
+
async readWalletState(ownerAddress) {
|
|
1360
|
+
try {
|
|
1361
|
+
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1362
|
+
const raw = portfolio;
|
|
1363
|
+
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1364
|
+
predeployed: raw?.predeployed,
|
|
1365
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1366
|
+
});
|
|
1367
|
+
return {
|
|
1368
|
+
predeployed: raw?.predeployed,
|
|
1369
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1370
|
+
};
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
console.warn(
|
|
1373
|
+
"[zyfai] getPositions wallet-state read failed (non-fatal):",
|
|
1374
|
+
error instanceof Error ? error.message : String(error)
|
|
1375
|
+
);
|
|
1376
|
+
return {};
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1339
1379
|
async isPredeployedPoolWallet(ownerAddress, smartWallet) {
|
|
1380
|
+
const { predeployed } = await this.readWalletState(ownerAddress);
|
|
1381
|
+
if (typeof predeployed === "boolean") {
|
|
1382
|
+
debugLog(
|
|
1383
|
+
"zyfai:onboard",
|
|
1384
|
+
`wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1385
|
+
{ source: "getPositions.predeployed", predeployed }
|
|
1386
|
+
);
|
|
1387
|
+
return predeployed;
|
|
1388
|
+
}
|
|
1389
|
+
const internals = this.sdk;
|
|
1390
|
+
if (typeof internals.isPredeployed === "boolean") {
|
|
1391
|
+
debugLog(
|
|
1392
|
+
"zyfai:onboard",
|
|
1393
|
+
`wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1394
|
+
{
|
|
1395
|
+
source: "login response (cached)",
|
|
1396
|
+
predeployed: internals.isPredeployed
|
|
1397
|
+
}
|
|
1398
|
+
);
|
|
1399
|
+
return internals.isPredeployed;
|
|
1400
|
+
}
|
|
1340
1401
|
try {
|
|
1341
1402
|
const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
|
|
1342
|
-
|
|
1403
|
+
const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
|
|
1404
|
+
debugLog(
|
|
1405
|
+
"zyfai:onboard",
|
|
1406
|
+
`wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1407
|
+
{
|
|
1408
|
+
source: "getSmartWalletByEOA fallback",
|
|
1409
|
+
poolRecord: byEoa?.smartWallet
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
return isPool;
|
|
1343
1413
|
} catch (error) {
|
|
1344
1414
|
console.warn(
|
|
1345
1415
|
"[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
|
|
@@ -1377,8 +1447,24 @@ var ZyfaiAgent = class {
|
|
|
1377
1447
|
ownerAddress,
|
|
1378
1448
|
chainId
|
|
1379
1449
|
);
|
|
1380
|
-
|
|
1381
|
-
|
|
1450
|
+
debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
|
|
1451
|
+
smartWallet: wallet.address,
|
|
1452
|
+
isDeployed: wallet.isDeployed
|
|
1453
|
+
});
|
|
1454
|
+
if (!wallet.isDeployed) {
|
|
1455
|
+
debugLog(
|
|
1456
|
+
"zyfai:onboard",
|
|
1457
|
+
"Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
|
|
1458
|
+
);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1462
|
+
await this.ensureSessionKey(
|
|
1463
|
+
ownerAddress,
|
|
1464
|
+
chainId,
|
|
1465
|
+
wallet.address,
|
|
1466
|
+
hasActiveSessionKey
|
|
1467
|
+
);
|
|
1382
1468
|
} catch (error) {
|
|
1383
1469
|
console.warn(
|
|
1384
1470
|
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
@@ -1386,20 +1472,51 @@ var ZyfaiAgent = class {
|
|
|
1386
1472
|
);
|
|
1387
1473
|
}
|
|
1388
1474
|
}
|
|
1389
|
-
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1475
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
|
|
1476
|
+
const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
|
|
1477
|
+
if (this.sessionKeyAttempts.has(attemptKey)) {
|
|
1478
|
+
debugLog(
|
|
1479
|
+
"zyfai:onboard",
|
|
1480
|
+
"session key: already attempted this session, skipping",
|
|
1481
|
+
{ chainId }
|
|
1482
|
+
);
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1390
1485
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1486
|
+
if (hasActiveSessionKey === true) {
|
|
1487
|
+
debugLog(
|
|
1488
|
+
"zyfai:onboard",
|
|
1489
|
+
"session key: already on record -> nothing to do",
|
|
1490
|
+
{ chainId }
|
|
1491
|
+
);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (hasActiveSessionKey === void 0) {
|
|
1495
|
+
const installed = await this.readSmartSessionsInstalled(
|
|
1496
|
+
chainId,
|
|
1497
|
+
smartWallet
|
|
1498
|
+
);
|
|
1499
|
+
debugLog(
|
|
1500
|
+
"zyfai:onboard",
|
|
1501
|
+
`session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
|
|
1502
|
+
{ smartWallet, chainId }
|
|
1503
|
+
);
|
|
1504
|
+
if (installed) return;
|
|
1505
|
+
}
|
|
1506
|
+
this.sessionKeyAttempts.add(attemptKey);
|
|
1399
1507
|
console.warn(
|
|
1400
|
-
`[zyfai]
|
|
1508
|
+
`[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
|
|
1401
1509
|
);
|
|
1510
|
+
debugLog("zyfai:onboard", "session key: calling createSessionKey", {
|
|
1511
|
+
chainId,
|
|
1512
|
+
smartWallet,
|
|
1513
|
+
hasActiveSessionKey
|
|
1514
|
+
});
|
|
1402
1515
|
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1516
|
+
debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
|
|
1517
|
+
chainId,
|
|
1518
|
+
smartWallet
|
|
1519
|
+
});
|
|
1403
1520
|
} catch (error) {
|
|
1404
1521
|
console.warn(
|
|
1405
1522
|
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
@@ -1407,6 +1524,16 @@ var ZyfaiAgent = class {
|
|
|
1407
1524
|
);
|
|
1408
1525
|
}
|
|
1409
1526
|
}
|
|
1527
|
+
/** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
|
|
1528
|
+
async readSmartSessionsInstalled(chainId, smartWallet) {
|
|
1529
|
+
return this.getPublicClient(chainId).readContract({
|
|
1530
|
+
address: smartWallet,
|
|
1531
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1532
|
+
functionName: "isModuleInstalled",
|
|
1533
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1534
|
+
args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1410
1537
|
/**
|
|
1411
1538
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1412
1539
|
* funds into it, and return its address.
|
|
@@ -1427,8 +1554,24 @@ var ZyfaiAgent = class {
|
|
|
1427
1554
|
*/
|
|
1428
1555
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1429
1556
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1557
|
+
debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
|
|
1558
|
+
eoa: ownerAddress,
|
|
1559
|
+
smartWallet: wallet.address,
|
|
1560
|
+
isDeployed: wallet.isDeployed,
|
|
1561
|
+
isOwner: wallet.isOwner
|
|
1562
|
+
});
|
|
1430
1563
|
if (wallet.isDeployed) {
|
|
1431
|
-
|
|
1564
|
+
debugLog(
|
|
1565
|
+
"zyfai:onboard",
|
|
1566
|
+
"Safe already has code -> no deploy needed, checking the session key"
|
|
1567
|
+
);
|
|
1568
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1569
|
+
await this.ensureSessionKey(
|
|
1570
|
+
ownerAddress,
|
|
1571
|
+
chainId,
|
|
1572
|
+
wallet.address,
|
|
1573
|
+
hasActiveSessionKey
|
|
1574
|
+
);
|
|
1432
1575
|
return wallet.address;
|
|
1433
1576
|
}
|
|
1434
1577
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
@@ -1444,14 +1587,27 @@ var ZyfaiAgent = class {
|
|
|
1444
1587
|
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1445
1588
|
);
|
|
1446
1589
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1590
|
+
debugLog(
|
|
1591
|
+
"zyfai:onboard",
|
|
1592
|
+
`Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
|
|
1593
|
+
);
|
|
1447
1594
|
try {
|
|
1448
1595
|
await first();
|
|
1596
|
+
debugLog(
|
|
1597
|
+
"zyfai:onboard",
|
|
1598
|
+
`${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
|
|
1599
|
+
);
|
|
1449
1600
|
} catch (error) {
|
|
1450
1601
|
console.warn(
|
|
1451
1602
|
`[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
|
|
1452
1603
|
error instanceof Error ? error.message : String(error)
|
|
1453
1604
|
);
|
|
1605
|
+
debugLog(
|
|
1606
|
+
"zyfai:onboard",
|
|
1607
|
+
`falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
|
|
1608
|
+
);
|
|
1454
1609
|
await second();
|
|
1610
|
+
debugLog("zyfai:onboard", "fallback deploy path succeeded");
|
|
1455
1611
|
}
|
|
1456
1612
|
const redeployed = await this.sdk.getSmartWalletAddress(
|
|
1457
1613
|
ownerAddress,
|
|
@@ -1912,12 +2068,9 @@ async function postSponsorTransferAuth(input) {
|
|
|
1912
2068
|
{
|
|
1913
2069
|
statusCode: res.status,
|
|
1914
2070
|
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
|
|
2071
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2072
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2073
|
+
safeToFallback: res.status === 503
|
|
1921
2074
|
}
|
|
1922
2075
|
);
|
|
1923
2076
|
}
|
|
@@ -2189,9 +2342,9 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2189
2342
|
};
|
|
2190
2343
|
}
|
|
2191
2344
|
|
|
2192
|
-
// src/lib/sponsored-
|
|
2345
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
2193
2346
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2194
|
-
function
|
|
2347
|
+
function makeSponsoredWethCallback(deps) {
|
|
2195
2348
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2196
2349
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2197
2350
|
return async (smartWallet, chainId, amount) => {
|
|
@@ -2200,7 +2353,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2200
2353
|
if (!token) {
|
|
2201
2354
|
throw new OwneyError(
|
|
2202
2355
|
"CHAIN_UNSUPPORTED",
|
|
2203
|
-
`No sponsored
|
|
2356
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2204
2357
|
);
|
|
2205
2358
|
}
|
|
2206
2359
|
const amountWei = BigInt(amount);
|
|
@@ -2212,14 +2365,14 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2212
2365
|
if (balance < amountWei) {
|
|
2213
2366
|
throw new OwneyError(
|
|
2214
2367
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2215
|
-
|
|
2368
|
+
"Insufficient WETH balance for this deposit.",
|
|
2216
2369
|
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2217
2370
|
);
|
|
2218
2371
|
}
|
|
2219
2372
|
} catch (err) {
|
|
2220
2373
|
if (err instanceof OwneyError) throw err;
|
|
2221
2374
|
console.warn(
|
|
2222
|
-
|
|
2375
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2223
2376
|
err instanceof Error ? err.message : String(err)
|
|
2224
2377
|
);
|
|
2225
2378
|
}
|
|
@@ -2227,7 +2380,7 @@ function makeSponsoredPermit2Callback(deps) {
|
|
|
2227
2380
|
if (allowance < amountWei) {
|
|
2228
2381
|
throw new OwneyError(
|
|
2229
2382
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
2230
|
-
|
|
2383
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
2231
2384
|
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
2232
2385
|
);
|
|
2233
2386
|
}
|
|
@@ -2308,8 +2461,6 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
2308
2461
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
2309
2462
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
2310
2463
|
};
|
|
2311
|
-
var PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS = 10;
|
|
2312
|
-
var PERMIT2_ALLOWANCE_POLL_INTERVAL_MS = 1500;
|
|
2313
2464
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
2314
2465
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
2315
2466
|
}
|
|
@@ -2334,7 +2485,7 @@ var OwneySDK = class {
|
|
|
2334
2485
|
routingApiBaseUrl;
|
|
2335
2486
|
referralSource;
|
|
2336
2487
|
cachedSponsoredCallback = null;
|
|
2337
|
-
|
|
2488
|
+
cachedWethSponsoredCallback = null;
|
|
2338
2489
|
initializingAgentsPromise = null;
|
|
2339
2490
|
constructor(config) {
|
|
2340
2491
|
this.apiKey = config.apiKey;
|
|
@@ -2362,7 +2513,7 @@ var OwneySDK = class {
|
|
|
2362
2513
|
}
|
|
2363
2514
|
this.state = { provider, walletAddress, chainId: null };
|
|
2364
2515
|
this.cachedSponsoredCallback = null;
|
|
2365
|
-
this.
|
|
2516
|
+
this.cachedWethSponsoredCallback = null;
|
|
2366
2517
|
}
|
|
2367
2518
|
/**
|
|
2368
2519
|
* Disconnect from all agents and clear connection state.
|
|
@@ -2376,7 +2527,7 @@ var OwneySDK = class {
|
|
|
2376
2527
|
this.disabledAgents.clear();
|
|
2377
2528
|
this.state = null;
|
|
2378
2529
|
this.cachedSponsoredCallback = null;
|
|
2379
|
-
this.
|
|
2530
|
+
this.cachedWethSponsoredCallback = null;
|
|
2380
2531
|
}
|
|
2381
2532
|
isAgentDisabled(agentId) {
|
|
2382
2533
|
return this.disabledAgents.has(agentId);
|
|
@@ -2453,23 +2604,25 @@ var OwneySDK = class {
|
|
|
2453
2604
|
return callback;
|
|
2454
2605
|
}
|
|
2455
2606
|
/**
|
|
2456
|
-
* Lazily builds (and caches) the Permit2 sponsored
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2607
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
2608
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
2609
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
2610
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
2460
2611
|
*/
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2612
|
+
getDefaultWethSponsoredCallback(onApproved) {
|
|
2613
|
+
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
2614
|
+
return this.cachedWethSponsoredCallback;
|
|
2464
2615
|
const provider = this.requireConnectedProvider();
|
|
2465
2616
|
const owner = this.state.walletAddress;
|
|
2466
|
-
const callback =
|
|
2617
|
+
const callback = makeSponsoredWethCallback({
|
|
2467
2618
|
apiKey: this.apiKey,
|
|
2468
2619
|
baseUrl: this.routingApiBaseUrl,
|
|
2469
2620
|
ownerAddress: owner,
|
|
2470
|
-
assetLabel: asset,
|
|
2471
2621
|
onApproved,
|
|
2472
|
-
tokenAddressByChain:
|
|
2622
|
+
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
2623
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2624
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
2625
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
2473
2626
|
getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
|
|
2474
2627
|
chain: VIEM_CHAIN2[cid],
|
|
2475
2628
|
transport: (0, import_viem5.custom)(provider)
|
|
@@ -2480,34 +2633,9 @@ var OwneySDK = class {
|
|
|
2480
2633
|
transport: (0, import_viem5.custom)(provider)
|
|
2481
2634
|
})
|
|
2482
2635
|
});
|
|
2483
|
-
if (!onApproved) this.
|
|
2636
|
+
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
2484
2637
|
return callback;
|
|
2485
2638
|
}
|
|
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
2639
|
getAgent(agentId) {
|
|
2512
2640
|
const agent = this.agents.get(agentId);
|
|
2513
2641
|
if (!agent) {
|
|
@@ -2703,11 +2831,7 @@ var OwneySDK = class {
|
|
|
2703
2831
|
}
|
|
2704
2832
|
const state = this.requireState();
|
|
2705
2833
|
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));
|
|
2834
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
|
|
2711
2835
|
if (agentId) {
|
|
2712
2836
|
if (this.isAgentDisabled(agentId)) {
|
|
2713
2837
|
throw new OwneyError(
|
|
@@ -2776,16 +2900,13 @@ var OwneySDK = class {
|
|
|
2776
2900
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
2777
2901
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
2778
2902
|
* 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.
|
|
2903
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
2904
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
2905
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
2906
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
2907
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
2908
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
2909
|
+
* them to pay for a different one via the fallback below.
|
|
2789
2910
|
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
2790
2911
|
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
2791
2912
|
* and retry once with `undefined`, falling through to a user-paid
|
|
@@ -2797,49 +2918,6 @@ var OwneySDK = class {
|
|
|
2797
2918
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
2798
2919
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
2799
2920
|
*/
|
|
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
2921
|
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
2844
2922
|
const attempt = (callback) => withFailureReporting(
|
|
2845
2923
|
this.apiKey,
|
|
@@ -2857,16 +2935,12 @@ var OwneySDK = class {
|
|
|
2857
2935
|
try {
|
|
2858
2936
|
return await attempt(effectiveCallback);
|
|
2859
2937
|
} catch (error) {
|
|
2860
|
-
if (!approvalAttempted && appCallback === void 0 &&
|
|
2938
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2861
2939
|
approvalAttempted = true;
|
|
2862
2940
|
console.warn(
|
|
2863
|
-
|
|
2864
|
-
);
|
|
2865
|
-
await this.approvePermit2(asset);
|
|
2866
|
-
await this.waitForPermit2Allowance(
|
|
2867
|
-
asset,
|
|
2868
|
-
BigInt(amount)
|
|
2941
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
2869
2942
|
);
|
|
2943
|
+
await this.approvePermit2();
|
|
2870
2944
|
continue;
|
|
2871
2945
|
}
|
|
2872
2946
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3380,32 +3454,23 @@ var OwneySDK = class {
|
|
|
3380
3454
|
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
3381
3455
|
}
|
|
3382
3456
|
/**
|
|
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.
|
|
3457
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
3458
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
3459
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
3460
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
3461
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
3390
3462
|
* @returns the approval transaction hash.
|
|
3391
3463
|
*/
|
|
3392
3464
|
async approvePermit2(asset = "WETH") {
|
|
3465
|
+
void asset;
|
|
3393
3466
|
const state = this.requireState();
|
|
3394
3467
|
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];
|
|
3468
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
3469
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3405
3470
|
if (!token) {
|
|
3406
3471
|
throw new OwneyError(
|
|
3407
3472
|
"CHAIN_UNSUPPORTED",
|
|
3408
|
-
`No sponsored
|
|
3473
|
+
`No sponsored WETH on chain ${chainId}`
|
|
3409
3474
|
);
|
|
3410
3475
|
}
|
|
3411
3476
|
const provider = this.requireConnectedProvider();
|
package/dist/index.d.cts
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")
|