@owney/sdk 0.7.7-beta.5 → 0.7.7

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,
package/dist/index.d.cts CHANGED
@@ -191,6 +191,29 @@ interface OwneyPosition {
191
191
  * response later; undefined until then. */
192
192
  liquidity?: number;
193
193
  }
194
+ /**
195
+ * Funds that have landed in the agent's wallet but are not yet deployed into a
196
+ * protocol. Sourced from Zyfai `staleBalances[].isPending`. Absent when nothing
197
+ * is pending, and for agents that do not report it (sail).
198
+ */
199
+ interface OwneyPendingAllocation {
200
+ /** Deliberately loose (not OwneySupportedChainId) because Zyfai reports balances on unsupported chains. */
201
+ chainId: number;
202
+ /** Raw agent symbol, not normalized to OwneySupportedTokens; carries Zyfai's casing and "eth" alias for WETH. */
203
+ asset: string;
204
+ /** Decimal string, consistent with `OwneyToken.amount`. */
205
+ amount: string;
206
+ /**
207
+ * Zyfai's own `isPending` flag. TRUE means it is actively allocating. FALSE
208
+ * does NOT mean the entry is stale or safe to ignore: Zyfai flips it within
209
+ * minutes but leaves funds listed here indefinitely when it has not indexed
210
+ * the pool they landed in. Entries are surfaced regardless of this flag; use
211
+ * it only to decide wording, never to decide whether the money counts.
212
+ */
213
+ isPending: boolean;
214
+ /** ISO timestamp from Zyfai's `updatedAt`, when the payload carries one. */
215
+ since?: string;
216
+ }
194
217
  interface AgentBalance {
195
218
  smartWallet?: `0x${string}`;
196
219
  totalBalance: string;
@@ -202,6 +225,8 @@ interface AgentBalance {
202
225
  * them (zyfai). Omitted for agents that don't expose positions (sail).
203
226
  */
204
227
  positions?: OwneyPosition[];
228
+ /** Funds received but not yet deployed. Omitted when nothing is pending. */
229
+ pendingAllocations?: OwneyPendingAllocation[];
205
230
  }
206
231
  interface OwneyBalances {
207
232
  totalBalance: string;
package/dist/index.d.ts CHANGED
@@ -191,6 +191,29 @@ interface OwneyPosition {
191
191
  * response later; undefined until then. */
192
192
  liquidity?: number;
193
193
  }
194
+ /**
195
+ * Funds that have landed in the agent's wallet but are not yet deployed into a
196
+ * protocol. Sourced from Zyfai `staleBalances[].isPending`. Absent when nothing
197
+ * is pending, and for agents that do not report it (sail).
198
+ */
199
+ interface OwneyPendingAllocation {
200
+ /** Deliberately loose (not OwneySupportedChainId) because Zyfai reports balances on unsupported chains. */
201
+ chainId: number;
202
+ /** Raw agent symbol, not normalized to OwneySupportedTokens; carries Zyfai's casing and "eth" alias for WETH. */
203
+ asset: string;
204
+ /** Decimal string, consistent with `OwneyToken.amount`. */
205
+ amount: string;
206
+ /**
207
+ * Zyfai's own `isPending` flag. TRUE means it is actively allocating. FALSE
208
+ * does NOT mean the entry is stale or safe to ignore: Zyfai flips it within
209
+ * minutes but leaves funds listed here indefinitely when it has not indexed
210
+ * the pool they landed in. Entries are surfaced regardless of this flag; use
211
+ * it only to decide wording, never to decide whether the money counts.
212
+ */
213
+ isPending: boolean;
214
+ /** ISO timestamp from Zyfai's `updatedAt`, when the payload carries one. */
215
+ since?: string;
216
+ }
194
217
  interface AgentBalance {
195
218
  smartWallet?: `0x${string}`;
196
219
  totalBalance: string;
@@ -202,6 +225,8 @@ interface AgentBalance {
202
225
  * them (zyfai). Omitted for agents that don't expose positions (sail).
203
226
  */
204
227
  positions?: OwneyPosition[];
228
+ /** Funds received but not yet deployed. Omitted when nothing is pending. */
229
+ pendingAllocations?: OwneyPendingAllocation[];
205
230
  }
206
231
  interface OwneyBalances {
207
232
  totalBalance: string;
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,
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",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",