@owney/sdk 0.7.5 → 0.7.7-beta.1

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
@@ -825,6 +825,9 @@ function debugLog(scope, message, data) {
825
825
  }
826
826
 
827
827
  // src/agents/zyfai/zyfai.agent.ts
828
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem.parseAbi)([
829
+ "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
830
+ ]);
828
831
  var DEFAULT_ZYFAI_RPC_URLS = {
829
832
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
830
833
  42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
@@ -1142,7 +1145,12 @@ var ZyfaiAgent = class {
1142
1145
  const validChainId = isValidChainId(chainId);
1143
1146
  await Promise.all([
1144
1147
  this.ensureProtocolsSynced(validChainId, "USDC"),
1145
- this.ensureProtocolsSynced(validChainId, "WETH")
1148
+ this.ensureProtocolsSynced(validChainId, "WETH"),
1149
+ // Retrofit a session key for old wallets that are already deployed and
1150
+ // funded but never got one — otherwise their balance stays idle until
1151
+ // they happen to deposit again. Only touches Safes that are missing the
1152
+ // validator, so pool-wallet users see no prompt. Non-fatal.
1153
+ this.ensureSessionKeyIfDeployed(validChainId)
1146
1154
  ]);
1147
1155
  } catch (error) {
1148
1156
  throw error;
@@ -1328,7 +1336,43 @@ var ZyfaiAgent = class {
1328
1336
  * recoverable (ensureWalletDeployed tries the other deploy call), but
1329
1337
  * failing the deposit outright is not.
1330
1338
  */
1339
+ /**
1340
+ * Zyfai's own answer to "what kind of wallet is this, and can the agent act on
1341
+ * it", read from the positions payload (`GET /data/position`, which
1342
+ * `getPositions` wraps).
1343
+ *
1344
+ * `predeployed` and `hasActiveSessionKey` are both returned at runtime but
1345
+ * declared on neither `Portfolio` nor their docs, hence the cast. Verified
1346
+ * live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
1347
+ * false}` while a pool wallet reports `{true, true}`.
1348
+ *
1349
+ * Preferred over the SDK's private `isPredeployed`, which is cached from the
1350
+ * login response and can go stale mid-session. Returns an empty object on any
1351
+ * failure so every caller degrades to its own fallback.
1352
+ */
1353
+ async readWalletState(ownerAddress) {
1354
+ try {
1355
+ const { portfolio } = await this.sdk.getPositions(ownerAddress);
1356
+ const raw = portfolio;
1357
+ return {
1358
+ predeployed: raw?.predeployed,
1359
+ hasActiveSessionKey: raw?.hasActiveSessionKey
1360
+ };
1361
+ } catch (error) {
1362
+ console.warn(
1363
+ "[zyfai] getPositions wallet-state read failed (non-fatal):",
1364
+ error instanceof Error ? error.message : String(error)
1365
+ );
1366
+ return {};
1367
+ }
1368
+ }
1331
1369
  async isPredeployedPoolWallet(ownerAddress, smartWallet) {
1370
+ const { predeployed } = await this.readWalletState(ownerAddress);
1371
+ if (typeof predeployed === "boolean") return predeployed;
1372
+ const internals = this.sdk;
1373
+ if (typeof internals.isPredeployed === "boolean") {
1374
+ return internals.isPredeployed;
1375
+ }
1332
1376
  try {
1333
1377
  const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
1334
1378
  return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
@@ -1340,6 +1384,85 @@ var ZyfaiAgent = class {
1340
1384
  return false;
1341
1385
  }
1342
1386
  }
1387
+ /**
1388
+ * Ensure Zyfai's automation can actually act on the user's Safe for this
1389
+ * chain, by making sure a session key exists.
1390
+ *
1391
+ * Old wallets (pre-0.2.44) are deployed WITHOUT the smart-sessions validator,
1392
+ * so Zyfai's rebalancer has no authorised path to move funds: deposits land
1393
+ * and then sit as raw tokens forever. Pre-deployed pool wallets ship with the
1394
+ * module already installed, which is why they never needed this.
1395
+ *
1396
+ * The gate is the on-chain module, not the wallet's class: it is the exact
1397
+ * condition that matters, it needs no guessing from API shapes, and it means
1398
+ * pool-wallet users are never prompted for a signature they don't need.
1399
+ *
1400
+ * Non-fatal by design. A failure here must not block a deposit — the funds
1401
+ * still arrive safely in the Safe, they just won't be deployed into a pool
1402
+ * until a session key exists.
1403
+ */
1404
+ /**
1405
+ * Activation-time variant: only acts when the Safe already has code on this
1406
+ * chain. Activation must never deploy anything — that is deposit's job, and it
1407
+ * would mean surprise on-chain work just for opening the app.
1408
+ */
1409
+ async ensureSessionKeyIfDeployed(chainId) {
1410
+ try {
1411
+ const ownerAddress = this.getAddress();
1412
+ const wallet = await this.sdk.getSmartWalletAddress(
1413
+ ownerAddress,
1414
+ chainId
1415
+ );
1416
+ if (!wallet.isDeployed) return;
1417
+ const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1418
+ await this.ensureSessionKey(
1419
+ ownerAddress,
1420
+ chainId,
1421
+ wallet.address,
1422
+ hasActiveSessionKey
1423
+ );
1424
+ } catch (error) {
1425
+ console.warn(
1426
+ "[zyfai] session-key check at activation failed (non-fatal):",
1427
+ error instanceof Error ? error.message : String(error)
1428
+ );
1429
+ }
1430
+ }
1431
+ async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
1432
+ try {
1433
+ if (hasActiveSessionKey === false) {
1434
+ console.warn(
1435
+ `[zyfai] no session key on record for ${ownerAddress}; creating one for chain ${chainId}.`
1436
+ );
1437
+ await this.sdk.createSessionKey(ownerAddress, chainId);
1438
+ return;
1439
+ }
1440
+ const installed = await this.readSmartSessionsInstalled(
1441
+ chainId,
1442
+ smartWallet
1443
+ );
1444
+ if (installed) return;
1445
+ console.warn(
1446
+ `[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
1447
+ );
1448
+ await this.sdk.createSessionKey(ownerAddress, chainId);
1449
+ } catch (error) {
1450
+ console.warn(
1451
+ "[zyfai] createSessionKey skipped (non-fatal):",
1452
+ error instanceof Error ? error.message : String(error)
1453
+ );
1454
+ }
1455
+ }
1456
+ /** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
1457
+ async readSmartSessionsInstalled(chainId, smartWallet) {
1458
+ return this.getPublicClient(chainId).readContract({
1459
+ address: smartWallet,
1460
+ abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1461
+ functionName: "isModuleInstalled",
1462
+ // moduleTypeId 1 = validator (ERC-7579)
1463
+ args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
1464
+ });
1465
+ }
1343
1466
  /**
1344
1467
  * Guarantee the user's Safe has code on `chainId` before anything transfers
1345
1468
  * funds into it, and return its address.
@@ -1360,17 +1483,27 @@ var ZyfaiAgent = class {
1360
1483
  */
1361
1484
  async ensureWalletDeployed(ownerAddress, chainId) {
1362
1485
  const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
1363
- if (wallet.isDeployed) return wallet.address;
1486
+ if (wallet.isDeployed) {
1487
+ const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1488
+ await this.ensureSessionKey(
1489
+ ownerAddress,
1490
+ chainId,
1491
+ wallet.address,
1492
+ hasActiveSessionKey
1493
+ );
1494
+ return wallet.address;
1495
+ }
1364
1496
  const poolFirst = await this.isPredeployedPoolWallet(
1365
1497
  ownerAddress,
1366
1498
  wallet.address
1367
1499
  );
1368
1500
  const deployAsPool = () => this.sdk.deployOnChains([chainId]);
1369
1501
  const deployAsLegacy = () => (
1370
- // `createSessionKey: false` — session keys are backend-owned since the
1371
- // predeployed-pool rollout; asking for one here costs a signature prompt
1372
- // and is rejected anyway.
1373
- this.sdk.deploySafe(ownerAddress, chainId, "aggressive", false)
1502
+ // `createSessionKey: true` — per Zyfai, the old-wallet flow is
1503
+ // deploySafe(true) -> createSessionKey() -> deposit. Passing false (which
1504
+ // this did originally) deploys a Safe with no smart-sessions validator, so
1505
+ // deposits land and are never allocated into a pool.
1506
+ this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
1374
1507
  );
1375
1508
  const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
1376
1509
  try {
@@ -1394,6 +1527,7 @@ var ZyfaiAgent = class {
1394
1527
  this.id
1395
1528
  );
1396
1529
  }
1530
+ await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
1397
1531
  return redeployed.address;
1398
1532
  }
1399
1533
  async deposit(state, chainId, amount, asset, depositCallback) {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/agents/zyfai/zyfai.agent.ts
2
- import { ZyfaiSDK } from "@zyfai/sdk";
3
- import { createPublicClient, http } from "viem";
2
+ import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
3
+ import { createPublicClient, http, parseAbi } from "viem";
4
4
  import { base, arbitrum, mainnet } from "viem/chains";
5
5
 
6
6
  // src/types/config.ts
@@ -792,6 +792,9 @@ function debugLog(scope, message, data) {
792
792
  }
793
793
 
794
794
  // src/agents/zyfai/zyfai.agent.ts
795
+ var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
796
+ "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
797
+ ]);
795
798
  var DEFAULT_ZYFAI_RPC_URLS = {
796
799
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
797
800
  42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
@@ -1109,7 +1112,12 @@ var ZyfaiAgent = class {
1109
1112
  const validChainId = isValidChainId(chainId);
1110
1113
  await Promise.all([
1111
1114
  this.ensureProtocolsSynced(validChainId, "USDC"),
1112
- this.ensureProtocolsSynced(validChainId, "WETH")
1115
+ this.ensureProtocolsSynced(validChainId, "WETH"),
1116
+ // Retrofit a session key for old wallets that are already deployed and
1117
+ // funded but never got one — otherwise their balance stays idle until
1118
+ // they happen to deposit again. Only touches Safes that are missing the
1119
+ // validator, so pool-wallet users see no prompt. Non-fatal.
1120
+ this.ensureSessionKeyIfDeployed(validChainId)
1113
1121
  ]);
1114
1122
  } catch (error) {
1115
1123
  throw error;
@@ -1295,7 +1303,43 @@ var ZyfaiAgent = class {
1295
1303
  * recoverable (ensureWalletDeployed tries the other deploy call), but
1296
1304
  * failing the deposit outright is not.
1297
1305
  */
1306
+ /**
1307
+ * Zyfai's own answer to "what kind of wallet is this, and can the agent act on
1308
+ * it", read from the positions payload (`GET /data/position`, which
1309
+ * `getPositions` wraps).
1310
+ *
1311
+ * `predeployed` and `hasActiveSessionKey` are both returned at runtime but
1312
+ * declared on neither `Portfolio` nor their docs, hence the cast. Verified
1313
+ * live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
1314
+ * false}` while a pool wallet reports `{true, true}`.
1315
+ *
1316
+ * Preferred over the SDK's private `isPredeployed`, which is cached from the
1317
+ * login response and can go stale mid-session. Returns an empty object on any
1318
+ * failure so every caller degrades to its own fallback.
1319
+ */
1320
+ async readWalletState(ownerAddress) {
1321
+ try {
1322
+ const { portfolio } = await this.sdk.getPositions(ownerAddress);
1323
+ const raw = portfolio;
1324
+ return {
1325
+ predeployed: raw?.predeployed,
1326
+ hasActiveSessionKey: raw?.hasActiveSessionKey
1327
+ };
1328
+ } catch (error) {
1329
+ console.warn(
1330
+ "[zyfai] getPositions wallet-state read failed (non-fatal):",
1331
+ error instanceof Error ? error.message : String(error)
1332
+ );
1333
+ return {};
1334
+ }
1335
+ }
1298
1336
  async isPredeployedPoolWallet(ownerAddress, smartWallet) {
1337
+ const { predeployed } = await this.readWalletState(ownerAddress);
1338
+ if (typeof predeployed === "boolean") return predeployed;
1339
+ const internals = this.sdk;
1340
+ if (typeof internals.isPredeployed === "boolean") {
1341
+ return internals.isPredeployed;
1342
+ }
1299
1343
  try {
1300
1344
  const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
1301
1345
  return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
@@ -1307,6 +1351,85 @@ var ZyfaiAgent = class {
1307
1351
  return false;
1308
1352
  }
1309
1353
  }
1354
+ /**
1355
+ * Ensure Zyfai's automation can actually act on the user's Safe for this
1356
+ * chain, by making sure a session key exists.
1357
+ *
1358
+ * Old wallets (pre-0.2.44) are deployed WITHOUT the smart-sessions validator,
1359
+ * so Zyfai's rebalancer has no authorised path to move funds: deposits land
1360
+ * and then sit as raw tokens forever. Pre-deployed pool wallets ship with the
1361
+ * module already installed, which is why they never needed this.
1362
+ *
1363
+ * The gate is the on-chain module, not the wallet's class: it is the exact
1364
+ * condition that matters, it needs no guessing from API shapes, and it means
1365
+ * pool-wallet users are never prompted for a signature they don't need.
1366
+ *
1367
+ * Non-fatal by design. A failure here must not block a deposit — the funds
1368
+ * still arrive safely in the Safe, they just won't be deployed into a pool
1369
+ * until a session key exists.
1370
+ */
1371
+ /**
1372
+ * Activation-time variant: only acts when the Safe already has code on this
1373
+ * chain. Activation must never deploy anything — that is deposit's job, and it
1374
+ * would mean surprise on-chain work just for opening the app.
1375
+ */
1376
+ async ensureSessionKeyIfDeployed(chainId) {
1377
+ try {
1378
+ const ownerAddress = this.getAddress();
1379
+ const wallet = await this.sdk.getSmartWalletAddress(
1380
+ ownerAddress,
1381
+ chainId
1382
+ );
1383
+ if (!wallet.isDeployed) return;
1384
+ const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1385
+ await this.ensureSessionKey(
1386
+ ownerAddress,
1387
+ chainId,
1388
+ wallet.address,
1389
+ hasActiveSessionKey
1390
+ );
1391
+ } catch (error) {
1392
+ console.warn(
1393
+ "[zyfai] session-key check at activation failed (non-fatal):",
1394
+ error instanceof Error ? error.message : String(error)
1395
+ );
1396
+ }
1397
+ }
1398
+ async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
1399
+ try {
1400
+ if (hasActiveSessionKey === false) {
1401
+ console.warn(
1402
+ `[zyfai] no session key on record for ${ownerAddress}; creating one for chain ${chainId}.`
1403
+ );
1404
+ await this.sdk.createSessionKey(ownerAddress, chainId);
1405
+ return;
1406
+ }
1407
+ const installed = await this.readSmartSessionsInstalled(
1408
+ chainId,
1409
+ smartWallet
1410
+ );
1411
+ if (installed) return;
1412
+ console.warn(
1413
+ `[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
1414
+ );
1415
+ await this.sdk.createSessionKey(ownerAddress, chainId);
1416
+ } catch (error) {
1417
+ console.warn(
1418
+ "[zyfai] createSessionKey skipped (non-fatal):",
1419
+ error instanceof Error ? error.message : String(error)
1420
+ );
1421
+ }
1422
+ }
1423
+ /** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
1424
+ async readSmartSessionsInstalled(chainId, smartWallet) {
1425
+ return this.getPublicClient(chainId).readContract({
1426
+ address: smartWallet,
1427
+ abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1428
+ functionName: "isModuleInstalled",
1429
+ // moduleTypeId 1 = validator (ERC-7579)
1430
+ args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
1431
+ });
1432
+ }
1310
1433
  /**
1311
1434
  * Guarantee the user's Safe has code on `chainId` before anything transfers
1312
1435
  * funds into it, and return its address.
@@ -1327,17 +1450,27 @@ var ZyfaiAgent = class {
1327
1450
  */
1328
1451
  async ensureWalletDeployed(ownerAddress, chainId) {
1329
1452
  const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
1330
- if (wallet.isDeployed) return wallet.address;
1453
+ if (wallet.isDeployed) {
1454
+ const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1455
+ await this.ensureSessionKey(
1456
+ ownerAddress,
1457
+ chainId,
1458
+ wallet.address,
1459
+ hasActiveSessionKey
1460
+ );
1461
+ return wallet.address;
1462
+ }
1331
1463
  const poolFirst = await this.isPredeployedPoolWallet(
1332
1464
  ownerAddress,
1333
1465
  wallet.address
1334
1466
  );
1335
1467
  const deployAsPool = () => this.sdk.deployOnChains([chainId]);
1336
1468
  const deployAsLegacy = () => (
1337
- // `createSessionKey: false` — session keys are backend-owned since the
1338
- // predeployed-pool rollout; asking for one here costs a signature prompt
1339
- // and is rejected anyway.
1340
- this.sdk.deploySafe(ownerAddress, chainId, "aggressive", false)
1469
+ // `createSessionKey: true` — per Zyfai, the old-wallet flow is
1470
+ // deploySafe(true) -> createSessionKey() -> deposit. Passing false (which
1471
+ // this did originally) deploys a Safe with no smart-sessions validator, so
1472
+ // deposits land and are never allocated into a pool.
1473
+ this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
1341
1474
  );
1342
1475
  const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
1343
1476
  try {
@@ -1361,6 +1494,7 @@ var ZyfaiAgent = class {
1361
1494
  this.id
1362
1495
  );
1363
1496
  }
1497
+ await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
1364
1498
  return redeployed.address;
1365
1499
  }
1366
1500
  async deposit(state, chainId, amount, asset, depositCallback) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.5",
3
+ "version": "0.7.7-beta.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",