@owney/sdk 0.7.25-beta.10 → 0.7.25-beta.11
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 +901 -242
- package/dist/index.d.cts +317 -225
- package/dist/index.d.ts +317 -225
- package/dist/index.js +840 -181
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
+
// src/lib/funding-evidence.ts
|
|
2
|
+
import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
|
|
3
|
+
function notifyFunding(callback, evidence) {
|
|
4
|
+
try {
|
|
5
|
+
void Promise.resolve(callback?.({ ...evidence })).catch(() => {
|
|
6
|
+
});
|
|
7
|
+
} catch {
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
var fullWithdrawalAbi = parseAbi(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
11
|
+
var withdrawalEventAbi = parseAbi(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
12
|
+
function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
|
|
13
|
+
if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || transaction.to?.toLowerCase() !== evidence.agentWallet?.toLowerCase() || transaction.from.toLowerCase() !== evidence.walletAddress.toLowerCase()) return false;
|
|
14
|
+
try {
|
|
15
|
+
const decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data: transaction.input });
|
|
16
|
+
return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
22
|
+
if (receipt.status !== "success") return { ...evidence, transfer: "failed", delivery: "failed" };
|
|
23
|
+
let amount = 0n;
|
|
24
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25
|
+
const attributed = /* @__PURE__ */ new Map();
|
|
26
|
+
const agent = evidence.agentWallet?.toLowerCase();
|
|
27
|
+
const owner = evidence.walletAddress.toLowerCase();
|
|
28
|
+
const logIndexes = [];
|
|
29
|
+
const transferLogs = [];
|
|
30
|
+
const zeroTransferLogs = [];
|
|
31
|
+
for (const log of [...receipt.logs].sort((a, b) => a.logIndex - b.logIndex)) {
|
|
32
|
+
if (log.address.toLowerCase() !== tokenAddress.toLowerCase()) continue;
|
|
33
|
+
if (seen.has(log.logIndex)) continue;
|
|
34
|
+
seen.add(log.logIndex);
|
|
35
|
+
try {
|
|
36
|
+
const event = decodeEventLog({ abi: erc20Abi, data: log.data, topics: log.topics });
|
|
37
|
+
if (event.eventName !== "Transfer") continue;
|
|
38
|
+
const { from, to, value } = event.args;
|
|
39
|
+
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
40
|
+
zeroTransferLogs.push(log.logIndex);
|
|
41
|
+
let matched = 0n;
|
|
42
|
+
if (direction === "deposit") {
|
|
43
|
+
if (to.toLowerCase() === agent) matched = value;
|
|
44
|
+
} else {
|
|
45
|
+
const sender = from.toLowerCase();
|
|
46
|
+
const recipient = to.toLowerCase();
|
|
47
|
+
const available = sender === agent ? value : attributed.get(sender) ?? 0n;
|
|
48
|
+
const carried = available < value ? available : value;
|
|
49
|
+
if (sender !== agent) attributed.set(sender, available - carried);
|
|
50
|
+
if (recipient === owner) matched = carried;
|
|
51
|
+
else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
52
|
+
}
|
|
53
|
+
if (matched === 0n) continue;
|
|
54
|
+
amount += matched;
|
|
55
|
+
transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
|
|
56
|
+
logIndexes.push(log.logIndex);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const expected = /^\d+$/.test(evidence.intendedAmount) ? BigInt(evidence.intendedAmount) : void 0;
|
|
61
|
+
const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && receipt.logs.some((log) => {
|
|
62
|
+
if (log.address.toLowerCase() !== agent) return false;
|
|
63
|
+
try {
|
|
64
|
+
const { args } = decodeEventLog({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
|
|
65
|
+
return args.owner.toLowerCase() === owner && args.recipient.toLowerCase() === owner && args.token.toLowerCase() === tokenAddress.toLowerCase() && args.amount === 0n;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (emptyPayout) return {
|
|
71
|
+
...evidence,
|
|
72
|
+
blockNumber: receipt.blockNumber.toString(),
|
|
73
|
+
confirmedAmount: "0",
|
|
74
|
+
logIndexes: zeroTransferLogs,
|
|
75
|
+
transferLogs: zeroTransferLogs.map((logIndex) => ({ logIndex, amountRaw: "0" })),
|
|
76
|
+
transfer: "failed",
|
|
77
|
+
delivery: "failed"
|
|
78
|
+
};
|
|
79
|
+
const delivered = amount > 0n && (expected === void 0 || amount >= expected);
|
|
80
|
+
return {
|
|
81
|
+
...evidence,
|
|
82
|
+
blockNumber: receipt.blockNumber.toString(),
|
|
83
|
+
logIndexes,
|
|
84
|
+
transferLogs,
|
|
85
|
+
...amount > 0n ? { confirmedAmount: amount.toString() } : {},
|
|
86
|
+
transfer: delivered ? "confirmed" : "unknown",
|
|
87
|
+
delivery: direction === "withdraw" ? delivered ? "confirmed" : "unknown" : "not-applicable"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
1
91
|
// src/lib/deposit-batch-callback.ts
|
|
2
92
|
var batchCallbacks = /* @__PURE__ */ new WeakMap();
|
|
3
93
|
var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
|
|
@@ -212,9 +302,72 @@ var AgentReads = class {
|
|
|
212
302
|
}
|
|
213
303
|
};
|
|
214
304
|
|
|
305
|
+
// src/agents/zyfai/zyfai.withdraw-submission.ts
|
|
306
|
+
var listenersByClient = /* @__PURE__ */ new WeakMap();
|
|
307
|
+
function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
308
|
+
const client = sdk.httpClient;
|
|
309
|
+
if (!client || typeof client.get !== "function" || typeof client.post !== "function")
|
|
310
|
+
return { attempted: () => void 0, rejected: () => false, request: () => void 0, stop: () => {
|
|
311
|
+
} };
|
|
312
|
+
let listeners = listenersByClient.get(client);
|
|
313
|
+
if (!listeners) {
|
|
314
|
+
listeners = /* @__PURE__ */ new Set();
|
|
315
|
+
listenersByClient.set(client, listeners);
|
|
316
|
+
const observers = listeners;
|
|
317
|
+
for (const method of ["get", "post"]) {
|
|
318
|
+
const original = client[method];
|
|
319
|
+
client[method] = async function(...args) {
|
|
320
|
+
const [url, options] = args;
|
|
321
|
+
const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
|
|
322
|
+
const payload = method === "get" ? options?.params : options;
|
|
323
|
+
const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
|
|
324
|
+
const startedAt = Date.now();
|
|
325
|
+
const request2 = {
|
|
326
|
+
attempted: true,
|
|
327
|
+
method: method === "get" ? "GET" : "POST",
|
|
328
|
+
path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
|
|
329
|
+
};
|
|
330
|
+
const notify = (outcome) => {
|
|
331
|
+
if (outcome === "rejected" && matching.length !== 1) return;
|
|
332
|
+
for (const observer of matching) {
|
|
333
|
+
try {
|
|
334
|
+
observer.notify(outcome, { ...request2 });
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
notify("attempted");
|
|
340
|
+
try {
|
|
341
|
+
return await original.apply(this, args);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
const message = error instanceof Error ? error.message : "";
|
|
344
|
+
request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
345
|
+
if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
|
|
346
|
+
throw error;
|
|
347
|
+
} finally {
|
|
348
|
+
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
349
|
+
notify("finished");
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
let submitted = false;
|
|
355
|
+
let rejected = false;
|
|
356
|
+
let request = { attempted: false };
|
|
357
|
+
const listener = { chainId, token, notify: (outcome, evidence) => {
|
|
358
|
+
request = evidence;
|
|
359
|
+
if (outcome === "attempted") submitted = true;
|
|
360
|
+
else if (outcome === "rejected") rejected = true;
|
|
361
|
+
} };
|
|
362
|
+
listeners.add(listener);
|
|
363
|
+
return { attempted: () => submitted, rejected: () => rejected, request: () => ({ ...request }), stop: () => {
|
|
364
|
+
listeners.delete(listener);
|
|
365
|
+
} };
|
|
366
|
+
}
|
|
367
|
+
|
|
215
368
|
// src/agents/zyfai/zyfai.agent.ts
|
|
216
369
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
217
|
-
import { parseAbi } from "viem";
|
|
370
|
+
import { formatUnits, parseAbi as parseAbi2 } from "viem";
|
|
218
371
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
219
372
|
|
|
220
373
|
// src/types/config.ts
|
|
@@ -387,7 +540,11 @@ function mapPendingAllocations(raw2) {
|
|
|
387
540
|
}
|
|
388
541
|
function mapBalances(raw2, _chainId, smartWallet) {
|
|
389
542
|
const portfolio = raw2.portfolio;
|
|
390
|
-
|
|
543
|
+
if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
|
|
544
|
+
throw new Error("Zyfai balance snapshot is unavailable.");
|
|
545
|
+
const portfolioByChain = portfolio.portfolioByChain;
|
|
546
|
+
const fetchedAt = Date.now();
|
|
547
|
+
const provenance = portfolio;
|
|
391
548
|
let totalBalance = 0;
|
|
392
549
|
const tokens = [];
|
|
393
550
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
@@ -396,7 +553,8 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
396
553
|
symbol,
|
|
397
554
|
{ balance, balanceWithFee, decimals }
|
|
398
555
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
399
|
-
|
|
556
|
+
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
557
|
+
const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
|
|
400
558
|
totalBalance += amount;
|
|
401
559
|
tokens.push({
|
|
402
560
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
@@ -446,6 +604,15 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
446
604
|
totalBalanceAsset: "usdc",
|
|
447
605
|
tokens,
|
|
448
606
|
positions,
|
|
607
|
+
readMetadata: tokens.map((token) => ({
|
|
608
|
+
chainId: token.chainId,
|
|
609
|
+
asset: token.asset,
|
|
610
|
+
fetchedAt,
|
|
611
|
+
providerUpdatedAt: typeof provenance.updatedAt === "string" ? provenance.updatedAt : void 0,
|
|
612
|
+
snapshotId: provenance.snapshotId === void 0 ? void 0 : String(provenance.snapshotId),
|
|
613
|
+
blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
|
|
614
|
+
positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
|
|
615
|
+
})),
|
|
449
616
|
...pendingAllocations ? { pendingAllocations } : {}
|
|
450
617
|
};
|
|
451
618
|
}
|
|
@@ -786,28 +953,6 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
|
|
|
786
953
|
return { walletAddress: raw2.walletAddress, chainId, assets };
|
|
787
954
|
}
|
|
788
955
|
|
|
789
|
-
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
790
|
-
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
791
|
-
function topicToAddress(topic) {
|
|
792
|
-
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
793
|
-
}
|
|
794
|
-
function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
795
|
-
const wantRecipient = recipient.toLowerCase();
|
|
796
|
-
const wantToken = token.toLowerCase();
|
|
797
|
-
let total = 0n;
|
|
798
|
-
let matched = false;
|
|
799
|
-
for (const log of logs) {
|
|
800
|
-
if (log.address.toLowerCase() !== wantToken) continue;
|
|
801
|
-
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
|
|
802
|
-
if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
|
|
803
|
-
if (!log.data || log.data === "0x") continue;
|
|
804
|
-
total += BigInt(log.data);
|
|
805
|
-
matched = true;
|
|
806
|
-
}
|
|
807
|
-
if (!matched) return void 0;
|
|
808
|
-
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
809
|
-
}
|
|
810
|
-
|
|
811
956
|
// src/agents/zyfai/zyfai.history-cursor.ts
|
|
812
957
|
var InvalidHistoryCursorError = class extends Error {
|
|
813
958
|
code = "invalid_cursor";
|
|
@@ -1234,7 +1379,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
|
1234
1379
|
}
|
|
1235
1380
|
|
|
1236
1381
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1237
|
-
var ERC7579_IS_MODULE_INSTALLED_ABI =
|
|
1382
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi2([
|
|
1238
1383
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1239
1384
|
]);
|
|
1240
1385
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
@@ -1293,10 +1438,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1293
1438
|
connectAuthPromise = null;
|
|
1294
1439
|
rpcUrls;
|
|
1295
1440
|
publicClients = /* @__PURE__ */ new Map();
|
|
1296
|
-
//
|
|
1297
|
-
//
|
|
1298
|
-
|
|
1299
|
-
withdrawAmountCache = /* @__PURE__ */ new Map();
|
|
1441
|
+
// Receipt-derived amounts and log identities, scoped to the owner and asset.
|
|
1442
|
+
// Failures remain retryable; successful immutable evidence has a bounded cache.
|
|
1443
|
+
fundingHistoryCache = /* @__PURE__ */ new Map();
|
|
1300
1444
|
earningsReads = /* @__PURE__ */ new Map();
|
|
1301
1445
|
earningsRefreshes = /* @__PURE__ */ new Map();
|
|
1302
1446
|
earningsSnapshot = null;
|
|
@@ -1324,8 +1468,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1324
1468
|
return client;
|
|
1325
1469
|
}
|
|
1326
1470
|
/**
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1471
|
+
* Resolves funding amounts and log identities from token receipts. Withdrawals
|
|
1472
|
+
* must travel from this agent wallet to its owner; top-ups must reach the agent.
|
|
1473
|
+
* The Zyfai history
|
|
1329
1474
|
* payload only reports the *remaining* position for a partial withdraw (see
|
|
1330
1475
|
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
1331
1476
|
* balance instead of the amount withdrawn.
|
|
@@ -1337,48 +1482,62 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1337
1482
|
* Entries with no symbol (older payload shapes) fall back to USDC to
|
|
1338
1483
|
* preserve prior behavior.
|
|
1339
1484
|
*
|
|
1340
|
-
* Mutates `entries` in place
|
|
1341
|
-
*
|
|
1342
|
-
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
1343
|
-
* rather than a wrong number.
|
|
1485
|
+
* Mutates `entries` in place, sharing receipt requests within the page. RPC
|
|
1486
|
+
* failures leave provider top-ups unchanged and withdrawal amounts unknown.
|
|
1344
1487
|
*/
|
|
1345
|
-
async
|
|
1346
|
-
const
|
|
1347
|
-
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
1488
|
+
async resolveFundingTransfers(entries, chainId, recipient, smartWallet, tokenSymbol) {
|
|
1489
|
+
const movements = entries.filter(
|
|
1490
|
+
(e) => (e.action === "Withdraw" || e.action === "Top up") && e.transactions[0]?.txHashes[0]
|
|
1348
1491
|
);
|
|
1349
|
-
if (
|
|
1492
|
+
if (movements.length === 0) return;
|
|
1350
1493
|
const client = this.getPublicClient(chainId);
|
|
1494
|
+
const receipts = /* @__PURE__ */ new Map();
|
|
1351
1495
|
await Promise.all(
|
|
1352
|
-
|
|
1496
|
+
movements.map(async (entry) => {
|
|
1353
1497
|
const tx = entry.transactions[0];
|
|
1354
1498
|
const txHash = tx.txHashes[0];
|
|
1355
|
-
const rawSymbol = tx.tokenSymbol;
|
|
1499
|
+
const rawSymbol = tx.tokenSymbol ?? tokenSymbol;
|
|
1356
1500
|
const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
|
|
1357
1501
|
const asset = SupportedAssets.find(
|
|
1358
1502
|
(a) => a.chainId === chainId && a.symbol === normalizedSymbol
|
|
1359
|
-
) ?? SupportedAssets.find(
|
|
1360
|
-
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
1361
1503
|
);
|
|
1362
1504
|
if (!asset) return;
|
|
1363
|
-
|
|
1364
|
-
|
|
1505
|
+
tx.tokenSymbol = asset.symbol;
|
|
1506
|
+
tx.tokenAddress = asset.address;
|
|
1507
|
+
tx.agentWallet = smartWallet;
|
|
1508
|
+
entry.id = `${chainId}:${txHash.toLowerCase()}:${entry.action}:${asset.address.toLowerCase()}:${smartWallet.toLowerCase()}`;
|
|
1509
|
+
if (!/^0x[0-9a-f]{64}$/i.test(txHash)) return;
|
|
1510
|
+
const cacheKey = `${entry.id}:${recipient.toLowerCase()}`;
|
|
1511
|
+
const cached = this.fundingHistoryCache.get(cacheKey);
|
|
1365
1512
|
if (cached !== void 0) {
|
|
1366
|
-
tx
|
|
1513
|
+
Object.assign(tx, cached);
|
|
1367
1514
|
return;
|
|
1368
1515
|
}
|
|
1369
1516
|
try {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
asset.
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1517
|
+
if (!receipts.has(txHash)) receipts.set(txHash, client.getTransactionReceipt({ hash: txHash }));
|
|
1518
|
+
const receipt = await receipts.get(txHash);
|
|
1519
|
+
const evidence = inspectFundingReceipt(receipt, {
|
|
1520
|
+
agentId: "zyfai",
|
|
1521
|
+
walletAddress: recipient,
|
|
1522
|
+
chainId,
|
|
1523
|
+
token: asset.symbol,
|
|
1524
|
+
agentWallet: smartWallet,
|
|
1525
|
+
intendedAmount: "MAX",
|
|
1526
|
+
txHash,
|
|
1527
|
+
transfer: "unknown",
|
|
1528
|
+
registration: "not-applicable",
|
|
1529
|
+
delivery: "unknown"
|
|
1530
|
+
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
1531
|
+
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
1532
|
+
Object.assign(tx, {
|
|
1533
|
+
amount: formatUnits(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
1534
|
+
amountRaw: evidence.confirmedAmount,
|
|
1535
|
+
blockNumber: evidence.blockNumber,
|
|
1536
|
+
logIndexes: evidence.logIndexes,
|
|
1537
|
+
transferLogs: evidence.transferLogs
|
|
1538
|
+
});
|
|
1539
|
+
if (this.fundingHistoryCache.size >= 256) this.fundingHistoryCache.delete(this.fundingHistoryCache.keys().next().value);
|
|
1540
|
+
this.fundingHistoryCache.set(cacheKey, { ...tx });
|
|
1382
1541
|
}
|
|
1383
1542
|
} catch {
|
|
1384
1543
|
}
|
|
@@ -1467,7 +1626,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1467
1626
|
* Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
|
|
1468
1627
|
* cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
|
|
1469
1628
|
* we drop the cache and try again — the second attempt has no cache entry,
|
|
1470
|
-
* so it falls through to a real SIWE prompt. Zyfai
|
|
1629
|
+
* so it falls through to a real SIWE prompt. Zyfai labels every HTTP 401 with
|
|
1471
1630
|
* an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
|
|
1472
1631
|
* JWT from a misconfigured API key; we retry once either way, and if the
|
|
1473
1632
|
* second attempt still fails the original error propagates.
|
|
@@ -1500,6 +1659,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1500
1659
|
await this.connectAuth(state, chainId);
|
|
1501
1660
|
}
|
|
1502
1661
|
}
|
|
1662
|
+
/** A rejected execution session must not be reused by the next foreground attempt. */
|
|
1663
|
+
invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
|
|
1664
|
+
const message = error instanceof Error ? error.message : "";
|
|
1665
|
+
if (!/(?:^|: )Unauthorized: Invalid API key$/.test(message)) return;
|
|
1666
|
+
const internals = this.sdk;
|
|
1667
|
+
if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
|
|
1668
|
+
clearSession(state.walletAddress, chainId);
|
|
1669
|
+
internals.httpClient.clearAuthToken();
|
|
1670
|
+
internals.authenticatedUserId = null;
|
|
1671
|
+
this.connectedChainId = null;
|
|
1672
|
+
}
|
|
1503
1673
|
/**
|
|
1504
1674
|
* Sync newly-available protocols onto an existing user's profile at wallet
|
|
1505
1675
|
* connection. Zyfai's own protocol sync (`updateUserProtocols`) runs only when
|
|
@@ -2045,7 +2215,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2045
2215
|
await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
|
|
2046
2216
|
return redeployed.address;
|
|
2047
2217
|
}
|
|
2048
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
2218
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
|
|
2049
2219
|
try {
|
|
2050
2220
|
const validChainId = isValidChainId(chainId);
|
|
2051
2221
|
await this.ensureConnected(state, validChainId);
|
|
@@ -2054,17 +2224,48 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2054
2224
|
this.getAddress(),
|
|
2055
2225
|
validChainId
|
|
2056
2226
|
);
|
|
2227
|
+
notifyFunding(onProgress, {
|
|
2228
|
+
agentId: this.id,
|
|
2229
|
+
chainId,
|
|
2230
|
+
token: asset,
|
|
2231
|
+
walletAddress: state.walletAddress,
|
|
2232
|
+
agentWallet: smartWallet,
|
|
2233
|
+
intendedAmount: amount,
|
|
2234
|
+
transfer: "pending",
|
|
2235
|
+
registration: "pending",
|
|
2236
|
+
delivery: "not-applicable"
|
|
2237
|
+
});
|
|
2057
2238
|
const txHash = await depositCallback(smartWallet, validChainId, amount);
|
|
2058
|
-
|
|
2239
|
+
let evidence2 = {
|
|
2240
|
+
agentId: this.id,
|
|
2241
|
+
chainId,
|
|
2242
|
+
token: asset,
|
|
2243
|
+
walletAddress: state.walletAddress,
|
|
2244
|
+
agentWallet: smartWallet,
|
|
2245
|
+
intendedAmount: amount,
|
|
2246
|
+
txHash,
|
|
2247
|
+
transfer: "pending",
|
|
2248
|
+
registration: "pending",
|
|
2249
|
+
delivery: "not-applicable"
|
|
2250
|
+
};
|
|
2251
|
+
notifyFunding(onProgress, evidence2);
|
|
2252
|
+
try {
|
|
2253
|
+
const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: txHash });
|
|
2254
|
+
const token = SupportedAssets.find((token2) => token2.chainId === chainId && token2.symbol === asset);
|
|
2255
|
+
evidence2 = inspectFundingReceipt(receipt, evidence2, token.address, "deposit");
|
|
2256
|
+
} catch {
|
|
2257
|
+
evidence2 = { ...evidence2, transfer: "unknown" };
|
|
2258
|
+
}
|
|
2259
|
+
notifyFunding(onProgress, evidence2);
|
|
2059
2260
|
try {
|
|
2261
|
+
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
2060
2262
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
logError
|
|
2065
|
-
);
|
|
2263
|
+
evidence2 = { ...evidence2, registration: "confirmed" };
|
|
2264
|
+
} catch {
|
|
2265
|
+
evidence2 = { ...evidence2, registration: "unknown" };
|
|
2066
2266
|
}
|
|
2067
|
-
|
|
2267
|
+
notifyFunding(onProgress, evidence2);
|
|
2268
|
+
return { txHash, smartWallet, amount, evidence: evidence2 };
|
|
2068
2269
|
}
|
|
2069
2270
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
2070
2271
|
const raw2 = await this.sdk.depositFunds(
|
|
@@ -2074,22 +2275,88 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2074
2275
|
asset,
|
|
2075
2276
|
"aggressive"
|
|
2076
2277
|
);
|
|
2077
|
-
|
|
2278
|
+
let evidence = {
|
|
2279
|
+
agentId: this.id,
|
|
2280
|
+
chainId,
|
|
2281
|
+
token: asset,
|
|
2282
|
+
walletAddress: state.walletAddress,
|
|
2283
|
+
agentWallet: raw2.smartWallet,
|
|
2284
|
+
intendedAmount: amount,
|
|
2285
|
+
txHash: raw2.txHash,
|
|
2286
|
+
transfer: "unknown",
|
|
2287
|
+
registration: "unknown",
|
|
2288
|
+
delivery: "not-applicable"
|
|
2289
|
+
};
|
|
2290
|
+
notifyFunding(onProgress, evidence);
|
|
2291
|
+
try {
|
|
2292
|
+
const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: raw2.txHash });
|
|
2293
|
+
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
|
|
2294
|
+
evidence = inspectFundingReceipt(receipt, evidence, token.address, "deposit");
|
|
2295
|
+
} catch {
|
|
2296
|
+
}
|
|
2297
|
+
notifyFunding(onProgress, evidence);
|
|
2298
|
+
return { ...mapDeposit(raw2), evidence };
|
|
2078
2299
|
} catch (error) {
|
|
2079
2300
|
throw error;
|
|
2080
2301
|
}
|
|
2081
2302
|
}
|
|
2082
2303
|
// --- IAgent: Fund operations ---
|
|
2083
|
-
async withdraw(state, chainId, token, amount) {
|
|
2304
|
+
async withdraw(state, chainId, token, amount, onProgress) {
|
|
2084
2305
|
const validChainId = isValidChainId(chainId);
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2306
|
+
try {
|
|
2307
|
+
await this.ensureConnected(state, validChainId);
|
|
2308
|
+
} catch (error) {
|
|
2309
|
+
notifyFunding(onProgress, {
|
|
2310
|
+
agentId: this.id,
|
|
2311
|
+
chainId,
|
|
2312
|
+
token,
|
|
2313
|
+
walletAddress: state.walletAddress,
|
|
2314
|
+
intendedAmount: amount ?? "MAX",
|
|
2315
|
+
transfer: "failed",
|
|
2316
|
+
registration: "not-applicable",
|
|
2317
|
+
delivery: "failed"
|
|
2318
|
+
});
|
|
2319
|
+
throw error;
|
|
2320
|
+
}
|
|
2321
|
+
const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2322
|
+
const authToken = this.sdk.httpClient?.authToken;
|
|
2323
|
+
let raw2;
|
|
2324
|
+
try {
|
|
2325
|
+
raw2 = await this.sdk.withdrawFunds(this.getAddress(), validChainId, amount, token);
|
|
2326
|
+
} catch (error) {
|
|
2327
|
+
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
2328
|
+
if (observation.attempted() !== void 0) {
|
|
2329
|
+
const failed = observation.attempted() === false || observation.rejected();
|
|
2330
|
+
notifyFunding(onProgress, {
|
|
2331
|
+
agentId: this.id,
|
|
2332
|
+
chainId,
|
|
2333
|
+
token,
|
|
2334
|
+
walletAddress: state.walletAddress,
|
|
2335
|
+
intendedAmount: amount ?? "MAX",
|
|
2336
|
+
transfer: failed ? "failed" : "unknown",
|
|
2337
|
+
registration: "not-applicable",
|
|
2338
|
+
delivery: failed ? "failed" : "unknown",
|
|
2339
|
+
withdrawalRequest: observation.request()
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
throw error;
|
|
2343
|
+
} finally {
|
|
2344
|
+
observation.stop();
|
|
2345
|
+
}
|
|
2092
2346
|
if (!raw2.success) {
|
|
2347
|
+
const unresolved = !!raw2.txHash || /already|pending|queued|in.progress/i.test(raw2.message ?? "");
|
|
2348
|
+
notifyFunding(onProgress, {
|
|
2349
|
+
agentId: this.id,
|
|
2350
|
+
chainId,
|
|
2351
|
+
token,
|
|
2352
|
+
walletAddress: state.walletAddress,
|
|
2353
|
+
intendedAmount: amount ?? "MAX",
|
|
2354
|
+
txHash: raw2.txHash,
|
|
2355
|
+
transfer: unresolved ? "unknown" : "failed",
|
|
2356
|
+
registration: "not-applicable",
|
|
2357
|
+
delivery: unresolved ? "unknown" : "failed",
|
|
2358
|
+
withdrawalRequest: observation.request()
|
|
2359
|
+
});
|
|
2093
2360
|
throw new OwneyError(
|
|
2094
2361
|
"WITHDRAW_FAILED",
|
|
2095
2362
|
raw2.message || "Zyfai withdraw failed.",
|
|
@@ -2097,13 +2364,45 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2097
2364
|
this.id
|
|
2098
2365
|
);
|
|
2099
2366
|
}
|
|
2100
|
-
|
|
2367
|
+
const evidence = {
|
|
2368
|
+
agentId: this.id,
|
|
2369
|
+
chainId,
|
|
2370
|
+
token,
|
|
2371
|
+
walletAddress: state.walletAddress,
|
|
2372
|
+
intendedAmount: amount ?? "MAX",
|
|
2373
|
+
txHash: raw2.txHash,
|
|
2374
|
+
transfer: "pending",
|
|
2375
|
+
registration: "not-applicable",
|
|
2376
|
+
delivery: "pending",
|
|
2377
|
+
withdrawalRequest: observation.request()
|
|
2378
|
+
};
|
|
2379
|
+
notifyFunding(onProgress, evidence);
|
|
2380
|
+
return { ...mapWithdraw(raw2), evidence };
|
|
2381
|
+
}
|
|
2382
|
+
/** Registration recovery never signs, transfers, activates, or refreshes authentication. */
|
|
2383
|
+
async retryDepositRegistration(state, evidence) {
|
|
2384
|
+
const internals = this.sdk;
|
|
2385
|
+
if (!internals.httpClient.authToken || internals.authenticatedUserId === null || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== evidence.chainId) {
|
|
2386
|
+
throw new OwneyError("NOT_CONNECTED", "Reconnect the wallet before recovering deposit registration.");
|
|
2387
|
+
}
|
|
2388
|
+
if (evidence.transfer !== "confirmed" || !evidence.txHash) {
|
|
2389
|
+
throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
|
|
2390
|
+
}
|
|
2391
|
+
const cid = isValidChainId(evidence.chainId);
|
|
2392
|
+
const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
|
|
2393
|
+
await this.sdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
|
|
2394
|
+
return { ...evidence, registration: "confirmed" };
|
|
2101
2395
|
}
|
|
2102
2396
|
// --- IAgent: Portfolio reads ---
|
|
2397
|
+
async getFundingWallet(state, chainId) {
|
|
2398
|
+
return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
|
|
2399
|
+
}
|
|
2103
2400
|
async getBalances(state, chainId) {
|
|
2401
|
+
const requestedAt = Date.now();
|
|
2104
2402
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
2105
2403
|
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
2106
|
-
|
|
2404
|
+
const balance = mapBalances(raw2, validChainId, smartWallet);
|
|
2405
|
+
return { ...balance, readMetadata: balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) };
|
|
2107
2406
|
}
|
|
2108
2407
|
earningsKey(state, chainId, smartWallet) {
|
|
2109
2408
|
return JSON.stringify([
|
|
@@ -2176,7 +2475,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2176
2475
|
/**
|
|
2177
2476
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
2178
2477
|
* takes lowercase `assetType` and denominates WETH as "eth" (the same
|
|
2179
|
-
* spelling `
|
|
2478
|
+
* spelling `resolveFundingTransfers` normalizes on the way in). Anything
|
|
2180
2479
|
* unrecognised returns undefined so the request stays unfiltered rather
|
|
2181
2480
|
* than silently narrowing to nothing.
|
|
2182
2481
|
*/
|
|
@@ -2195,6 +2494,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2195
2494
|
}
|
|
2196
2495
|
async getHistory(state, chainId, options) {
|
|
2197
2496
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
2497
|
+
return this.readHistory(state, smartWallet, validChainId, options);
|
|
2498
|
+
}
|
|
2499
|
+
/** Recovery reads never enter connectAuth or wallet activation. */
|
|
2500
|
+
async getFundingHistory(state, chainId, options) {
|
|
2501
|
+
const smartWallet = await this.getFundingWallet(state, chainId);
|
|
2502
|
+
return this.readHistory(state, smartWallet, isValidChainId(chainId), options);
|
|
2503
|
+
}
|
|
2504
|
+
async readHistory(state, smartWallet, validChainId, options) {
|
|
2198
2505
|
const limit = options?.limit ?? 10;
|
|
2199
2506
|
const assetType = _ZyfaiAgent.toZyfaiAssetType(options?.tokenSymbol);
|
|
2200
2507
|
const cursor = options?.cursor ? decodeHistoryCursor(options.cursor) : null;
|
|
@@ -2236,9 +2543,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2236
2543
|
rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
|
|
2237
2544
|
}) : void 0;
|
|
2238
2545
|
const data = mapEntries(pageData, validChainId);
|
|
2239
|
-
await this.
|
|
2546
|
+
await this.resolveFundingTransfers(data, validChainId, state.walletAddress, smartWallet, options?.tokenSymbol);
|
|
2547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2548
|
+
const unique = data.filter((entry) => {
|
|
2549
|
+
if (!entry.id || !entry.transactions[0]?.transferLogs?.length) return true;
|
|
2550
|
+
if (seen.has(entry.id)) return false;
|
|
2551
|
+
seen.add(entry.id);
|
|
2552
|
+
return true;
|
|
2553
|
+
});
|
|
2240
2554
|
return {
|
|
2241
|
-
data,
|
|
2555
|
+
data: unique,
|
|
2242
2556
|
hasMore,
|
|
2243
2557
|
nextCursor
|
|
2244
2558
|
};
|
|
@@ -2292,7 +2606,7 @@ import {
|
|
|
2292
2606
|
createWalletClient as createWalletClient2,
|
|
2293
2607
|
custom as custom2,
|
|
2294
2608
|
encodeFunctionData,
|
|
2295
|
-
erc20Abi,
|
|
2609
|
+
erc20Abi as erc20Abi2,
|
|
2296
2610
|
getAddress as getAddress2,
|
|
2297
2611
|
isAddress as isAddress2
|
|
2298
2612
|
} from "viem";
|
|
@@ -2913,7 +3227,7 @@ var YieldseekerApiClient = class {
|
|
|
2913
3227
|
};
|
|
2914
3228
|
|
|
2915
3229
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2916
|
-
import { formatUnits, isAddress } from "viem";
|
|
3230
|
+
import { formatUnits as formatUnits2, isAddress } from "viem";
|
|
2917
3231
|
|
|
2918
3232
|
// src/lib/helpers/snapshot-apy.ts
|
|
2919
3233
|
var DAY_MS = 864e5;
|
|
@@ -2980,10 +3294,10 @@ function raw(value, endpoint) {
|
|
|
2980
3294
|
return BigInt(value);
|
|
2981
3295
|
}
|
|
2982
3296
|
function decimal(value, decimals, endpoint) {
|
|
2983
|
-
return
|
|
3297
|
+
return formatUnits2(raw(value, endpoint), decimals);
|
|
2984
3298
|
}
|
|
2985
3299
|
function usd(rawAmount, decimals, price) {
|
|
2986
|
-
return Number(
|
|
3300
|
+
return Number(formatUnits2(rawAmount, decimals)) * price;
|
|
2987
3301
|
}
|
|
2988
3302
|
function percent(value) {
|
|
2989
3303
|
const result = Number(value);
|
|
@@ -3079,7 +3393,17 @@ function mapYieldseekerBalances(contexts) {
|
|
|
3079
3393
|
totalBalanceAsset: "usdc",
|
|
3080
3394
|
assetBalances,
|
|
3081
3395
|
tokens,
|
|
3082
|
-
positions
|
|
3396
|
+
positions,
|
|
3397
|
+
readMetadata: contexts.map((context) => ({
|
|
3398
|
+
chainId: context.agent.chainId,
|
|
3399
|
+
asset: context.asset,
|
|
3400
|
+
fetchedAt: context.fetchedAt,
|
|
3401
|
+
readVersion: context.readVersion,
|
|
3402
|
+
providerUpdatedAt: context.snapshot.updatedDate,
|
|
3403
|
+
snapshotId: String(context.snapshot.agentWalletSnapshotId),
|
|
3404
|
+
netDepositsRaw: context.snapshot.netDepositsBase,
|
|
3405
|
+
positionsStatus: context.positionsUnavailable ? "unavailable" : "available"
|
|
3406
|
+
}))
|
|
3083
3407
|
};
|
|
3084
3408
|
}
|
|
3085
3409
|
function mapYieldseekerEarnings(contexts) {
|
|
@@ -3091,7 +3415,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
3091
3415
|
chain: "BASE",
|
|
3092
3416
|
chainId: 8453,
|
|
3093
3417
|
asset: context.asset,
|
|
3094
|
-
amount:
|
|
3418
|
+
amount: formatUnits2(amount, context.snapshot.baseAssetDecimals)
|
|
3095
3419
|
});
|
|
3096
3420
|
lifetimeEarnings += usd(
|
|
3097
3421
|
amount,
|
|
@@ -3193,6 +3517,7 @@ function transactionHashes(details) {
|
|
|
3193
3517
|
function actionEntry(action) {
|
|
3194
3518
|
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3195
3519
|
return {
|
|
3520
|
+
id: `yieldseeker:action:${action.agentActionId}`,
|
|
3196
3521
|
agent: "yieldseeker",
|
|
3197
3522
|
action: actionType(action.actionType),
|
|
3198
3523
|
date: action.createdDate,
|
|
@@ -3222,7 +3547,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
|
|
|
3222
3547
|
const owner = ownerAddress.toLowerCase();
|
|
3223
3548
|
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3224
3549
|
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3225
|
-
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3550
|
+
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset || raw(movement.assetAmount, "historic position") <= 0n) {
|
|
3226
3551
|
return void 0;
|
|
3227
3552
|
}
|
|
3228
3553
|
let action;
|
|
@@ -3247,6 +3572,12 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
|
|
|
3247
3572
|
transactions: [
|
|
3248
3573
|
{
|
|
3249
3574
|
txHashes: [movement.transactionHash],
|
|
3575
|
+
tokenAddress: movement.assetAddress,
|
|
3576
|
+
agentWallet: wallet.walletAddress,
|
|
3577
|
+
logIndexes: [movement.logIndex],
|
|
3578
|
+
transferLogs: [{ logIndex: movement.logIndex, amountRaw: movement.assetAmount }],
|
|
3579
|
+
amountRaw: movement.assetAmount,
|
|
3580
|
+
blockNumber: String(movement.blockNumber),
|
|
3250
3581
|
chainId: agent.chainId,
|
|
3251
3582
|
tokenSymbol: asset,
|
|
3252
3583
|
amount: decimal(
|
|
@@ -3292,7 +3623,8 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3292
3623
|
ungrouped.push(entry);
|
|
3293
3624
|
continue;
|
|
3294
3625
|
}
|
|
3295
|
-
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
|
|
3626
|
+
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}:${tx.tokenAddress?.toLowerCase() ?? ""}:${tx.agentWallet?.toLowerCase() ?? ""}`;
|
|
3627
|
+
entry.id = key2;
|
|
3296
3628
|
const previous = grouped.get(key2);
|
|
3297
3629
|
if (!previous) {
|
|
3298
3630
|
grouped.set(key2, entry);
|
|
@@ -3305,14 +3637,52 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3305
3637
|
}
|
|
3306
3638
|
previous.positions.push(...entry.positions);
|
|
3307
3639
|
previous.transactions.push(...entry.transactions);
|
|
3640
|
+
} else if ((entry.action === "Top up" || entry.action === "Withdraw") && tx.amountRaw) {
|
|
3641
|
+
const target = previous.transactions[0];
|
|
3642
|
+
const known = new Set(target.logIndexes ?? []);
|
|
3643
|
+
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
3644
|
+
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
3645
|
+
target.amount = formatUnits2(
|
|
3646
|
+
BigInt(target.amountRaw),
|
|
3647
|
+
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
3648
|
+
);
|
|
3649
|
+
target.logIndexes = [...known, ...tx.logIndexes.filter((index) => !known.has(index))];
|
|
3650
|
+
target.transferLogs = [...target.transferLogs ?? [], ...(tx.transferLogs ?? []).filter((log) => !known.has(log.logIndex))];
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
const values = [...grouped.values(), ...ungrouped].filter((entry) => {
|
|
3655
|
+
const tx = entry.transactions[0];
|
|
3656
|
+
return tx?.amountRaw !== void 0 || ![...grouped.values()].some((other) => other !== entry && other.action === entry.action && other.transactions[0]?.amountRaw !== void 0 && other.transactions[0]?.txHashes[0]?.toLowerCase() === tx?.txHashes[0]?.toLowerCase());
|
|
3657
|
+
});
|
|
3658
|
+
let boundary;
|
|
3659
|
+
const scope = JSON.stringify([
|
|
3660
|
+
options.ownerAddress.toLowerCase(),
|
|
3661
|
+
contexts.map((context) => `${context.agent.chainId}:${context.wallet.walletAddress.toLowerCase()}:${context.asset}`).sort(),
|
|
3662
|
+
options.fromDate ?? null
|
|
3663
|
+
]);
|
|
3664
|
+
if (options.cursor) {
|
|
3665
|
+
try {
|
|
3666
|
+
boundary = JSON.parse(decodeURIComponent(atob(options.cursor)));
|
|
3667
|
+
if (!boundary || boundary.scope !== scope || typeof boundary.id !== "string" || !Number.isFinite(Date.parse(boundary.date)) || !Number.isFinite(Date.parse(boundary.toDate))) throw new Error();
|
|
3668
|
+
} catch {
|
|
3669
|
+
throw new InvalidHistoryCursorError("Invalid Yieldseeker history cursor.");
|
|
3308
3670
|
}
|
|
3309
3671
|
}
|
|
3310
|
-
const
|
|
3672
|
+
const toDate = boundary?.toDate ?? options.toDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3673
|
+
const filtered = values.filter((entry) => (!options.fromDate || entry.date >= options.fromDate) && entry.date <= toDate && (!boundary || entry.date < boundary.date || entry.date === boundary.date && (entry.id ?? "") > boundary.id)).sort((left, right) => right.date.localeCompare(left.date) || (left.id ?? "").localeCompare(right.id ?? ""));
|
|
3674
|
+
const data = filtered.slice(0, Math.max(1, options.limit));
|
|
3675
|
+
const hasMore = filtered.length > data.length;
|
|
3676
|
+
const last = data.at(-1);
|
|
3311
3677
|
return {
|
|
3312
|
-
data
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3678
|
+
data,
|
|
3679
|
+
hasMore,
|
|
3680
|
+
...hasMore && last ? { nextCursor: btoa(encodeURIComponent(JSON.stringify({
|
|
3681
|
+
date: last.date,
|
|
3682
|
+
id: last.id ?? "",
|
|
3683
|
+
toDate,
|
|
3684
|
+
scope
|
|
3685
|
+
}))) } : {}
|
|
3316
3686
|
};
|
|
3317
3687
|
}
|
|
3318
3688
|
function mapYieldseekerProfile(address, contexts) {
|
|
@@ -3487,7 +3857,7 @@ var YieldseekerAgent = class {
|
|
|
3487
3857
|
this.assertAsset(targetAsset);
|
|
3488
3858
|
await this.ensureAgent(state, chainId, targetAsset);
|
|
3489
3859
|
}
|
|
3490
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3860
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
|
|
3491
3861
|
this.assertChain(chainId);
|
|
3492
3862
|
this.assertAsset(asset);
|
|
3493
3863
|
if (BigInt(amount) <= 0n) {
|
|
@@ -3501,7 +3871,20 @@ var YieldseekerAgent = class {
|
|
|
3501
3871
|
const context = await this.ensureAgent(state, chainId, asset);
|
|
3502
3872
|
const generation = this.readGeneration;
|
|
3503
3873
|
const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
|
|
3874
|
+
notifyFunding(onProgress, {
|
|
3875
|
+
agentId: this.id,
|
|
3876
|
+
chainId,
|
|
3877
|
+
token: asset,
|
|
3878
|
+
walletAddress: state.walletAddress,
|
|
3879
|
+
agentWallet: context.wallet.walletAddress,
|
|
3880
|
+
intendedAmount: amount,
|
|
3881
|
+
transfer: "pending",
|
|
3882
|
+
registration: "not-applicable",
|
|
3883
|
+
delivery: "not-applicable"
|
|
3884
|
+
});
|
|
3504
3885
|
let txHash;
|
|
3886
|
+
let receipt;
|
|
3887
|
+
const submitted = (hash) => notifyFunding(onProgress, { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, txHash: hash, transfer: "pending", registration: "not-applicable", delivery: "not-applicable" });
|
|
3505
3888
|
if (depositCallback) {
|
|
3506
3889
|
provideDepositVerificationContext(depositCallback, {
|
|
3507
3890
|
agentId: "yieldseeker",
|
|
@@ -3514,30 +3897,57 @@ var YieldseekerAgent = class {
|
|
|
3514
3897
|
chainId,
|
|
3515
3898
|
amount
|
|
3516
3899
|
);
|
|
3517
|
-
|
|
3900
|
+
submitted(txHash);
|
|
3901
|
+
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
3518
3902
|
} else {
|
|
3519
3903
|
txHash = await this.submitTransaction(state, chainId, {
|
|
3520
3904
|
from: getAddress2(state.walletAddress),
|
|
3521
3905
|
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3522
3906
|
data: encodeFunctionData({
|
|
3523
|
-
abi:
|
|
3907
|
+
abi: erc20Abi2,
|
|
3524
3908
|
functionName: "transfer",
|
|
3525
3909
|
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3526
3910
|
}),
|
|
3527
3911
|
value: "0",
|
|
3528
3912
|
chainId
|
|
3913
|
+
}, submitted, (value) => {
|
|
3914
|
+
receipt = value;
|
|
3529
3915
|
});
|
|
3530
3916
|
}
|
|
3531
3917
|
if (this.readGeneration === generation) {
|
|
3532
3918
|
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3533
3919
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3920
|
+
const base5 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, confirmedAmount: amount, txHash, transfer: "confirmed", registration: "not-applicable", delivery: "not-applicable" };
|
|
3921
|
+
const evidence = receipt ? inspectFundingReceipt(receipt, base5, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base5;
|
|
3922
|
+
notifyFunding(onProgress, evidence);
|
|
3923
|
+
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
3924
|
+
}
|
|
3925
|
+
async withdraw(state, chainId, asset, amount, onProgress) {
|
|
3926
|
+
let ownerTransferAttempted = false;
|
|
3927
|
+
let evidence = {
|
|
3928
|
+
agentId: this.id,
|
|
3929
|
+
chainId,
|
|
3930
|
+
token: asset,
|
|
3931
|
+
walletAddress: state.walletAddress,
|
|
3932
|
+
intendedAmount: amount ?? "MAX",
|
|
3933
|
+
transfer: "pending",
|
|
3934
|
+
registration: "not-applicable",
|
|
3935
|
+
delivery: "pending"
|
|
3538
3936
|
};
|
|
3937
|
+
try {
|
|
3938
|
+
return await this.withdrawInternal(state, chainId, asset, amount, (next) => {
|
|
3939
|
+
evidence = { ...evidence, ...next };
|
|
3940
|
+
notifyFunding(onProgress, evidence);
|
|
3941
|
+
}, () => {
|
|
3942
|
+
ownerTransferAttempted = true;
|
|
3943
|
+
});
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
if (!ownerTransferAttempted || error instanceof OwneyError && error.code === "AGENT_TRANSACTION_REVERTED")
|
|
3946
|
+
notifyFunding(onProgress, { ...evidence, transfer: "failed", delivery: "failed" });
|
|
3947
|
+
throw error;
|
|
3948
|
+
}
|
|
3539
3949
|
}
|
|
3540
|
-
async
|
|
3950
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted) {
|
|
3541
3951
|
this.assertChain(chainId);
|
|
3542
3952
|
this.assertAsset(asset);
|
|
3543
3953
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -3567,17 +3977,32 @@ var YieldseekerAgent = class {
|
|
|
3567
3977
|
context
|
|
3568
3978
|
);
|
|
3569
3979
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3570
|
-
const
|
|
3571
|
-
(
|
|
3572
|
-
|
|
3573
|
-
|
|
3980
|
+
const readIdle = async () => {
|
|
3981
|
+
const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
3982
|
+
address: metadata.address,
|
|
3983
|
+
abi: erc20Abi2,
|
|
3984
|
+
functionName: "balanceOf",
|
|
3985
|
+
args: [getAddress2(context.wallet.walletAddress)]
|
|
3986
|
+
}), chainId, "eth_call", this.id);
|
|
3987
|
+
this.assertSession(generation, chainId, asset);
|
|
3988
|
+
return balance;
|
|
3989
|
+
};
|
|
3990
|
+
const idle = await readIdle();
|
|
3574
3991
|
const deployed = portfolio.positions.reduce(
|
|
3575
3992
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3576
3993
|
0n
|
|
3577
3994
|
);
|
|
3578
3995
|
const totalAvailable = idle + deployed;
|
|
3579
3996
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3580
|
-
|
|
3997
|
+
if (requested === 0n) {
|
|
3998
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
3999
|
+
throw new OwneyError(
|
|
4000
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4001
|
+
"No Yieldseeker funds are available to withdraw.",
|
|
4002
|
+
{ asset, available: "0" },
|
|
4003
|
+
this.id
|
|
4004
|
+
);
|
|
4005
|
+
}
|
|
3581
4006
|
if (requested > totalAvailable) {
|
|
3582
4007
|
throw new OwneyError(
|
|
3583
4008
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -3622,7 +4047,22 @@ var YieldseekerAgent = class {
|
|
|
3622
4047
|
remaining: remaining.toString()
|
|
3623
4048
|
});
|
|
3624
4049
|
}
|
|
4050
|
+
const availableForOwner = confirmedMovement ? await readIdle() : idle;
|
|
4051
|
+
if (availableForOwner === 0n || amount !== void 0 && availableForOwner < requested) {
|
|
4052
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
4053
|
+
throw new OwneyError(
|
|
4054
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4055
|
+
"Yieldseeker funds are not available for the owner transfer.",
|
|
4056
|
+
{ asset, requested: amount ?? "MAX", available: availableForOwner.toString() },
|
|
4057
|
+
this.id
|
|
4058
|
+
);
|
|
4059
|
+
}
|
|
4060
|
+
const payout = amount === void 0 ? availableForOwner : requested;
|
|
4061
|
+
const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
|
|
3625
4062
|
const account = getAddress2(state.walletAddress);
|
|
4063
|
+
let receipt;
|
|
4064
|
+
const base5 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount === void 0 ? "MAX" : requested.toString(), transfer: "pending", registration: "not-applicable", delivery: "pending" };
|
|
4065
|
+
onOwnerTransferAttempted?.();
|
|
3626
4066
|
const txHash = await this.submitTransaction(state, chainId, {
|
|
3627
4067
|
from: account,
|
|
3628
4068
|
to: getAddress2(context.wallet.walletAddress),
|
|
@@ -3637,14 +4077,21 @@ var YieldseekerAgent = class {
|
|
|
3637
4077
|
}),
|
|
3638
4078
|
value: "0",
|
|
3639
4079
|
chainId
|
|
4080
|
+
}, (hash) => notifyFunding(onProgress, { ...base5, txHash: hash }), (value) => {
|
|
4081
|
+
receipt = value;
|
|
3640
4082
|
});
|
|
3641
4083
|
confirmedMovement = true;
|
|
3642
4084
|
settlement = plannedMovement;
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
4085
|
+
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base5, txHash }, metadata.address, "withdraw") : { ...base5, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4086
|
+
notifyFunding(onProgress, evidence);
|
|
4087
|
+
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4088
|
+
throw new OwneyError(
|
|
4089
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4090
|
+
"The Yieldseeker withdrawal completed without transferring funds.",
|
|
4091
|
+
{ asset, available: "0", txHash },
|
|
4092
|
+
this.id
|
|
4093
|
+
);
|
|
4094
|
+
return { txHash, type: amount === void 0 ? "full" : "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
3648
4095
|
} finally {
|
|
3649
4096
|
if (confirmedMovement && this.readGeneration === generation) {
|
|
3650
4097
|
void this.refreshSnapshotAfterMovement(
|
|
@@ -3657,9 +4104,39 @@ var YieldseekerAgent = class {
|
|
|
3657
4104
|
}
|
|
3658
4105
|
}
|
|
3659
4106
|
}
|
|
3660
|
-
async getBalances(state, chainId) {
|
|
4107
|
+
async getBalances(state, chainId, asset) {
|
|
3661
4108
|
this.assertChain(chainId);
|
|
3662
|
-
|
|
4109
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
4110
|
+
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true }));
|
|
4111
|
+
}
|
|
4112
|
+
invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS) {
|
|
4113
|
+
this.assertAsset(asset);
|
|
4114
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
4115
|
+
const failure = this.snapshotFailures.get(key2);
|
|
4116
|
+
this.advancePortfolioVersion(key2);
|
|
4117
|
+
if (failure && failure.retryAt > Date.now()) this.snapshotFailures.set(key2, failure);
|
|
4118
|
+
if (!this.snapshotMovements.has(key2)) this.snapshotMovements.set(key2, null);
|
|
4119
|
+
const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4120
|
+
this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
|
|
4121
|
+
this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, until));
|
|
4122
|
+
}
|
|
4123
|
+
endFundingBalanceRefresh(state, chainId, asset, read2) {
|
|
4124
|
+
this.assertChain(chainId);
|
|
4125
|
+
this.assertAsset(asset);
|
|
4126
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
4127
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4128
|
+
const snapshot = cached?.value?.agentSnapshot;
|
|
4129
|
+
if (!cached || !snapshot || read2.chainId !== chainId || read2.asset.toUpperCase() !== asset || read2.readVersion !== this.portfolioVersion(key2) || read2.fetchedAt !== cached.fetchedAt || read2.snapshotId !== String(snapshot.agentWalletSnapshotId) || read2.providerUpdatedAt !== snapshot.updatedDate || read2.netDepositsRaw !== snapshot.netDepositsBase) return false;
|
|
4130
|
+
this.snapshotMovements.delete(key2);
|
|
4131
|
+
this.reconcileUntil.delete(key2);
|
|
4132
|
+
cached.expiresAt = cached.fetchedAt + YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4133
|
+
return true;
|
|
4134
|
+
}
|
|
4135
|
+
async getFundingWallet(state, chainId, asset) {
|
|
4136
|
+
this.assertAsset(asset);
|
|
4137
|
+
const context = this.agentContexts.get(this.contextKey(state, chainId, asset));
|
|
4138
|
+
if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
|
|
4139
|
+
return context.wallet.walletAddress;
|
|
3663
4140
|
}
|
|
3664
4141
|
async getEarnings(state, chainId) {
|
|
3665
4142
|
this.assertChain(chainId);
|
|
@@ -3696,6 +4173,7 @@ var YieldseekerAgent = class {
|
|
|
3696
4173
|
);
|
|
3697
4174
|
return mapYieldseekerHistory(contexts, {
|
|
3698
4175
|
limit: options?.limit ?? 10,
|
|
4176
|
+
cursor: options?.cursor,
|
|
3699
4177
|
ownerAddress: state.walletAddress,
|
|
3700
4178
|
vaultAddresses,
|
|
3701
4179
|
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
@@ -3770,6 +4248,7 @@ var YieldseekerAgent = class {
|
|
|
3770
4248
|
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
3771
4249
|
this.readCache.set(key2, {
|
|
3772
4250
|
expiresAt: Date.now() + ttlMs,
|
|
4251
|
+
fetchedAt: Date.now(),
|
|
3773
4252
|
value
|
|
3774
4253
|
});
|
|
3775
4254
|
}
|
|
@@ -3970,6 +4449,7 @@ var YieldseekerAgent = class {
|
|
|
3970
4449
|
if (agent) {
|
|
3971
4450
|
this.readCache.set(this.agentListKey(state, chainId), {
|
|
3972
4451
|
expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
4452
|
+
fetchedAt: Date.now(),
|
|
3973
4453
|
value: {
|
|
3974
4454
|
agents: [...agents, agent]
|
|
3975
4455
|
}
|
|
@@ -4030,7 +4510,6 @@ var YieldseekerAgent = class {
|
|
|
4030
4510
|
...previous,
|
|
4031
4511
|
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4032
4512
|
delta: previous.delta + delta,
|
|
4033
|
-
expectedBalance: previous.expectedBalance + delta,
|
|
4034
4513
|
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
4035
4514
|
};
|
|
4036
4515
|
}
|
|
@@ -4043,7 +4522,6 @@ var YieldseekerAgent = class {
|
|
|
4043
4522
|
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4044
4523
|
delta,
|
|
4045
4524
|
decimals: snapshot.baseAssetDecimals,
|
|
4046
|
-
expectedBalance: BigInt(snapshot.totalValueBase) + delta,
|
|
4047
4525
|
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4048
4526
|
};
|
|
4049
4527
|
}
|
|
@@ -4072,9 +4550,8 @@ var YieldseekerAgent = class {
|
|
|
4072
4550
|
const movement = this.snapshotMovements.get(contextKey);
|
|
4073
4551
|
const snapshot = response.agentSnapshot;
|
|
4074
4552
|
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4075
|
-
const balance = BigInt(snapshot.totalValueBase);
|
|
4076
4553
|
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4077
|
-
const reconciled = movement.delta > 0n ?
|
|
4554
|
+
const reconciled = movement.delta > 0n ? netDeposits >= movement.expectedNetDeposits : netDeposits <= movement.expectedNetDeposits;
|
|
4078
4555
|
if (reconciled) {
|
|
4079
4556
|
this.snapshotMovements.delete(contextKey);
|
|
4080
4557
|
this.reconcileUntil.delete(contextKey);
|
|
@@ -4128,12 +4605,20 @@ var YieldseekerAgent = class {
|
|
|
4128
4605
|
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4129
4606
|
const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4130
4607
|
try {
|
|
4131
|
-
|
|
4608
|
+
let positionsUnavailable = false;
|
|
4609
|
+
const safeParts = options.allowMissingPositions ? parts.catch(() => {
|
|
4610
|
+
positionsUnavailable = true;
|
|
4611
|
+
return { positions: [] };
|
|
4612
|
+
}) : parts;
|
|
4613
|
+
const [snapshot, portfolioParts] = await Promise.all([read2, safeParts]);
|
|
4132
4614
|
assertSession();
|
|
4133
4615
|
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4134
4616
|
return {
|
|
4135
4617
|
...context,
|
|
4136
4618
|
snapshot: snapshot.agentSnapshot,
|
|
4619
|
+
fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
|
|
4620
|
+
readVersion: version,
|
|
4621
|
+
positionsUnavailable,
|
|
4137
4622
|
...portfolioParts
|
|
4138
4623
|
};
|
|
4139
4624
|
} catch (error) {
|
|
@@ -4324,9 +4809,11 @@ var YieldseekerAgent = class {
|
|
|
4324
4809
|
this.id
|
|
4325
4810
|
);
|
|
4326
4811
|
}
|
|
4327
|
-
async submitTransaction(state, chainId, transaction) {
|
|
4812
|
+
async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
|
|
4328
4813
|
if (this.transactionExecutor) {
|
|
4329
|
-
|
|
4814
|
+
const hash2 = await this.transactionExecutor(state, chainId, transaction);
|
|
4815
|
+
onSubmitted?.(hash2);
|
|
4816
|
+
return hash2;
|
|
4330
4817
|
}
|
|
4331
4818
|
this.assertTransaction(transaction, state, chainId);
|
|
4332
4819
|
const account = getAddress2(state.walletAddress);
|
|
@@ -4351,6 +4838,7 @@ var YieldseekerAgent = class {
|
|
|
4351
4838
|
data: transaction.data,
|
|
4352
4839
|
value: BigInt(transaction.value)
|
|
4353
4840
|
});
|
|
4841
|
+
onSubmitted?.(hash);
|
|
4354
4842
|
const receipt = await withPaidRpcDiagnostics(
|
|
4355
4843
|
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4356
4844
|
hash,
|
|
@@ -4368,6 +4856,7 @@ var YieldseekerAgent = class {
|
|
|
4368
4856
|
this.id
|
|
4369
4857
|
);
|
|
4370
4858
|
}
|
|
4859
|
+
onReceipt?.(receipt);
|
|
4371
4860
|
return hash;
|
|
4372
4861
|
}
|
|
4373
4862
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
@@ -4392,6 +4881,7 @@ var YieldseekerAgent = class {
|
|
|
4392
4881
|
this.id
|
|
4393
4882
|
);
|
|
4394
4883
|
}
|
|
4884
|
+
return receipt;
|
|
4395
4885
|
}
|
|
4396
4886
|
assertTransaction(transaction, state, chainId) {
|
|
4397
4887
|
if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress2(transaction.from) !== getAddress2(state.walletAddress)) {
|
|
@@ -4753,7 +5243,7 @@ import {
|
|
|
4753
5243
|
} from "viem";
|
|
4754
5244
|
|
|
4755
5245
|
// src/lib/permit2-batch.ts
|
|
4756
|
-
import { parseAbi as
|
|
5246
|
+
import { parseAbi as parseAbi3, hashStruct } from "viem";
|
|
4757
5247
|
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
4758
5248
|
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
4759
5249
|
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
@@ -4766,7 +5256,7 @@ var ERC2612_PERMIT_TYPES = {
|
|
|
4766
5256
|
{ name: "deadline", type: "uint256" }
|
|
4767
5257
|
]
|
|
4768
5258
|
};
|
|
4769
|
-
var ERC2612_READ_ABI =
|
|
5259
|
+
var ERC2612_READ_ABI = parseAbi3([
|
|
4770
5260
|
"function name() view returns (string)",
|
|
4771
5261
|
"function version() view returns (string)",
|
|
4772
5262
|
"function nonces(address owner) view returns (uint256)"
|
|
@@ -4804,7 +5294,7 @@ var PERMIT_BATCH_TYPES = {
|
|
|
4804
5294
|
{ name: "amount", type: "uint256" }
|
|
4805
5295
|
]
|
|
4806
5296
|
};
|
|
4807
|
-
var PERMIT2_BATCH_ABI =
|
|
5297
|
+
var PERMIT2_BATCH_ABI = parseAbi3([
|
|
4808
5298
|
"struct TokenPermissions { address token; uint256 amount; }",
|
|
4809
5299
|
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
4810
5300
|
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
@@ -4904,6 +5394,10 @@ async function execute(i, key2, plan) {
|
|
|
4904
5394
|
};
|
|
4905
5395
|
save(key2, body);
|
|
4906
5396
|
}
|
|
5397
|
+
try {
|
|
5398
|
+
i.onSubmitted?.(keccak256(body.serializedTransaction));
|
|
5399
|
+
} catch {
|
|
5400
|
+
}
|
|
4907
5401
|
const result = await postSponsorBatchTransfer({
|
|
4908
5402
|
apiKey: i.apiKey,
|
|
4909
5403
|
baseUrl: i.baseUrl,
|
|
@@ -5032,7 +5526,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
5032
5526
|
transfers,
|
|
5033
5527
|
pub,
|
|
5034
5528
|
wallet,
|
|
5035
|
-
onApproved: deps.onApproved
|
|
5529
|
+
onApproved: deps.onApproved,
|
|
5530
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
5036
5531
|
});
|
|
5037
5532
|
};
|
|
5038
5533
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -5118,7 +5613,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
5118
5613
|
}
|
|
5119
5614
|
|
|
5120
5615
|
// src/lib/sponsored-calls-deposit.ts
|
|
5121
|
-
import { encodeFunctionData as encodeFunctionData2, erc20Abi as
|
|
5616
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi3, toHex } from "viem";
|
|
5122
5617
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
5123
5618
|
var DEFAULT_MAX_POLLS = 30;
|
|
5124
5619
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -5164,7 +5659,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5164
5659
|
to: token,
|
|
5165
5660
|
value: "0x0",
|
|
5166
5661
|
data: encodeFunctionData2({
|
|
5167
|
-
abi:
|
|
5662
|
+
abi: erc20Abi3,
|
|
5168
5663
|
functionName: "transfer",
|
|
5169
5664
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
5170
5665
|
})
|
|
@@ -5226,6 +5721,12 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5226
5721
|
params: [callsId]
|
|
5227
5722
|
});
|
|
5228
5723
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
5724
|
+
if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
5725
|
+
try {
|
|
5726
|
+
deps.onSubmitted?.(txHash, transfers);
|
|
5727
|
+
} catch {
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5229
5730
|
if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
|
|
5230
5731
|
throw new OwneyError(
|
|
5231
5732
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -5335,6 +5836,7 @@ var OwneySDK = class {
|
|
|
5335
5836
|
paymasterServiceUrl;
|
|
5336
5837
|
cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
|
|
5337
5838
|
agentReads = new AgentReads();
|
|
5839
|
+
recoveredFundingReads = /* @__PURE__ */ new Set();
|
|
5338
5840
|
activationChecks = /* @__PURE__ */ new Map();
|
|
5339
5841
|
readAgent(agent, method, fetch2, params = null) {
|
|
5340
5842
|
const key2 = JSON.stringify([
|
|
@@ -5448,8 +5950,8 @@ var OwneySDK = class {
|
|
|
5448
5950
|
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
5449
5951
|
}
|
|
5450
5952
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
5451
|
-
getDefaultSponsoredCallback(onApproved) {
|
|
5452
|
-
if (!onApproved && this.cachedSponsoredCallback)
|
|
5953
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
5954
|
+
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
5453
5955
|
return this.cachedSponsoredCallback;
|
|
5454
5956
|
const provider = this.requireConnectedProvider();
|
|
5455
5957
|
const owner = this.state.walletAddress;
|
|
@@ -5458,6 +5960,7 @@ var OwneySDK = class {
|
|
|
5458
5960
|
baseUrl: this.routingApiBaseUrl,
|
|
5459
5961
|
ownerAddress: owner,
|
|
5460
5962
|
onApproved,
|
|
5963
|
+
onSubmitted,
|
|
5461
5964
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
5462
5965
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
5463
5966
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -5473,13 +5976,13 @@ var OwneySDK = class {
|
|
|
5473
5976
|
transport: custom3(provider)
|
|
5474
5977
|
})
|
|
5475
5978
|
});
|
|
5476
|
-
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
5979
|
+
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
5477
5980
|
return callback;
|
|
5478
5981
|
}
|
|
5479
5982
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
5480
|
-
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
5983
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
5481
5984
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
5482
|
-
if (!onApproved && cached) return cached;
|
|
5985
|
+
if (!onApproved && !onSubmitted && cached) return cached;
|
|
5483
5986
|
const provider = this.requireConnectedProvider();
|
|
5484
5987
|
const callback = makeSponsoredCallsCallback({
|
|
5485
5988
|
apiKey: this.apiKey,
|
|
@@ -5488,9 +5991,10 @@ var OwneySDK = class {
|
|
|
5488
5991
|
ownerAddress: this.state.walletAddress,
|
|
5489
5992
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
5490
5993
|
onApproved,
|
|
5994
|
+
onSubmitted,
|
|
5491
5995
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
5492
5996
|
});
|
|
5493
|
-
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
5997
|
+
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
5494
5998
|
return callback;
|
|
5495
5999
|
}
|
|
5496
6000
|
/**
|
|
@@ -5499,8 +6003,8 @@ var OwneySDK = class {
|
|
|
5499
6003
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
5500
6004
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
5501
6005
|
*/
|
|
5502
|
-
getDefaultWethSponsoredCallback(onApproved) {
|
|
5503
|
-
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
6006
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6007
|
+
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
5504
6008
|
return this.cachedWethSponsoredCallback;
|
|
5505
6009
|
const provider = this.requireConnectedProvider();
|
|
5506
6010
|
const owner = this.state.walletAddress;
|
|
@@ -5509,6 +6013,7 @@ var OwneySDK = class {
|
|
|
5509
6013
|
baseUrl: this.routingApiBaseUrl,
|
|
5510
6014
|
ownerAddress: owner,
|
|
5511
6015
|
onApproved,
|
|
6016
|
+
onSubmitted,
|
|
5512
6017
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
5513
6018
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
5514
6019
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -5524,7 +6029,7 @@ var OwneySDK = class {
|
|
|
5524
6029
|
transport: custom3(provider)
|
|
5525
6030
|
})
|
|
5526
6031
|
});
|
|
5527
|
-
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
6032
|
+
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
5528
6033
|
return callback;
|
|
5529
6034
|
}
|
|
5530
6035
|
getAgent(agentId) {
|
|
@@ -5534,6 +6039,9 @@ var OwneySDK = class {
|
|
|
5534
6039
|
}
|
|
5535
6040
|
return agent;
|
|
5536
6041
|
}
|
|
6042
|
+
getActiveAgentIds() {
|
|
6043
|
+
return [...this.getActiveAgents().keys()].sort();
|
|
6044
|
+
}
|
|
5537
6045
|
getActiveAgents() {
|
|
5538
6046
|
const active = /* @__PURE__ */ new Map();
|
|
5539
6047
|
for (const id of this.activeAgents) {
|
|
@@ -5828,12 +6336,23 @@ var OwneySDK = class {
|
|
|
5828
6336
|
*/
|
|
5829
6337
|
async deposit(options) {
|
|
5830
6338
|
const { amount, asset, depositCallback, agentId, onApproved } = options;
|
|
6339
|
+
const prepared = /* @__PURE__ */ new Map();
|
|
6340
|
+
const onProgress = options.onProgress ? (evidence) => {
|
|
6341
|
+
if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
|
|
6342
|
+
notifyFunding(options.onProgress, evidence);
|
|
6343
|
+
} : void 0;
|
|
6344
|
+
const onSubmitted = onProgress ? (hash, destinations) => {
|
|
6345
|
+
for (const destination of destinations) {
|
|
6346
|
+
const evidence = prepared.get(destination.to.toLowerCase());
|
|
6347
|
+
if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
|
|
6348
|
+
}
|
|
6349
|
+
} : void 0;
|
|
5831
6350
|
if (BigInt(amount) === 0n) {
|
|
5832
6351
|
return { agentResults: {} };
|
|
5833
6352
|
}
|
|
5834
6353
|
const state = this.requireState();
|
|
5835
6354
|
const chainId = this.requireChainId();
|
|
5836
|
-
const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
|
|
6355
|
+
const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
|
|
5837
6356
|
if (agentId) {
|
|
5838
6357
|
if (this.isAgentDisabled(agentId)) {
|
|
5839
6358
|
throw new OwneyError(
|
|
@@ -5852,7 +6371,8 @@ var OwneySDK = class {
|
|
|
5852
6371
|
amount,
|
|
5853
6372
|
asset,
|
|
5854
6373
|
effectiveCallback,
|
|
5855
|
-
depositCallback
|
|
6374
|
+
depositCallback,
|
|
6375
|
+
onProgress
|
|
5856
6376
|
);
|
|
5857
6377
|
}
|
|
5858
6378
|
const totalAmount = BigInt(amount);
|
|
@@ -5916,7 +6436,7 @@ var OwneySDK = class {
|
|
|
5916
6436
|
run: (callback) => withFailureReporting(
|
|
5917
6437
|
this.apiKey,
|
|
5918
6438
|
agent.id,
|
|
5919
|
-
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
6439
|
+
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
5920
6440
|
this.routingApiBaseUrl
|
|
5921
6441
|
)
|
|
5922
6442
|
})),
|
|
@@ -5953,7 +6473,8 @@ var OwneySDK = class {
|
|
|
5953
6473
|
agentAmount,
|
|
5954
6474
|
asset,
|
|
5955
6475
|
effectiveCallback,
|
|
5956
|
-
depositCallback
|
|
6476
|
+
depositCallback,
|
|
6477
|
+
onProgress
|
|
5957
6478
|
);
|
|
5958
6479
|
} catch (error) {
|
|
5959
6480
|
if (Object.keys(agentResults).length === 0) throw error;
|
|
@@ -6000,17 +6521,11 @@ var OwneySDK = class {
|
|
|
6000
6521
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
6001
6522
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
6002
6523
|
*/
|
|
6003
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
6524
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
6004
6525
|
const attempt = (callback) => withFailureReporting(
|
|
6005
6526
|
this.apiKey,
|
|
6006
6527
|
agent.id,
|
|
6007
|
-
() => agent.deposit(
|
|
6008
|
-
state,
|
|
6009
|
-
chainId,
|
|
6010
|
-
amount,
|
|
6011
|
-
asset,
|
|
6012
|
-
callback
|
|
6013
|
-
),
|
|
6528
|
+
() => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
|
|
6014
6529
|
this.routingApiBaseUrl
|
|
6015
6530
|
);
|
|
6016
6531
|
let approvalAttempted = false;
|
|
@@ -6190,15 +6705,81 @@ var OwneySDK = class {
|
|
|
6190
6705
|
while (current && typeof current === "object" && !seen.has(current)) {
|
|
6191
6706
|
seen.add(current);
|
|
6192
6707
|
const candidate = current;
|
|
6193
|
-
if (candidate.code === 4001 || candidate.code === "4001") return true;
|
|
6708
|
+
if (candidate.code === 4001 || candidate.code === "4001" || candidate.code === "ACTION_REJECTED") return true;
|
|
6194
6709
|
const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
|
|
6195
|
-
if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
|
|
6710
|
+
if (/user (?:rejected|denied)|rejected by user|request rejected/i.test(message)) {
|
|
6196
6711
|
return true;
|
|
6197
6712
|
}
|
|
6198
6713
|
current = candidate.cause;
|
|
6199
6714
|
}
|
|
6200
6715
|
return false;
|
|
6201
6716
|
}
|
|
6717
|
+
captureReadScope(requestedChain) {
|
|
6718
|
+
const original = this.requireState();
|
|
6719
|
+
const chainId = this.requireChainId();
|
|
6720
|
+
if (requestedChain !== void 0 && requestedChain !== chainId) {
|
|
6721
|
+
throw new OwneyError("NO_ACTIVE_CHAIN", "Account read scope changed. Please retry.");
|
|
6722
|
+
}
|
|
6723
|
+
const wallet = original.walletAddress;
|
|
6724
|
+
return { state: { ...original }, chainId, assertCurrent: () => {
|
|
6725
|
+
if (this.state !== original || this.state.walletAddress !== wallet || this.state.chainId !== chainId)
|
|
6726
|
+
throw new OwneyError("NOT_CONNECTED", "Account read scope changed. Please retry.");
|
|
6727
|
+
} };
|
|
6728
|
+
}
|
|
6729
|
+
/** Revalidate a saved transfer against the authenticated account and actual receipt. */
|
|
6730
|
+
async inspectFunding(evidence, direction, options = {}) {
|
|
6731
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
6732
|
+
if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || !evidence.txHash)
|
|
6733
|
+
throw new OwneyError("AGENT_API_ERROR", "Funding evidence does not match the connected account.");
|
|
6734
|
+
const agent = this.getAgent(evidence.agentId);
|
|
6735
|
+
const verifiedWallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
6736
|
+
const agentWallet = evidence.agentWallet ?? verifiedWallet;
|
|
6737
|
+
if (!agentWallet || agentWallet.toLowerCase() !== verifiedWallet.toLowerCase())
|
|
6738
|
+
throw new OwneyError("AGENT_API_ERROR", "Funding wallet does not match this account.");
|
|
6739
|
+
const token = SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token);
|
|
6740
|
+
if (!token) throw new OwneyError("CHAIN_UNSUPPORTED", "Unsupported funding asset.");
|
|
6741
|
+
const rpc = this.getPaidRpcClient(chainId);
|
|
6742
|
+
const receipt = await rpc.getTransactionReceipt({ hash: evidence.txHash });
|
|
6743
|
+
assertCurrent();
|
|
6744
|
+
let verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet }, token.address, direction);
|
|
6745
|
+
if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
|
|
6746
|
+
const transaction = await rpc.getTransaction({ hash: evidence.txHash });
|
|
6747
|
+
assertCurrent();
|
|
6748
|
+
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
|
|
6749
|
+
verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
|
|
6750
|
+
}
|
|
6751
|
+
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
6752
|
+
if ((verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
|
|
6753
|
+
if (options.refreshBalance !== false)
|
|
6754
|
+
agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
|
|
6755
|
+
this.recoveredFundingReads.add(key2);
|
|
6756
|
+
if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
|
|
6757
|
+
}
|
|
6758
|
+
return verified;
|
|
6759
|
+
}
|
|
6760
|
+
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
6761
|
+
endFundingBalanceRefresh(evidence, read2) {
|
|
6762
|
+
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
6763
|
+
if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || read2.chainId !== chainId || read2.asset.toUpperCase() !== evidence.token) return false;
|
|
6764
|
+
return this.getAgent(evidence.agentId).endFundingBalanceRefresh?.(state, chainId, evidence.token, read2) ?? false;
|
|
6765
|
+
}
|
|
6766
|
+
/** History lookup for receipt recovery; adapters may not authenticate or sign here. */
|
|
6767
|
+
async getFundingHistory(evidence, fromDate, options) {
|
|
6768
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
6769
|
+
if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase())
|
|
6770
|
+
throw new OwneyError("NOT_CONNECTED", "Funding account changed.");
|
|
6771
|
+
const agent = this.getAgent(evidence.agentId);
|
|
6772
|
+
if (!agent.getFundingHistory) return { data: [], hasMore: false };
|
|
6773
|
+
const filters = { tokenSymbol: evidence.token, fromDate, limit: 50, ...options };
|
|
6774
|
+
const page = await this.readAgent(agent, "history", () => agent.getFundingHistory(state, chainId, filters), filters);
|
|
6775
|
+
assertCurrent();
|
|
6776
|
+
return page;
|
|
6777
|
+
}
|
|
6778
|
+
async recoverDepositRegistration(evidence) {
|
|
6779
|
+
const verified = await this.inspectFunding(evidence, "deposit");
|
|
6780
|
+
if (verified.agentId !== "zyfai") return verified;
|
|
6781
|
+
return this.getAgent("zyfai").retryDepositRegistration(this.requireState(), verified);
|
|
6782
|
+
}
|
|
6202
6783
|
// --- Fund operations ---
|
|
6203
6784
|
/**
|
|
6204
6785
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -6210,17 +6791,66 @@ var OwneySDK = class {
|
|
|
6210
6791
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
6211
6792
|
*/
|
|
6212
6793
|
async withdraw(options) {
|
|
6794
|
+
const state = this.requireState();
|
|
6795
|
+
const chainId = this.requireChainId();
|
|
6796
|
+
const ids = options.agentId ? [options.agentId] : this.getActiveAgentIds().filter((id) => !options.excludeAgentIds?.includes(id));
|
|
6797
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
6798
|
+
try {
|
|
6799
|
+
return await this.withdrawFromAgents(options, state, chainId, attempted);
|
|
6800
|
+
} catch (error) {
|
|
6801
|
+
for (const id of ids) if (!attempted.has(id)) {
|
|
6802
|
+
notifyFunding(options.onProgress, {
|
|
6803
|
+
agentId: id,
|
|
6804
|
+
chainId,
|
|
6805
|
+
token: options.asset,
|
|
6806
|
+
walletAddress: state.walletAddress,
|
|
6807
|
+
intendedAmount: options.amount ?? "MAX",
|
|
6808
|
+
transfer: "failed",
|
|
6809
|
+
registration: "not-applicable",
|
|
6810
|
+
delivery: "failed"
|
|
6811
|
+
});
|
|
6812
|
+
}
|
|
6813
|
+
throw error;
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
async withdrawFromAgents(options, state, chainId, attempted) {
|
|
6213
6817
|
const { asset, amount, agentId, onAgentResult } = options;
|
|
6214
6818
|
const notifyAgentResult = (id, result) => {
|
|
6215
6819
|
try {
|
|
6820
|
+
if (result.evidence) notifyFunding(options.onProgress, result.evidence);
|
|
6216
6821
|
onAgentResult?.(id, result);
|
|
6217
6822
|
} catch (error) {
|
|
6218
6823
|
console.warn("Withdrawal result observer failed:", error);
|
|
6219
6824
|
}
|
|
6220
6825
|
};
|
|
6221
|
-
const state = this.requireState();
|
|
6222
|
-
const chainId = this.requireChainId();
|
|
6223
6826
|
const token = asset;
|
|
6827
|
+
const submissionEvidence = /* @__PURE__ */ new Map();
|
|
6828
|
+
const submit = async (agent, requested2) => {
|
|
6829
|
+
attempted.add(agent.id);
|
|
6830
|
+
let evidence = {
|
|
6831
|
+
agentId: agent.id,
|
|
6832
|
+
chainId,
|
|
6833
|
+
token,
|
|
6834
|
+
walletAddress: state.walletAddress,
|
|
6835
|
+
intendedAmount: requested2 ?? "MAX",
|
|
6836
|
+
transfer: "pending",
|
|
6837
|
+
registration: "not-applicable",
|
|
6838
|
+
delivery: "pending"
|
|
6839
|
+
};
|
|
6840
|
+
const progress = (next) => {
|
|
6841
|
+
evidence = { ...evidence, ...next };
|
|
6842
|
+
submissionEvidence.set(agent.id, evidence);
|
|
6843
|
+
notifyFunding(options.onProgress, evidence);
|
|
6844
|
+
};
|
|
6845
|
+
progress(evidence);
|
|
6846
|
+
try {
|
|
6847
|
+
return await agent.withdraw(state, chainId, token, requested2, progress);
|
|
6848
|
+
} catch (error) {
|
|
6849
|
+
if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
6850
|
+
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
6851
|
+
throw error;
|
|
6852
|
+
}
|
|
6853
|
+
};
|
|
6224
6854
|
const assetInfo = SupportedAssets.find(
|
|
6225
6855
|
(a) => a.chainId === chainId && a.symbol === token
|
|
6226
6856
|
);
|
|
@@ -6236,23 +6866,24 @@ var OwneySDK = class {
|
|
|
6236
6866
|
const result = await withFailureReporting(
|
|
6237
6867
|
this.apiKey,
|
|
6238
6868
|
agent.id,
|
|
6239
|
-
() => agent
|
|
6869
|
+
() => submit(agent, amount),
|
|
6240
6870
|
this.routingApiBaseUrl
|
|
6241
6871
|
);
|
|
6242
6872
|
notifyAgentResult(agent.id, result);
|
|
6243
6873
|
return result;
|
|
6244
6874
|
}
|
|
6245
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6875
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
|
|
6246
6876
|
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
6247
6877
|
if (!amount) {
|
|
6248
6878
|
const results2 = {};
|
|
6249
6879
|
const agentErrors2 = {};
|
|
6250
6880
|
for (const agent of withdrawalAgents) {
|
|
6251
6881
|
try {
|
|
6252
|
-
results2[agent.id] = await agent
|
|
6882
|
+
results2[agent.id] = await submit(agent);
|
|
6253
6883
|
notifyAgentResult(agent.id, results2[agent.id]);
|
|
6254
6884
|
} catch (err) {
|
|
6255
6885
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6886
|
+
if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
|
|
6256
6887
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
6257
6888
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
6258
6889
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -6271,6 +6902,13 @@ var OwneySDK = class {
|
|
|
6271
6902
|
{ asset, agentErrors: agentErrors2 }
|
|
6272
6903
|
);
|
|
6273
6904
|
}
|
|
6905
|
+
if (Object.keys(agentErrors2).length > 0) {
|
|
6906
|
+
throw new OwneyError(
|
|
6907
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
6908
|
+
"Some agents could not complete the withdrawal.",
|
|
6909
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2 }
|
|
6910
|
+
);
|
|
6911
|
+
}
|
|
6274
6912
|
const amountsKnown = Object.values(results2).every(
|
|
6275
6913
|
(result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
|
|
6276
6914
|
);
|
|
@@ -6280,7 +6918,7 @@ var OwneySDK = class {
|
|
|
6280
6918
|
};
|
|
6281
6919
|
}
|
|
6282
6920
|
const requested = BigInt(amount);
|
|
6283
|
-
const aggregated = await this.getBalances();
|
|
6921
|
+
const aggregated = await this.getBalances(void 0, { chainId, asset: token });
|
|
6284
6922
|
const unavailableAgents = eligibleAgents.filter(
|
|
6285
6923
|
(agent) => !(agent.id in aggregated.agentBalances)
|
|
6286
6924
|
);
|
|
@@ -6342,12 +6980,7 @@ var OwneySDK = class {
|
|
|
6342
6980
|
const p = orderedPlans[i];
|
|
6343
6981
|
if (p.planned === 0n) continue;
|
|
6344
6982
|
try {
|
|
6345
|
-
results[p.agent.id] = await p.agent.
|
|
6346
|
-
state,
|
|
6347
|
-
chainId,
|
|
6348
|
-
token,
|
|
6349
|
-
p.planned.toString()
|
|
6350
|
-
);
|
|
6983
|
+
results[p.agent.id] = await submit(p.agent, p.planned.toString());
|
|
6351
6984
|
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
6352
6985
|
} catch (err) {
|
|
6353
6986
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
@@ -6362,7 +6995,8 @@ var OwneySDK = class {
|
|
|
6362
6995
|
);
|
|
6363
6996
|
const failedAmount = p.planned;
|
|
6364
6997
|
p.planned = 0n;
|
|
6365
|
-
|
|
6998
|
+
if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
|
|
6999
|
+
redistributeShare(orderedPlans, i, failedAmount);
|
|
6366
7000
|
}
|
|
6367
7001
|
}
|
|
6368
7002
|
if (Object.keys(results).length === 0) {
|
|
@@ -6399,12 +7033,19 @@ var OwneySDK = class {
|
|
|
6399
7033
|
* @param agentId - Optional. Agent to query. Omit for aggregated balances.
|
|
6400
7034
|
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
6401
7035
|
*/
|
|
6402
|
-
async getBalances(agentId) {
|
|
6403
|
-
const state = this.
|
|
6404
|
-
const
|
|
7036
|
+
async getBalances(agentId, options) {
|
|
7037
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
7038
|
+
const asset = options?.asset;
|
|
7039
|
+
const readBalance = (agent) => this.readAgent(
|
|
7040
|
+
agent,
|
|
7041
|
+
"balances",
|
|
7042
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
7043
|
+
asset ? { asset } : null
|
|
7044
|
+
);
|
|
6405
7045
|
if (agentId) {
|
|
6406
7046
|
const agent = this.getAgent(agentId);
|
|
6407
|
-
const result = await
|
|
7047
|
+
const result = await readBalance(agent);
|
|
7048
|
+
assertCurrent();
|
|
6408
7049
|
return {
|
|
6409
7050
|
...result,
|
|
6410
7051
|
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
@@ -6415,7 +7056,7 @@ var OwneySDK = class {
|
|
|
6415
7056
|
const entries = [...this.getActiveAgents().entries()];
|
|
6416
7057
|
const balanceResults = await Promise.allSettled(
|
|
6417
7058
|
entries.map(async ([id, agent]) => {
|
|
6418
|
-
const b = await
|
|
7059
|
+
const b = await readBalance(agent);
|
|
6419
7060
|
return [
|
|
6420
7061
|
id,
|
|
6421
7062
|
{
|
|
@@ -6425,6 +7066,7 @@ var OwneySDK = class {
|
|
|
6425
7066
|
];
|
|
6426
7067
|
})
|
|
6427
7068
|
);
|
|
7069
|
+
assertCurrent();
|
|
6428
7070
|
let successCount = 0;
|
|
6429
7071
|
const agentErrors = {};
|
|
6430
7072
|
const agentRetryAt = {};
|
|
@@ -6692,12 +7334,13 @@ var OwneySDK = class {
|
|
|
6692
7334
|
* @returns {OwneyAgentHistory} `data` (up to `limit` entries), `hasMore`, `nextCursor`.
|
|
6693
7335
|
*/
|
|
6694
7336
|
async getHistory(options) {
|
|
6695
|
-
const state = this.
|
|
6696
|
-
const chainId = this.requireChainId();
|
|
7337
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
6697
7338
|
const { agentId, filters } = options ?? {};
|
|
6698
7339
|
if (agentId) {
|
|
6699
7340
|
const agent = this.getAgent(agentId);
|
|
6700
|
-
|
|
7341
|
+
const page = await this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
|
|
7342
|
+
assertCurrent();
|
|
7343
|
+
return page;
|
|
6701
7344
|
}
|
|
6702
7345
|
const activeAgents = [...this.getActiveAgents().values()];
|
|
6703
7346
|
const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
|
|
@@ -6707,20 +7350,35 @@ var OwneySDK = class {
|
|
|
6707
7350
|
if (filters?.cursor && agentCursor === void 0) {
|
|
6708
7351
|
return { agentId: agent.id, page: null };
|
|
6709
7352
|
}
|
|
6710
|
-
const agentFilters = { ...filters, cursor: agentCursor };
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
7353
|
+
const agentFilters = { ...filters, cursor: agentCursor || void 0 };
|
|
7354
|
+
try {
|
|
7355
|
+
const page = await this.readAgent(
|
|
7356
|
+
agent,
|
|
7357
|
+
"history",
|
|
7358
|
+
() => agent.getHistory(state, chainId, agentFilters),
|
|
7359
|
+
agentFilters
|
|
7360
|
+
);
|
|
7361
|
+
return { agentId: agent.id, page };
|
|
7362
|
+
} catch (error) {
|
|
7363
|
+
return { agentId: agent.id, page: null, error };
|
|
7364
|
+
}
|
|
6718
7365
|
})
|
|
6719
7366
|
);
|
|
7367
|
+
assertCurrent();
|
|
7368
|
+
const agentErrors = {};
|
|
7369
|
+
const agentRetryAt = {};
|
|
6720
7370
|
const merged = [];
|
|
6721
7371
|
const nextCursorMap = {};
|
|
6722
7372
|
let hasMore = false;
|
|
6723
|
-
for (const
|
|
7373
|
+
for (const result of perAgent) {
|
|
7374
|
+
const { agentId: id, page } = result;
|
|
7375
|
+
if ("error" in result) {
|
|
7376
|
+
agentErrors[id] = result.error instanceof Error ? result.error.message : String(result.error);
|
|
7377
|
+
const delay = rateLimitDelay(result.error);
|
|
7378
|
+
if (delay !== void 0) agentRetryAt[id] = Date.now() + delay;
|
|
7379
|
+
nextCursorMap[id] = cursorMap[id] ?? "";
|
|
7380
|
+
hasMore = true;
|
|
7381
|
+
}
|
|
6724
7382
|
if (!page) continue;
|
|
6725
7383
|
merged.push(...page.data);
|
|
6726
7384
|
if (page.hasMore && page.nextCursor) {
|
|
@@ -6740,6 +7398,7 @@ var OwneySDK = class {
|
|
|
6740
7398
|
});
|
|
6741
7399
|
return {
|
|
6742
7400
|
data: merged,
|
|
7401
|
+
...Object.keys(agentErrors).length ? { agentErrors, agentRetryAt } : {},
|
|
6743
7402
|
hasMore,
|
|
6744
7403
|
nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
|
|
6745
7404
|
};
|