@owney/sdk 0.7.7-beta.5 → 0.7.7-beta.6

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 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 (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
227
+ if (typeof e.tokenSymbol !== "string" || e.tokenSymbol === "") continue;
228
+ if (typeof e.balance !== "string") continue;
229
+ let amount;
230
+ try {
231
+ amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
232
+ } catch {
233
+ continue;
234
+ }
235
+ if (!Number.isFinite(amount) || amount <= 0) continue;
236
+ pending.push({
237
+ chainId: e.chainId,
238
+ asset: e.tokenSymbol,
239
+ amount: String(amount),
240
+ isPending: e.isPending === true,
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
- return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
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
- if (!wallet.isDeployed) return;
1381
- await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
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
- const installed = await this.getPublicClient(chainId).readContract({
1392
- address: smartWallet,
1393
- abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1394
- functionName: "isModuleInstalled",
1395
- // moduleTypeId 1 = validator (ERC-7579)
1396
- args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
1397
- });
1398
- if (installed) return;
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] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
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
- await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
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,
@@ -1839,7 +2025,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1839
2025
  }
1840
2026
 
1841
2027
  // src/client.ts
1842
- var import_viem6 = require("viem");
2028
+ var import_viem5 = require("viem");
1843
2029
  var import_chains2 = require("viem/chains");
1844
2030
 
1845
2031
  // src/lib/transfer-auth.ts
@@ -2269,97 +2455,6 @@ function makeSponsoredWethCallback(deps) {
2269
2455
  };
2270
2456
  }
2271
2457
 
