@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 +201 -15
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +201 -15
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
1381
|
-
|
|
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
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
1400
|
+
const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
|
|
1401
|
+
debugLog(
|
|
1402
|
+
"zyfai:onboard",
|
|
1403
|
+
`wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
|
|
1404
|
+
{
|
|
1405
|
+
source: "getSmartWalletByEOA fallback",
|
|
1406
|
+
poolRecord: byEoa?.smartWallet
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1409
|
+
return isPool;
|
|
1310
1410
|
} catch (error) {
|
|
1311
1411
|
console.warn(
|
|
1312
1412
|
"[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
|
|
@@ -1344,8 +1444,24 @@ var ZyfaiAgent = class {
|
|
|
1344
1444
|
ownerAddress,
|
|
1345
1445
|
chainId
|
|
1346
1446
|
);
|
|
1347
|
-
|
|
1348
|
-
|
|
1447
|
+
debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
|
|
1448
|
+
smartWallet: wallet.address,
|
|
1449
|
+
isDeployed: wallet.isDeployed
|
|
1450
|
+
});
|
|
1451
|
+
if (!wallet.isDeployed) {
|
|
1452
|
+
debugLog(
|
|
1453
|
+
"zyfai:onboard",
|
|
1454
|
+
"Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
|
|
1455
|
+
);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1459
|
+
await this.ensureSessionKey(
|
|
1460
|
+
ownerAddress,
|
|
1461
|
+
chainId,
|
|
1462
|
+
wallet.address,
|
|
1463
|
+
hasActiveSessionKey
|
|
1464
|
+
);
|
|
1349
1465
|
} catch (error) {
|
|
1350
1466
|
console.warn(
|
|
1351
1467
|
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
@@ -1353,20 +1469,51 @@ var ZyfaiAgent = class {
|
|
|
1353
1469
|
);
|
|
1354
1470
|
}
|
|
1355
1471
|
}
|
|
1356
|
-
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1472
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
|
|
1473
|
+
const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
|
|
1474
|
+
if (this.sessionKeyAttempts.has(attemptKey)) {
|
|
1475
|
+
debugLog(
|
|
1476
|
+
"zyfai:onboard",
|
|
1477
|
+
"session key: already attempted this session, skipping",
|
|
1478
|
+
{ chainId }
|
|
1479
|
+
);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1357
1482
|
try {
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1483
|
+
if (hasActiveSessionKey === true) {
|
|
1484
|
+
debugLog(
|
|
1485
|
+
"zyfai:onboard",
|
|
1486
|
+
"session key: already on record -> nothing to do",
|
|
1487
|
+
{ chainId }
|
|
1488
|
+
);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (hasActiveSessionKey === void 0) {
|
|
1492
|
+
const installed = await this.readSmartSessionsInstalled(
|
|
1493
|
+
chainId,
|
|
1494
|
+
smartWallet
|
|
1495
|
+
);
|
|
1496
|
+
debugLog(
|
|
1497
|
+
"zyfai:onboard",
|
|
1498
|
+
`session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
|
|
1499
|
+
{ smartWallet, chainId }
|
|
1500
|
+
);
|
|
1501
|
+
if (installed) return;
|
|
1502
|
+
}
|
|
1503
|
+
this.sessionKeyAttempts.add(attemptKey);
|
|
1366
1504
|
console.warn(
|
|
1367
|
-
`[zyfai]
|
|
1505
|
+
`[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
|
|
1368
1506
|
);
|
|
1507
|
+
debugLog("zyfai:onboard", "session key: calling createSessionKey", {
|
|
1508
|
+
chainId,
|
|
1509
|
+
smartWallet,
|
|
1510
|
+
hasActiveSessionKey
|
|
1511
|
+
});
|
|
1369
1512
|
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1513
|
+
debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
|
|
1514
|
+
chainId,
|
|
1515
|
+
smartWallet
|
|
1516
|
+
});
|
|
1370
1517
|
} catch (error) {
|
|
1371
1518
|
console.warn(
|
|
1372
1519
|
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
@@ -1374,6 +1521,16 @@ var ZyfaiAgent = class {
|
|
|
1374
1521
|
);
|
|
1375
1522
|
}
|
|
1376
1523
|
}
|
|
1524
|
+
/** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
|
|
1525
|
+
async readSmartSessionsInstalled(chainId, smartWallet) {
|
|
1526
|
+
return this.getPublicClient(chainId).readContract({
|
|
1527
|
+
address: smartWallet,
|
|
1528
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1529
|
+
functionName: "isModuleInstalled",
|
|
1530
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1531
|
+
args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1377
1534
|
/**
|
|
1378
1535
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1379
1536
|
* funds into it, and return its address.
|
|
@@ -1394,8 +1551,24 @@ var ZyfaiAgent = class {
|
|
|
1394
1551
|
*/
|
|
1395
1552
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1396
1553
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1554
|
+
debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
|
|
1555
|
+
eoa: ownerAddress,
|
|
1556
|
+
smartWallet: wallet.address,
|
|
1557
|
+
isDeployed: wallet.isDeployed,
|
|
1558
|
+
isOwner: wallet.isOwner
|
|
1559
|
+
});
|
|
1397
1560
|
if (wallet.isDeployed) {
|
|
1398
|
-
|
|
1561
|
+
debugLog(
|
|
1562
|
+
"zyfai:onboard",
|
|
1563
|
+
"Safe already has code -> no deploy needed, checking the session key"
|
|
1564
|
+
);
|
|
1565
|
+
const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
|
|
1566
|
+
await this.ensureSessionKey(
|
|
1567
|
+
ownerAddress,
|
|
1568
|
+
chainId,
|
|
1569
|
+
wallet.address,
|
|
1570
|
+
hasActiveSessionKey
|
|
1571
|
+
);
|
|
1399
1572
|
return wallet.address;
|
|
1400
1573
|
}
|
|
1401
1574
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
@@ -1411,14 +1584,27 @@ var ZyfaiAgent = class {
|
|
|
1411
1584
|
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1412
1585
|
);
|
|
1413
1586
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1587
|
+
debugLog(
|
|
1588
|
+
"zyfai:onboard",
|
|
1589
|
+
`Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
|
|
1590
|
+
);
|
|
1414
1591
|
try {
|
|
1415
1592
|
await first();
|
|
1593
|
+
debugLog(
|
|
1594
|
+
"zyfai:onboard",
|
|
1595
|
+
`${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
|
|
1596
|
+
);
|
|
1416
1597
|
} catch (error) {
|
|
1417
1598
|
console.warn(
|
|
1418
1599
|
`[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
|
|
1419
1600
|
error instanceof Error ? error.message : String(error)
|
|
1420
1601
|
);
|
|
1602
|
+
debugLog(
|
|
1603
|
+
"zyfai:onboard",
|
|
1604
|
+
`falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
|
|
1605
|
+
);
|
|
1421
1606
|
await second();
|
|
1607
|
+
debugLog("zyfai:onboard", "fallback deploy path succeeded");
|
|
1422
1608
|
}
|
|
1423
1609
|
const redeployed = await this.sdk.getSmartWalletAddress(
|
|
1424
1610
|
ownerAddress,
|