@owney/sdk 0.7.25-beta.1 → 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/README.md +79 -14
- package/dist/index.cjs +1789 -449
- package/dist/index.d.cts +284 -121
- package/dist/index.d.ts +284 -121
- package/dist/index.js +1753 -410
- 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;
|
|
@@ -125,6 +215,34 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
125
215
|
}
|
|
126
216
|
|
|
127
217
|
// src/lib/agent-reads.ts
|
|
218
|
+
function rateLimitDiagnostics(error) {
|
|
219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
220
|
+
let details = {};
|
|
221
|
+
function visit(value, depth = 0) {
|
|
222
|
+
if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
|
|
223
|
+
return;
|
|
224
|
+
seen.add(value);
|
|
225
|
+
const record = value;
|
|
226
|
+
for (const key2 of [
|
|
227
|
+
"rpcSource",
|
|
228
|
+
"chainId",
|
|
229
|
+
"rpcMethod",
|
|
230
|
+
"providerRequestId",
|
|
231
|
+
"statusCode"
|
|
232
|
+
]) {
|
|
233
|
+
const candidate = record[key2];
|
|
234
|
+
if (candidate !== void 0 && details[key2] === void 0)
|
|
235
|
+
details = { ...details, [key2]: candidate };
|
|
236
|
+
}
|
|
237
|
+
for (const key2 of ["details", "cause", "response", "fields", "error"])
|
|
238
|
+
visit(record[key2], depth + 1);
|
|
239
|
+
}
|
|
240
|
+
visit(error);
|
|
241
|
+
return {
|
|
242
|
+
rpcSource: details.rpcSource ?? "agent-api",
|
|
243
|
+
...details
|
|
244
|
+
};
|
|
245
|
+
}
|
|
128
246
|
var AgentReads = class {
|
|
129
247
|
inFlight = /* @__PURE__ */ new Map();
|
|
130
248
|
cooldowns = /* @__PURE__ */ new Map();
|
|
@@ -132,14 +250,15 @@ var AgentReads = class {
|
|
|
132
250
|
clearInFlight() {
|
|
133
251
|
this.inFlight.clear();
|
|
134
252
|
}
|
|
135
|
-
limited(agentId, until) {
|
|
253
|
+
limited(agentId, until, diagnostics) {
|
|
136
254
|
return new OwneyError(
|
|
137
255
|
"AGENT_RATE_LIMITED",
|
|
138
256
|
"Too many requests. Please wait before trying again.",
|
|
139
257
|
{
|
|
140
258
|
statusCode: 429,
|
|
141
259
|
retryAt: until,
|
|
142
|
-
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
|
|
260
|
+
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3)),
|
|
261
|
+
...diagnostics
|
|
143
262
|
},
|
|
144
263
|
agentId
|
|
145
264
|
);
|
|
@@ -147,7 +266,9 @@ var AgentReads = class {
|
|
|
147
266
|
run(agentId, key2, fetch2) {
|
|
148
267
|
const cooldown = this.cooldowns.get(agentId);
|
|
149
268
|
if (cooldown && cooldown.until > Date.now()) {
|
|
150
|
-
return Promise.reject(
|
|
269
|
+
return Promise.reject(
|
|
270
|
+
this.limited(agentId, cooldown.until, cooldown.diagnostics)
|
|
271
|
+
);
|
|
151
272
|
}
|
|
152
273
|
const requestKey = JSON.stringify([agentId, key2]);
|
|
153
274
|
const existing = this.inFlight.get(requestKey);
|
|
@@ -168,8 +289,9 @@ var AgentReads = class {
|
|
|
168
289
|
Math.min(3e4 * 2 ** (failures - 1), 3e5)
|
|
169
290
|
);
|
|
170
291
|
const until = Math.max(previous?.until ?? 0, Date.now() + delay);
|
|
171
|
-
|
|
172
|
-
|
|
292
|
+
const diagnostics = rateLimitDiagnostics(error);
|
|
293
|
+
this.cooldowns.set(agentId, { until, failures, diagnostics });
|
|
294
|
+
throw this.limited(agentId, until, diagnostics);
|
|
173
295
|
}
|
|
174
296
|
).finally(() => {
|
|
175
297
|
if (this.inFlight.get(requestKey) === promise)
|
|
@@ -180,9 +302,72 @@ var AgentReads = class {
|
|
|
180
302
|
}
|
|
181
303
|
};
|
|
182
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
|
+
|
|
183
368
|
// src/agents/zyfai/zyfai.agent.ts
|
|
184
369
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
185
|
-
import {
|
|
370
|
+
import { formatUnits, parseAbi as parseAbi2 } from "viem";
|
|
186
371
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
187
372
|
|
|
188
373
|
// src/types/config.ts
|
|
@@ -355,7 +540,11 @@ function mapPendingAllocations(raw2) {
|
|
|
355
540
|
}
|
|
356
541
|
function mapBalances(raw2, _chainId, smartWallet) {
|
|
357
542
|
const portfolio = raw2.portfolio;
|
|
358
|
-
|
|
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;
|
|
359
548
|
let totalBalance = 0;
|
|
360
549
|
const tokens = [];
|
|
361
550
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
@@ -364,7 +553,8 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
364
553
|
symbol,
|
|
365
554
|
{ balance, balanceWithFee, decimals }
|
|
366
555
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
367
|
-
|
|
556
|
+
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
557
|
+
const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
|
|
368
558
|
totalBalance += amount;
|
|
369
559
|
tokens.push({
|
|
370
560
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
@@ -414,6 +604,15 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
414
604
|
totalBalanceAsset: "usdc",
|
|
415
605
|
tokens,
|
|
416
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
|
+
})),
|
|
417
616
|
...pendingAllocations ? { pendingAllocations } : {}
|
|
418
617
|
};
|
|
419
618
|
}
|
|
@@ -754,28 +953,6 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
|
|
|
754
953
|
return { walletAddress: raw2.walletAddress, chainId, assets };
|
|
755
954
|
}
|
|
756
955
|
|
|
757
|
-
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
758
|
-
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
759
|
-
function topicToAddress(topic) {
|
|
760
|
-
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
761
|
-
}
|
|
762
|
-
function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
763
|
-
const wantRecipient = recipient.toLowerCase();
|
|
764
|
-
const wantToken = token.toLowerCase();
|
|
765
|
-
let total = 0n;
|
|
766
|
-
let matched = false;
|
|
767
|
-
for (const log of logs) {
|
|
768
|
-
if (log.address.toLowerCase() !== wantToken) continue;
|
|
769
|
-
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
|
|
770
|
-
if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
|
|
771
|
-
if (!log.data || log.data === "0x") continue;
|
|
772
|
-
total += BigInt(log.data);
|
|
773
|
-
matched = true;
|
|
774
|
-
}
|
|
775
|
-
if (!matched) return void 0;
|
|
776
|
-
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
777
|
-
}
|
|
778
|
-
|
|
779
956
|
// src/agents/zyfai/zyfai.history-cursor.ts
|
|
780
957
|
var InvalidHistoryCursorError = class extends Error {
|
|
781
958
|
code = "invalid_cursor";
|
|
@@ -1063,15 +1240,148 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
1063
1240
|
return !protocolListsEqual(current.protocols, desiredProtocols);
|
|
1064
1241
|
}
|
|
1065
1242
|
|
|
1243
|
+
// src/lib/paid-rpc.ts
|
|
1244
|
+
import {
|
|
1245
|
+
createPublicClient,
|
|
1246
|
+
http
|
|
1247
|
+
} from "viem";
|
|
1248
|
+
var PAID_RPC_RETRY_COUNT = 3;
|
|
1249
|
+
var PAID_RPC_RETRY_DELAY_MS = 1e3;
|
|
1250
|
+
function configuredRpcProxyBaseUrl(baseUrl) {
|
|
1251
|
+
const configured = (baseUrl ?? "")?.trim();
|
|
1252
|
+
if (!configured) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
"OWNEY_ROUTING_API_BASE_URL is required for RPC proxy requests unless routingApiBaseUrl or all rpcUrls are configured."
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
return configured;
|
|
1258
|
+
}
|
|
1259
|
+
function rpcProxyUrl(chainId, apiKey, baseUrl) {
|
|
1260
|
+
const url = new URL(
|
|
1261
|
+
`${configuredRpcProxyBaseUrl(baseUrl).replace(/\/$/, "")}/api/v1/rpc/${chainId}`
|
|
1262
|
+
);
|
|
1263
|
+
if (apiKey) url.searchParams.set("apiKey", apiKey);
|
|
1264
|
+
return url.toString();
|
|
1265
|
+
}
|
|
1266
|
+
function resolveRpcUrl(rpcUrls, chainId, apiKey = "", baseUrl) {
|
|
1267
|
+
const url = rpcUrls?.[chainId]?.trim();
|
|
1268
|
+
if (url) return url;
|
|
1269
|
+
return rpcProxyUrl(chainId, apiKey, baseUrl);
|
|
1270
|
+
}
|
|
1271
|
+
function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
|
|
1272
|
+
return {
|
|
1273
|
+
1: resolveRpcUrl(rpcUrls, 1, apiKey, baseUrl),
|
|
1274
|
+
8453: resolveRpcUrl(rpcUrls, 8453, apiKey, baseUrl),
|
|
1275
|
+
42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function createPaidRpcClient(chain, rpcUrls) {
|
|
1279
|
+
const url = resolveRpcUrl(
|
|
1280
|
+
rpcUrls,
|
|
1281
|
+
chain.id
|
|
1282
|
+
);
|
|
1283
|
+
return createPublicClient({
|
|
1284
|
+
chain,
|
|
1285
|
+
transport: http(url, {
|
|
1286
|
+
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1287
|
+
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
1288
|
+
})
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
function headerValue(error, name) {
|
|
1292
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1293
|
+
let value;
|
|
1294
|
+
function visit(candidate, depth = 0) {
|
|
1295
|
+
if (value || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1296
|
+
return;
|
|
1297
|
+
if (seen.has(candidate)) return;
|
|
1298
|
+
seen.add(candidate);
|
|
1299
|
+
const record = candidate;
|
|
1300
|
+
const headers = record.headers;
|
|
1301
|
+
let found;
|
|
1302
|
+
if (typeof headers?.get === "function") {
|
|
1303
|
+
found = headers.get(name);
|
|
1304
|
+
} else {
|
|
1305
|
+
const headerRecord = headers;
|
|
1306
|
+
found = headerRecord?.[name] ?? headerRecord?.[name.toLowerCase()];
|
|
1307
|
+
}
|
|
1308
|
+
if (typeof found === "string" && found) {
|
|
1309
|
+
value = found;
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
for (const key2 of ["cause", "details", "response", "error"])
|
|
1313
|
+
visit(record[key2], depth + 1);
|
|
1314
|
+
}
|
|
1315
|
+
visit(error);
|
|
1316
|
+
return value;
|
|
1317
|
+
}
|
|
1318
|
+
function statusCode(error) {
|
|
1319
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1320
|
+
let status;
|
|
1321
|
+
function visit(candidate, depth = 0) {
|
|
1322
|
+
if (status || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1323
|
+
return;
|
|
1324
|
+
if (seen.has(candidate)) return;
|
|
1325
|
+
seen.add(candidate);
|
|
1326
|
+
const record = candidate;
|
|
1327
|
+
for (const key2 of ["status", "statusCode"]) {
|
|
1328
|
+
const parsed = Number(record[key2]);
|
|
1329
|
+
if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1330
|
+
status = parsed;
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
for (const key2 of ["cause", "details", "response", "error"])
|
|
1335
|
+
visit(record[key2], depth + 1);
|
|
1336
|
+
}
|
|
1337
|
+
visit(error);
|
|
1338
|
+
return status;
|
|
1339
|
+
}
|
|
1340
|
+
function paidRpcError(error, chainId, rpcMethod, agentId) {
|
|
1341
|
+
const delay = rateLimitDelay(error);
|
|
1342
|
+
const providerRequestId = headerValue(error, "x-alchemy-request-id") ?? headerValue(error, "x-request-id");
|
|
1343
|
+
if (delay === void 0) {
|
|
1344
|
+
return new OwneyError(
|
|
1345
|
+
"AGENT_API_ERROR",
|
|
1346
|
+
"The blockchain RPC request failed.",
|
|
1347
|
+
{
|
|
1348
|
+
rpcSource: "paid-rpc",
|
|
1349
|
+
chainId,
|
|
1350
|
+
rpcMethod,
|
|
1351
|
+
...statusCode(error) ? { statusCode: statusCode(error) } : {},
|
|
1352
|
+
...providerRequestId ? { providerRequestId } : {}
|
|
1353
|
+
},
|
|
1354
|
+
agentId
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
const retryAt = Date.now() + delay;
|
|
1358
|
+
return new OwneyError(
|
|
1359
|
+
"AGENT_RATE_LIMITED",
|
|
1360
|
+
"The blockchain RPC is rate limited. Please wait before trying again.",
|
|
1361
|
+
{
|
|
1362
|
+
rpcSource: "paid-rpc",
|
|
1363
|
+
chainId,
|
|
1364
|
+
rpcMethod,
|
|
1365
|
+
statusCode: 429,
|
|
1366
|
+
retryAt,
|
|
1367
|
+
retryAfterSeconds: Math.max(0, Math.ceil(delay / 1e3)),
|
|
1368
|
+
...providerRequestId ? { providerRequestId } : {}
|
|
1369
|
+
},
|
|
1370
|
+
agentId
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
1374
|
+
try {
|
|
1375
|
+
return await operation();
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
throw paidRpcError(error, chainId, rpcMethod, agentId);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1066
1381
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1067
|
-
var ERC7579_IS_MODULE_INSTALLED_ABI =
|
|
1382
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi2([
|
|
1068
1383
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1069
1384
|
]);
|
|
1070
|
-
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
1071
|
-
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1072
|
-
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1073
|
-
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
1074
|
-
};
|
|
1075
1385
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
1076
1386
|
8453: "0x4200000000000000000000000000000000000006",
|
|
1077
1387
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
@@ -1128,16 +1438,15 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1128
1438
|
connectAuthPromise = null;
|
|
1129
1439
|
rpcUrls;
|
|
1130
1440
|
publicClients = /* @__PURE__ */ new Map();
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
|
|
1134
|
-
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();
|
|
1135
1444
|
earningsReads = /* @__PURE__ */ new Map();
|
|
1136
1445
|
earningsRefreshes = /* @__PURE__ */ new Map();
|
|
1137
1446
|
earningsSnapshot = null;
|
|
1138
1447
|
earningsGeneration = 0;
|
|
1139
1448
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1140
|
-
this.rpcUrls = rpcUrls
|
|
1449
|
+
this.rpcUrls = resolveRpcUrls(rpcUrls);
|
|
1141
1450
|
this.sdk = new ZyfaiSDK({
|
|
1142
1451
|
apiKey,
|
|
1143
1452
|
rpcUrls: this.rpcUrls,
|
|
@@ -1151,16 +1460,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1151
1460
|
getPublicClient(chainId) {
|
|
1152
1461
|
const cached = this.publicClients.get(chainId);
|
|
1153
1462
|
if (cached) return cached;
|
|
1154
|
-
const client =
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1463
|
+
const client = createPaidRpcClient(
|
|
1464
|
+
VIEM_CHAIN[chainId],
|
|
1465
|
+
this.rpcUrls
|
|
1466
|
+
);
|
|
1158
1467
|
this.publicClients.set(chainId, client);
|
|
1159
1468
|
return client;
|
|
1160
1469
|
}
|
|
1161
1470
|
/**
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
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
|
|
1164
1474
|
* payload only reports the *remaining* position for a partial withdraw (see
|
|
1165
1475
|
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
1166
1476
|
* balance instead of the amount withdrawn.
|
|
@@ -1172,48 +1482,62 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1172
1482
|
* Entries with no symbol (older payload shapes) fall back to USDC to
|
|
1173
1483
|
* preserve prior behavior.
|
|
1174
1484
|
*
|
|
1175
|
-
* Mutates `entries` in place
|
|
1176
|
-
*
|
|
1177
|
-
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
1178
|
-
* 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.
|
|
1179
1487
|
*/
|
|
1180
|
-
async
|
|
1181
|
-
const
|
|
1182
|
-
(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]
|
|
1183
1491
|
);
|
|
1184
|
-
if (
|
|
1492
|
+
if (movements.length === 0) return;
|
|
1185
1493
|
const client = this.getPublicClient(chainId);
|
|
1494
|
+
const receipts = /* @__PURE__ */ new Map();
|
|
1186
1495
|
await Promise.all(
|
|
1187
|
-
|
|
1496
|
+
movements.map(async (entry) => {
|
|
1188
1497
|
const tx = entry.transactions[0];
|
|
1189
1498
|
const txHash = tx.txHashes[0];
|
|
1190
|
-
const rawSymbol = tx.tokenSymbol;
|
|
1499
|
+
const rawSymbol = tx.tokenSymbol ?? tokenSymbol;
|
|
1191
1500
|
const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
|
|
1192
1501
|
const asset = SupportedAssets.find(
|
|
1193
1502
|
(a) => a.chainId === chainId && a.symbol === normalizedSymbol
|
|
1194
|
-
) ?? SupportedAssets.find(
|
|
1195
|
-
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
1196
1503
|
);
|
|
1197
1504
|
if (!asset) return;
|
|
1198
|
-
|
|
1199
|
-
|
|
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);
|
|
1200
1512
|
if (cached !== void 0) {
|
|
1201
|
-
tx
|
|
1513
|
+
Object.assign(tx, cached);
|
|
1202
1514
|
return;
|
|
1203
1515
|
}
|
|
1204
1516
|
try {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
asset.
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
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 });
|
|
1217
1541
|
}
|
|
1218
1542
|
} catch {
|
|
1219
1543
|
}
|
|
@@ -1302,7 +1626,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1302
1626
|
* Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
|
|
1303
1627
|
* cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
|
|
1304
1628
|
* we drop the cache and try again — the second attempt has no cache entry,
|
|
1305
|
-
* 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
|
|
1306
1630
|
* an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
|
|
1307
1631
|
* JWT from a misconfigured API key; we retry once either way, and if the
|
|
1308
1632
|
* second attempt still fails the original error propagates.
|
|
@@ -1335,6 +1659,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1335
1659
|
await this.connectAuth(state, chainId);
|
|
1336
1660
|
}
|
|
1337
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
|
+
}
|
|
1338
1673
|
/**
|
|
1339
1674
|
* Sync newly-available protocols onto an existing user's profile at wallet
|
|
1340
1675
|
* connection. Zyfai's own protocol sync (`updateUserProtocols`) runs only when
|
|
@@ -1880,7 +2215,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1880
2215
|
await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
|
|
1881
2216
|
return redeployed.address;
|
|
1882
2217
|
}
|
|
1883
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
2218
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
|
|
1884
2219
|
try {
|
|
1885
2220
|
const validChainId = isValidChainId(chainId);
|
|
1886
2221
|
await this.ensureConnected(state, validChainId);
|
|
@@ -1889,17 +2224,48 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1889
2224
|
this.getAddress(),
|
|
1890
2225
|
validChainId
|
|
1891
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
|
+
});
|
|
1892
2238
|
const txHash = await depositCallback(smartWallet, validChainId, amount);
|
|
1893
|
-
|
|
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);
|
|
1894
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);
|
|
2260
|
+
try {
|
|
2261
|
+
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
1895
2262
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
logError
|
|
1900
|
-
);
|
|
2263
|
+
evidence2 = { ...evidence2, registration: "confirmed" };
|
|
2264
|
+
} catch {
|
|
2265
|
+
evidence2 = { ...evidence2, registration: "unknown" };
|
|
1901
2266
|
}
|
|
1902
|
-
|
|
2267
|
+
notifyFunding(onProgress, evidence2);
|
|
2268
|
+
return { txHash, smartWallet, amount, evidence: evidence2 };
|
|
1903
2269
|
}
|
|
1904
2270
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
1905
2271
|
const raw2 = await this.sdk.depositFunds(
|
|
@@ -1909,22 +2275,88 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1909
2275
|
asset,
|
|
1910
2276
|
"aggressive"
|
|
1911
2277
|
);
|
|
1912
|
-
|
|
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 };
|
|
1913
2299
|
} catch (error) {
|
|
1914
2300
|
throw error;
|
|
1915
2301
|
}
|
|
1916
2302
|
}
|
|
1917
2303
|
// --- IAgent: Fund operations ---
|
|
1918
|
-
async withdraw(state, chainId, token, amount) {
|
|
2304
|
+
async withdraw(state, chainId, token, amount, onProgress) {
|
|
1919
2305
|
const validChainId = isValidChainId(chainId);
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
+
}
|
|
1927
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
|
+
});
|
|
1928
2360
|
throw new OwneyError(
|
|
1929
2361
|
"WITHDRAW_FAILED",
|
|
1930
2362
|
raw2.message || "Zyfai withdraw failed.",
|
|
@@ -1932,13 +2364,45 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1932
2364
|
this.id
|
|
1933
2365
|
);
|
|
1934
2366
|
}
|
|
1935
|
-
|
|
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" };
|
|
1936
2395
|
}
|
|
1937
2396
|
// --- IAgent: Portfolio reads ---
|
|
2397
|
+
async getFundingWallet(state, chainId) {
|
|
2398
|
+
return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
|
|
2399
|
+
}
|
|
1938
2400
|
async getBalances(state, chainId) {
|
|
2401
|
+
const requestedAt = Date.now();
|
|
1939
2402
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
1940
2403
|
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
1941
|
-
|
|
2404
|
+
const balance = mapBalances(raw2, validChainId, smartWallet);
|
|
2405
|
+
return { ...balance, readMetadata: balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) };
|
|
1942
2406
|
}
|
|
1943
2407
|
earningsKey(state, chainId, smartWallet) {
|
|
1944
2408
|
return JSON.stringify([
|
|
@@ -2011,7 +2475,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2011
2475
|
/**
|
|
2012
2476
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
2013
2477
|
* takes lowercase `assetType` and denominates WETH as "eth" (the same
|
|
2014
|
-
* spelling `
|
|
2478
|
+
* spelling `resolveFundingTransfers` normalizes on the way in). Anything
|
|
2015
2479
|
* unrecognised returns undefined so the request stays unfiltered rather
|
|
2016
2480
|
* than silently narrowing to nothing.
|
|
2017
2481
|
*/
|
|
@@ -2030,6 +2494,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2030
2494
|
}
|
|
2031
2495
|
async getHistory(state, chainId, options) {
|
|
2032
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) {
|
|
2033
2505
|
const limit = options?.limit ?? 10;
|
|
2034
2506
|
const assetType = _ZyfaiAgent.toZyfaiAssetType(options?.tokenSymbol);
|
|
2035
2507
|
const cursor = options?.cursor ? decodeHistoryCursor(options.cursor) : null;
|
|
@@ -2071,9 +2543,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2071
2543
|
rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
|
|
2072
2544
|
}) : void 0;
|
|
2073
2545
|
const data = mapEntries(pageData, validChainId);
|
|
2074
|
-
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
|
+
});
|
|
2075
2554
|
return {
|
|
2076
|
-
data,
|
|
2555
|
+
data: unique,
|
|
2077
2556
|
hasMore,
|
|
2078
2557
|
nextCursor
|
|
2079
2558
|
};
|
|
@@ -2127,7 +2606,7 @@ import {
|
|
|
2127
2606
|
createWalletClient as createWalletClient2,
|
|
2128
2607
|
custom as custom2,
|
|
2129
2608
|
encodeFunctionData,
|
|
2130
|
-
erc20Abi,
|
|
2609
|
+
erc20Abi as erc20Abi2,
|
|
2131
2610
|
getAddress as getAddress2,
|
|
2132
2611
|
isAddress as isAddress2
|
|
2133
2612
|
} from "viem";
|
|
@@ -2287,11 +2766,12 @@ async function postSponsorBatchTransfer(input) {
|
|
|
2287
2766
|
// src/lib/permit2.ts
|
|
2288
2767
|
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2289
2768
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2769
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2290
2770
|
function permit2ApprovalAmount(requiredAmount) {
|
|
2291
2771
|
if (requiredAmount <= 0n) {
|
|
2292
2772
|
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2293
2773
|
}
|
|
2294
|
-
return
|
|
2774
|
+
return MAX_UINT256;
|
|
2295
2775
|
}
|
|
2296
2776
|
var ERC20_ALLOWANCE_ABI = [
|
|
2297
2777
|
{
|
|
@@ -2327,12 +2807,13 @@ function randomPermit2Nonce() {
|
|
|
2327
2807
|
globalThis.crypto.getRandomValues(bytes);
|
|
2328
2808
|
return BigInt(bytesToHex2(bytes));
|
|
2329
2809
|
}
|
|
2330
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2810
|
+
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
2331
2811
|
return publicClient.readContract({
|
|
2332
2812
|
address: token,
|
|
2333
2813
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2334
2814
|
functionName: "allowance",
|
|
2335
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2815
|
+
args: [owner, PERMIT2_ADDRESS],
|
|
2816
|
+
...blockNumber === void 0 ? {} : { blockNumber }
|
|
2336
2817
|
});
|
|
2337
2818
|
}
|
|
2338
2819
|
async function readErc20Balance(publicClient, token, owner) {
|
|
@@ -2720,10 +3201,11 @@ var YieldseekerApiClient = class {
|
|
|
2720
3201
|
const payload = await response.json().catch(() => null);
|
|
2721
3202
|
if (!response.ok) {
|
|
2722
3203
|
const error = providerError(payload, `HTTP_${response.status}`);
|
|
3204
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
2723
3205
|
throw new YieldseekerApiError(
|
|
2724
3206
|
response.status,
|
|
2725
3207
|
error.code,
|
|
2726
|
-
error.fields
|
|
3208
|
+
retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
|
|
2727
3209
|
);
|
|
2728
3210
|
}
|
|
2729
3211
|
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
@@ -2745,7 +3227,7 @@ var YieldseekerApiClient = class {
|
|
|
2745
3227
|
};
|
|
2746
3228
|
|
|
2747
3229
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2748
|
-
import { formatUnits, isAddress } from "viem";
|
|
3230
|
+
import { formatUnits as formatUnits2, isAddress } from "viem";
|
|
2749
3231
|
|
|
2750
3232
|
// src/lib/helpers/snapshot-apy.ts
|
|
2751
3233
|
var DAY_MS = 864e5;
|
|
@@ -2812,10 +3294,10 @@ function raw(value, endpoint) {
|
|
|
2812
3294
|
return BigInt(value);
|
|
2813
3295
|
}
|
|
2814
3296
|
function decimal(value, decimals, endpoint) {
|
|
2815
|
-
return
|
|
3297
|
+
return formatUnits2(raw(value, endpoint), decimals);
|
|
2816
3298
|
}
|
|
2817
3299
|
function usd(rawAmount, decimals, price) {
|
|
2818
|
-
return Number(
|
|
3300
|
+
return Number(formatUnits2(rawAmount, decimals)) * price;
|
|
2819
3301
|
}
|
|
2820
3302
|
function percent(value) {
|
|
2821
3303
|
const result = Number(value);
|
|
@@ -2854,13 +3336,14 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
2854
3336
|
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2855
3337
|
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2856
3338
|
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2857
|
-
// share quantity
|
|
3339
|
+
// share quantity separate from the withdrawable underlying asset amount.
|
|
2858
3340
|
amount: decimal(
|
|
2859
3341
|
value.assetsBase,
|
|
2860
3342
|
baseAssetDecimals,
|
|
2861
3343
|
"yield positions"
|
|
2862
3344
|
),
|
|
2863
3345
|
amountRaw: String(value.assetsRaw),
|
|
3346
|
+
withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
|
|
2864
3347
|
apy: percent(option.riskAdjustedApy),
|
|
2865
3348
|
tvl: Number(option.totalDepositsUsd),
|
|
2866
3349
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -2910,7 +3393,17 @@ function mapYieldseekerBalances(contexts) {
|
|
|
2910
3393
|
totalBalanceAsset: "usdc",
|
|
2911
3394
|
assetBalances,
|
|
2912
3395
|
tokens,
|
|
2913
|
-
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
|
+
}))
|
|
2914
3407
|
};
|
|
2915
3408
|
}
|
|
2916
3409
|
function mapYieldseekerEarnings(contexts) {
|
|
@@ -2922,7 +3415,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
2922
3415
|
chain: "BASE",
|
|
2923
3416
|
chainId: 8453,
|
|
2924
3417
|
asset: context.asset,
|
|
2925
|
-
amount:
|
|
3418
|
+
amount: formatUnits2(amount, context.snapshot.baseAssetDecimals)
|
|
2926
3419
|
});
|
|
2927
3420
|
lifetimeEarnings += usd(
|
|
2928
3421
|
amount,
|
|
@@ -3024,6 +3517,7 @@ function transactionHashes(details) {
|
|
|
3024
3517
|
function actionEntry(action) {
|
|
3025
3518
|
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3026
3519
|
return {
|
|
3520
|
+
id: `yieldseeker:action:${action.agentActionId}`,
|
|
3027
3521
|
agent: "yieldseeker",
|
|
3028
3522
|
action: actionType(action.actionType),
|
|
3029
3523
|
date: action.createdDate,
|
|
@@ -3053,7 +3547,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
|
|
|
3053
3547
|
const owner = ownerAddress.toLowerCase();
|
|
3054
3548
|
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3055
3549
|
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3056
|
-
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) {
|
|
3057
3551
|
return void 0;
|
|
3058
3552
|
}
|
|
3059
3553
|
let action;
|
|
@@ -3078,6 +3572,12 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
|
|
|
3078
3572
|
transactions: [
|
|
3079
3573
|
{
|
|
3080
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),
|
|
3081
3581
|
chainId: agent.chainId,
|
|
3082
3582
|
tokenSymbol: asset,
|
|
3083
3583
|
amount: decimal(
|
|
@@ -3123,7 +3623,8 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3123
3623
|
ungrouped.push(entry);
|
|
3124
3624
|
continue;
|
|
3125
3625
|
}
|
|
3126
|
-
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;
|
|
3127
3628
|
const previous = grouped.get(key2);
|
|
3128
3629
|
if (!previous) {
|
|
3129
3630
|
grouped.set(key2, entry);
|
|
@@ -3136,14 +3637,52 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3136
3637
|
}
|
|
3137
3638
|
previous.positions.push(...entry.positions);
|
|
3138
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
|
+
}
|
|
3139
3652
|
}
|
|
3140
3653
|
}
|
|
3141
|
-
const
|
|
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.");
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
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);
|
|
3142
3677
|
return {
|
|
3143
|
-
data
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
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
|
+
}))) } : {}
|
|
3147
3686
|
};
|
|
3148
3687
|
}
|
|
3149
3688
|
function mapYieldseekerProfile(address, contexts) {
|
|
@@ -3185,10 +3724,18 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
3185
3724
|
|
|
3186
3725
|
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3187
3726
|
var OWNEY_AGENT_NAME = "owney";
|
|
3727
|
+
var OWNEY_AGENT_RULE_PRESET = "explorative";
|
|
3188
3728
|
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3189
3729
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3190
3730
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3191
3731
|
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3732
|
+
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
3733
|
+
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
3734
|
+
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
3735
|
+
var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
3736
|
+
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
3737
|
+
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
3738
|
+
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3192
3739
|
function generateYieldseekerUsername() {
|
|
3193
3740
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3194
3741
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -3232,6 +3779,7 @@ function query(params) {
|
|
|
3232
3779
|
var YieldseekerAgent = class {
|
|
3233
3780
|
id = "yieldseeker";
|
|
3234
3781
|
balanceComposition = "tokens-plus-positions";
|
|
3782
|
+
withdrawalRequiresWalletApproval = true;
|
|
3235
3783
|
supportedChainIds = [8453];
|
|
3236
3784
|
supportedAssets = [
|
|
3237
3785
|
{
|
|
@@ -3245,11 +3793,26 @@ var YieldseekerAgent = class {
|
|
|
3245
3793
|
];
|
|
3246
3794
|
api;
|
|
3247
3795
|
auth;
|
|
3796
|
+
rpcUrls;
|
|
3797
|
+
receiptClient;
|
|
3248
3798
|
transactionExecutor;
|
|
3249
3799
|
unwindReceiptWaiter;
|
|
3250
3800
|
agentContexts = /* @__PURE__ */ new Map();
|
|
3251
3801
|
users = /* @__PURE__ */ new Map();
|
|
3252
3802
|
pendingAgents = /* @__PURE__ */ new Map();
|
|
3803
|
+
pendingWalletContexts = /* @__PURE__ */ new Map();
|
|
3804
|
+
readCache = /* @__PURE__ */ new Map();
|
|
3805
|
+
pendingReads = /* @__PURE__ */ new Map();
|
|
3806
|
+
readGeneration = 0;
|
|
3807
|
+
portfolioVersions = /* @__PURE__ */ new Map();
|
|
3808
|
+
snapshotFailures = /* @__PURE__ */ new Map();
|
|
3809
|
+
standardReadFailures = /* @__PURE__ */ new Map();
|
|
3810
|
+
reconcileUntil = /* @__PURE__ */ new Map();
|
|
3811
|
+
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
3812
|
+
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
3813
|
+
// settlement; keep slow reconciliation after the fast window expires.
|
|
3814
|
+
snapshotMovements = /* @__PURE__ */ new Map();
|
|
3815
|
+
movementEpochs = /* @__PURE__ */ new Map();
|
|
3253
3816
|
yieldOptions = /* @__PURE__ */ new Map();
|
|
3254
3817
|
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3255
3818
|
constructor(owneyApiKey, options = {}) {
|
|
@@ -3259,10 +3822,16 @@ var YieldseekerAgent = class {
|
|
|
3259
3822
|
options.fetchFn
|
|
3260
3823
|
);
|
|
3261
3824
|
this.auth = new YieldseekerAuth(options.auth);
|
|
3825
|
+
this.rpcUrls = options.rpcUrls;
|
|
3262
3826
|
this.transactionExecutor = options.transactionExecutor;
|
|
3263
3827
|
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3264
3828
|
}
|
|
3829
|
+
getReceiptClient() {
|
|
3830
|
+
this.receiptClient ??= createPaidRpcClient(base3, this.rpcUrls);
|
|
3831
|
+
return this.receiptClient;
|
|
3832
|
+
}
|
|
3265
3833
|
async disconnect() {
|
|
3834
|
+
this.readGeneration += 1;
|
|
3266
3835
|
this.auth.clear();
|
|
3267
3836
|
for (const key2 of this.users.keys()) {
|
|
3268
3837
|
const [walletAddress, chainId] = key2.split(":");
|
|
@@ -3271,6 +3840,16 @@ var YieldseekerAgent = class {
|
|
|
3271
3840
|
this.users.clear();
|
|
3272
3841
|
this.agentContexts.clear();
|
|
3273
3842
|
this.pendingAgents.clear();
|
|
3843
|
+
this.pendingWalletContexts.clear();
|
|
3844
|
+
this.readCache.clear();
|
|
3845
|
+
this.pendingReads.clear();
|
|
3846
|
+
this.portfolioVersions.clear();
|
|
3847
|
+
this.snapshotFailures.clear();
|
|
3848
|
+
this.standardReadFailures.clear();
|
|
3849
|
+
this.reconcileUntil.clear();
|
|
3850
|
+
this.activityRefreshUntil.clear();
|
|
3851
|
+
this.snapshotMovements.clear();
|
|
3852
|
+
this.movementEpochs.clear();
|
|
3274
3853
|
}
|
|
3275
3854
|
async activateAgent(state, chainId, asset) {
|
|
3276
3855
|
this.assertChain(chainId);
|
|
@@ -3278,7 +3857,7 @@ var YieldseekerAgent = class {
|
|
|
3278
3857
|
this.assertAsset(targetAsset);
|
|
3279
3858
|
await this.ensureAgent(state, chainId, targetAsset);
|
|
3280
3859
|
}
|
|
3281
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3860
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
|
|
3282
3861
|
this.assertChain(chainId);
|
|
3283
3862
|
this.assertAsset(asset);
|
|
3284
3863
|
if (BigInt(amount) <= 0n) {
|
|
@@ -3290,49 +3869,85 @@ var YieldseekerAgent = class {
|
|
|
3290
3869
|
);
|
|
3291
3870
|
}
|
|
3292
3871
|
const context = await this.ensureAgent(state, chainId, asset);
|
|
3872
|
+
const generation = this.readGeneration;
|
|
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
|
+
});
|
|
3293
3885
|
let txHash;
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
amount
|
|
3306
|
-
);
|
|
3307
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3308
|
-
} else {
|
|
3309
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3310
|
-
from: getAddress2(state.walletAddress),
|
|
3311
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3312
|
-
data: encodeFunctionData({
|
|
3313
|
-
abi: erc20Abi,
|
|
3314
|
-
functionName: "transfer",
|
|
3315
|
-
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3316
|
-
}),
|
|
3317
|
-
value: "0",
|
|
3318
|
-
chainId
|
|
3319
|
-
});
|
|
3320
|
-
}
|
|
3321
|
-
} finally {
|
|
3322
|
-
await this.refreshSnapshotAfterMovement(
|
|
3323
|
-
state,
|
|
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" });
|
|
3888
|
+
if (depositCallback) {
|
|
3889
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3890
|
+
agentId: "yieldseeker",
|
|
3891
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3892
|
+
userId: context.user.userId,
|
|
3893
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3894
|
+
});
|
|
3895
|
+
txHash = await depositCallback(
|
|
3896
|
+
context.wallet.walletAddress,
|
|
3324
3897
|
chainId,
|
|
3325
|
-
|
|
3326
|
-
"deposit"
|
|
3898
|
+
amount
|
|
3327
3899
|
);
|
|
3900
|
+
submitted(txHash);
|
|
3901
|
+
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
3902
|
+
} else {
|
|
3903
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3904
|
+
from: getAddress2(state.walletAddress),
|
|
3905
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3906
|
+
data: encodeFunctionData({
|
|
3907
|
+
abi: erc20Abi2,
|
|
3908
|
+
functionName: "transfer",
|
|
3909
|
+
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3910
|
+
}),
|
|
3911
|
+
value: "0",
|
|
3912
|
+
chainId
|
|
3913
|
+
}, submitted, (value) => {
|
|
3914
|
+
receipt = value;
|
|
3915
|
+
});
|
|
3328
3916
|
}
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3917
|
+
if (this.readGeneration === generation) {
|
|
3918
|
+
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3919
|
+
}
|
|
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"
|
|
3333
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
|
+
}
|
|
3334
3949
|
}
|
|
3335
|
-
async
|
|
3950
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted) {
|
|
3336
3951
|
this.assertChain(chainId);
|
|
3337
3952
|
this.assertAsset(asset);
|
|
3338
3953
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -3352,6 +3967,9 @@ var YieldseekerAgent = class {
|
|
|
3352
3967
|
this.id
|
|
3353
3968
|
);
|
|
3354
3969
|
}
|
|
3970
|
+
const generation = this.readGeneration;
|
|
3971
|
+
let confirmedMovement = false;
|
|
3972
|
+
let settlement = null;
|
|
3355
3973
|
try {
|
|
3356
3974
|
const portfolio = await this.loadPortfolioContext(
|
|
3357
3975
|
state,
|
|
@@ -3359,16 +3977,32 @@ var YieldseekerAgent = class {
|
|
|
3359
3977
|
context
|
|
3360
3978
|
);
|
|
3361
3979
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3362
|
-
const
|
|
3363
|
-
(
|
|
3364
|
-
|
|
3365
|
-
|
|
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();
|
|
3366
3991
|
const deployed = portfolio.positions.reduce(
|
|
3367
3992
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3368
3993
|
0n
|
|
3369
3994
|
);
|
|
3370
3995
|
const totalAvailable = idle + deployed;
|
|
3371
3996
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
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
|
+
}
|
|
3372
4006
|
if (requested > totalAvailable) {
|
|
3373
4007
|
throw new OwneyError(
|
|
3374
4008
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -3404,6 +4038,7 @@ var YieldseekerAgent = class {
|
|
|
3404
4038
|
throw this.invalidResponse("position withdrawal");
|
|
3405
4039
|
}
|
|
3406
4040
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
4041
|
+
confirmedMovement = true;
|
|
3407
4042
|
remaining -= assetsRaw;
|
|
3408
4043
|
}
|
|
3409
4044
|
if (remaining > 0n) {
|
|
@@ -3412,7 +4047,22 @@ var YieldseekerAgent = class {
|
|
|
3412
4047
|
remaining: remaining.toString()
|
|
3413
4048
|
});
|
|
3414
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);
|
|
3415
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?.();
|
|
3416
4066
|
const txHash = await this.submitTransaction(state, chainId, {
|
|
3417
4067
|
from: account,
|
|
3418
4068
|
to: getAddress2(context.wallet.walletAddress),
|
|
@@ -3427,24 +4077,66 @@ var YieldseekerAgent = class {
|
|
|
3427
4077
|
}),
|
|
3428
4078
|
value: "0",
|
|
3429
4079
|
chainId
|
|
4080
|
+
}, (hash) => notifyFunding(onProgress, { ...base5, txHash: hash }), (value) => {
|
|
4081
|
+
receipt = value;
|
|
3430
4082
|
});
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
4083
|
+
confirmedMovement = true;
|
|
4084
|
+
settlement = plannedMovement;
|
|
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 };
|
|
3436
4095
|
} finally {
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
4096
|
+
if (confirmedMovement && this.readGeneration === generation) {
|
|
4097
|
+
void this.refreshSnapshotAfterMovement(
|
|
4098
|
+
state,
|
|
4099
|
+
chainId,
|
|
4100
|
+
context,
|
|
4101
|
+
"withdrawal",
|
|
4102
|
+
settlement
|
|
4103
|
+
);
|
|
4104
|
+
}
|
|
3443
4105
|
}
|
|
3444
4106
|
}
|
|
3445
|
-
async getBalances(state, chainId) {
|
|
4107
|
+
async getBalances(state, chainId, asset) {
|
|
4108
|
+
this.assertChain(chainId);
|
|
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) {
|
|
3446
4124
|
this.assertChain(chainId);
|
|
3447
|
-
|
|
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;
|
|
3448
4140
|
}
|
|
3449
4141
|
async getEarnings(state, chainId) {
|
|
3450
4142
|
this.assertChain(chainId);
|
|
@@ -3464,7 +4156,7 @@ var YieldseekerAgent = class {
|
|
|
3464
4156
|
this.assertChain(chainId);
|
|
3465
4157
|
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3466
4158
|
if (asset !== void 0) this.assertAsset(asset);
|
|
3467
|
-
const contexts = await this.
|
|
4159
|
+
const contexts = await this.loadActivityPortfolio(state, chainId, {
|
|
3468
4160
|
...asset ? { asset } : {},
|
|
3469
4161
|
historic: true,
|
|
3470
4162
|
actions: true
|
|
@@ -3481,6 +4173,7 @@ var YieldseekerAgent = class {
|
|
|
3481
4173
|
);
|
|
3482
4174
|
return mapYieldseekerHistory(contexts, {
|
|
3483
4175
|
limit: options?.limit ?? 10,
|
|
4176
|
+
cursor: options?.cursor,
|
|
3484
4177
|
ownerAddress: state.walletAddress,
|
|
3485
4178
|
vaultAddresses,
|
|
3486
4179
|
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
@@ -3491,7 +4184,7 @@ var YieldseekerAgent = class {
|
|
|
3491
4184
|
this.assertChain(chainId);
|
|
3492
4185
|
return mapYieldseekerProfile(
|
|
3493
4186
|
state.walletAddress,
|
|
3494
|
-
await this.
|
|
4187
|
+
await this.loadActivityPortfolio(state, chainId, {})
|
|
3495
4188
|
);
|
|
3496
4189
|
}
|
|
3497
4190
|
async getAgentApy(days, options) {
|
|
@@ -3533,18 +4226,109 @@ var YieldseekerAgent = class {
|
|
|
3533
4226
|
contextKey(state, chainId, asset) {
|
|
3534
4227
|
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3535
4228
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
4229
|
+
assertSession(generation, chainId, asset) {
|
|
4230
|
+
if (this.readGeneration !== generation) {
|
|
4231
|
+
throw new OwneyError(
|
|
4232
|
+
"NOT_CONNECTED",
|
|
4233
|
+
"Wallet session changed during the Yieldseeker request.",
|
|
4234
|
+
{ rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
|
|
4235
|
+
this.id
|
|
4236
|
+
);
|
|
3544
4237
|
}
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
4238
|
+
}
|
|
4239
|
+
cachedRead(key2, ttlMs, read2) {
|
|
4240
|
+
const cached = this.readCache.get(key2);
|
|
4241
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
4242
|
+
return Promise.resolve(cached.value);
|
|
4243
|
+
}
|
|
4244
|
+
const pending = this.pendingReads.get(key2);
|
|
4245
|
+
if (pending) return pending;
|
|
4246
|
+
const generation = this.readGeneration;
|
|
4247
|
+
const request = Promise.resolve().then(read2).then((value) => {
|
|
4248
|
+
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
4249
|
+
this.readCache.set(key2, {
|
|
4250
|
+
expiresAt: Date.now() + ttlMs,
|
|
4251
|
+
fetchedAt: Date.now(),
|
|
4252
|
+
value
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
return value;
|
|
4256
|
+
}).finally(() => {
|
|
4257
|
+
if (this.pendingReads.get(key2) === request) {
|
|
4258
|
+
this.pendingReads.delete(key2);
|
|
4259
|
+
}
|
|
4260
|
+
});
|
|
4261
|
+
this.pendingReads.set(key2, request);
|
|
4262
|
+
return request;
|
|
4263
|
+
}
|
|
4264
|
+
agentListKey(state, chainId) {
|
|
4265
|
+
return `agents:${this.userKey(state, chainId)}`;
|
|
4266
|
+
}
|
|
4267
|
+
async listAgents(state, chainId, user) {
|
|
4268
|
+
const response = await this.cachedRead(
|
|
4269
|
+
this.agentListKey(state, chainId),
|
|
4270
|
+
YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
4271
|
+
async () => {
|
|
4272
|
+
const response2 = await this.walletRequest(
|
|
4273
|
+
state,
|
|
4274
|
+
chainId,
|
|
4275
|
+
`/users/${user.userId}/agents`
|
|
4276
|
+
);
|
|
4277
|
+
if (!Array.isArray(response2?.agents)) {
|
|
4278
|
+
throw this.invalidResponse("agent list");
|
|
4279
|
+
}
|
|
4280
|
+
return response2;
|
|
4281
|
+
}
|
|
4282
|
+
);
|
|
4283
|
+
return response.agents;
|
|
4284
|
+
}
|
|
4285
|
+
async contextForAgent(state, chainId, user, agent, asset) {
|
|
4286
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
4287
|
+
const cached = this.agentContexts.get(key2);
|
|
4288
|
+
if (cached?.agent.agentId === agent.agentId) return cached;
|
|
4289
|
+
const pending = this.pendingWalletContexts.get(key2);
|
|
4290
|
+
if (pending) return pending;
|
|
4291
|
+
const generation = this.readGeneration;
|
|
4292
|
+
const request = this.walletRequest(
|
|
4293
|
+
state,
|
|
4294
|
+
chainId,
|
|
4295
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
4296
|
+
).then((walletResponse) => {
|
|
4297
|
+
this.assertSession(generation, chainId, asset);
|
|
4298
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
4299
|
+
throw this.invalidResponse("agent wallet");
|
|
4300
|
+
}
|
|
4301
|
+
const context = {
|
|
4302
|
+
user,
|
|
4303
|
+
agent,
|
|
4304
|
+
wallet: walletResponse.agentWallet,
|
|
4305
|
+
asset
|
|
4306
|
+
};
|
|
4307
|
+
this.agentContexts.set(key2, context);
|
|
4308
|
+
return context;
|
|
4309
|
+
});
|
|
4310
|
+
this.pendingWalletContexts.set(key2, request);
|
|
4311
|
+
try {
|
|
4312
|
+
return await request;
|
|
4313
|
+
} finally {
|
|
4314
|
+
if (this.pendingWalletContexts.get(key2) === request) {
|
|
4315
|
+
this.pendingWalletContexts.delete(key2);
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
async resolveUser(state, chainId) {
|
|
4320
|
+
const key2 = this.userKey(state, chainId);
|
|
4321
|
+
const inMemory = this.users.get(key2);
|
|
4322
|
+
if (inMemory) return inMemory;
|
|
4323
|
+
const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
|
|
4324
|
+
if (persisted) {
|
|
4325
|
+
this.users.set(key2, persisted);
|
|
4326
|
+
return persisted;
|
|
4327
|
+
}
|
|
4328
|
+
const walletAddress = getAddress2(state.walletAddress);
|
|
4329
|
+
const generation = this.readGeneration;
|
|
4330
|
+
let user = null;
|
|
4331
|
+
try {
|
|
3548
4332
|
const login = await this.providerRequest(
|
|
3549
4333
|
state,
|
|
3550
4334
|
chainId,
|
|
@@ -3589,6 +4373,7 @@ var YieldseekerAgent = class {
|
|
|
3589
4373
|
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3590
4374
|
throw this.invalidResponse("wallet identity");
|
|
3591
4375
|
}
|
|
4376
|
+
this.assertSession(generation, chainId);
|
|
3592
4377
|
const resolved = { userId: user.userId };
|
|
3593
4378
|
this.users.set(key2, resolved);
|
|
3594
4379
|
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
@@ -3604,10 +4389,13 @@ var YieldseekerAgent = class {
|
|
|
3604
4389
|
if (cached) return cached;
|
|
3605
4390
|
const pending = this.pendingAgents.get(key2);
|
|
3606
4391
|
if (pending) return pending;
|
|
4392
|
+
const generation = this.readGeneration;
|
|
3607
4393
|
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3608
4394
|
async (context) => {
|
|
4395
|
+
this.assertSession(generation, chainId, asset);
|
|
3609
4396
|
if (!context) throw this.invalidResponse("agent creation");
|
|
3610
4397
|
await this.deployAgent(state, chainId, context);
|
|
4398
|
+
this.assertSession(generation, chainId, asset);
|
|
3611
4399
|
this.agentContexts.set(key2, context);
|
|
3612
4400
|
return context;
|
|
3613
4401
|
}
|
|
@@ -3616,29 +4404,27 @@ var YieldseekerAgent = class {
|
|
|
3616
4404
|
try {
|
|
3617
4405
|
return await request;
|
|
3618
4406
|
} finally {
|
|
3619
|
-
this.pendingAgents.delete(key2);
|
|
4407
|
+
if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
|
|
3620
4408
|
}
|
|
3621
4409
|
}
|
|
3622
4410
|
async findAgent(state, chainId, asset) {
|
|
3623
4411
|
const key2 = this.contextKey(state, chainId, asset);
|
|
3624
4412
|
const cached = this.agentContexts.get(key2);
|
|
3625
4413
|
if (cached) return cached;
|
|
4414
|
+
const generation = this.readGeneration;
|
|
3626
4415
|
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
4416
|
+
this.assertSession(generation, chainId, asset);
|
|
3627
4417
|
if (context) this.agentContexts.set(key2, context);
|
|
3628
4418
|
return context;
|
|
3629
4419
|
}
|
|
3630
4420
|
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
4421
|
+
const generation = this.readGeneration;
|
|
3631
4422
|
const user = await this.resolveUser(state, chainId);
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
`/users/${user.userId}/agents`
|
|
3636
|
-
);
|
|
3637
|
-
if (!Array.isArray(response?.agents)) {
|
|
3638
|
-
throw this.invalidResponse("agent list");
|
|
3639
|
-
}
|
|
4423
|
+
this.assertSession(generation, chainId, asset);
|
|
4424
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
4425
|
+
this.assertSession(generation, chainId, asset);
|
|
3640
4426
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3641
|
-
let agent =
|
|
4427
|
+
let agent = agents.find(
|
|
3642
4428
|
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3643
4429
|
);
|
|
3644
4430
|
if (!agent && createIfMissing) {
|
|
@@ -3654,99 +4440,260 @@ var YieldseekerAgent = class {
|
|
|
3654
4440
|
chainId,
|
|
3655
4441
|
assetAddress: metadata.address,
|
|
3656
4442
|
type: "vault",
|
|
3657
|
-
rulePreset:
|
|
4443
|
+
rulePreset: OWNEY_AGENT_RULE_PRESET
|
|
3658
4444
|
}
|
|
3659
4445
|
}
|
|
3660
4446
|
);
|
|
4447
|
+
this.assertSession(generation, chainId, asset);
|
|
3661
4448
|
agent = created?.agent;
|
|
4449
|
+
if (agent) {
|
|
4450
|
+
this.readCache.set(this.agentListKey(state, chainId), {
|
|
4451
|
+
expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
4452
|
+
fetchedAt: Date.now(),
|
|
4453
|
+
value: {
|
|
4454
|
+
agents: [...agents, agent]
|
|
4455
|
+
}
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
3662
4458
|
}
|
|
3663
4459
|
if (!agent) return null;
|
|
3664
4460
|
this.assertAgent(agent);
|
|
3665
|
-
|
|
3666
|
-
state,
|
|
3667
|
-
chainId,
|
|
3668
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3669
|
-
);
|
|
3670
|
-
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3671
|
-
throw this.invalidResponse("agent wallet");
|
|
3672
|
-
}
|
|
3673
|
-
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
4461
|
+
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3674
4462
|
}
|
|
3675
4463
|
async loadPortfolio(state, chainId, options) {
|
|
3676
|
-
const
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
4464
|
+
const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
|
|
4465
|
+
return Promise.all(
|
|
4466
|
+
contexts.map(
|
|
4467
|
+
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
4468
|
+
)
|
|
3681
4469
|
);
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
4470
|
+
}
|
|
4471
|
+
async loadActivityPortfolio(state, chainId, options) {
|
|
4472
|
+
const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
|
|
4473
|
+
return Promise.all(
|
|
4474
|
+
contexts.map(
|
|
4475
|
+
(context) => this.loadActivityContext(state, chainId, context, options)
|
|
4476
|
+
)
|
|
4477
|
+
);
|
|
4478
|
+
}
|
|
4479
|
+
async resolvePortfolioContexts(state, chainId, assetFilter) {
|
|
4480
|
+
const generation = this.readGeneration;
|
|
4481
|
+
const user = await this.resolveUser(state, chainId);
|
|
4482
|
+
this.assertSession(generation, chainId);
|
|
4483
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
4484
|
+
this.assertSession(generation, chainId);
|
|
3685
4485
|
const contexts = [];
|
|
3686
|
-
for (const agent of
|
|
4486
|
+
for (const agent of agents) {
|
|
3687
4487
|
const asset = this.assetForAgent(agent);
|
|
3688
|
-
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" ||
|
|
4488
|
+
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || assetFilter && assetFilter !== asset) {
|
|
3689
4489
|
continue;
|
|
3690
4490
|
}
|
|
3691
4491
|
this.assertAgent(agent);
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
4492
|
+
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
4493
|
+
}
|
|
4494
|
+
const resolvedContexts = await Promise.all(contexts);
|
|
4495
|
+
this.assertSession(generation, chainId);
|
|
4496
|
+
return resolvedContexts;
|
|
4497
|
+
}
|
|
4498
|
+
portfolioVersion(contextKey) {
|
|
4499
|
+
return this.portfolioVersions.get(contextKey) ?? 0;
|
|
4500
|
+
}
|
|
4501
|
+
portfolioCacheMs(contextKey) {
|
|
4502
|
+
return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4503
|
+
}
|
|
4504
|
+
snapshotMovement(state, chainId, context, delta) {
|
|
4505
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
4506
|
+
const previous = this.snapshotMovements.get(key2);
|
|
4507
|
+
if (this.snapshotMovements.has(key2)) {
|
|
4508
|
+
if (!previous || previous.delta > 0n !== delta > 0n) return null;
|
|
4509
|
+
return {
|
|
4510
|
+
...previous,
|
|
4511
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4512
|
+
delta: previous.delta + delta,
|
|
4513
|
+
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
3705
4514
|
};
|
|
3706
|
-
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3707
|
-
contexts.push(context);
|
|
3708
4515
|
}
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
);
|
|
4516
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4517
|
+
if (!cached || cached.expiresAt <= Date.now()) return null;
|
|
4518
|
+
const snapshot = cached.value.agentSnapshot;
|
|
4519
|
+
if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
|
|
4520
|
+
if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
|
|
4521
|
+
return {
|
|
4522
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4523
|
+
delta,
|
|
4524
|
+
decimals: snapshot.baseAssetDecimals,
|
|
4525
|
+
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
advancePortfolioVersion(contextKey) {
|
|
4529
|
+
const version = this.portfolioVersion(contextKey) + 1;
|
|
4530
|
+
this.portfolioVersions.set(contextKey, version);
|
|
4531
|
+
this.snapshotFailures.delete(contextKey);
|
|
4532
|
+
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4533
|
+
const key2 = `${kind}:${contextKey}`;
|
|
4534
|
+
this.readCache.delete(key2);
|
|
4535
|
+
this.pendingReads.delete(key2);
|
|
4536
|
+
}
|
|
4537
|
+
return version;
|
|
4538
|
+
}
|
|
4539
|
+
requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
|
|
4540
|
+
return this.walletRequest(
|
|
4541
|
+
state,
|
|
4542
|
+
chainId,
|
|
4543
|
+
`${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
|
|
4544
|
+
).then((response) => {
|
|
4545
|
+
if (!response?.agentSnapshot) {
|
|
4546
|
+
throw this.invalidResponse("agent snapshot");
|
|
4547
|
+
}
|
|
4548
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4549
|
+
this.snapshotFailures.delete(contextKey);
|
|
4550
|
+
const movement = this.snapshotMovements.get(contextKey);
|
|
4551
|
+
const snapshot = response.agentSnapshot;
|
|
4552
|
+
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4553
|
+
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4554
|
+
const reconciled = movement.delta > 0n ? netDeposits >= movement.expectedNetDeposits : netDeposits <= movement.expectedNetDeposits;
|
|
4555
|
+
if (reconciled) {
|
|
4556
|
+
this.snapshotMovements.delete(contextKey);
|
|
4557
|
+
this.reconcileUntil.delete(contextKey);
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
return response;
|
|
4562
|
+
}).catch((error) => {
|
|
4563
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4564
|
+
this.snapshotFailures.set(contextKey, {
|
|
4565
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
|
|
4566
|
+
error
|
|
4567
|
+
});
|
|
4568
|
+
}
|
|
4569
|
+
throw error;
|
|
4570
|
+
});
|
|
4571
|
+
}
|
|
4572
|
+
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
4573
|
+
const key2 = `snapshot:${contextKey}`;
|
|
4574
|
+
const cached = this.readCache.get(key2);
|
|
4575
|
+
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
4576
|
+
const failure = this.snapshotFailures.get(contextKey);
|
|
4577
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4578
|
+
this.advancePortfolioVersion(contextKey);
|
|
4579
|
+
}
|
|
4580
|
+
const version = this.portfolioVersion(contextKey);
|
|
4581
|
+
const generation = this.readGeneration;
|
|
4582
|
+
return {
|
|
4583
|
+
version,
|
|
4584
|
+
read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
|
|
4585
|
+
// Force only during the bounded post-transaction window. Keep unknown
|
|
4586
|
+
// or unreconciled movements for verification, but do not let them turn
|
|
4587
|
+
// routine polling into forced refreshes indefinitely.
|
|
4588
|
+
this.requestPortfolioSnapshot(
|
|
4589
|
+
state,
|
|
4590
|
+
chainId,
|
|
4591
|
+
context,
|
|
4592
|
+
contextKey,
|
|
4593
|
+
version,
|
|
4594
|
+
generation,
|
|
4595
|
+
this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
|
|
4596
|
+
)
|
|
4597
|
+
))
|
|
4598
|
+
};
|
|
3714
4599
|
}
|
|
3715
4600
|
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3716
|
-
const
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4601
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4602
|
+
const generation = this.readGeneration;
|
|
4603
|
+
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4604
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4605
|
+
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4606
|
+
const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4607
|
+
try {
|
|
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]);
|
|
4614
|
+
assertSession();
|
|
4615
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4616
|
+
return {
|
|
4617
|
+
...context,
|
|
4618
|
+
snapshot: snapshot.agentSnapshot,
|
|
4619
|
+
fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
|
|
4620
|
+
readVersion: version,
|
|
4621
|
+
positionsUnavailable,
|
|
4622
|
+
...portfolioParts
|
|
4623
|
+
};
|
|
4624
|
+
} catch (error) {
|
|
4625
|
+
assertSession();
|
|
4626
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4627
|
+
throw error;
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
throw new OwneyError(
|
|
4631
|
+
"AGENT_API_ERROR",
|
|
4632
|
+
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4633
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4634
|
+
this.id
|
|
4635
|
+
);
|
|
4636
|
+
}
|
|
4637
|
+
async loadActivityContext(state, chainId, context, options) {
|
|
4638
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4639
|
+
const generation = this.readGeneration;
|
|
4640
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4641
|
+
const version = this.portfolioVersion(contextKey);
|
|
4642
|
+
try {
|
|
4643
|
+
const parts = await this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4644
|
+
this.assertSession(generation, chainId, context.asset);
|
|
4645
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4646
|
+
return { ...context, ...parts };
|
|
4647
|
+
} catch (error) {
|
|
4648
|
+
this.assertSession(generation, chainId, context.asset);
|
|
4649
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4650
|
+
throw error;
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
throw new OwneyError(
|
|
4654
|
+
"AGENT_API_ERROR",
|
|
4655
|
+
"Yieldseeker activity changed during the read. Please retry.",
|
|
4656
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4657
|
+
this.id
|
|
4658
|
+
);
|
|
4659
|
+
}
|
|
4660
|
+
async loadPortfolioParts(state, chainId, context, options, contextKey) {
|
|
4661
|
+
const [positions, historic, actions] = await Promise.all([
|
|
4662
|
+
this.cachedRead(
|
|
4663
|
+
`positions:${contextKey}`,
|
|
4664
|
+
this.portfolioCacheMs(contextKey),
|
|
4665
|
+
async () => {
|
|
4666
|
+
const response = await this.walletRequest(
|
|
4667
|
+
state,
|
|
4668
|
+
chainId,
|
|
4669
|
+
this.agentPath(context, "yield-positions")
|
|
4670
|
+
);
|
|
4671
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
4672
|
+
throw this.invalidResponse("yield positions");
|
|
4673
|
+
}
|
|
4674
|
+
return response;
|
|
4675
|
+
}
|
|
3729
4676
|
),
|
|
3730
|
-
options.historic ? this.
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
this.
|
|
4677
|
+
options.historic ? this.cachedRead(
|
|
4678
|
+
`historic:${contextKey}`,
|
|
4679
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4680
|
+
() => this.walletRequest(
|
|
4681
|
+
state,
|
|
4682
|
+
chainId,
|
|
4683
|
+
this.agentPath(context, "wallet/historic-position")
|
|
4684
|
+
)
|
|
3734
4685
|
) : Promise.resolve(void 0),
|
|
3735
|
-
options.actions ? this.
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
this.
|
|
4686
|
+
options.actions ? this.cachedRead(
|
|
4687
|
+
`actions:${contextKey}`,
|
|
4688
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4689
|
+
() => this.walletRequest(
|
|
4690
|
+
state,
|
|
4691
|
+
chainId,
|
|
4692
|
+
this.agentPath(context, "actions")
|
|
4693
|
+
)
|
|
3739
4694
|
) : Promise.resolve(void 0)
|
|
3740
4695
|
]);
|
|
3741
|
-
if (!snapshot?.agentSnapshot) {
|
|
3742
|
-
throw this.invalidResponse("agent snapshot");
|
|
3743
|
-
}
|
|
3744
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3745
|
-
throw this.invalidResponse("yield positions");
|
|
3746
|
-
}
|
|
3747
4696
|
return {
|
|
3748
|
-
...context,
|
|
3749
|
-
snapshot: snapshot.agentSnapshot,
|
|
3750
4697
|
positions: positions.yieldPositions,
|
|
3751
4698
|
...historic?.position ? { historic: historic.position } : {},
|
|
3752
4699
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
@@ -3768,18 +4715,21 @@ var YieldseekerAgent = class {
|
|
|
3768
4715
|
}
|
|
3769
4716
|
context.wallet = deployed.agentWallet;
|
|
3770
4717
|
}
|
|
3771
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4718
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4719
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4720
|
+
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4721
|
+
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4722
|
+
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4723
|
+
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4724
|
+
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4725
|
+
const version = this.advancePortfolioVersion(contextKey);
|
|
4726
|
+
const generation = this.readGeneration;
|
|
3772
4727
|
try {
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
shouldForceRefresh: true
|
|
3778
|
-
})}`
|
|
4728
|
+
await this.cachedRead(
|
|
4729
|
+
`snapshot:${contextKey}`,
|
|
4730
|
+
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4731
|
+
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
3779
4732
|
);
|
|
3780
|
-
if (!response?.agentSnapshot) {
|
|
3781
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
3782
|
-
}
|
|
3783
4733
|
} catch (error) {
|
|
3784
4734
|
console.warn(
|
|
3785
4735
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
@@ -3791,10 +4741,26 @@ var YieldseekerAgent = class {
|
|
|
3791
4741
|
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3792
4742
|
}
|
|
3793
4743
|
async walletRequest(state, chainId, path, options = {}) {
|
|
4744
|
+
const read2 = (options.method ?? "GET") === "GET";
|
|
4745
|
+
const userKey = this.userKey(state, chainId);
|
|
4746
|
+
if (read2) {
|
|
4747
|
+
const failure = this.standardReadFailures.get(userKey);
|
|
4748
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4749
|
+
if (failure) this.standardReadFailures.delete(userKey);
|
|
4750
|
+
}
|
|
4751
|
+
const generation = this.readGeneration;
|
|
3794
4752
|
try {
|
|
3795
4753
|
return await this.providerRequest(state, chainId, path, options);
|
|
3796
4754
|
} catch (error) {
|
|
3797
|
-
|
|
4755
|
+
const mapped = this.mapApiError(error);
|
|
4756
|
+
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4757
|
+
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4758
|
+
this.standardReadFailures.set(userKey, {
|
|
4759
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4760
|
+
error: mapped
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
throw mapped;
|
|
3798
4764
|
}
|
|
3799
4765
|
}
|
|
3800
4766
|
async providerRequest(state, chainId, path, options = {}) {
|
|
@@ -3835,6 +4801,7 @@ var YieldseekerAgent = class {
|
|
|
3835
4801
|
code,
|
|
3836
4802
|
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3837
4803
|
{
|
|
4804
|
+
rpcSource: "agent-api",
|
|
3838
4805
|
statusCode: error.status,
|
|
3839
4806
|
providerCode: error.providerCode,
|
|
3840
4807
|
...error.responseFields ? { fields: error.responseFields } : {}
|
|
@@ -3842,9 +4809,11 @@ var YieldseekerAgent = class {
|
|
|
3842
4809
|
this.id
|
|
3843
4810
|
);
|
|
3844
4811
|
}
|
|
3845
|
-
async submitTransaction(state, chainId, transaction) {
|
|
4812
|
+
async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
|
|
3846
4813
|
if (this.transactionExecutor) {
|
|
3847
|
-
|
|
4814
|
+
const hash2 = await this.transactionExecutor(state, chainId, transaction);
|
|
4815
|
+
onSubmitted?.(hash2);
|
|
4816
|
+
return hash2;
|
|
3848
4817
|
}
|
|
3849
4818
|
this.assertTransaction(transaction, state, chainId);
|
|
3850
4819
|
const account = getAddress2(state.walletAddress);
|
|
@@ -3853,12 +4822,12 @@ var YieldseekerAgent = class {
|
|
|
3853
4822
|
chain: base3,
|
|
3854
4823
|
transport: custom2(state.provider)
|
|
3855
4824
|
});
|
|
3856
|
-
const
|
|
4825
|
+
const walletChainClient = createPublicClient3({
|
|
3857
4826
|
chain: base3,
|
|
3858
4827
|
transport: custom2(state.provider)
|
|
3859
4828
|
});
|
|
3860
4829
|
await ensureWalletOnChain(
|
|
3861
|
-
|
|
4830
|
+
walletChainClient,
|
|
3862
4831
|
walletClient,
|
|
3863
4832
|
8453
|
|
3864
4833
|
);
|
|
@@ -3869,10 +4838,16 @@ var YieldseekerAgent = class {
|
|
|
3869
4838
|
data: transaction.data,
|
|
3870
4839
|
value: BigInt(transaction.value)
|
|
3871
4840
|
});
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
4841
|
+
onSubmitted?.(hash);
|
|
4842
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4843
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4844
|
+
hash,
|
|
4845
|
+
confirmations: 1
|
|
4846
|
+
}),
|
|
4847
|
+
8453,
|
|
4848
|
+
"eth_getTransactionReceipt",
|
|
4849
|
+
this.id
|
|
4850
|
+
);
|
|
3876
4851
|
if (receipt.status !== "success") {
|
|
3877
4852
|
throw new OwneyError(
|
|
3878
4853
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3881,6 +4856,7 @@ var YieldseekerAgent = class {
|
|
|
3881
4856
|
this.id
|
|
3882
4857
|
);
|
|
3883
4858
|
}
|
|
4859
|
+
onReceipt?.(receipt);
|
|
3884
4860
|
return hash;
|
|
3885
4861
|
}
|
|
3886
4862
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
@@ -3888,14 +4864,15 @@ var YieldseekerAgent = class {
|
|
|
3888
4864
|
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3889
4865
|
return;
|
|
3890
4866
|
}
|
|
3891
|
-
const
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
4867
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4868
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4869
|
+
hash: transactionHash,
|
|
4870
|
+
confirmations: 1
|
|
4871
|
+
}),
|
|
4872
|
+
8453,
|
|
4873
|
+
"eth_getTransactionReceipt",
|
|
4874
|
+
this.id
|
|
4875
|
+
);
|
|
3899
4876
|
if (receipt.status !== "success") {
|
|
3900
4877
|
throw new OwneyError(
|
|
3901
4878
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3904,6 +4881,7 @@ var YieldseekerAgent = class {
|
|
|
3904
4881
|
this.id
|
|
3905
4882
|
);
|
|
3906
4883
|
}
|
|
4884
|
+
return receipt;
|
|
3907
4885
|
}
|
|
3908
4886
|
assertTransaction(transaction, state, chainId) {
|
|
3909
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)) {
|
|
@@ -4084,16 +5062,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4084
5062
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
4085
5063
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4086
5064
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4087
|
-
|
|
4088
|
-
try {
|
|
4089
|
-
balance += BigInt(position2.amountRaw);
|
|
4090
|
-
continue;
|
|
4091
|
-
} catch {
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
balance += parseUnits(position2.amount, decimals);
|
|
5065
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits(position2.amount, decimals);
|
|
4095
5066
|
}
|
|
4096
5067
|
}
|
|
5068
|
+
const assetTotal = agentBalance?.assetBalances?.find(
|
|
5069
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
5070
|
+
);
|
|
5071
|
+
if (assetTotal) {
|
|
5072
|
+
const total = parseUnits(assetTotal.amount, decimals);
|
|
5073
|
+
if (total < balance) balance = total > 0n ? total : 0n;
|
|
5074
|
+
}
|
|
4097
5075
|
return { agent, balance };
|
|
4098
5076
|
});
|
|
4099
5077
|
}
|
|
@@ -4265,8 +5243,43 @@ import {
|
|
|
4265
5243
|
} from "viem";
|
|
4266
5244
|
|
|
4267
5245
|
// src/lib/permit2-batch.ts
|
|
4268
|
-
import { parseAbi as
|
|
5246
|
+
import { parseAbi as parseAbi3, hashStruct } from "viem";
|
|
4269
5247
|
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
5248
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
5249
|
+
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
5250
|
+
var ERC2612_PERMIT_TYPES = {
|
|
5251
|
+
Permit: [
|
|
5252
|
+
{ name: "owner", type: "address" },
|
|
5253
|
+
{ name: "spender", type: "address" },
|
|
5254
|
+
{ name: "value", type: "uint256" },
|
|
5255
|
+
{ name: "nonce", type: "uint256" },
|
|
5256
|
+
{ name: "deadline", type: "uint256" }
|
|
5257
|
+
]
|
|
5258
|
+
};
|
|
5259
|
+
var ERC2612_READ_ABI = parseAbi3([
|
|
5260
|
+
"function name() view returns (string)",
|
|
5261
|
+
"function version() view returns (string)",
|
|
5262
|
+
"function nonces(address owner) view returns (uint256)"
|
|
5263
|
+
]);
|
|
5264
|
+
function erc2612TypedData(input) {
|
|
5265
|
+
return {
|
|
5266
|
+
domain: {
|
|
5267
|
+
name: input.permit.tokenName,
|
|
5268
|
+
version: input.permit.tokenVersion,
|
|
5269
|
+
chainId: input.chainId,
|
|
5270
|
+
verifyingContract: input.token
|
|
5271
|
+
},
|
|
5272
|
+
types: ERC2612_PERMIT_TYPES,
|
|
5273
|
+
primaryType: "Permit",
|
|
5274
|
+
message: {
|
|
5275
|
+
owner: input.owner,
|
|
5276
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
5277
|
+
value: BigInt(input.permit.value),
|
|
5278
|
+
nonce: BigInt(input.permit.nonce),
|
|
5279
|
+
deadline: BigInt(input.permit.deadline)
|
|
5280
|
+
}
|
|
5281
|
+
};
|
|
5282
|
+
}
|
|
4270
5283
|
var PERMIT_BATCH_TYPES = {
|
|
4271
5284
|
PermitBatchWitnessTransferFrom: [
|
|
4272
5285
|
{ name: "permitted", type: "TokenPermissions[]" },
|
|
@@ -4281,7 +5294,7 @@ var PERMIT_BATCH_TYPES = {
|
|
|
4281
5294
|
{ name: "amount", type: "uint256" }
|
|
4282
5295
|
]
|
|
4283
5296
|
};
|
|
4284
|
-
var PERMIT2_BATCH_ABI =
|
|
5297
|
+
var PERMIT2_BATCH_ABI = parseAbi3([
|
|
4285
5298
|
"struct TokenPermissions { address token; uint256 amount; }",
|
|
4286
5299
|
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
4287
5300
|
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
@@ -4381,6 +5394,10 @@ async function execute(i, key2, plan) {
|
|
|
4381
5394
|
};
|
|
4382
5395
|
save(key2, body);
|
|
4383
5396
|
}
|
|
5397
|
+
try {
|
|
5398
|
+
i.onSubmitted?.(keccak256(body.serializedTransaction));
|
|
5399
|
+
} catch {
|
|
5400
|
+
}
|
|
4384
5401
|
const result = await postSponsorBatchTransfer({
|
|
4385
5402
|
apiKey: i.apiKey,
|
|
4386
5403
|
baseUrl: i.baseUrl,
|
|
@@ -4418,28 +5435,67 @@ async function execute(i, key2, plan) {
|
|
|
4418
5435
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4419
5436
|
"Insufficient token balance for this deposit."
|
|
4420
5437
|
);
|
|
4421
|
-
if (allowance < total)
|
|
5438
|
+
if (allowance < total && (i.chainId !== 8453 || !isAddressEqual(i.token, BASE_USDC_ADDRESS)))
|
|
4422
5439
|
throw new OwneyError(
|
|
4423
5440
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
4424
5441
|
"token deposits need a one-time Permit2 approval."
|
|
4425
5442
|
);
|
|
4426
|
-
const
|
|
5443
|
+
const now = (await i.pub.getBlock()).timestamp;
|
|
5444
|
+
let erc2612Permit;
|
|
5445
|
+
if (allowance < total) {
|
|
5446
|
+
const [tokenName, tokenVersion, permitNonce] = await Promise.all([
|
|
5447
|
+
i.pub.readContract({
|
|
5448
|
+
address: i.token,
|
|
5449
|
+
abi: ERC2612_READ_ABI,
|
|
5450
|
+
functionName: "name"
|
|
5451
|
+
}),
|
|
5452
|
+
i.pub.readContract({
|
|
5453
|
+
address: i.token,
|
|
5454
|
+
abi: ERC2612_READ_ABI,
|
|
5455
|
+
functionName: "version"
|
|
5456
|
+
}),
|
|
5457
|
+
i.pub.readContract({
|
|
5458
|
+
address: i.token,
|
|
5459
|
+
abi: ERC2612_READ_ABI,
|
|
5460
|
+
functionName: "nonces",
|
|
5461
|
+
args: [i.owner]
|
|
5462
|
+
})
|
|
5463
|
+
]);
|
|
5464
|
+
const unsignedPermit = {
|
|
5465
|
+
value: MAX_UINT256.toString(),
|
|
5466
|
+
nonce: permitNonce.toString(),
|
|
5467
|
+
deadline: (now + 900n).toString(),
|
|
5468
|
+
tokenName,
|
|
5469
|
+
tokenVersion
|
|
5470
|
+
};
|
|
5471
|
+
const signature2 = await i.wallet.signTypedData({
|
|
5472
|
+
account: i.owner,
|
|
5473
|
+
...erc2612TypedData({
|
|
5474
|
+
chainId: 8453,
|
|
5475
|
+
token: i.token,
|
|
5476
|
+
owner: i.owner,
|
|
5477
|
+
permit: unsignedPermit
|
|
5478
|
+
})
|
|
5479
|
+
});
|
|
5480
|
+
erc2612Permit = { ...unsignedPermit, signature: signature2 };
|
|
5481
|
+
}
|
|
5482
|
+
const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
|
|
4427
5483
|
apiKey: i.apiKey,
|
|
4428
5484
|
baseUrl: i.baseUrl,
|
|
4429
5485
|
chainId: i.chainId
|
|
4430
5486
|
});
|
|
4431
|
-
const now = (await i.pub.getBlock()).timestamp;
|
|
4432
5487
|
const unsigned = {
|
|
4433
5488
|
chainId: i.chainId,
|
|
4434
5489
|
token: i.token,
|
|
4435
5490
|
from: i.owner,
|
|
4436
5491
|
transfers: i.transfers,
|
|
4437
5492
|
nonce: randomPermit2Nonce().toString(),
|
|
4438
|
-
deadline: (now + 900n).toString()
|
|
5493
|
+
deadline: (now + 900n).toString(),
|
|
5494
|
+
...erc2612Permit ? { erc2612Permit } : {}
|
|
4439
5495
|
};
|
|
4440
5496
|
const signature = await i.wallet.signTypedData({
|
|
4441
5497
|
account: i.owner,
|
|
4442
|
-
...batchTypedData(unsigned,
|
|
5498
|
+
...batchTypedData(unsigned, spender)
|
|
4443
5499
|
});
|
|
4444
5500
|
i.onApproved?.();
|
|
4445
5501
|
return send({ ...unsigned, signature });
|
|
@@ -4459,8 +5515,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
4459
5515
|
"CHAIN_UNSUPPORTED",
|
|
4460
5516
|
`No sponsored token configured for chain ${chainId}`
|
|
4461
5517
|
);
|
|
4462
|
-
const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
|
|
4463
|
-
await ensureWalletOnChain(
|
|
5518
|
+
const pub = deps.getPublicClient(chainId), walletChain = deps.getWalletChainClient?.(chainId) ?? pub, wallet = deps.getWalletClient(chainId);
|
|
5519
|
+
await ensureWalletOnChain(walletChain, wallet, chainId);
|
|
4464
5520
|
return sponsorTokenBatch({
|
|
4465
5521
|
apiKey: deps.apiKey,
|
|
4466
5522
|
baseUrl: deps.baseUrl,
|
|
@@ -4470,7 +5526,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
4470
5526
|
transfers,
|
|
4471
5527
|
pub,
|
|
4472
5528
|
wallet,
|
|
4473
|
-
onApproved: deps.onApproved
|
|
5529
|
+
onApproved: deps.onApproved,
|
|
5530
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
4474
5531
|
});
|
|
4475
5532
|
};
|
|
4476
5533
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -4556,7 +5613,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
4556
5613
|
}
|
|
4557
5614
|
|
|
4558
5615
|
// src/lib/sponsored-calls-deposit.ts
|
|
4559
|
-
import { encodeFunctionData as encodeFunctionData2, erc20Abi as
|
|
5616
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi3, toHex } from "viem";
|
|
4560
5617
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4561
5618
|
var DEFAULT_MAX_POLLS = 30;
|
|
4562
5619
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -4602,7 +5659,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4602
5659
|
to: token,
|
|
4603
5660
|
value: "0x0",
|
|
4604
5661
|
data: encodeFunctionData2({
|
|
4605
|
-
abi:
|
|
5662
|
+
abi: erc20Abi3,
|
|
4606
5663
|
functionName: "transfer",
|
|
4607
5664
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
4608
5665
|
})
|
|
@@ -4664,6 +5721,12 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4664
5721
|
params: [callsId]
|
|
4665
5722
|
});
|
|
4666
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
|
+
}
|
|
4667
5730
|
if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
|
|
4668
5731
|
throw new OwneyError(
|
|
4669
5732
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4700,6 +5763,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4700
5763
|
}
|
|
4701
5764
|
|
|
4702
5765
|
// src/client.ts
|
|
5766
|
+
var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
|
|
5767
|
+
var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
|
|
4703
5768
|
function encodeMultiAgentCursor(map) {
|
|
4704
5769
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
4705
5770
|
}
|
|
@@ -4760,6 +5825,7 @@ var OwneySDK = class {
|
|
|
4760
5825
|
// leave every user's agent profile alone.
|
|
4761
5826
|
orgAgentConfig;
|
|
4762
5827
|
orgAgentConfigPromise = null;
|
|
5828
|
+
rpcUrls;
|
|
4763
5829
|
zyfaiRpcUrls;
|
|
4764
5830
|
yieldseekerApiBaseUrl;
|
|
4765
5831
|
yieldseekerSiweOrigin;
|
|
@@ -4770,6 +5836,7 @@ var OwneySDK = class {
|
|
|
4770
5836
|
paymasterServiceUrl;
|
|
4771
5837
|
cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
|
|
4772
5838
|
agentReads = new AgentReads();
|
|
5839
|
+
recoveredFundingReads = /* @__PURE__ */ new Set();
|
|
4773
5840
|
activationChecks = /* @__PURE__ */ new Map();
|
|
4774
5841
|
readAgent(agent, method, fetch2, params = null) {
|
|
4775
5842
|
const key2 = JSON.stringify([
|
|
@@ -4784,7 +5851,12 @@ var OwneySDK = class {
|
|
|
4784
5851
|
constructor(config) {
|
|
4785
5852
|
this.apiKey = config.apiKey;
|
|
4786
5853
|
if (config.debug) setOwneyDebug(true);
|
|
4787
|
-
this.
|
|
5854
|
+
this.rpcUrls = resolveRpcUrls(
|
|
5855
|
+
config.rpcUrls,
|
|
5856
|
+
config.apiKey,
|
|
5857
|
+
config.routingApiBaseUrl
|
|
5858
|
+
);
|
|
5859
|
+
this.zyfaiRpcUrls = config.rpcUrls ? void 0 : config.zyfaiRpcUrls;
|
|
4788
5860
|
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4789
5861
|
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
4790
5862
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
@@ -4874,9 +5946,12 @@ var OwneySDK = class {
|
|
|
4874
5946
|
}
|
|
4875
5947
|
return this.state.provider;
|
|
4876
5948
|
}
|
|
5949
|
+
getPaidRpcClient(chainId) {
|
|
5950
|
+
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
5951
|
+
}
|
|
4877
5952
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
4878
|
-
getDefaultSponsoredCallback(onApproved) {
|
|
4879
|
-
if (!onApproved && this.cachedSponsoredCallback)
|
|
5953
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
5954
|
+
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
4880
5955
|
return this.cachedSponsoredCallback;
|
|
4881
5956
|
const provider = this.requireConnectedProvider();
|
|
4882
5957
|
const owner = this.state.walletAddress;
|
|
@@ -4885,11 +5960,13 @@ var OwneySDK = class {
|
|
|
4885
5960
|
baseUrl: this.routingApiBaseUrl,
|
|
4886
5961
|
ownerAddress: owner,
|
|
4887
5962
|
onApproved,
|
|
5963
|
+
onSubmitted,
|
|
4888
5964
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
4889
5965
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4890
5966
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4891
5967
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4892
|
-
getPublicClient: (cid) =>
|
|
5968
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
5969
|
+
getWalletChainClient: (cid) => createPublicClient4({
|
|
4893
5970
|
chain: VIEM_CHAIN2[cid],
|
|
4894
5971
|
transport: custom3(provider)
|
|
4895
5972
|
}),
|
|
@@ -4899,13 +5976,13 @@ var OwneySDK = class {
|
|
|
4899
5976
|
transport: custom3(provider)
|
|
4900
5977
|
})
|
|
4901
5978
|
});
|
|
4902
|
-
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
5979
|
+
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
4903
5980
|
return callback;
|
|
4904
5981
|
}
|
|
4905
5982
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
4906
|
-
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
5983
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
4907
5984
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
4908
|
-
if (!onApproved && cached) return cached;
|
|
5985
|
+
if (!onApproved && !onSubmitted && cached) return cached;
|
|
4909
5986
|
const provider = this.requireConnectedProvider();
|
|
4910
5987
|
const callback = makeSponsoredCallsCallback({
|
|
4911
5988
|
apiKey: this.apiKey,
|
|
@@ -4914,9 +5991,10 @@ var OwneySDK = class {
|
|
|
4914
5991
|
ownerAddress: this.state.walletAddress,
|
|
4915
5992
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
4916
5993
|
onApproved,
|
|
5994
|
+
onSubmitted,
|
|
4917
5995
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
4918
5996
|
});
|
|
4919
|
-
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
5997
|
+
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
4920
5998
|
return callback;
|
|
4921
5999
|
}
|
|
4922
6000
|
/**
|
|
@@ -4925,8 +6003,8 @@ var OwneySDK = class {
|
|
|
4925
6003
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
4926
6004
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
4927
6005
|
*/
|
|
4928
|
-
getDefaultWethSponsoredCallback(onApproved) {
|
|
4929
|
-
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
6006
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6007
|
+
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
4930
6008
|
return this.cachedWethSponsoredCallback;
|
|
4931
6009
|
const provider = this.requireConnectedProvider();
|
|
4932
6010
|
const owner = this.state.walletAddress;
|
|
@@ -4935,11 +6013,13 @@ var OwneySDK = class {
|
|
|
4935
6013
|
baseUrl: this.routingApiBaseUrl,
|
|
4936
6014
|
ownerAddress: owner,
|
|
4937
6015
|
onApproved,
|
|
6016
|
+
onSubmitted,
|
|
4938
6017
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
4939
6018
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4940
6019
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4941
6020
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4942
|
-
getPublicClient: (cid) =>
|
|
6021
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
6022
|
+
getWalletChainClient: (cid) => createPublicClient4({
|
|
4943
6023
|
chain: VIEM_CHAIN2[cid],
|
|
4944
6024
|
transport: custom3(provider)
|
|
4945
6025
|
}),
|
|
@@ -4949,7 +6029,7 @@ var OwneySDK = class {
|
|
|
4949
6029
|
transport: custom3(provider)
|
|
4950
6030
|
})
|
|
4951
6031
|
});
|
|
4952
|
-
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
6032
|
+
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
4953
6033
|
return callback;
|
|
4954
6034
|
}
|
|
4955
6035
|
getAgent(agentId) {
|
|
@@ -4959,6 +6039,9 @@ var OwneySDK = class {
|
|
|
4959
6039
|
}
|
|
4960
6040
|
return agent;
|
|
4961
6041
|
}
|
|
6042
|
+
getActiveAgentIds() {
|
|
6043
|
+
return [...this.getActiveAgents().keys()].sort();
|
|
6044
|
+
}
|
|
4962
6045
|
getActiveAgents() {
|
|
4963
6046
|
const active = /* @__PURE__ */ new Map();
|
|
4964
6047
|
for (const id of this.activeAgents) {
|
|
@@ -5052,12 +6135,21 @@ var OwneySDK = class {
|
|
|
5052
6135
|
createAgent(agentId, key2) {
|
|
5053
6136
|
if (agentId === "zyfai") {
|
|
5054
6137
|
if (!key2) return null;
|
|
5055
|
-
return new ZyfaiAgent(
|
|
6138
|
+
return new ZyfaiAgent(
|
|
6139
|
+
key2,
|
|
6140
|
+
this.zyfaiRpcUrls ? resolveRpcUrls(
|
|
6141
|
+
this.zyfaiRpcUrls,
|
|
6142
|
+
this.apiKey,
|
|
6143
|
+
this.routingApiBaseUrl
|
|
6144
|
+
) : this.rpcUrls,
|
|
6145
|
+
this.referralSource
|
|
6146
|
+
);
|
|
5056
6147
|
}
|
|
5057
6148
|
if (agentId === "yieldseeker") {
|
|
5058
6149
|
return new YieldseekerAgent(this.apiKey, {
|
|
5059
6150
|
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5060
|
-
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
6151
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
|
|
6152
|
+
rpcUrls: this.rpcUrls
|
|
5061
6153
|
});
|
|
5062
6154
|
}
|
|
5063
6155
|
return null;
|
|
@@ -5244,12 +6336,23 @@ var OwneySDK = class {
|
|
|
5244
6336
|
*/
|
|
5245
6337
|
async deposit(options) {
|
|
5246
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;
|
|
5247
6350
|
if (BigInt(amount) === 0n) {
|
|
5248
6351
|
return { agentResults: {} };
|
|
5249
6352
|
}
|
|
5250
6353
|
const state = this.requireState();
|
|
5251
6354
|
const chainId = this.requireChainId();
|
|
5252
|
-
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));
|
|
5253
6356
|
if (agentId) {
|
|
5254
6357
|
if (this.isAgentDisabled(agentId)) {
|
|
5255
6358
|
throw new OwneyError(
|
|
@@ -5268,7 +6371,8 @@ var OwneySDK = class {
|
|
|
5268
6371
|
amount,
|
|
5269
6372
|
asset,
|
|
5270
6373
|
effectiveCallback,
|
|
5271
|
-
depositCallback
|
|
6374
|
+
depositCallback,
|
|
6375
|
+
onProgress
|
|
5272
6376
|
);
|
|
5273
6377
|
}
|
|
5274
6378
|
const totalAmount = BigInt(amount);
|
|
@@ -5332,7 +6436,7 @@ var OwneySDK = class {
|
|
|
5332
6436
|
run: (callback) => withFailureReporting(
|
|
5333
6437
|
this.apiKey,
|
|
5334
6438
|
agent.id,
|
|
5335
|
-
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
6439
|
+
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
5336
6440
|
this.routingApiBaseUrl
|
|
5337
6441
|
)
|
|
5338
6442
|
})),
|
|
@@ -5348,7 +6452,8 @@ var OwneySDK = class {
|
|
|
5348
6452
|
);
|
|
5349
6453
|
await this.approvePermit2(
|
|
5350
6454
|
asset,
|
|
5351
|
-
requiredAmount
|
|
6455
|
+
requiredAmount,
|
|
6456
|
+
cid
|
|
5352
6457
|
);
|
|
5353
6458
|
return batchTransfer(cid, transfers);
|
|
5354
6459
|
}
|
|
@@ -5368,7 +6473,8 @@ var OwneySDK = class {
|
|
|
5368
6473
|
agentAmount,
|
|
5369
6474
|
asset,
|
|
5370
6475
|
effectiveCallback,
|
|
5371
|
-
depositCallback
|
|
6476
|
+
depositCallback,
|
|
6477
|
+
onProgress
|
|
5372
6478
|
);
|
|
5373
6479
|
} catch (error) {
|
|
5374
6480
|
if (Object.keys(agentResults).length === 0) throw error;
|
|
@@ -5392,10 +6498,12 @@ var OwneySDK = class {
|
|
|
5392
6498
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
5393
6499
|
* two independent auto-recovery mechanisms:
|
|
5394
6500
|
*
|
|
5395
|
-
* 1. Missing Permit2 allowance
|
|
5396
|
-
* callback and the attempt fails with
|
|
5397
|
-
*
|
|
5398
|
-
*
|
|
6501
|
+
* 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
|
|
6502
|
+
* app did not supply its own callback and the attempt fails with
|
|
6503
|
+
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
6504
|
+
* for that token. Base USDC bundles a gasless ERC-2612 approval inside
|
|
6505
|
+
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
6506
|
+
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
5399
6507
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5400
6508
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
5401
6509
|
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
@@ -5413,17 +6521,11 @@ var OwneySDK = class {
|
|
|
5413
6521
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
5414
6522
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
5415
6523
|
*/
|
|
5416
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
6524
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
5417
6525
|
const attempt = (callback) => withFailureReporting(
|
|
5418
6526
|
this.apiKey,
|
|
5419
6527
|
agent.id,
|
|
5420
|
-
() => agent.deposit(
|
|
5421
|
-
state,
|
|
5422
|
-
chainId,
|
|
5423
|
-
amount,
|
|
5424
|
-
asset,
|
|
5425
|
-
callback
|
|
5426
|
-
),
|
|
6528
|
+
() => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
|
|
5427
6529
|
this.routingApiBaseUrl
|
|
5428
6530
|
);
|
|
5429
6531
|
let approvalAttempted = false;
|
|
@@ -5438,7 +6540,8 @@ var OwneySDK = class {
|
|
|
5438
6540
|
);
|
|
5439
6541
|
await this.approvePermit2(
|
|
5440
6542
|
asset,
|
|
5441
|
-
BigInt(amount)
|
|
6543
|
+
BigInt(amount),
|
|
6544
|
+
chainId
|
|
5442
6545
|
);
|
|
5443
6546
|
continue;
|
|
5444
6547
|
}
|
|
@@ -5585,6 +6688,98 @@ var OwneySDK = class {
|
|
|
5585
6688
|
}
|
|
5586
6689
|
return eligible;
|
|
5587
6690
|
}
|
|
6691
|
+
/**
|
|
6692
|
+
* Run owner-approved withdrawals before relayer-only withdrawals. Wallet
|
|
6693
|
+
* approval is the only point at which the user can cancel the aggregate
|
|
6694
|
+
* operation, so no relayer leg should commit before it has completed.
|
|
6695
|
+
*/
|
|
6696
|
+
orderAgentsForWithdrawal(agents) {
|
|
6697
|
+
return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
|
|
6698
|
+
const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
|
|
6699
|
+
return approvalOrder || left.index - right.index;
|
|
6700
|
+
}).map(({ agent }) => agent);
|
|
6701
|
+
}
|
|
6702
|
+
isUserRejectedWithdrawal(error) {
|
|
6703
|
+
let current = error;
|
|
6704
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6705
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
6706
|
+
seen.add(current);
|
|
6707
|
+
const candidate = current;
|
|
6708
|
+
if (candidate.code === 4001 || candidate.code === "4001" || candidate.code === "ACTION_REJECTED") return true;
|
|
6709
|
+
const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
|
|
6710
|
+
if (/user (?:rejected|denied)|rejected by user|request rejected/i.test(message)) {
|
|
6711
|
+
return true;
|
|
6712
|
+
}
|
|
6713
|
+
current = candidate.cause;
|
|
6714
|
+
}
|
|
6715
|
+
return false;
|
|
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
|
+
}
|
|
5588
6783
|
// --- Fund operations ---
|
|
5589
6784
|
/**
|
|
5590
6785
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -5596,10 +6791,66 @@ var OwneySDK = class {
|
|
|
5596
6791
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
5597
6792
|
*/
|
|
5598
6793
|
async withdraw(options) {
|
|
5599
|
-
const { asset, amount, agentId } = options;
|
|
5600
6794
|
const state = this.requireState();
|
|
5601
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) {
|
|
6817
|
+
const { asset, amount, agentId, onAgentResult } = options;
|
|
6818
|
+
const notifyAgentResult = (id, result) => {
|
|
6819
|
+
try {
|
|
6820
|
+
if (result.evidence) notifyFunding(options.onProgress, result.evidence);
|
|
6821
|
+
onAgentResult?.(id, result);
|
|
6822
|
+
} catch (error) {
|
|
6823
|
+
console.warn("Withdrawal result observer failed:", error);
|
|
6824
|
+
}
|
|
6825
|
+
};
|
|
5602
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
|
+
};
|
|
5603
6854
|
const assetInfo = SupportedAssets.find(
|
|
5604
6855
|
(a) => a.chainId === chainId && a.symbol === token
|
|
5605
6856
|
);
|
|
@@ -5612,21 +6863,27 @@ var OwneySDK = class {
|
|
|
5612
6863
|
if (agentId) {
|
|
5613
6864
|
const agent = this.getAgent(agentId);
|
|
5614
6865
|
this.validateAssetSupport(agent, chainId, asset);
|
|
5615
|
-
|
|
6866
|
+
const result = await withFailureReporting(
|
|
5616
6867
|
this.apiKey,
|
|
5617
6868
|
agent.id,
|
|
5618
|
-
() => agent
|
|
6869
|
+
() => submit(agent, amount),
|
|
5619
6870
|
this.routingApiBaseUrl
|
|
5620
6871
|
);
|
|
6872
|
+
notifyAgentResult(agent.id, result);
|
|
6873
|
+
return result;
|
|
5621
6874
|
}
|
|
5622
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6875
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
|
|
6876
|
+
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
5623
6877
|
if (!amount) {
|
|
5624
6878
|
const results2 = {};
|
|
5625
6879
|
const agentErrors2 = {};
|
|
5626
|
-
for (const agent of
|
|
6880
|
+
for (const agent of withdrawalAgents) {
|
|
5627
6881
|
try {
|
|
5628
|
-
results2[agent.id] = await agent
|
|
6882
|
+
results2[agent.id] = await submit(agent);
|
|
6883
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
5629
6884
|
} catch (err) {
|
|
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;
|
|
5630
6887
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
5631
6888
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
5632
6889
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5645,6 +6902,13 @@ var OwneySDK = class {
|
|
|
5645
6902
|
{ asset, agentErrors: agentErrors2 }
|
|
5646
6903
|
);
|
|
5647
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
|
+
}
|
|
5648
6912
|
const amountsKnown = Object.values(results2).every(
|
|
5649
6913
|
(result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
|
|
5650
6914
|
);
|
|
@@ -5654,7 +6918,7 @@ var OwneySDK = class {
|
|
|
5654
6918
|
};
|
|
5655
6919
|
}
|
|
5656
6920
|
const requested = BigInt(amount);
|
|
5657
|
-
const aggregated = await this.getBalances();
|
|
6921
|
+
const aggregated = await this.getBalances(void 0, { chainId, asset: token });
|
|
5658
6922
|
const unavailableAgents = eligibleAgents.filter(
|
|
5659
6923
|
(agent) => !(agent.id in aggregated.agentBalances)
|
|
5660
6924
|
);
|
|
@@ -5707,21 +6971,19 @@ var OwneySDK = class {
|
|
|
5707
6971
|
planned: 0n
|
|
5708
6972
|
}));
|
|
5709
6973
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
6974
|
+
const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
|
|
5710
6975
|
const results = {};
|
|
5711
6976
|
const agentErrors = {
|
|
5712
6977
|
...aggregated.agentErrors ?? {}
|
|
5713
6978
|
};
|
|
5714
|
-
for (let i = 0; i <
|
|
5715
|
-
const p =
|
|
6979
|
+
for (let i = 0; i < orderedPlans.length; i++) {
|
|
6980
|
+
const p = orderedPlans[i];
|
|
5716
6981
|
if (p.planned === 0n) continue;
|
|
5717
6982
|
try {
|
|
5718
|
-
results[p.agent.id] = await p.agent.
|
|
5719
|
-
|
|
5720
|
-
chainId,
|
|
5721
|
-
token,
|
|
5722
|
-
p.planned.toString()
|
|
5723
|
-
);
|
|
6983
|
+
results[p.agent.id] = await submit(p.agent, p.planned.toString());
|
|
6984
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
5724
6985
|
} catch (err) {
|
|
6986
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
5725
6987
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
5726
6988
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
5727
6989
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5733,7 +6995,8 @@ var OwneySDK = class {
|
|
|
5733
6995
|
);
|
|
5734
6996
|
const failedAmount = p.planned;
|
|
5735
6997
|
p.planned = 0n;
|
|
5736
|
-
|
|
6998
|
+
if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
|
|
6999
|
+
redistributeShare(orderedPlans, i, failedAmount);
|
|
5737
7000
|
}
|
|
5738
7001
|
}
|
|
5739
7002
|
if (Object.keys(results).length === 0) {
|
|
@@ -5770,12 +7033,19 @@ var OwneySDK = class {
|
|
|
5770
7033
|
* @param agentId - Optional. Agent to query. Omit for aggregated balances.
|
|
5771
7034
|
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
5772
7035
|
*/
|
|
5773
|
-
async getBalances(agentId) {
|
|
5774
|
-
const state = this.
|
|
5775
|
-
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
|
+
);
|
|
5776
7045
|
if (agentId) {
|
|
5777
7046
|
const agent = this.getAgent(agentId);
|
|
5778
|
-
const result = await
|
|
7047
|
+
const result = await readBalance(agent);
|
|
7048
|
+
assertCurrent();
|
|
5779
7049
|
return {
|
|
5780
7050
|
...result,
|
|
5781
7051
|
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
@@ -5786,7 +7056,7 @@ var OwneySDK = class {
|
|
|
5786
7056
|
const entries = [...this.getActiveAgents().entries()];
|
|
5787
7057
|
const balanceResults = await Promise.allSettled(
|
|
5788
7058
|
entries.map(async ([id, agent]) => {
|
|
5789
|
-
const b = await
|
|
7059
|
+
const b = await readBalance(agent);
|
|
5790
7060
|
return [
|
|
5791
7061
|
id,
|
|
5792
7062
|
{
|
|
@@ -5796,6 +7066,7 @@ var OwneySDK = class {
|
|
|
5796
7066
|
];
|
|
5797
7067
|
})
|
|
5798
7068
|
);
|
|
7069
|
+
assertCurrent();
|
|
5799
7070
|
let successCount = 0;
|
|
5800
7071
|
const agentErrors = {};
|
|
5801
7072
|
const agentRetryAt = {};
|
|
@@ -5843,23 +7114,11 @@ var OwneySDK = class {
|
|
|
5843
7114
|
const agent = this.getAgent(agentId);
|
|
5844
7115
|
return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
|
|
5845
7116
|
}
|
|
5846
|
-
let totalEarnings = 0;
|
|
5847
|
-
const results = {};
|
|
5848
7117
|
const entries = [...this.getActiveAgents().entries()];
|
|
5849
|
-
|
|
5850
|
-
entries
|
|
5851
|
-
|
|
5852
|
-
return [id, e];
|
|
5853
|
-
})
|
|
7118
|
+
return this.aggregateEarnings(
|
|
7119
|
+
entries,
|
|
7120
|
+
(agent) => this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId))
|
|
5854
7121
|
);
|
|
5855
|
-
for (const [id, e] of earningsResults) {
|
|
5856
|
-
results[id] = e;
|
|
5857
|
-
totalEarnings += Number(e.lifetimeEarnings);
|
|
5858
|
-
}
|
|
5859
|
-
return {
|
|
5860
|
-
totalEarnings: String(totalEarnings),
|
|
5861
|
-
agentEarnings: results
|
|
5862
|
-
};
|
|
5863
7122
|
}
|
|
5864
7123
|
/**
|
|
5865
7124
|
* Return refreshed earnings while letting each agent own its refresh policy.
|
|
@@ -5874,18 +7133,42 @@ var OwneySDK = class {
|
|
|
5874
7133
|
() => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
|
|
5875
7134
|
);
|
|
5876
7135
|
if (agentId) return refreshAgent(this.getAgent(agentId));
|
|
5877
|
-
|
|
7136
|
+
return this.aggregateEarnings([...this.getActiveAgents().entries()], refreshAgent);
|
|
7137
|
+
}
|
|
7138
|
+
async aggregateEarnings(entries, read2) {
|
|
7139
|
+
const settled = await Promise.allSettled(entries.map(([, agent]) => read2(agent)));
|
|
5878
7140
|
const agentEarnings = {};
|
|
5879
|
-
const
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
)
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
7141
|
+
const agentErrors = {};
|
|
7142
|
+
const agentRetryAt = {};
|
|
7143
|
+
const failures = [];
|
|
7144
|
+
let totalEarnings = 0;
|
|
7145
|
+
for (let i = 0; i < settled.length; i++) {
|
|
7146
|
+
const [id] = entries[i];
|
|
7147
|
+
const result = settled[i];
|
|
7148
|
+
if (result.status === "fulfilled") {
|
|
7149
|
+
agentEarnings[id] = result.value;
|
|
7150
|
+
totalEarnings += Number(result.value.lifetimeEarnings);
|
|
7151
|
+
continue;
|
|
7152
|
+
}
|
|
7153
|
+
const reason = result.reason;
|
|
7154
|
+
failures.push(reason);
|
|
7155
|
+
const retryDelay = rateLimitDelay(reason);
|
|
7156
|
+
if (retryDelay !== void 0) agentRetryAt[id] = Date.now() + retryDelay;
|
|
7157
|
+
agentErrors[id] = reason instanceof Error ? reason.message : String(reason);
|
|
7158
|
+
console.error(`[owney-sdk] Earnings fetch failed for agent "${id}":`, reason);
|
|
7159
|
+
}
|
|
7160
|
+
if (entries.length > 0 && Object.keys(agentEarnings).length === 0) {
|
|
7161
|
+
throw new OwneyError(
|
|
7162
|
+
"EARNINGS_ALL_FAILED",
|
|
7163
|
+
"Failed to fetch earnings for all active agents.",
|
|
7164
|
+
{ agentErrors, failures }
|
|
7165
|
+
);
|
|
5887
7166
|
}
|
|
5888
|
-
return {
|
|
7167
|
+
return {
|
|
7168
|
+
totalEarnings: String(totalEarnings),
|
|
7169
|
+
agentEarnings,
|
|
7170
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
|
|
7171
|
+
};
|
|
5889
7172
|
}
|
|
5890
7173
|
/**
|
|
5891
7174
|
* Get the weighted APY for the user's account over a time period.
|
|
@@ -6051,12 +7334,13 @@ var OwneySDK = class {
|
|
|
6051
7334
|
* @returns {OwneyAgentHistory} `data` (up to `limit` entries), `hasMore`, `nextCursor`.
|
|
6052
7335
|
*/
|
|
6053
7336
|
async getHistory(options) {
|
|
6054
|
-
const state = this.
|
|
6055
|
-
const chainId = this.requireChainId();
|
|
7337
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
6056
7338
|
const { agentId, filters } = options ?? {};
|
|
6057
7339
|
if (agentId) {
|
|
6058
7340
|
const agent = this.getAgent(agentId);
|
|
6059
|
-
|
|
7341
|
+
const page = await this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
|
|
7342
|
+
assertCurrent();
|
|
7343
|
+
return page;
|
|
6060
7344
|
}
|
|
6061
7345
|
const activeAgents = [...this.getActiveAgents().values()];
|
|
6062
7346
|
const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
|
|
@@ -6066,20 +7350,35 @@ var OwneySDK = class {
|
|
|
6066
7350
|
if (filters?.cursor && agentCursor === void 0) {
|
|
6067
7351
|
return { agentId: agent.id, page: null };
|
|
6068
7352
|
}
|
|
6069
|
-
const agentFilters = { ...filters, cursor: agentCursor };
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
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
|
+
}
|
|
6077
7365
|
})
|
|
6078
7366
|
);
|
|
7367
|
+
assertCurrent();
|
|
7368
|
+
const agentErrors = {};
|
|
7369
|
+
const agentRetryAt = {};
|
|
6079
7370
|
const merged = [];
|
|
6080
7371
|
const nextCursorMap = {};
|
|
6081
7372
|
let hasMore = false;
|
|
6082
|
-
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
|
+
}
|
|
6083
7382
|
if (!page) continue;
|
|
6084
7383
|
merged.push(...page.data);
|
|
6085
7384
|
if (page.hasMore && page.nextCursor) {
|
|
@@ -6099,6 +7398,7 @@ var OwneySDK = class {
|
|
|
6099
7398
|
});
|
|
6100
7399
|
return {
|
|
6101
7400
|
data: merged,
|
|
7401
|
+
...Object.keys(agentErrors).length ? { agentErrors, agentRetryAt } : {},
|
|
6102
7402
|
hasMore,
|
|
6103
7403
|
nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
|
|
6104
7404
|
};
|
|
@@ -6159,16 +7459,18 @@ var OwneySDK = class {
|
|
|
6159
7459
|
}
|
|
6160
7460
|
/**
|
|
6161
7461
|
* User-paid approval of Permit2 on the selected token for the active chain.
|
|
6162
|
-
*
|
|
6163
|
-
*
|
|
6164
|
-
*
|
|
7462
|
+
* Grants the maximum ERC20 allowance so later deposits do not require another
|
|
7463
|
+
* approval. Resolves after one confirmation so the subsequent deposit attempt
|
|
7464
|
+
* sees the new allowance. Deposit retries pass their captured chain id so a
|
|
7465
|
+
* concurrent activation cannot redirect the approval to another network.
|
|
6165
7466
|
*
|
|
6166
7467
|
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
7468
|
+
* @param expectedChainId Chain captured by the deposit that requested approval.
|
|
6167
7469
|
* @returns the approval transaction hash.
|
|
6168
7470
|
*/
|
|
6169
|
-
async approvePermit2(asset = "WETH", requiredAmount = 0n) {
|
|
7471
|
+
async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId) {
|
|
6170
7472
|
const state = this.requireState();
|
|
6171
|
-
const chainId = this.requireChainId();
|
|
7473
|
+
const chainId = expectedChainId ?? this.requireChainId();
|
|
6172
7474
|
this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
|
|
6173
7475
|
const token = sponsoredTokensFor(asset)[chainId];
|
|
6174
7476
|
if (!token) {
|
|
@@ -6178,16 +7480,18 @@ var OwneySDK = class {
|
|
|
6178
7480
|
);
|
|
6179
7481
|
}
|
|
6180
7482
|
const provider = this.requireConnectedProvider();
|
|
6181
|
-
const
|
|
7483
|
+
const walletChainClient = createPublicClient4({
|
|
6182
7484
|
chain: VIEM_CHAIN2[chainId],
|
|
6183
7485
|
transport: custom3(provider)
|
|
6184
7486
|
});
|
|
7487
|
+
const publicClient = this.getPaidRpcClient(chainId);
|
|
6185
7488
|
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6186
7489
|
const wallet = createWalletClient3({
|
|
6187
7490
|
account: state.walletAddress,
|
|
6188
7491
|
chain: VIEM_CHAIN2[chainId],
|
|
6189
7492
|
transport: custom3(provider)
|
|
6190
7493
|
});
|
|
7494
|
+
await ensureWalletOnChain(walletChainClient, wallet, chainId);
|
|
6191
7495
|
const hash = await wallet.writeContract({
|
|
6192
7496
|
address: token,
|
|
6193
7497
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -6196,14 +7500,53 @@ var OwneySDK = class {
|
|
|
6196
7500
|
account: state.walletAddress,
|
|
6197
7501
|
chain: VIEM_CHAIN2[chainId]
|
|
6198
7502
|
});
|
|
6199
|
-
const receipt = await
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
7503
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
7504
|
+
() => publicClient.waitForTransactionReceipt({
|
|
7505
|
+
hash,
|
|
7506
|
+
confirmations: 1
|
|
7507
|
+
}),
|
|
7508
|
+
chainId,
|
|
7509
|
+
"eth_getTransactionReceipt"
|
|
7510
|
+
);
|
|
6203
7511
|
if (receipt.status !== "success") {
|
|
6204
7512
|
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
6205
7513
|
}
|
|
6206
|
-
|
|
7514
|
+
let observedAllowance = 0n;
|
|
7515
|
+
let verificationError;
|
|
7516
|
+
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
|
|
7517
|
+
try {
|
|
7518
|
+
observedAllowance = await readPermit2Allowance(
|
|
7519
|
+
publicClient,
|
|
7520
|
+
token,
|
|
7521
|
+
state.walletAddress,
|
|
7522
|
+
attempt === 0 ? receipt.blockNumber : void 0
|
|
7523
|
+
);
|
|
7524
|
+
verificationError = void 0;
|
|
7525
|
+
if (observedAllowance >= requiredAmount) return hash;
|
|
7526
|
+
} catch (error) {
|
|
7527
|
+
verificationError = error;
|
|
7528
|
+
}
|
|
7529
|
+
if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
|
|
7530
|
+
await new Promise(
|
|
7531
|
+
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
|
|
7532
|
+
);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
7535
|
+
throw new OwneyError(
|
|
7536
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
7537
|
+
"Permit2 approval was confirmed, but the required token allowance was not observable.",
|
|
7538
|
+
{
|
|
7539
|
+
approvalConfirmed: true,
|
|
7540
|
+
approvalTxHash: hash,
|
|
7541
|
+
owner: state.walletAddress,
|
|
7542
|
+
token,
|
|
7543
|
+
spender: PERMIT2_ADDRESS,
|
|
7544
|
+
chainId,
|
|
7545
|
+
requiredAmount: requiredAmount.toString(),
|
|
7546
|
+
observedAllowance: observedAllowance.toString(),
|
|
7547
|
+
...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
|
|
7548
|
+
}
|
|
7549
|
+
);
|
|
6207
7550
|
}
|
|
6208
7551
|
// --- Discovery (no wallet required) ---
|
|
6209
7552
|
/**
|