2272
- // src/lib/sponsored-calls-deposit.ts
2273
- var import_viem5 = require("viem");
2274
- var DEFAULT_POLL_INTERVAL_MS = 1500;
2275
- var DEFAULT_MAX_POLLS = 30;
2276
- async function paymasterSupported(provider, owner, chainId) {
2277
- const caps = await provider.request({
2278
- method: "wallet_getCapabilities",
2279
- params: [owner]
2280
- });
2281
- const forChain = caps?.[(0, import_viem5.toHex)(chainId)] ?? caps?.[String(chainId)];
2282
- return Boolean(forChain?.paymasterService?.supported);
2283
- }
2284
- function makeSponsoredCallsCallback(deps) {
2285
- const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2286
- const maxPolls = deps.maxPolls ?? DEFAULT_MAX_POLLS;
2287
- const absolutePaymasterUrl = () => {
2288
- const configured = deps.paymasterServiceUrl;
2289
- if (/^https?:\/\//i.test(configured)) return configured;
2290
- const origin = globalThis.location?.origin;
2291
- if (!origin) {
2292
- throw new OwneyError(
2293
- "SPONSORED_CALLS_UNSUPPORTED",
2294
- `paymasterServiceUrl "${configured}" is relative and no page origin is available to resolve it.`
2295
- );
2296
- }
2297
- return new URL(configured, origin).toString();
2298
- };
2299
- return async (smartWallet, chainId, amount) => {
2300
- const cid = chainId;
2301
- const token = deps.tokenAddressByChain[cid];
2302
- if (!token) {
2303
- throw new OwneyError(
2304
- "CHAIN_UNSUPPORTED",
2305
- `No sponsored token configured for chain ${chainId}`
2306
- );
2307
- }
2308
- if (!await paymasterSupported(deps.provider, deps.ownerAddress, chainId)) {
2309
- throw new OwneyError(
2310
- "SPONSORED_CALLS_UNSUPPORTED",
2311
- `Wallet does not advertise paymasterService on chain ${chainId}.`,
2312
- { chainId }
2313
- );
2314
- }
2315
- const data = (0, import_viem5.encodeFunctionData)({
2316
- abi: import_viem5.erc20Abi,
2317
- functionName: "transfer",
2318
- args: [smartWallet, BigInt(amount)]
2319
- });
2320
- const sendResult = await deps.provider.request({
2321
- method: "wallet_sendCalls",
2322
- params: [
2323
- {
2324
- version: "2.0.0",
2325
- from: deps.ownerAddress,
2326
- chainId: (0, import_viem5.toHex)(chainId),
2327
- atomicRequired: false,
2328
- calls: [{ to: token, value: "0x0", data }],
2329
- capabilities: {
2330
- paymasterService: { url: absolutePaymasterUrl() }
2331
- }
2332
- }
2333
- ]
2334
- });
2335
- deps.onApproved?.();
2336
- const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
2337
- if (!callsId) {
2338
- throw new OwneyError(
2339
- "SPONSORED_CALLS_NO_ID",
2340
- "Wallet accepted wallet_sendCalls but returned no calls id.",
2341
- { chainId }
2342
- );
2343
- }
2344
- for (let attempt = 0; attempt < maxPolls; attempt += 1) {
2345
- const status = await deps.provider.request({
2346
- method: "wallet_getCallsStatus",
2347
- params: [callsId]
2348
- });
2349
- const txHash = status?.receipts?.[0]?.transactionHash;
2350
- if (txHash) return txHash;
2351
- if (pollIntervalMs > 0) {
2352
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2353
- }
2354
- }
2355
- throw new OwneyError(
2356
- "SPONSORED_CALLS_NO_RECEIPT",
2357
- `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
2358
- { chainId, callsId }
2359
- );
2360
- };
2361
- }
2362
-
2363
2458
  // src/client.ts
2364
2459
  function encodeMultiAgentCursor(map) {
2365
2460
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -2421,15 +2516,12 @@ var OwneySDK = class {
2421
2516
  referralSource;
2422
2517
  cachedSponsoredCallback = null;
2423
2518
  cachedWethSponsoredCallback = null;
2424
- paymasterServiceUrl;
2425
- cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
2426
2519
  initializingAgentsPromise = null;
2427
2520
  constructor(config) {
2428
2521
  this.apiKey = config.apiKey;
2429
2522
  if (config.debug) setOwneyDebug(true);
2430
2523
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2431
2524
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2432
- this.paymasterServiceUrl = config.paymasterServiceUrl;
2433
2525
  this.referralSource = config.referralSource;
2434
2526
  }
2435
2527
  /**
@@ -2452,7 +2544,6 @@ var OwneySDK = class {
2452
2544
  this.state = { provider, walletAddress, chainId: null };
2453
2545
  this.cachedSponsoredCallback = null;
2454
2546
  this.cachedWethSponsoredCallback = null;
2455
- this.cachedSponsoredCallsCallbacks.clear();
2456
2547
  }
2457
2548
  /**
2458
2549
  * Disconnect from all agents and clear connection state.
@@ -2467,7 +2558,6 @@ var OwneySDK = class {
2467
2558
  this.state = null;
2468
2559
  this.cachedSponsoredCallback = null;
2469
2560
  this.cachedWethSponsoredCallback = null;
2470
- this.cachedSponsoredCallsCallbacks.clear();
2471
2561
  }
2472
2562
  isAgentDisabled(agentId) {
2473
2563
  return this.disabledAgents.has(agentId);
@@ -2530,14 +2620,14 @@ var OwneySDK = class {
2530
2620
  // Casts work around viem's chain-narrowed Client vs the generic
2531
2621
  // PublicClient/WalletClient param types — structurally identical at
2532
2622
  // runtime, but the two share a name TS treats as unrelated.
2533
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2623
+ getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2534
2624
  chain: VIEM_CHAIN2[cid],
2535
- transport: (0, import_viem6.custom)(provider)
2625
+ transport: (0, import_viem5.custom)(provider)
2536
2626
  }),
2537
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2627
+ getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2538
2628
  account: owner,
2539
2629
  chain: VIEM_CHAIN2[cid],
2540
- transport: (0, import_viem6.custom)(provider)
2630
+ transport: (0, import_viem5.custom)(provider)
2541
2631
  })
2542
2632
  });
2543
2633
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2549,26 +2639,6 @@ var OwneySDK = class {
2549
2639
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2550
2640
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
2551
2641
  */
2552
- getDefaultSponsoredCallsCallback(asset, onApproved) {
2553
- const cached = this.cachedSponsoredCallsCallbacks.get(asset);
2554
- if (!onApproved && cached) return cached;
2555
- const provider = this.requireConnectedProvider();
2556
- const callback = makeSponsoredCallsCallback({
2557
- provider,
2558
- ownerAddress: this.state.walletAddress,
2559
- paymasterServiceUrl: this.paymasterServiceUrl,
2560
- onApproved,
2561
- tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
2562
- });
2563
- if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
2564
- return callback;
2565
- }
2566
- /**
2567
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
2568
- * callback used when the caller omits `depositCallback` for a WETH deposit.
2569
- * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2570
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
2571
- */
2572
2642
  getDefaultWethSponsoredCallback(onApproved) {
2573
2643
  if (!onApproved && this.cachedWethSponsoredCallback)
2574
2644
  return this.cachedWethSponsoredCallback;
@@ -2583,14 +2653,14 @@ var OwneySDK = class {
2583
2653
  // Casts work around viem's chain-narrowed Client vs the generic
2584
2654
  // PublicClient/WalletClient param types — structurally identical at
2585
2655
  // runtime, but the two share a name TS treats as unrelated.
2586
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2656
+ getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2587
2657
  chain: VIEM_CHAIN2[cid],
2588
- transport: (0, import_viem6.custom)(provider)
2658
+ transport: (0, import_viem5.custom)(provider)
2589
2659
  }),
2590
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2660
+ getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2591
2661
  account: owner,
2592
2662
  chain: VIEM_CHAIN2[cid],
2593
- transport: (0, import_viem6.custom)(provider)
2663
+ transport: (0, import_viem5.custom)(provider)
2594
2664
  })
2595
2665
  });
2596
2666
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2791,7 +2861,7 @@ var OwneySDK = class {
2791
2861
  }
2792
2862
  const state = this.requireState();
2793
2863
  const chainId = this.requireChainId();
2794
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2864
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2795
2865
  if (agentId) {
2796
2866
  if (this.isAgentDisabled(agentId)) {
2797
2867
  throw new OwneyError(
@@ -3434,10 +3504,10 @@ var OwneySDK = class {
3434
3504
  );
3435
3505
  }
3436
3506
  const provider = this.requireConnectedProvider();
3437
- const wallet = (0, import_viem6.createWalletClient)({
3507
+ const wallet = (0, import_viem5.createWalletClient)({
3438
3508
  account: state.walletAddress,
3439
3509
  chain: VIEM_CHAIN2[chainId],
3440
- transport: (0, import_viem6.custom)(provider)
3510
+ transport: (0, import_viem5.custom)(provider)
3441
3511
  });
3442
3512
  const hash = await wallet.writeContract({
3443
3513
  address: token,
@@ -3447,9 +3517,9 @@ var OwneySDK = class {
3447
3517
  account: state.walletAddress,
3448
3518
  chain: VIEM_CHAIN2[chainId]
3449
3519
  });
3450
- const publicClient = (0, import_viem6.createPublicClient)({
3520
+ const publicClient = (0, import_viem5.createPublicClient)({
3451
3521
  chain: VIEM_CHAIN2[chainId],
3452
- transport: (0, import_viem6.custom)(provider)
3522
+ transport: (0, import_viem5.custom)(provider)
3453
3523
  });
3454
3524
  const receipt = await publicClient.waitForTransactionReceipt({
3455
3525
  hash,
@@ -3558,7 +3628,7 @@ var OwneySDK = class {
3558
3628
  };
3559
3629
 
3560
3630
  // src/agents/zyfai/zyfai.siwx.ts
3561
- var import_viem7 = require("viem");
3631
+ var import_viem6 = require("viem");
3562
3632
  var import_siwe = require("siwe");
3563
3633
  var import_sdk2 = require("@zyfai/sdk");
3564
3634
 
@@ -3685,7 +3755,7 @@ function buildSIWXConfig(deps) {
3685
3755
  issuedAt,
3686
3756
  toString() {
3687
3757
  return new import_siwe.SiweMessage({
3688
- address: (0, import_viem7.getAddress)(accountAddress),
3758
+ address: (0, import_viem6.getAddress)(accountAddress),
3689
3759
  chainId: numericChainId(chainId),
3690
3760
  domain,
3691
3761
  uri,
package/dist/index.d.cts CHANGED
@@ -30,17 +30,6 @@ interface OwneySDKConfig {
30
30
  * already-deployed app: set `globalThis.__OWNEY_DEBUG__ = true` in devtools.
31
31
  */
32
32
  debug?: boolean;
33
- /**
34
- * Absolute URL of an ERC-7677 paymaster service. When set, deposits go out as
35
- * an EIP-5792 `wallet_sendCalls` with `capabilities.paymasterService`, so a
36
- * wallet that advertises sponsorship pays no native gas — the case EIP-3009
37
- * cannot serve, because USDC verifies it with `ecrecover` and a smart account
38
- * signs via ERC-1271.
39
- *
40
- * MUST be absolute: the wallet fetches this URL itself, so a relative path is
41
- * unresolvable. Omit it and every rail behaves exactly as before.
42
- */
43
- paymasterServiceUrl?: string;
44
33
  }
45
34
  interface ConnectionState {
46
35
  provider: any;
@@ -191,6 +180,29 @@ interface OwneyPosition {
191
180
  * response later; undefined until then. */
192
181
  liquidity?: number;
193
182
  }
183
+ /**
184
+ * Funds that have landed in the agent's wallet but are not yet deployed into a
185
+ * protocol. Sourced from Zyfai `staleBalances[].isPending`. Absent when nothing
186
+ * is pending, and for agents that do not report it (sail).
187
+ */
188
+ interface OwneyPendingAllocation {
189
+ /** Deliberately loose (not OwneySupportedChainId) because Zyfai reports balances on unsupported chains. */
190
+ chainId: number;
191
+ /** Raw agent symbol, not normalized to OwneySupportedTokens; carries Zyfai's casing and "eth" alias for WETH. */
192
+ asset: string;
193
+ /** Decimal string, consistent with `OwneyToken.amount`. */
194
+ amount: string;
195
+ /**
196
+ * Zyfai's own `isPending` flag. TRUE means it is actively allocating. FALSE
197
+ * does NOT mean the entry is stale or safe to ignore: Zyfai flips it within
198
+ * minutes but leaves funds listed here indefinitely when it has not indexed
199
+ * the pool they landed in. Entries are surfaced regardless of this flag; use
200
+ * it only to decide wording, never to decide whether the money counts.
201
+ */
202
+ isPending: boolean;
203
+ /** ISO timestamp from Zyfai's `updatedAt`, when the payload carries one. */
204
+ since?: string;
205
+ }
194
206
  interface AgentBalance {
195
207
  smartWallet?: `0x${string}`;
196
208
  totalBalance: string;
@@ -202,6 +214,8 @@ interface AgentBalance {
202
214
  * them (zyfai). Omitted for agents that don't expose positions (sail).
203
215
  */
204
216
  positions?: OwneyPosition[];
217
+ /** Funds received but not yet deployed. Omitted when nothing is pending. */
218
+ pendingAllocations?: OwneyPendingAllocation[];
205
219
  }
206
220
  interface OwneyBalances {
207
221
  totalBalance: string;
@@ -404,8 +418,6 @@ declare class OwneySDK {
404
418
  private referralSource?;
405
419
  private cachedSponsoredCallback;
406
420
  private cachedWethSponsoredCallback;
407
- private paymasterServiceUrl?;
408
- private cachedSponsoredCallsCallbacks;
409
421
  private initializingAgentsPromise;
410
422
  constructor(config: OwneySDKConfig);
411
423
  /**
@@ -446,13 +458,6 @@ declare class OwneySDK {
446
458
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
447
459
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
448
460
  */
449
- private getDefaultSponsoredCallsCallback;
450
- /**
451
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
452
- * callback used when the caller omits `depositCallback` for a WETH deposit.
453
- * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
454
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
455
- */
456
461
  private getDefaultWethSponsoredCallback;
457
462
  private getAgent;
458
463
  private getActiveAgents;
@@ -629,7 +634,7 @@ declare class OwneySDK {
629
634
  getAllocationApy({ agentId, }?: AllocationApyOptions): Promise<OwneyAllocationApy>;
630
635
  }
631
636
 
632
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
637
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
633
638
  declare class OwneyError extends Error {
634
639
  readonly code: OwneyErrorCode;
635
640
  readonly details?: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -30,17 +30,6 @@ interface OwneySDKConfig {
30
30
  * already-deployed app: set `globalThis.__OWNEY_DEBUG__ = true` in devtools.
31
31
  */
32
32
  debug?: boolean;
33
- /**
34
- * Absolute URL of an ERC-7677 paymaster service. When set, deposits go out as
35
- * an EIP-5792 `wallet_sendCalls` with `capabilities.paymasterService`, so a
36
- * wallet that advertises sponsorship pays no native gas — the case EIP-3009
37
- * cannot serve, because USDC verifies it with `ecrecover` and a smart account
38
- * signs via ERC-1271.
39
- *
40
- * MUST be absolute: the wallet fetches this URL itself, so a relative path is
41
- * unresolvable. Omit it and every rail behaves exactly as before.
42
- */
43
- paymasterServiceUrl?: string;
44
33
  }
45
34
  interface ConnectionState {
46
35
  provider: any;
@@ -191,6 +180,29 @@ interface OwneyPosition {
191
180
  * response later; undefined until then. */
192
181
  liquidity?: number;
193
182
  }
183
+ /**
184
+ * Funds that have landed in the agent's wallet but are not yet deployed into a
185
+ * protocol. Sourced from Zyfai `staleBalances[].isPending`. Absent when nothing
186
+ * is pending, and for agents that do not report it (sail).
187
+ */
188
+ interface OwneyPendingAllocation {
189
+ /** Deliberately loose (not OwneySupportedChainId) because Zyfai reports balances on unsupported chains. */
190
+ chainId: number;
191
+ /** Raw agent symbol, not normalized to OwneySupportedTokens; carries Zyfai's casing and "eth" alias for WETH. */
192
+ asset: string;
193
+ /** Decimal string, consistent with `OwneyToken.amount`. */
194
+ amount: string;
195
+ /**
196
+ * Zyfai's own `isPending` flag. TRUE means it is actively allocating. FALSE
197
+ * does NOT mean the entry is stale or safe to ignore: Zyfai flips it within
198
+ * minutes but leaves funds listed here indefinitely when it has not indexed
199
+ * the pool they landed in. Entries are surfaced regardless of this flag; use
200
+ * it only to decide wording, never to decide whether the money counts.
201
+ */
202
+ isPending: boolean;
203
+ /** ISO timestamp from Zyfai's `updatedAt`, when the payload carries one. */
204
+ since?: string;
205
+ }
194
206
  interface AgentBalance {
195
207
  smartWallet?: `0x${string}`;
196
208
  totalBalance: string;
@@ -202,6 +214,8 @@ interface AgentBalance {
202
214
  * them (zyfai). Omitted for agents that don't expose positions (sail).
203
215
  */
204
216
  positions?: OwneyPosition[];
217
+ /** Funds received but not yet deployed. Omitted when nothing is pending. */
218
+ pendingAllocations?: OwneyPendingAllocation[];
205
219
  }
206
220
  interface OwneyBalances {
207
221
  totalBalance: string;
@@ -404,8 +418,6 @@ declare class OwneySDK {
404
418
  private referralSource?;
405
419
  private cachedSponsoredCallback;
406
420
  private cachedWethSponsoredCallback;
407
- private paymasterServiceUrl?;
408
- private cachedSponsoredCallsCallbacks;
409
421
  private initializingAgentsPromise;
410
422
  constructor(config: OwneySDKConfig);
411
423
  /**
@@ -446,13 +458,6 @@ declare class OwneySDK {
446
458
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
447
459
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
448
460
  */
449
- private getDefaultSponsoredCallsCallback;
450
- /**
451
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
452
- * callback used when the caller omits `depositCallback` for a WETH deposit.
453
- * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
454
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
455
- */
456
461
  private getDefaultWethSponsoredCallback;
457
462
  private getAgent;
458
463
  private getActiveAgents;
@@ -629,7 +634,7 @@ declare class OwneySDK {
629
634
  getAllocationApy({ agentId, }?: AllocationApyOptions): Promise<OwneyAllocationApy>;
630
635
  }
631
636
 
632
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
637
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
633
638
  declare class OwneyError extends Error {
634
639
  readonly code: OwneyErrorCode;
635
640
  readonly details?: Record<string, unknown>;
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 (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
194
+ if (typeof e.tokenSymbol !== "string" || e.tokenSymbol === "") continue;
195
+ if (typeof e.balance !== "string") continue;
196
+ let amount;
197
+ try {
198
+ amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
199
+ } catch {
200
+ continue;
201
+ }
202
+ if (!Number.isFinite(amount) || amount <= 0) continue;
203
+ pending.push({
204
+ chainId: e.chainId,
205
+ asset: e.tokenSymbol,
206
+ amount: String(amount),
207
+ isPending: e.isPending === true,
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
- return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
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
- if (!wallet.isDeployed) return;
1348
- await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
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
- const installed = await this.getPublicClient(chainId).readContract({
1359
- address: smartWallet,
1360
- abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1361
- functionName: "isModuleInstalled",
1362
- // moduleTypeId 1 = validator (ERC-7579)
1363
- args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
1364
- });
1365
- if (installed) return;
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] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
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
- await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
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,
@@ -2240,97 +2426,6 @@ function makeSponsoredWethCallback(deps) {
2240
2426
  };
2241
2427
  }
2242
2428
 
2243
- // src/lib/sponsored-calls-deposit.ts
2244
- import { encodeFunctionData, erc20Abi, toHex } from "viem";
2245
- var DEFAULT_POLL_INTERVAL_MS = 1500;
2246
- var DEFAULT_MAX_POLLS = 30;
2247
- async function paymasterSupported(provider, owner, chainId) {
2248
- const caps = await provider.request({
2249
- method: "wallet_getCapabilities",
2250
- params: [owner]
2251
- });
2252
- const forChain = caps?.[toHex(chainId)] ?? caps?.[String(chainId)];
2253
- return Boolean(forChain?.paymasterService?.supported);
2254
- }
2255
- function makeSponsoredCallsCallback(deps) {
2256
- const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2257
- const maxPolls = deps.maxPolls ?? DEFAULT_MAX_POLLS;
2258
- const absolutePaymasterUrl = () => {
2259
- const configured = deps.paymasterServiceUrl;
2260
- if (/^https?:\/\//i.test(configured)) return configured;
2261
- const origin = globalThis.location?.origin;
2262
- if (!origin) {
2263
- throw new OwneyError(
2264
- "SPONSORED_CALLS_UNSUPPORTED",
2265
- `paymasterServiceUrl "${configured}" is relative and no page origin is available to resolve it.`
2266
- );
2267
- }
2268
- return new URL(configured, origin).toString();
2269
- };
2270
- return async (smartWallet, chainId, amount) => {
2271
- const cid = chainId;
2272
- const token = deps.tokenAddressByChain[cid];
2273
- if (!token) {
2274
- throw new OwneyError(
2275
- "CHAIN_UNSUPPORTED",
2276
- `No sponsored token configured for chain ${chainId}`
2277
- );
2278
- }
2279
- if (!await paymasterSupported(deps.provider, deps.ownerAddress, chainId)) {
2280
- throw new OwneyError(
2281
- "SPONSORED_CALLS_UNSUPPORTED",
2282
- `Wallet does not advertise paymasterService on chain ${chainId}.`,
2283
- { chainId }
2284
- );
2285
- }
2286
- const data = encodeFunctionData({
2287
- abi: erc20Abi,
2288
- functionName: "transfer",
2289
- args: [smartWallet, BigInt(amount)]
2290
- });
2291
- const sendResult = await deps.provider.request({
2292
- method: "wallet_sendCalls",
2293
- params: [
2294
- {
2295
- version: "2.0.0",
2296
- from: deps.ownerAddress,
2297
- chainId: toHex(chainId),
2298
- atomicRequired: false,
2299
- calls: [{ to: token, value: "0x0", data }],
2300
- capabilities: {
2301
- paymasterService: { url: absolutePaymasterUrl() }
2302
- }
2303
- }
2304
- ]
2305
- });
2306
- deps.onApproved?.();
2307
- const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
2308
- if (!callsId) {
2309
- throw new OwneyError(
2310
- "SPONSORED_CALLS_NO_ID",
2311
- "Wallet accepted wallet_sendCalls but returned no calls id.",
2312
- { chainId }
2313
- );
2314
- }
2315
- for (let attempt = 0; attempt < maxPolls; attempt += 1) {
2316
- const status = await deps.provider.request({
2317
- method: "wallet_getCallsStatus",
2318
- params: [callsId]
2319
- });
2320
- const txHash = status?.receipts?.[0]?.transactionHash;
2321
- if (txHash) return txHash;
2322
- if (pollIntervalMs > 0) {
2323
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2324
- }
2325
- }
2326
- throw new OwneyError(
2327
- "SPONSORED_CALLS_NO_RECEIPT",
2328
- `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
2329
- { chainId, callsId }
2330
- );
2331
- };
2332
- }
2333
-
2334
2429
  // src/client.ts
2335
2430
  function encodeMultiAgentCursor(map) {
2336
2431
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -2392,15 +2487,12 @@ var OwneySDK = class {
2392
2487
  referralSource;
2393
2488
  cachedSponsoredCallback = null;
2394
2489
  cachedWethSponsoredCallback = null;
2395
- paymasterServiceUrl;
2396
- cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
2397
2490
  initializingAgentsPromise = null;
2398
2491
  constructor(config) {
2399
2492
  this.apiKey = config.apiKey;
2400
2493
  if (config.debug) setOwneyDebug(true);
2401
2494
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2402
2495
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2403
- this.paymasterServiceUrl = config.paymasterServiceUrl;
2404
2496
  this.referralSource = config.referralSource;
2405
2497
  }
2406
2498
  /**
@@ -2423,7 +2515,6 @@ var OwneySDK = class {
2423
2515
  this.state = { provider, walletAddress, chainId: null };
2424
2516
  this.cachedSponsoredCallback = null;
2425
2517
  this.cachedWethSponsoredCallback = null;
2426
- this.cachedSponsoredCallsCallbacks.clear();
2427
2518
  }
2428
2519
  /**
2429
2520
  * Disconnect from all agents and clear connection state.
@@ -2438,7 +2529,6 @@ var OwneySDK = class {
2438
2529
  this.state = null;
2439
2530
  this.cachedSponsoredCallback = null;
2440
2531
  this.cachedWethSponsoredCallback = null;
2441
- this.cachedSponsoredCallsCallbacks.clear();
2442
2532
  }
2443
2533
  isAgentDisabled(agentId) {
2444
2534
  return this.disabledAgents.has(agentId);
@@ -2520,26 +2610,6 @@ var OwneySDK = class {
2520
2610
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2521
2611
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
2522
2612
  */
2523
- getDefaultSponsoredCallsCallback(asset, onApproved) {
2524
- const cached = this.cachedSponsoredCallsCallbacks.get(asset);
2525
- if (!onApproved && cached) return cached;
2526
- const provider = this.requireConnectedProvider();
2527
- const callback = makeSponsoredCallsCallback({
2528
- provider,
2529
- ownerAddress: this.state.walletAddress,
2530
- paymasterServiceUrl: this.paymasterServiceUrl,
2531
- onApproved,
2532
- tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
2533
- });
2534
- if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
2535
- return callback;
2536
- }
2537
- /**
2538
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
2539
- * callback used when the caller omits `depositCallback` for a WETH deposit.
2540
- * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2541
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
2542
- */
2543
2613
  getDefaultWethSponsoredCallback(onApproved) {
2544
2614
  if (!onApproved && this.cachedWethSponsoredCallback)
2545
2615
  return this.cachedWethSponsoredCallback;
@@ -2762,7 +2832,7 @@ var OwneySDK = class {
2762
2832
  }
2763
2833
  const state = this.requireState();
2764
2834
  const chainId = this.requireChainId();
2765
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2835
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2766
2836
  if (agentId) {
2767
2837
  if (this.isAgentDisabled(agentId)) {
2768
2838
  throw new OwneyError(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.7-beta.5",
3
+ "version": "0.7.7-beta.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",