@owney/sdk 0.7.25-beta.9 → 0.7.25
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 +26 -74
- package/dist/index.cjs +698 -3256
- package/dist/index.d.cts +181 -354
- package/dist/index.d.ts +181 -354
- package/dist/index.js +694 -3271
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
// src/lib/deposit-batch-callback.ts
|
|
2
|
-
var batchCallbacks = /* @__PURE__ */ new WeakMap();
|
|
3
|
-
var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
|
|
4
|
-
function toBatchTransfer(to, amount, verification) {
|
|
5
|
-
return {
|
|
6
|
-
to,
|
|
7
|
-
amount,
|
|
8
|
-
...verification ? {
|
|
9
|
-
yieldseeker: {
|
|
10
|
-
signature: verification.signature,
|
|
11
|
-
userId: verification.userId,
|
|
12
|
-
agentId: verification.yieldseekerAgentId
|
|
13
|
-
}
|
|
14
|
-
} : {}
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function registerDepositBatch(callback, transfer) {
|
|
18
|
-
batchCallbacks.set(callback, transfer);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
1
|
// src/errors.ts
|
|
22
2
|
var OwneyError = class extends Error {
|
|
23
3
|
code;
|
|
@@ -125,34 +105,6 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
125
105
|
}
|
|
126
106
|
|
|
127
107
|
// src/lib/agent-reads.ts
|
|
128
|
-
function rateLimitDiagnostics(error) {
|
|
129
|
-
const seen = /* @__PURE__ */ new Set();
|
|
130
|
-
let details = {};
|
|
131
|
-
function visit(value, depth = 0) {
|
|
132
|
-
if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
|
|
133
|
-
return;
|
|
134
|
-
seen.add(value);
|
|
135
|
-
const record = value;
|
|
136
|
-
for (const key2 of [
|
|
137
|
-
"rpcSource",
|
|
138
|
-
"chainId",
|
|
139
|
-
"rpcMethod",
|
|
140
|
-
"providerRequestId",
|
|
141
|
-
"statusCode"
|
|
142
|
-
]) {
|
|
143
|
-
const candidate = record[key2];
|
|
144
|
-
if (candidate !== void 0 && details[key2] === void 0)
|
|
145
|
-
details = { ...details, [key2]: candidate };
|
|
146
|
-
}
|
|
147
|
-
for (const key2 of ["details", "cause", "response", "fields", "error"])
|
|
148
|
-
visit(record[key2], depth + 1);
|
|
149
|
-
}
|
|
150
|
-
visit(error);
|
|
151
|
-
return {
|
|
152
|
-
rpcSource: details.rpcSource ?? "agent-api",
|
|
153
|
-
...details
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
108
|
var AgentReads = class {
|
|
157
109
|
inFlight = /* @__PURE__ */ new Map();
|
|
158
110
|
cooldowns = /* @__PURE__ */ new Map();
|
|
@@ -160,15 +112,14 @@ var AgentReads = class {
|
|
|
160
112
|
clearInFlight() {
|
|
161
113
|
this.inFlight.clear();
|
|
162
114
|
}
|
|
163
|
-
limited(agentId, until
|
|
115
|
+
limited(agentId, until) {
|
|
164
116
|
return new OwneyError(
|
|
165
117
|
"AGENT_RATE_LIMITED",
|
|
166
118
|
"Too many requests. Please wait before trying again.",
|
|
167
119
|
{
|
|
168
120
|
statusCode: 429,
|
|
169
121
|
retryAt: until,
|
|
170
|
-
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
|
|
171
|
-
...diagnostics
|
|
122
|
+
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
|
|
172
123
|
},
|
|
173
124
|
agentId
|
|
174
125
|
);
|
|
@@ -176,9 +127,7 @@ var AgentReads = class {
|
|
|
176
127
|
run(agentId, key2, fetch2) {
|
|
177
128
|
const cooldown = this.cooldowns.get(agentId);
|
|
178
129
|
if (cooldown && cooldown.until > Date.now()) {
|
|
179
|
-
return Promise.reject(
|
|
180
|
-
this.limited(agentId, cooldown.until, cooldown.diagnostics)
|
|
181
|
-
);
|
|
130
|
+
return Promise.reject(this.limited(agentId, cooldown.until));
|
|
182
131
|
}
|
|
183
132
|
const requestKey = JSON.stringify([agentId, key2]);
|
|
184
133
|
const existing = this.inFlight.get(requestKey);
|
|
@@ -199,9 +148,8 @@ var AgentReads = class {
|
|
|
199
148
|
Math.min(3e4 * 2 ** (failures - 1), 3e5)
|
|
200
149
|
);
|
|
201
150
|
const until = Math.max(previous?.until ?? 0, Date.now() + delay);
|
|
202
|
-
|
|
203
|
-
this.
|
|
204
|
-
throw this.limited(agentId, until, diagnostics);
|
|
151
|
+
this.cooldowns.set(agentId, { until, failures });
|
|
152
|
+
throw this.limited(agentId, until);
|
|
205
153
|
}
|
|
206
154
|
).finally(() => {
|
|
207
155
|
if (this.inFlight.get(requestKey) === promise)
|
|
@@ -214,7 +162,7 @@ var AgentReads = class {
|
|
|
214
162
|
|
|
215
163
|
// src/agents/zyfai/zyfai.agent.ts
|
|
216
164
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
217
|
-
import { parseAbi } from "viem";
|
|
165
|
+
import { createPublicClient, http, parseAbi } from "viem";
|
|
218
166
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
219
167
|
|
|
220
168
|
// src/types/config.ts
|
|
@@ -329,18 +277,18 @@ function tokenDecimals(symbol, explicit) {
|
|
|
329
277
|
return explicit;
|
|
330
278
|
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
331
279
|
}
|
|
332
|
-
function mapDeposit(
|
|
280
|
+
function mapDeposit(raw) {
|
|
333
281
|
return {
|
|
334
|
-
txHash:
|
|
335
|
-
smartWallet:
|
|
336
|
-
amount:
|
|
282
|
+
txHash: raw.txHash,
|
|
283
|
+
smartWallet: raw.smartWallet,
|
|
284
|
+
amount: raw.amount
|
|
337
285
|
};
|
|
338
286
|
}
|
|
339
|
-
function mapWithdraw(
|
|
287
|
+
function mapWithdraw(raw) {
|
|
340
288
|
return {
|
|
341
|
-
txHash:
|
|
342
|
-
type:
|
|
343
|
-
amount:
|
|
289
|
+
txHash: raw.txHash,
|
|
290
|
+
type: raw.type,
|
|
291
|
+
amount: raw.amount
|
|
344
292
|
};
|
|
345
293
|
}
|
|
346
294
|
var CHAIN_ID_TO_NAME = {
|
|
@@ -359,10 +307,10 @@ function resolveChainId(chain) {
|
|
|
359
307
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
360
308
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
361
309
|
}
|
|
362
|
-
function mapPendingAllocations(
|
|
363
|
-
if (!Array.isArray(
|
|
310
|
+
function mapPendingAllocations(raw) {
|
|
311
|
+
if (!Array.isArray(raw)) return void 0;
|
|
364
312
|
const pending = [];
|
|
365
|
-
for (const entry of
|
|
313
|
+
for (const entry of raw) {
|
|
366
314
|
if (typeof entry !== "object" || entry === null) continue;
|
|
367
315
|
const e = entry;
|
|
368
316
|
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
@@ -385,8 +333,8 @@ function mapPendingAllocations(raw2) {
|
|
|
385
333
|
}
|
|
386
334
|
return pending.length > 0 ? pending : void 0;
|
|
387
335
|
}
|
|
388
|
-
function mapBalances(
|
|
389
|
-
const portfolio =
|
|
336
|
+
function mapBalances(raw, _chainId, smartWallet) {
|
|
337
|
+
const portfolio = raw.portfolio;
|
|
390
338
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
391
339
|
let totalBalance = 0;
|
|
392
340
|
const tokens = [];
|
|
@@ -455,8 +403,8 @@ function sumTokenValues(tokens) {
|
|
|
455
403
|
function sumTokenEarnings(tokens) {
|
|
456
404
|
return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
|
|
457
405
|
}
|
|
458
|
-
function mapEarnings(
|
|
459
|
-
const totalEarningsByChain =
|
|
406
|
+
function mapEarnings(raw, smartWallet) {
|
|
407
|
+
const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
|
|
460
408
|
const tokens = [];
|
|
461
409
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(
|
|
462
410
|
totalEarningsByChain
|
|
@@ -475,15 +423,15 @@ function mapEarnings(raw2, smartWallet) {
|
|
|
475
423
|
return {
|
|
476
424
|
smartWallet,
|
|
477
425
|
lifetimeEarnings: sumTokenEarnings(
|
|
478
|
-
|
|
426
|
+
raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
|
|
479
427
|
),
|
|
480
428
|
tokens
|
|
481
429
|
};
|
|
482
430
|
}
|
|
483
|
-
function mapWeightedApyByChain(
|
|
484
|
-
if (!
|
|
431
|
+
function mapWeightedApyByChain(raw) {
|
|
432
|
+
if (!raw) return void 0;
|
|
485
433
|
const out = {};
|
|
486
|
-
for (const [chainKey, tokenApy] of Object.entries(
|
|
434
|
+
for (const [chainKey, tokenApy] of Object.entries(raw)) {
|
|
487
435
|
const chainId = Number(chainKey);
|
|
488
436
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
489
437
|
const perAsset = {};
|
|
@@ -523,8 +471,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
|
523
471
|
}
|
|
524
472
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
525
473
|
}
|
|
526
|
-
function mapApyHistory(
|
|
527
|
-
const history = Object.entries(
|
|
474
|
+
function mapApyHistory(raw, chainId, tokenSymbol) {
|
|
475
|
+
const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
|
|
528
476
|
date,
|
|
529
477
|
apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
|
|
530
478
|
// Provider position balances are treated as decimal amounts of the
|
|
@@ -540,9 +488,9 @@ function mapApyHistory(raw2, chainId, tokenSymbol) {
|
|
|
540
488
|
} : {}
|
|
541
489
|
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
542
490
|
return {
|
|
543
|
-
walletAddress:
|
|
544
|
-
weightedApyAfterFee:
|
|
545
|
-
apyByChainAndAsset: mapWeightedApyByChain(
|
|
491
|
+
walletAddress: raw.walletAddress,
|
|
492
|
+
weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
|
|
493
|
+
apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
|
|
546
494
|
history
|
|
547
495
|
};
|
|
548
496
|
}
|
|
@@ -638,23 +586,23 @@ function mapEntries(rawEntries, chainId) {
|
|
|
638
586
|
};
|
|
639
587
|
});
|
|
640
588
|
}
|
|
641
|
-
function mapUserProfile(
|
|
589
|
+
function mapUserProfile(raw, userAddress) {
|
|
642
590
|
return {
|
|
643
591
|
address: userAddress,
|
|
644
|
-
smartWallet:
|
|
645
|
-
chains:
|
|
646
|
-
strategy:
|
|
647
|
-
hasActiveSessionKey:
|
|
648
|
-
protocols:
|
|
649
|
-
splitting:
|
|
650
|
-
minSplits:
|
|
592
|
+
smartWallet: raw.smartWallet || "",
|
|
593
|
+
chains: raw.chains || [],
|
|
594
|
+
strategy: raw.strategy,
|
|
595
|
+
hasActiveSessionKey: raw.hasActiveSessionKey || false,
|
|
596
|
+
protocols: raw.protocols || [],
|
|
597
|
+
splitting: raw.splitting,
|
|
598
|
+
minSplits: raw.minSplits
|
|
651
599
|
};
|
|
652
600
|
}
|
|
653
|
-
function mapApyByStrategy(
|
|
601
|
+
function mapApyByStrategy(raw) {
|
|
654
602
|
const apyPerAsset = {};
|
|
655
603
|
let apySum = 0;
|
|
656
604
|
let apyCount = 0;
|
|
657
|
-
for (const entry of
|
|
605
|
+
for (const entry of raw.data) {
|
|
658
606
|
const supported = SupportedAssets.find(
|
|
659
607
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
660
608
|
);
|
|
@@ -766,9 +714,9 @@ function netDeltaForSnapshot(entry, chainId, asset) {
|
|
|
766
714
|
debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
|
|
767
715
|
return gross;
|
|
768
716
|
}
|
|
769
|
-
function mapDailyEarnings(
|
|
717
|
+
function mapDailyEarnings(raw, chainId, tokenSymbol) {
|
|
770
718
|
const wanted = tokenSymbol?.toUpperCase();
|
|
771
|
-
const snapshots = [...
|
|
719
|
+
const snapshots = [...raw.data ?? []].sort(
|
|
772
720
|
(a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
|
|
773
721
|
);
|
|
774
722
|
const byAsset = /* @__PURE__ */ new Map();
|
|
@@ -783,7 +731,7 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
|
|
|
783
731
|
}
|
|
784
732
|
}
|
|
785
733
|
const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
|
|
786
|
-
return { walletAddress:
|
|
734
|
+
return { walletAddress: raw.walletAddress, chainId, assets };
|
|
787
735
|
}
|
|
788
736
|
|
|
789
737
|
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
@@ -894,15 +842,15 @@ var readSession = (address, _chainId) => {
|
|
|
894
842
|
if (typeof window === "undefined") return null;
|
|
895
843
|
const key2 = buildKey(address);
|
|
896
844
|
const store = storage();
|
|
897
|
-
let
|
|
845
|
+
let raw = null;
|
|
898
846
|
try {
|
|
899
|
-
|
|
847
|
+
raw = store?.getItem(key2) ?? null;
|
|
900
848
|
} catch {
|
|
901
|
-
|
|
849
|
+
raw = null;
|
|
902
850
|
}
|
|
903
|
-
if (
|
|
851
|
+
if (raw) {
|
|
904
852
|
try {
|
|
905
|
-
const parsed = JSON.parse(
|
|
853
|
+
const parsed = JSON.parse(raw);
|
|
906
854
|
if (isFreshSession(parsed)) return parsed;
|
|
907
855
|
} catch {
|
|
908
856
|
}
|
|
@@ -1070,8 +1018,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
|
|
|
1070
1018
|
}
|
|
1071
1019
|
return result;
|
|
1072
1020
|
}
|
|
1073
|
-
function flattenAvailablePools(
|
|
1074
|
-
const byChain =
|
|
1021
|
+
function flattenAvailablePools(raw) {
|
|
1022
|
+
const byChain = raw ?? {};
|
|
1075
1023
|
const names = [];
|
|
1076
1024
|
for (const byToken of Object.values(byChain ?? {})) {
|
|
1077
1025
|
for (const entry of Object.values(byToken ?? {})) {
|
|
@@ -1095,148 +1043,15 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
1095
1043
|
return !protocolListsEqual(current.protocols, desiredProtocols);
|
|
1096
1044
|
}
|
|
1097
1045
|
|
|
1098
|
-
// src/lib/paid-rpc.ts
|
|
1099
|
-
import {
|
|
1100
|
-
createPublicClient,
|
|
1101
|
-
http
|
|
1102
|
-
} from "viem";
|
|
1103
|
-
var PAID_RPC_RETRY_COUNT = 3;
|
|
1104
|
-
var PAID_RPC_RETRY_DELAY_MS = 1e3;
|
|
1105
|
-
function configuredRpcProxyBaseUrl(baseUrl) {
|
|
1106
|
-
const configured = (baseUrl ?? "")?.trim();
|
|
1107
|
-
if (!configured) {
|
|
1108
|
-
throw new Error(
|
|
1109
|
-
"OWNEY_ROUTING_API_BASE_URL is required for RPC proxy requests unless routingApiBaseUrl or all rpcUrls are configured."
|
|
1110
|
-
);
|
|
1111
|
-
}
|
|
1112
|
-
return configured;
|
|
1113
|
-
}
|
|
1114
|
-
function rpcProxyUrl(chainId, apiKey, baseUrl) {
|
|
1115
|
-
const url = new URL(
|
|
1116
|
-
`${configuredRpcProxyBaseUrl(baseUrl).replace(/\/$/, "")}/api/v1/rpc/${chainId}`
|
|
1117
|
-
);
|
|
1118
|
-
if (apiKey) url.searchParams.set("apiKey", apiKey);
|
|
1119
|
-
return url.toString();
|
|
1120
|
-
}
|
|
1121
|
-
function resolveRpcUrl(rpcUrls, chainId, apiKey = "", baseUrl) {
|
|
1122
|
-
const url = rpcUrls?.[chainId]?.trim();
|
|
1123
|
-
if (url) return url;
|
|
1124
|
-
return rpcProxyUrl(chainId, apiKey, baseUrl);
|
|
1125
|
-
}
|
|
1126
|
-
function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
|
|
1127
|
-
return {
|
|
1128
|
-
1: resolveRpcUrl(rpcUrls, 1, apiKey, baseUrl),
|
|
1129
|
-
8453: resolveRpcUrl(rpcUrls, 8453, apiKey, baseUrl),
|
|
1130
|
-
42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
function createPaidRpcClient(chain, rpcUrls) {
|
|
1134
|
-
const url = resolveRpcUrl(
|
|
1135
|
-
rpcUrls,
|
|
1136
|
-
chain.id
|
|
1137
|
-
);
|
|
1138
|
-
return createPublicClient({
|
|
1139
|
-
chain,
|
|
1140
|
-
transport: http(url, {
|
|
1141
|
-
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1142
|
-
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
1143
|
-
})
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
function headerValue(error, name) {
|
|
1147
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1148
|
-
let value;
|
|
1149
|
-
function visit(candidate, depth = 0) {
|
|
1150
|
-
if (value || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1151
|
-
return;
|
|
1152
|
-
if (seen.has(candidate)) return;
|
|
1153
|
-
seen.add(candidate);
|
|
1154
|
-
const record = candidate;
|
|
1155
|
-
const headers = record.headers;
|
|
1156
|
-
let found;
|
|
1157
|
-
if (typeof headers?.get === "function") {
|
|
1158
|
-
found = headers.get(name);
|
|
1159
|
-
} else {
|
|
1160
|
-
const headerRecord = headers;
|
|
1161
|
-
found = headerRecord?.[name] ?? headerRecord?.[name.toLowerCase()];
|
|
1162
|
-
}
|
|
1163
|
-
if (typeof found === "string" && found) {
|
|
1164
|
-
value = found;
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
for (const key2 of ["cause", "details", "response", "error"])
|
|
1168
|
-
visit(record[key2], depth + 1);
|
|
1169
|
-
}
|
|
1170
|
-
visit(error);
|
|
1171
|
-
return value;
|
|
1172
|
-
}
|
|
1173
|
-
function statusCode(error) {
|
|
1174
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1175
|
-
let status;
|
|
1176
|
-
function visit(candidate, depth = 0) {
|
|
1177
|
-
if (status || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1178
|
-
return;
|
|
1179
|
-
if (seen.has(candidate)) return;
|
|
1180
|
-
seen.add(candidate);
|
|
1181
|
-
const record = candidate;
|
|
1182
|
-
for (const key2 of ["status", "statusCode"]) {
|
|
1183
|
-
const parsed = Number(record[key2]);
|
|
1184
|
-
if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1185
|
-
status = parsed;
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
for (const key2 of ["cause", "details", "response", "error"])
|
|
1190
|
-
visit(record[key2], depth + 1);
|
|
1191
|
-
}
|
|
1192
|
-
visit(error);
|
|
1193
|
-
return status;
|
|
1194
|
-
}
|
|
1195
|
-
function paidRpcError(error, chainId, rpcMethod, agentId) {
|
|
1196
|
-
const delay = rateLimitDelay(error);
|
|
1197
|
-
const providerRequestId = headerValue(error, "x-alchemy-request-id") ?? headerValue(error, "x-request-id");
|
|
1198
|
-
if (delay === void 0) {
|
|
1199
|
-
return new OwneyError(
|
|
1200
|
-
"AGENT_API_ERROR",
|
|
1201
|
-
"The blockchain RPC request failed.",
|
|
1202
|
-
{
|
|
1203
|
-
rpcSource: "paid-rpc",
|
|
1204
|
-
chainId,
|
|
1205
|
-
rpcMethod,
|
|
1206
|
-
...statusCode(error) ? { statusCode: statusCode(error) } : {},
|
|
1207
|
-
...providerRequestId ? { providerRequestId } : {}
|
|
1208
|
-
},
|
|
1209
|
-
agentId
|
|
1210
|
-
);
|
|
1211
|
-
}
|
|
1212
|
-
const retryAt = Date.now() + delay;
|
|
1213
|
-
return new OwneyError(
|
|
1214
|
-
"AGENT_RATE_LIMITED",
|
|
1215
|
-
"The blockchain RPC is rate limited. Please wait before trying again.",
|
|
1216
|
-
{
|
|
1217
|
-
rpcSource: "paid-rpc",
|
|
1218
|
-
chainId,
|
|
1219
|
-
rpcMethod,
|
|
1220
|
-
statusCode: 429,
|
|
1221
|
-
retryAt,
|
|
1222
|
-
retryAfterSeconds: Math.max(0, Math.ceil(delay / 1e3)),
|
|
1223
|
-
...providerRequestId ? { providerRequestId } : {}
|
|
1224
|
-
},
|
|
1225
|
-
agentId
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
1229
|
-
try {
|
|
1230
|
-
return await operation();
|
|
1231
|
-
} catch (error) {
|
|
1232
|
-
throw paidRpcError(error, chainId, rpcMethod, agentId);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
1046
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1237
1047
|
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
|
|
1238
1048
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1239
1049
|
]);
|
|
1050
|
+
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
1051
|
+
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1052
|
+
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1053
|
+
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
1054
|
+
};
|
|
1240
1055
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
1241
1056
|
8453: "0x4200000000000000000000000000000000000006",
|
|
1242
1057
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
@@ -1302,7 +1117,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1302
1117
|
earningsSnapshot = null;
|
|
1303
1118
|
earningsGeneration = 0;
|
|
1304
1119
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1305
|
-
this.rpcUrls =
|
|
1120
|
+
this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
|
|
1306
1121
|
this.sdk = new ZyfaiSDK({
|
|
1307
1122
|
apiKey,
|
|
1308
1123
|
rpcUrls: this.rpcUrls,
|
|
@@ -1316,10 +1131,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1316
1131
|
getPublicClient(chainId) {
|
|
1317
1132
|
const cached = this.publicClients.get(chainId);
|
|
1318
1133
|
if (cached) return cached;
|
|
1319
|
-
const client =
|
|
1320
|
-
VIEM_CHAIN[chainId],
|
|
1321
|
-
this.rpcUrls
|
|
1322
|
-
);
|
|
1134
|
+
const client = createPublicClient({
|
|
1135
|
+
chain: VIEM_CHAIN[chainId],
|
|
1136
|
+
transport: http(this.rpcUrls[chainId])
|
|
1137
|
+
});
|
|
1323
1138
|
this.publicClients.set(chainId, client);
|
|
1324
1139
|
return client;
|
|
1325
1140
|
}
|
|
@@ -1489,7 +1304,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1489
1304
|
* chain/account switch fires the provider's `accountsChanged` →
|
|
1490
1305
|
* `disconnectAccount` (nulls walletClient + authenticatedUserId) while our
|
|
1491
1306
|
* own `connectAuth` guard still believes this chain is connected and
|
|
1492
|
-
* early-returns. Without this, authenticated calls (
|
|
1307
|
+
* early-returns. Without this, authenticated calls (sendDeposit,
|
|
1493
1308
|
* withdrawFunds) fail with "No account connected".
|
|
1494
1309
|
*/
|
|
1495
1310
|
async ensureConnected(state, chainId) {
|
|
@@ -1707,8 +1522,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1707
1522
|
const poolResults = await Promise.all(
|
|
1708
1523
|
universe.map(async (protocol) => {
|
|
1709
1524
|
try {
|
|
1710
|
-
const
|
|
1711
|
-
return [protocol.id, flattenAvailablePools(
|
|
1525
|
+
const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
|
|
1526
|
+
return [protocol.id, flattenAvailablePools(raw)];
|
|
1712
1527
|
} catch (error) {
|
|
1713
1528
|
console.warn(
|
|
1714
1529
|
`[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
|
|
@@ -1774,14 +1589,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1774
1589
|
async readWalletState(ownerAddress) {
|
|
1775
1590
|
try {
|
|
1776
1591
|
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1777
|
-
const
|
|
1592
|
+
const raw = portfolio;
|
|
1778
1593
|
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1779
|
-
predeployed:
|
|
1780
|
-
hasActiveSessionKey:
|
|
1594
|
+
predeployed: raw?.predeployed,
|
|
1595
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1781
1596
|
});
|
|
1782
1597
|
return {
|
|
1783
|
-
predeployed:
|
|
1784
|
-
hasActiveSessionKey:
|
|
1598
|
+
predeployed: raw?.predeployed,
|
|
1599
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1785
1600
|
};
|
|
1786
1601
|
} catch (error) {
|
|
1787
1602
|
console.warn(
|
|
@@ -2067,14 +1882,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2067
1882
|
return { txHash, smartWallet, amount };
|
|
2068
1883
|
}
|
|
2069
1884
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
2070
|
-
const
|
|
1885
|
+
const raw = await this.sdk.sendDeposit(
|
|
2071
1886
|
this.getAddress(),
|
|
2072
1887
|
validChainId,
|
|
2073
1888
|
amount,
|
|
2074
1889
|
asset,
|
|
2075
1890
|
"aggressive"
|
|
2076
1891
|
);
|
|
2077
|
-
return mapDeposit(
|
|
1892
|
+
return mapDeposit(raw);
|
|
2078
1893
|
} catch (error) {
|
|
2079
1894
|
throw error;
|
|
2080
1895
|
}
|
|
@@ -2083,27 +1898,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2083
1898
|
async withdraw(state, chainId, token, amount) {
|
|
2084
1899
|
const validChainId = isValidChainId(chainId);
|
|
2085
1900
|
await this.ensureConnected(state, validChainId);
|
|
2086
|
-
const
|
|
1901
|
+
const raw = await this.sdk.withdrawFunds(
|
|
2087
1902
|
this.getAddress(),
|
|
2088
1903
|
validChainId,
|
|
2089
1904
|
amount,
|
|
2090
1905
|
token
|
|
2091
1906
|
);
|
|
2092
|
-
if (!
|
|
1907
|
+
if (!raw.success) {
|
|
2093
1908
|
throw new OwneyError(
|
|
2094
1909
|
"WITHDRAW_FAILED",
|
|
2095
|
-
|
|
2096
|
-
{ chainId: validChainId, token, amount, response:
|
|
1910
|
+
raw.message || "Zyfai withdraw failed.",
|
|
1911
|
+
{ chainId: validChainId, token, amount, response: raw },
|
|
2097
1912
|
this.id
|
|
2098
1913
|
);
|
|
2099
1914
|
}
|
|
2100
|
-
return mapWithdraw(
|
|
1915
|
+
return mapWithdraw(raw);
|
|
2101
1916
|
}
|
|
2102
1917
|
// --- IAgent: Portfolio reads ---
|
|
2103
1918
|
async getBalances(state, chainId) {
|
|
2104
1919
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
2105
|
-
const
|
|
2106
|
-
return mapBalances(
|
|
1920
|
+
const raw = await this.sdk.getPortfolio(this.getAddress());
|
|
1921
|
+
return mapBalances(raw, validChainId, smartWallet);
|
|
2107
1922
|
}
|
|
2108
1923
|
earningsKey(state, chainId, smartWallet) {
|
|
2109
1924
|
return JSON.stringify([
|
|
@@ -2116,11 +1931,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2116
1931
|
const existing = this.earningsReads.get(key2);
|
|
2117
1932
|
if (existing) return existing;
|
|
2118
1933
|
const generation = this.earningsGeneration;
|
|
2119
|
-
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((
|
|
1934
|
+
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
|
|
2120
1935
|
if (generation === this.earningsGeneration) {
|
|
2121
|
-
this.earningsSnapshot = { key: key2, raw
|
|
1936
|
+
this.earningsSnapshot = { key: key2, raw, at: Date.now() };
|
|
2122
1937
|
}
|
|
2123
|
-
return
|
|
1938
|
+
return raw;
|
|
2124
1939
|
}).finally(() => {
|
|
2125
1940
|
if (this.earningsReads.get(key2) === pending)
|
|
2126
1941
|
this.earningsReads.delete(key2);
|
|
@@ -2130,11 +1945,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2130
1945
|
}
|
|
2131
1946
|
async getEarnings(state, chainId) {
|
|
2132
1947
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
2133
|
-
const
|
|
1948
|
+
const raw = await this.readEarnings(
|
|
2134
1949
|
this.earningsKey(state, chainId, smartWallet),
|
|
2135
1950
|
smartWallet
|
|
2136
1951
|
);
|
|
2137
|
-
return mapEarnings(
|
|
1952
|
+
return mapEarnings(raw, smartWallet);
|
|
2138
1953
|
}
|
|
2139
1954
|
async refreshEarnings(state, chainId) {
|
|
2140
1955
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -2161,17 +1976,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2161
1976
|
}
|
|
2162
1977
|
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
2163
1978
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
2164
|
-
const
|
|
2165
|
-
return mapApyHistory(
|
|
1979
|
+
const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
1980
|
+
return mapApyHistory(raw, chainId, tokenSymbol);
|
|
2166
1981
|
}
|
|
2167
1982
|
async getDailyEarnings(state, chainId, days, tokenSymbol) {
|
|
2168
1983
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
2169
1984
|
const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
|
|
2170
|
-
const
|
|
1985
|
+
const raw = await this.sdk.getDailyEarnings(
|
|
2171
1986
|
smartWallet,
|
|
2172
1987
|
start.toISOString().slice(0, 10)
|
|
2173
1988
|
);
|
|
2174
|
-
return mapDailyEarnings(
|
|
1989
|
+
return mapDailyEarnings(raw, chainId, tokenSymbol);
|
|
2175
1990
|
}
|
|
2176
1991
|
/**
|
|
2177
1992
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
@@ -2206,7 +2021,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2206
2021
|
const matched = [];
|
|
2207
2022
|
let backendExhausted = false;
|
|
2208
2023
|
for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
|
|
2209
|
-
const
|
|
2024
|
+
const raw = await this.sdk.getHistory(smartWallet, validChainId, {
|
|
2210
2025
|
limit: backendPageSize,
|
|
2211
2026
|
offset,
|
|
2212
2027
|
fromDate: options?.fromDate,
|
|
@@ -2217,13 +2032,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2217
2032
|
// asset's rows and handing back a page that filters to nothing.
|
|
2218
2033
|
assetType
|
|
2219
2034
|
});
|
|
2220
|
-
|
|
2035
|
+
raw.data.forEach((entry, idx) => {
|
|
2221
2036
|
if (entry.chainId === validChainId) {
|
|
2222
2037
|
matched.push({ entry, rawIdx: offset + idx });
|
|
2223
2038
|
}
|
|
2224
2039
|
});
|
|
2225
|
-
offset +=
|
|
2226
|
-
if (
|
|
2040
|
+
offset += raw.data.length;
|
|
2041
|
+
if (raw.data.length < backendPageSize) {
|
|
2227
2042
|
backendExhausted = true;
|
|
2228
2043
|
break;
|
|
2229
2044
|
}
|
|
@@ -2245,18 +2060,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2245
2060
|
}
|
|
2246
2061
|
async getUserProfile(state, chainId) {
|
|
2247
2062
|
await this.connectAuth(state, chainId);
|
|
2248
|
-
const
|
|
2063
|
+
const raw = await this.sdk.getUserDetails();
|
|
2249
2064
|
debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
|
|
2250
2065
|
asset: "USDC (default \u2014 no asset passed)",
|
|
2251
|
-
splitting:
|
|
2252
|
-
minSplits:
|
|
2253
|
-
strategy:
|
|
2254
|
-
chains:
|
|
2255
|
-
protocolCount:
|
|
2256
|
-
hasActiveSessionKey:
|
|
2257
|
-
smartWallet:
|
|
2066
|
+
splitting: raw.splitting,
|
|
2067
|
+
minSplits: raw.minSplits,
|
|
2068
|
+
strategy: raw.strategy,
|
|
2069
|
+
chains: raw.chains,
|
|
2070
|
+
protocolCount: raw.protocols?.length,
|
|
2071
|
+
hasActiveSessionKey: raw.hasActiveSessionKey,
|
|
2072
|
+
smartWallet: raw.smartWallet
|
|
2258
2073
|
});
|
|
2259
|
-
return mapUserProfile(
|
|
2074
|
+
return mapUserProfile(raw, this.connectedAddress);
|
|
2260
2075
|
}
|
|
2261
2076
|
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
2262
2077
|
await this.connectAuth(state, chainId);
|
|
@@ -2275,2242 +2090,111 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2275
2090
|
}
|
|
2276
2091
|
// --- IAgent: Discovery (no wallet required) ---
|
|
2277
2092
|
async getAgentApy(days, options) {
|
|
2278
|
-
const
|
|
2093
|
+
const raw = await this.sdk.getAPYPerStrategy(
|
|
2279
2094
|
false,
|
|
2280
2095
|
DayFilterMapping[days],
|
|
2281
2096
|
"aggressive",
|
|
2282
2097
|
options?.chainId,
|
|
2283
2098
|
options?.tokenSymbol
|
|
2284
2099
|
);
|
|
2285
|
-
return mapApyByStrategy(
|
|
2100
|
+
return mapApyByStrategy(raw);
|
|
2286
2101
|
}
|
|
2287
2102
|
};
|
|
2288
2103
|
|
|
2289
|
-
// src/
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
custom as custom2,
|
|
2294
|
-
encodeFunctionData,
|
|
2295
|
-
erc20Abi,
|
|
2296
|
-
getAddress as getAddress2,
|
|
2297
|
-
isAddress as isAddress2
|
|
2298
|
-
} from "viem";
|
|
2299
|
-
import { base as base3 } from "viem/chains";
|
|
2300
|
-
|
|
2301
|
-
// src/lib/chain-guard.ts
|
|
2302
|
-
var CHAIN_NAMES = {
|
|
2303
|
-
1: "Ethereum",
|
|
2304
|
-
8453: "Base",
|
|
2305
|
-
42161: "Arbitrum"
|
|
2306
|
-
};
|
|
2307
|
-
function chainName(chainId) {
|
|
2308
|
-
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2309
|
-
}
|
|
2310
|
-
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2311
|
-
const actual = await pub.getChainId();
|
|
2312
|
-
if (actual === expected) return;
|
|
2104
|
+
// src/lib/routing-api.ts
|
|
2105
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2106
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2107
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
2313
2108
|
try {
|
|
2314
|
-
await
|
|
2109
|
+
const res = await fetch(url, {
|
|
2110
|
+
method: "GET",
|
|
2111
|
+
headers: {
|
|
2112
|
+
"Content-Type": "application/json",
|
|
2113
|
+
"x-owney-api-key": `${apiKey}`
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
if (!res.ok) {
|
|
2117
|
+
if (res.status !== 404) {
|
|
2118
|
+
console.warn(
|
|
2119
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
return null;
|
|
2123
|
+
}
|
|
2124
|
+
const json = await res.json();
|
|
2125
|
+
const policy = json.success ? json.data ?? null : null;
|
|
2126
|
+
debugLog(
|
|
2127
|
+
"owney-sdk",
|
|
2128
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
2129
|
+
policy ?? void 0
|
|
2130
|
+
);
|
|
2131
|
+
return policy;
|
|
2315
2132
|
} catch (error) {
|
|
2133
|
+
console.warn(
|
|
2134
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
2135
|
+
error instanceof Error ? error.message : String(error)
|
|
2136
|
+
);
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2141
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
2142
|
+
const res = await fetch(url, {
|
|
2143
|
+
method: "GET",
|
|
2144
|
+
headers: {
|
|
2145
|
+
"Content-Type": "application/json",
|
|
2146
|
+
"x-owney-api-key": `${apiKey}`
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
2149
|
+
if (!res.ok) {
|
|
2150
|
+
const text = await res.text().catch(() => "");
|
|
2316
2151
|
throw new OwneyError(
|
|
2317
|
-
"
|
|
2318
|
-
`
|
|
2319
|
-
{
|
|
2320
|
-
expectedChainId: expected,
|
|
2321
|
-
actualChainId: actual,
|
|
2322
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2323
|
-
}
|
|
2152
|
+
"API_ROUTING_ERROR",
|
|
2153
|
+
`Routing API error ${res.status}: ${text}`,
|
|
2154
|
+
{ statusCode: res.status, responseBody: text }
|
|
2324
2155
|
);
|
|
2325
2156
|
}
|
|
2326
|
-
const
|
|
2327
|
-
if (
|
|
2157
|
+
const json = await res.json();
|
|
2158
|
+
if (!json.success) {
|
|
2328
2159
|
throw new OwneyError(
|
|
2329
|
-
"
|
|
2330
|
-
`
|
|
2331
|
-
{
|
|
2160
|
+
"API_ROUTING_FAILED",
|
|
2161
|
+
`Routing API request failed: ${json.message}`,
|
|
2162
|
+
{ message: json.message }
|
|
2332
2163
|
);
|
|
2333
2164
|
}
|
|
2165
|
+
return json.data;
|
|
2334
2166
|
}
|
|
2335
2167
|
|
|
2336
|
-
// src/lib/
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
// src/lib/sponsor-client.ts
|
|
2340
|
-
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2341
|
-
async function postPaymasterIntent(input) {
|
|
2342
|
-
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2343
|
-
let res;
|
|
2168
|
+
// src/lib/health-report.ts
|
|
2169
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2170
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
2344
2171
|
try {
|
|
2345
|
-
|
|
2172
|
+
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
2346
2173
|
method: "POST",
|
|
2347
2174
|
headers: {
|
|
2348
|
-
"
|
|
2349
|
-
"x-owney-api-key":
|
|
2350
|
-
Authorization: `Signature ${input.yieldseekerSignature}`
|
|
2175
|
+
"Content-Type": "application/json",
|
|
2176
|
+
"x-owney-api-key": apiKey
|
|
2351
2177
|
},
|
|
2352
|
-
body: JSON.stringify(
|
|
2178
|
+
body: JSON.stringify({
|
|
2179
|
+
agent_type: agentType,
|
|
2180
|
+
error_code: errorCode,
|
|
2181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2182
|
+
})
|
|
2353
2183
|
});
|
|
2354
|
-
} catch (
|
|
2355
|
-
|
|
2356
|
-
"
|
|
2357
|
-
|
|
2358
|
-
{ cause: String(networkError), safeToFallback: true }
|
|
2359
|
-
);
|
|
2360
|
-
}
|
|
2361
|
-
const text = await res.text();
|
|
2362
|
-
let parsed = null;
|
|
2363
|
-
try {
|
|
2364
|
-
parsed = JSON.parse(text);
|
|
2365
|
-
} catch {
|
|
2366
|
-
}
|
|
2367
|
-
if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
|
|
2368
|
-
throw new OwneyError(
|
|
2369
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2370
|
-
`Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2371
|
-
{
|
|
2372
|
-
statusCode: res.status,
|
|
2373
|
-
responseBody: text.slice(0, 500),
|
|
2374
|
-
safeToFallback: true
|
|
2375
|
-
}
|
|
2376
|
-
);
|
|
2377
|
-
}
|
|
2378
|
-
return parsed.data;
|
|
2379
|
-
}
|
|
2380
|
-
async function getSponsorRelayerAddress(input) {
|
|
2381
|
-
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2382
|
-
let res;
|
|
2383
|
-
try {
|
|
2384
|
-
res = await fetch(
|
|
2385
|
-
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2386
|
-
{
|
|
2387
|
-
headers: { "x-owney-api-key": input.apiKey }
|
|
2388
|
-
}
|
|
2389
|
-
);
|
|
2390
|
-
} catch (networkError) {
|
|
2391
|
-
throw new OwneyError(
|
|
2392
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2393
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2394
|
-
{ cause: String(networkError), safeToFallback: true }
|
|
2395
|
-
);
|
|
2396
|
-
}
|
|
2397
|
-
const text = await res.text();
|
|
2398
|
-
let parsed = null;
|
|
2399
|
-
try {
|
|
2400
|
-
parsed = JSON.parse(text);
|
|
2401
|
-
} catch {
|
|
2402
|
-
}
|
|
2403
|
-
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
2404
|
-
throw new OwneyError(
|
|
2405
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2406
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2407
|
-
{
|
|
2408
|
-
statusCode: res.status,
|
|
2409
|
-
responseBody: text.slice(0, 500),
|
|
2410
|
-
safeToFallback: true
|
|
2411
|
-
}
|
|
2184
|
+
} catch (err) {
|
|
2185
|
+
console.warn(
|
|
2186
|
+
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
2187
|
+
err instanceof Error ? err.message : err
|
|
2412
2188
|
);
|
|
2413
2189
|
}
|
|
2414
|
-
return parsed.data.relayer;
|
|
2415
2190
|
}
|
|
2416
|
-
async function
|
|
2417
|
-
let res;
|
|
2191
|
+
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
2418
2192
|
try {
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
"content-type": "application/json",
|
|
2425
|
-
"x-owney-api-key": input.apiKey
|
|
2426
|
-
},
|
|
2427
|
-
body: JSON.stringify(input.body)
|
|
2428
|
-
}
|
|
2429
|
-
);
|
|
2430
|
-
} catch {
|
|
2431
|
-
throw new OwneyError(
|
|
2432
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2433
|
-
"Deposit status is unknown. Retry the same amount to check it.",
|
|
2434
|
-
{ safeToFallback: false }
|
|
2435
|
-
);
|
|
2436
|
-
}
|
|
2437
|
-
const parsed = await res.json().catch(() => null);
|
|
2438
|
-
if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
|
|
2439
|
-
throw new OwneyError(
|
|
2440
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2441
|
-
"Deposit could not be confirmed. Retry the same amount to check its status.",
|
|
2442
|
-
{
|
|
2443
|
-
statusCode: res.status,
|
|
2444
|
-
safeToFallback: false,
|
|
2445
|
-
notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
|
|
2446
|
-
}
|
|
2447
|
-
);
|
|
2448
|
-
}
|
|
2449
|
-
return parsed.data;
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
// src/lib/permit2.ts
|
|
2453
|
-
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2454
|
-
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2455
|
-
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2456
|
-
function permit2ApprovalAmount(requiredAmount) {
|
|
2457
|
-
if (requiredAmount <= 0n) {
|
|
2458
|
-
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2459
|
-
}
|
|
2460
|
-
return MAX_UINT256;
|
|
2461
|
-
}
|
|
2462
|
-
var ERC20_ALLOWANCE_ABI = [
|
|
2463
|
-
{
|
|
2464
|
-
type: "function",
|
|
2465
|
-
name: "allowance",
|
|
2466
|
-
stateMutability: "view",
|
|
2467
|
-
inputs: [
|
|
2468
|
-
{ name: "owner", type: "address" },
|
|
2469
|
-
{ name: "spender", type: "address" }
|
|
2470
|
-
],
|
|
2471
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2472
|
-
},
|
|
2473
|
-
{
|
|
2474
|
-
type: "function",
|
|
2475
|
-
name: "approve",
|
|
2476
|
-
stateMutability: "nonpayable",
|
|
2477
|
-
inputs: [
|
|
2478
|
-
{ name: "spender", type: "address" },
|
|
2479
|
-
{ name: "amount", type: "uint256" }
|
|
2480
|
-
],
|
|
2481
|
-
outputs: [{ name: "", type: "bool" }]
|
|
2482
|
-
},
|
|
2483
|
-
{
|
|
2484
|
-
type: "function",
|
|
2485
|
-
name: "balanceOf",
|
|
2486
|
-
stateMutability: "view",
|
|
2487
|
-
inputs: [{ name: "account", type: "address" }],
|
|
2488
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2489
|
-
}
|
|
2490
|
-
];
|
|
2491
|
-
function randomPermit2Nonce() {
|
|
2492
|
-
const bytes = new Uint8Array(32);
|
|
2493
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2494
|
-
return BigInt(bytesToHex2(bytes));
|
|
2495
|
-
}
|
|
2496
|
-
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
2497
|
-
return publicClient.readContract({
|
|
2498
|
-
address: token,
|
|
2499
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2500
|
-
functionName: "allowance",
|
|
2501
|
-
args: [owner, PERMIT2_ADDRESS],
|
|
2502
|
-
...blockNumber === void 0 ? {} : { blockNumber }
|
|
2503
|
-
});
|
|
2504
|
-
}
|
|
2505
|
-
async function readErc20Balance(publicClient, token, owner) {
|
|
2506
|
-
return publicClient.readContract({
|
|
2507
|
-
address: token,
|
|
2508
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2509
|
-
functionName: "balanceOf",
|
|
2510
|
-
args: [owner]
|
|
2511
|
-
});
|
|
2512
|
-
}
|
|
2513
|
-
|
|
2514
|
-
// src/lib/sponsored-deposit.ts
|
|
2515
|
-
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2516
|
-
var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
|
|
2517
|
-
function provideDepositVerificationContext(callback, context) {
|
|
2518
|
-
callback[verificationSetter]?.(context);
|
|
2519
|
-
}
|
|
2520
|
-
function makeVerificationAwareDepositCallback(implementation) {
|
|
2521
|
-
let nextVerification;
|
|
2522
|
-
const callback = async (smartWallet, chainId, amount) => {
|
|
2523
|
-
const verification = nextVerification;
|
|
2524
|
-
nextVerification = void 0;
|
|
2525
|
-
return implementation(smartWallet, chainId, amount, verification);
|
|
2526
|
-
};
|
|
2527
|
-
Object.defineProperty(callback, verificationSetter, {
|
|
2528
|
-
value: (context) => {
|
|
2529
|
-
nextVerification = context;
|
|
2530
|
-
}
|
|
2531
|
-
});
|
|
2532
|
-
return callback;
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
|
-
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2536
|
-
import { SiweMessage, generateNonce } from "siwe";
|
|
2537
|
-
import {
|
|
2538
|
-
createPublicClient as createPublicClient2,
|
|
2539
|
-
createWalletClient,
|
|
2540
|
-
custom,
|
|
2541
|
-
getAddress
|
|
2542
|
-
} from "viem";
|
|
2543
|
-
import { base as base2 } from "viem/chains";
|
|
2544
|
-
|
|
2545
|
-
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
2546
|
-
var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
|
|
2547
|
-
var INVALIDATED_KEY_PREFIXES = [
|
|
2548
|
-
"owney.yieldseeker.session",
|
|
2549
|
-
"owney.yieldseeker.session.v3",
|
|
2550
|
-
"owney.yieldseeker.session.v4"
|
|
2551
|
-
];
|
|
2552
|
-
var storage2 = () => {
|
|
2553
|
-
if (typeof window === "undefined") return null;
|
|
2554
|
-
try {
|
|
2555
|
-
return window.localStorage;
|
|
2556
|
-
} catch {
|
|
2557
|
-
return null;
|
|
2558
|
-
}
|
|
2559
|
-
};
|
|
2560
|
-
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2561
|
-
var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
|
|
2562
|
-
(prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
|
|
2563
|
-
);
|
|
2564
|
-
var clearInvalidatedSessions = (store, address, chainId) => {
|
|
2565
|
-
for (const key2 of invalidatedKeys(address, chainId)) {
|
|
2566
|
-
memorySessions2.delete(key2);
|
|
2567
|
-
try {
|
|
2568
|
-
store?.removeItem(key2);
|
|
2569
|
-
} catch {
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
};
|
|
2573
|
-
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2574
|
-
var isValidSession = (session) => {
|
|
2575
|
-
if (!session?.token) return false;
|
|
2576
|
-
try {
|
|
2577
|
-
const parsed = JSON.parse(atob(session.token));
|
|
2578
|
-
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2579
|
-
} catch {
|
|
2580
|
-
return false;
|
|
2581
|
-
}
|
|
2582
|
-
};
|
|
2583
|
-
var readYieldseekerSession = (address, chainId) => {
|
|
2584
|
-
if (typeof window === "undefined") return null;
|
|
2585
|
-
const key2 = buildKey2(address, chainId);
|
|
2586
|
-
const store = storage2();
|
|
2587
|
-
clearInvalidatedSessions(store, address, chainId);
|
|
2588
|
-
let raw2 = null;
|
|
2589
|
-
try {
|
|
2590
|
-
raw2 = store?.getItem(key2) ?? null;
|
|
2591
|
-
} catch {
|
|
2592
|
-
raw2 = null;
|
|
2593
|
-
}
|
|
2594
|
-
if (raw2) {
|
|
2595
|
-
try {
|
|
2596
|
-
const parsed = JSON.parse(raw2);
|
|
2597
|
-
if (isValidSession(parsed)) return parsed.token;
|
|
2598
|
-
} catch {
|
|
2599
|
-
}
|
|
2600
|
-
memorySessions2.delete(key2);
|
|
2601
|
-
try {
|
|
2602
|
-
store?.removeItem(key2);
|
|
2603
|
-
} catch {
|
|
2604
|
-
}
|
|
2605
|
-
return null;
|
|
2606
|
-
}
|
|
2607
|
-
const cached = memorySessions2.get(key2);
|
|
2608
|
-
if (isValidSession(cached)) return cached.token;
|
|
2609
|
-
if (cached) memorySessions2.delete(key2);
|
|
2610
|
-
return null;
|
|
2611
|
-
};
|
|
2612
|
-
var writeYieldseekerSession = (address, chainId, token) => {
|
|
2613
|
-
if (typeof window === "undefined") return;
|
|
2614
|
-
const session = { token };
|
|
2615
|
-
if (!isValidSession(session)) return;
|
|
2616
|
-
const key2 = buildKey2(address, chainId);
|
|
2617
|
-
memorySessions2.set(key2, session);
|
|
2618
|
-
const store = storage2();
|
|
2619
|
-
try {
|
|
2620
|
-
store?.setItem(key2, JSON.stringify(session));
|
|
2621
|
-
} catch {
|
|
2622
|
-
}
|
|
2623
|
-
};
|
|
2624
|
-
var clearYieldseekerSession = (address, chainId) => {
|
|
2625
|
-
const key2 = buildKey2(address, chainId);
|
|
2626
|
-
memorySessions2.delete(key2);
|
|
2627
|
-
const store = storage2();
|
|
2628
|
-
clearInvalidatedSessions(store, address, chainId);
|
|
2629
|
-
try {
|
|
2630
|
-
store?.removeItem(key2);
|
|
2631
|
-
} catch {
|
|
2632
|
-
}
|
|
2633
|
-
};
|
|
2634
|
-
|
|
2635
|
-
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2636
|
-
function resolveSiweOrigin(override) {
|
|
2637
|
-
const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
|
|
2638
|
-
if (!origin || origin === "null") {
|
|
2639
|
-
throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
|
|
2640
|
-
}
|
|
2641
|
-
const url = new URL(origin);
|
|
2642
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2643
|
-
throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
|
|
2644
|
-
}
|
|
2645
|
-
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
2646
|
-
throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
|
|
2647
|
-
}
|
|
2648
|
-
return url;
|
|
2649
|
-
}
|
|
2650
|
-
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2651
|
-
const url = resolveSiweOrigin(dependencies.origin);
|
|
2652
|
-
return new SiweMessage({
|
|
2653
|
-
scheme: url.protocol.slice(0, -1),
|
|
2654
|
-
domain: url.host,
|
|
2655
|
-
address: getAddress(address),
|
|
2656
|
-
uri: url.origin,
|
|
2657
|
-
version: "1",
|
|
2658
|
-
chainId,
|
|
2659
|
-
nonce: (dependencies.nonce ?? generateNonce)(),
|
|
2660
|
-
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2661
|
-
}).prepareMessage();
|
|
2662
|
-
}
|
|
2663
|
-
function encodeYieldseekerAuthToken(token) {
|
|
2664
|
-
const bytes = new TextEncoder().encode(JSON.stringify(token));
|
|
2665
|
-
let binary = "";
|
|
2666
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2667
|
-
return btoa(binary);
|
|
2668
|
-
}
|
|
2669
|
-
var YieldseekerAuth = class {
|
|
2670
|
-
constructor(dependencies = {}) {
|
|
2671
|
-
this.dependencies = dependencies;
|
|
2672
|
-
}
|
|
2673
|
-
dependencies;
|
|
2674
|
-
tokens = /* @__PURE__ */ new Map();
|
|
2675
|
-
pending = /* @__PURE__ */ new Map();
|
|
2676
|
-
scopes = /* @__PURE__ */ new Map();
|
|
2677
|
-
key(state, chainId) {
|
|
2678
|
-
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
2679
|
-
}
|
|
2680
|
-
async getToken(state, chainId) {
|
|
2681
|
-
const key2 = this.key(state, chainId);
|
|
2682
|
-
const scope = { address: state.walletAddress, chainId };
|
|
2683
|
-
this.scopes.set(key2, scope);
|
|
2684
|
-
const cached = this.tokens.get(key2);
|
|
2685
|
-
if (cached) return cached;
|
|
2686
|
-
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2687
|
-
if (persisted && this.matchesOrigin(persisted)) {
|
|
2688
|
-
this.tokens.set(key2, persisted);
|
|
2689
|
-
return persisted;
|
|
2690
|
-
}
|
|
2691
|
-
if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
|
|
2692
|
-
const inFlight = this.pending.get(key2);
|
|
2693
|
-
if (inFlight) return inFlight;
|
|
2694
|
-
const request = this.sign(state, chainId).then((token) => {
|
|
2695
|
-
if (this.pending.get(key2) !== request) throw new Error("Wallet sign-in session changed. Please try again.");
|
|
2696
|
-
this.tokens.set(key2, token);
|
|
2697
|
-
writeYieldseekerSession(scope.address, scope.chainId, token);
|
|
2698
|
-
return token;
|
|
2699
|
-
});
|
|
2700
|
-
this.pending.set(key2, request);
|
|
2701
|
-
try {
|
|
2702
|
-
return await request;
|
|
2703
|
-
} finally {
|
|
2704
|
-
if (this.pending.get(key2) === request) this.pending.delete(key2);
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
async refreshToken(state, chainId, rejectedToken) {
|
|
2708
|
-
const key2 = this.key(state, chainId);
|
|
2709
|
-
if (this.tokens.get(key2) === rejectedToken) {
|
|
2710
|
-
this.tokens.delete(key2);
|
|
2711
|
-
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2712
|
-
}
|
|
2713
|
-
return this.getToken(state, chainId);
|
|
2714
|
-
}
|
|
2715
|
-
matchesOrigin(token) {
|
|
2716
|
-
try {
|
|
2717
|
-
const message = new SiweMessage(JSON.parse(atob(token)).message);
|
|
2718
|
-
const url = resolveSiweOrigin(this.dependencies.origin);
|
|
2719
|
-
return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
|
|
2720
|
-
} catch {
|
|
2721
|
-
return false;
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
clear(state, chainId) {
|
|
2725
|
-
if (!state || chainId === void 0) {
|
|
2726
|
-
for (const scope of this.scopes.values()) {
|
|
2727
|
-
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2728
|
-
}
|
|
2729
|
-
this.tokens.clear();
|
|
2730
|
-
this.pending.clear();
|
|
2731
|
-
this.scopes.clear();
|
|
2732
|
-
return;
|
|
2733
|
-
}
|
|
2734
|
-
const key2 = this.key(state, chainId);
|
|
2735
|
-
this.tokens.delete(key2);
|
|
2736
|
-
this.pending.delete(key2);
|
|
2737
|
-
this.scopes.delete(key2);
|
|
2738
|
-
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2739
|
-
}
|
|
2740
|
-
async sign(state, chainId) {
|
|
2741
|
-
const account = getAddress(state.walletAddress);
|
|
2742
|
-
const publicClient = createPublicClient2({
|
|
2743
|
-
chain: base2,
|
|
2744
|
-
transport: custom(state.provider)
|
|
2745
|
-
});
|
|
2746
|
-
const walletClient = createWalletClient({
|
|
2747
|
-
account,
|
|
2748
|
-
chain: base2,
|
|
2749
|
-
transport: custom(state.provider)
|
|
2750
|
-
});
|
|
2751
|
-
await ensureWalletOnChain(
|
|
2752
|
-
publicClient,
|
|
2753
|
-
walletClient,
|
|
2754
|
-
8453
|
|
2755
|
-
);
|
|
2756
|
-
const message = createYieldseekerSiweMessage(
|
|
2757
|
-
account,
|
|
2758
|
-
chainId,
|
|
2759
|
-
this.dependencies
|
|
2760
|
-
);
|
|
2761
|
-
const signature = await walletClient.signMessage({ account, message });
|
|
2762
|
-
return encodeYieldseekerAuthToken({ message, signature });
|
|
2763
|
-
}
|
|
2764
|
-
};
|
|
2765
|
-
|
|
2766
|
-
// src/agents/yieldseeker/yieldseeker.identity-cache.ts
|
|
2767
|
-
var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
|
|
2768
|
-
var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2769
|
-
var memoryIdentities = /* @__PURE__ */ new Map();
|
|
2770
|
-
var storage3 = () => {
|
|
2771
|
-
if (typeof window === "undefined") return null;
|
|
2772
|
-
try {
|
|
2773
|
-
return window.localStorage;
|
|
2774
|
-
} catch {
|
|
2775
|
-
return null;
|
|
2776
|
-
}
|
|
2777
|
-
};
|
|
2778
|
-
var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
|
|
2779
|
-
function valid(value, walletAddress, chainId, now) {
|
|
2780
|
-
return Boolean(
|
|
2781
|
-
value && typeof value.userId === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value.userId) && value.walletAddress.toLowerCase() === walletAddress.toLowerCase() && value.chainId === chainId && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now
|
|
2782
|
-
);
|
|
2783
|
-
}
|
|
2784
|
-
function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
|
|
2785
|
-
if (typeof window === "undefined") return null;
|
|
2786
|
-
const key2 = keyFor(walletAddress, chainId);
|
|
2787
|
-
const store = storage3();
|
|
2788
|
-
let parsed = null;
|
|
2789
|
-
try {
|
|
2790
|
-
const raw2 = store?.getItem(key2);
|
|
2791
|
-
parsed = raw2 ? JSON.parse(raw2) : null;
|
|
2792
|
-
} catch {
|
|
2793
|
-
parsed = null;
|
|
2794
|
-
}
|
|
2795
|
-
const candidate = parsed ?? memoryIdentities.get(key2);
|
|
2796
|
-
if (valid(candidate, walletAddress, chainId, now)) {
|
|
2797
|
-
memoryIdentities.set(key2, candidate);
|
|
2798
|
-
return { userId: candidate.userId };
|
|
2799
|
-
}
|
|
2800
|
-
memoryIdentities.delete(key2);
|
|
2801
|
-
try {
|
|
2802
|
-
store?.removeItem(key2);
|
|
2803
|
-
} catch {
|
|
2804
|
-
}
|
|
2805
|
-
return null;
|
|
2806
|
-
}
|
|
2807
|
-
function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
|
|
2808
|
-
if (typeof window === "undefined") return;
|
|
2809
|
-
const identity = {
|
|
2810
|
-
userId,
|
|
2811
|
-
walletAddress,
|
|
2812
|
-
chainId,
|
|
2813
|
-
expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
|
|
2814
|
-
};
|
|
2815
|
-
if (!valid(identity, walletAddress, chainId, now)) return;
|
|
2816
|
-
const key2 = keyFor(walletAddress, chainId);
|
|
2817
|
-
memoryIdentities.set(key2, identity);
|
|
2818
|
-
try {
|
|
2819
|
-
storage3()?.setItem(key2, JSON.stringify(identity));
|
|
2820
|
-
} catch {
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
function clearYieldseekerIdentity(walletAddress, chainId) {
|
|
2824
|
-
const key2 = keyFor(walletAddress, chainId);
|
|
2825
|
-
memoryIdentities.delete(key2);
|
|
2826
|
-
try {
|
|
2827
|
-
storage3()?.removeItem(key2);
|
|
2828
|
-
} catch {
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2833
|
-
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2834
|
-
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2835
|
-
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2836
|
-
}
|
|
2837
|
-
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2838
|
-
var YieldseekerApiError = class extends Error {
|
|
2839
|
-
constructor(status, providerCode, responseFields) {
|
|
2840
|
-
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2841
|
-
this.status = status;
|
|
2842
|
-
this.providerCode = providerCode;
|
|
2843
|
-
this.responseFields = responseFields;
|
|
2844
|
-
this.name = "YieldseekerApiError";
|
|
2845
|
-
}
|
|
2846
|
-
status;
|
|
2847
|
-
providerCode;
|
|
2848
|
-
responseFields;
|
|
2849
|
-
get isAuthenticationError() {
|
|
2850
|
-
return this.status === 401 || this.status === 403;
|
|
2851
|
-
}
|
|
2852
|
-
};
|
|
2853
|
-
function providerError(body, fallback) {
|
|
2854
|
-
if (!body || typeof body !== "object") return { code: fallback };
|
|
2855
|
-
const record = body;
|
|
2856
|
-
return {
|
|
2857
|
-
code: typeof record.message === "string" ? record.message : fallback,
|
|
2858
|
-
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2859
|
-
};
|
|
2860
|
-
}
|
|
2861
|
-
var YieldseekerApiClient = class {
|
|
2862
|
-
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch.bind(globalThis)) {
|
|
2863
|
-
this.owneyApiKey = owneyApiKey;
|
|
2864
|
-
this.baseUrl = baseUrl;
|
|
2865
|
-
this.fetchFn = fetchFn;
|
|
2866
|
-
}
|
|
2867
|
-
owneyApiKey;
|
|
2868
|
-
baseUrl;
|
|
2869
|
-
fetchFn;
|
|
2870
|
-
async request(path, options = {}) {
|
|
2871
|
-
const controller = new AbortController();
|
|
2872
|
-
const timer = setTimeout(
|
|
2873
|
-
() => controller.abort(),
|
|
2874
|
-
options.timeoutMs ?? 15e3
|
|
2875
|
-
);
|
|
2876
|
-
try {
|
|
2877
|
-
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2878
|
-
method: options.method ?? "GET",
|
|
2879
|
-
headers: {
|
|
2880
|
-
"Content-Type": "application/json",
|
|
2881
|
-
"x-owney-api-key": this.owneyApiKey,
|
|
2882
|
-
...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
|
|
2883
|
-
},
|
|
2884
|
-
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2885
|
-
signal: controller.signal
|
|
2886
|
-
});
|
|
2887
|
-
const payload = await response.json().catch(() => null);
|
|
2888
|
-
if (!response.ok) {
|
|
2889
|
-
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2890
|
-
const retryAfter = response.headers.get("Retry-After");
|
|
2891
|
-
throw new YieldseekerApiError(
|
|
2892
|
-
response.status,
|
|
2893
|
-
error.code,
|
|
2894
|
-
retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
|
|
2895
|
-
);
|
|
2896
|
-
}
|
|
2897
|
-
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2898
|
-
return payload.data;
|
|
2899
|
-
}
|
|
2900
|
-
return payload;
|
|
2901
|
-
} catch (error) {
|
|
2902
|
-
if (error instanceof YieldseekerApiError) throw error;
|
|
2903
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2904
|
-
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2905
|
-
}
|
|
2906
|
-
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2907
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2908
|
-
});
|
|
2909
|
-
} finally {
|
|
2910
|
-
clearTimeout(timer);
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
};
|
|
2914
|
-
|
|
2915
|
-
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2916
|
-
import { formatUnits, isAddress } from "viem";
|
|
2917
|
-
|
|
2918
|
-
// src/lib/helpers/snapshot-apy.ts
|
|
2919
|
-
var DAY_MS = 864e5;
|
|
2920
|
-
function snapshotTime(date) {
|
|
2921
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
|
|
2922
|
-
const time = Date.parse(date);
|
|
2923
|
-
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
|
|
2924
|
-
}
|
|
2925
|
-
function returnFactor(value) {
|
|
2926
|
-
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
2927
|
-
if (typeof value === "string" && value.trim() === "") return void 0;
|
|
2928
|
-
const factor = Number(value);
|
|
2929
|
-
return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
|
|
2930
|
-
}
|
|
2931
|
-
function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
|
|
2932
|
-
if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
|
|
2933
|
-
return void 0;
|
|
2934
|
-
}
|
|
2935
|
-
const points = snapshots.flatMap((snapshot) => {
|
|
2936
|
-
const time = snapshotTime(snapshot.date);
|
|
2937
|
-
return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
|
|
2938
|
-
}).sort((a, b) => a.time - b.time);
|
|
2939
|
-
const end = points.at(-1);
|
|
2940
|
-
if (!end) return void 0;
|
|
2941
|
-
const cutoff = end.time - lookbackDays * DAY_MS;
|
|
2942
|
-
const start = points.find((point) => point.time >= cutoff);
|
|
2943
|
-
const actualDays = (end.time - start.time) / DAY_MS;
|
|
2944
|
-
if (actualDays <= 0) return void 0;
|
|
2945
|
-
const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
|
|
2946
|
-
const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
|
|
2947
|
-
if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
|
|
2948
|
-
return void 0;
|
|
2949
|
-
}
|
|
2950
|
-
const periodReturn = endFactor / startFactor - 1;
|
|
2951
|
-
const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
|
|
2952
|
-
return Number.isFinite(apy) ? apy : void 0;
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
// src/agents/yieldseeker/yieldseeker.types.ts
|
|
2956
|
-
var YIELDSEEKER_ASSET_METADATA = {
|
|
2957
|
-
USDC: {
|
|
2958
|
-
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2959
|
-
decimals: 6
|
|
2960
|
-
},
|
|
2961
|
-
WETH: {
|
|
2962
|
-
address: "0x4200000000000000000000000000000000000006",
|
|
2963
|
-
decimals: 18
|
|
2964
|
-
}
|
|
2965
|
-
};
|
|
2966
|
-
|
|
2967
|
-
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2968
|
-
function invalid(endpoint, detail) {
|
|
2969
|
-
throw new OwneyError(
|
|
2970
|
-
"AGENT_INVALID_RESPONSE",
|
|
2971
|
-
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2972
|
-
{ endpoint, detail },
|
|
2973
|
-
"yieldseeker"
|
|
2974
|
-
);
|
|
2975
|
-
}
|
|
2976
|
-
function raw(value, endpoint) {
|
|
2977
|
-
if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
|
|
2978
|
-
return invalid(endpoint, "expected a base-10 integer string");
|
|
2979
|
-
}
|
|
2980
|
-
return BigInt(value);
|
|
2981
|
-
}
|
|
2982
|
-
function decimal(value, decimals, endpoint) {
|
|
2983
|
-
return formatUnits(raw(value, endpoint), decimals);
|
|
2984
|
-
}
|
|
2985
|
-
function usd(rawAmount, decimals, price) {
|
|
2986
|
-
return Number(formatUnits(rawAmount, decimals)) * price;
|
|
2987
|
-
}
|
|
2988
|
-
function percent(value) {
|
|
2989
|
-
const result = Number(value);
|
|
2990
|
-
return Number.isFinite(result) ? result * 100 : 0;
|
|
2991
|
-
}
|
|
2992
|
-
var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
|
|
2993
|
-
function publicApyAfterYieldseekerFee(value) {
|
|
2994
|
-
const grossPercent = percent(value);
|
|
2995
|
-
if (grossPercent <= 0) return grossPercent;
|
|
2996
|
-
const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
|
|
2997
|
-
return Math.round(netPercent * 1e12) / 1e12;
|
|
2998
|
-
}
|
|
2999
|
-
function riskAdjustedApyForDays(option, days) {
|
|
3000
|
-
if (days === "7D") return option.riskAdjustedApy7dAverage;
|
|
3001
|
-
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
3002
|
-
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
3003
|
-
}
|
|
3004
|
-
function assetAddressValue(record, address) {
|
|
3005
|
-
const entry = Object.entries(record).find(
|
|
3006
|
-
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
3007
|
-
);
|
|
3008
|
-
return entry?.[1] ?? "0";
|
|
3009
|
-
}
|
|
3010
|
-
function position(value, asset, baseAssetDecimals) {
|
|
3011
|
-
const option = value?.yieldOption;
|
|
3012
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
|
|
3013
|
-
return invalid("yield positions", "missing vault metadata");
|
|
3014
|
-
}
|
|
3015
|
-
return {
|
|
3016
|
-
chain: "BASE",
|
|
3017
|
-
protocol: option.provider,
|
|
3018
|
-
protocolId: option.address,
|
|
3019
|
-
pool: option.name,
|
|
3020
|
-
asset,
|
|
3021
|
-
// `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
|
|
3022
|
-
// differ from the underlying asset. Yieldseeker already converts it to
|
|
3023
|
-
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
3024
|
-
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
3025
|
-
// share quantity separate from the withdrawable underlying asset amount.
|
|
3026
|
-
amount: decimal(
|
|
3027
|
-
value.assetsBase,
|
|
3028
|
-
baseAssetDecimals,
|
|
3029
|
-
"yield positions"
|
|
3030
|
-
),
|
|
3031
|
-
amountRaw: String(value.assetsRaw),
|
|
3032
|
-
withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
|
|
3033
|
-
apy: percent(option.riskAdjustedApy),
|
|
3034
|
-
tvl: Number(option.totalDepositsUsd),
|
|
3035
|
-
liquidity: Number(option.withdrawableDepositsUsd)
|
|
3036
|
-
};
|
|
3037
|
-
}
|
|
3038
|
-
function mapYieldseekerBalances(contexts) {
|
|
3039
|
-
const tokens = [];
|
|
3040
|
-
const assetBalances = [];
|
|
3041
|
-
const positions = [];
|
|
3042
|
-
let totalUsd = 0;
|
|
3043
|
-
for (const context of contexts) {
|
|
3044
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
3045
|
-
assetBalances.push({
|
|
3046
|
-
chain: "BASE",
|
|
3047
|
-
chainId: 8453,
|
|
3048
|
-
asset: context.asset,
|
|
3049
|
-
amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
|
|
3050
|
-
});
|
|
3051
|
-
const idle = assetAddressValue(
|
|
3052
|
-
context.snapshot.tokenBalances,
|
|
3053
|
-
metadata.address
|
|
3054
|
-
);
|
|
3055
|
-
tokens.push({
|
|
3056
|
-
chain: "BASE",
|
|
3057
|
-
chainId: 8453,
|
|
3058
|
-
asset: context.asset,
|
|
3059
|
-
amount: decimal(idle, metadata.decimals, "snapshot")
|
|
3060
|
-
});
|
|
3061
|
-
positions.push(
|
|
3062
|
-
...context.positions.map(
|
|
3063
|
-
(entry) => position(
|
|
3064
|
-
entry,
|
|
3065
|
-
context.asset,
|
|
3066
|
-
context.snapshot.baseAssetDecimals
|
|
3067
|
-
)
|
|
3068
|
-
)
|
|
3069
|
-
);
|
|
3070
|
-
totalUsd += usd(
|
|
3071
|
-
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
3072
|
-
context.snapshot.baseAssetDecimals,
|
|
3073
|
-
context.snapshot.baseAssetPriceUsd
|
|
3074
|
-
);
|
|
3075
|
-
}
|
|
3076
|
-
return {
|
|
3077
|
-
...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
|
|
3078
|
-
totalBalance: String(totalUsd),
|
|
3079
|
-
totalBalanceAsset: "usdc",
|
|
3080
|
-
assetBalances,
|
|
3081
|
-
tokens,
|
|
3082
|
-
positions
|
|
3083
|
-
};
|
|
3084
|
-
}
|
|
3085
|
-
function mapYieldseekerEarnings(contexts) {
|
|
3086
|
-
const tokens = [];
|
|
3087
|
-
let lifetimeEarnings = 0;
|
|
3088
|
-
for (const context of contexts) {
|
|
3089
|
-
const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
|
|
3090
|
-
tokens.push({
|
|
3091
|
-
chain: "BASE",
|
|
3092
|
-
chainId: 8453,
|
|
3093
|
-
asset: context.asset,
|
|
3094
|
-
amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
|
|
3095
|
-
});
|
|
3096
|
-
lifetimeEarnings += usd(
|
|
3097
|
-
amount,
|
|
3098
|
-
context.snapshot.baseAssetDecimals,
|
|
3099
|
-
context.snapshot.baseAssetPriceUsd
|
|
3100
|
-
);
|
|
3101
|
-
}
|
|
3102
|
-
return {
|
|
3103
|
-
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3104
|
-
lifetimeEarnings,
|
|
3105
|
-
tokens
|
|
3106
|
-
};
|
|
3107
|
-
}
|
|
3108
|
-
function apyForDays(context, days, now) {
|
|
3109
|
-
if (days === "7D") return percent(context.snapshot.apy7d);
|
|
3110
|
-
if (days === "30D") return percent(context.snapshot.apy30d);
|
|
3111
|
-
const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
|
|
3112
|
-
const apyPercent = apy === void 0 ? void 0 : apy * 100;
|
|
3113
|
-
return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
|
|
3114
|
-
}
|
|
3115
|
-
function dailyApy(point) {
|
|
3116
|
-
const total = raw(point.totalValueBase, "historic position");
|
|
3117
|
-
const earned = raw(point.dailyYieldBase, "historic position");
|
|
3118
|
-
const principal = total - earned;
|
|
3119
|
-
if (principal <= 0n || earned === 0n) return 0;
|
|
3120
|
-
return Number(earned) / Number(principal) * 365 * 100;
|
|
3121
|
-
}
|
|
3122
|
-
function aggregateHistory(contexts, dayCount, now) {
|
|
3123
|
-
const today = new Date(now).toISOString().slice(0, 10);
|
|
3124
|
-
const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
|
|
3125
|
-
const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
|
|
3126
|
-
const unit = assets.size === 1 ? [...assets][0] : "USD";
|
|
3127
|
-
const byDate = /* @__PURE__ */ new Map();
|
|
3128
|
-
for (const context of contexts) {
|
|
3129
|
-
const points = context.historic?.dailyYieldSnapshots ?? [];
|
|
3130
|
-
for (const point of points) {
|
|
3131
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
|
|
3132
|
-
const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
|
|
3133
|
-
const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
|
|
3134
|
-
if (!Number.isFinite(amount) || amount < 0) {
|
|
3135
|
-
invalid("historic position", "expected a finite non-negative balance");
|
|
3136
|
-
}
|
|
3137
|
-
const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
|
|
3138
|
-
current.weighted += dailyApy(point) * amount;
|
|
3139
|
-
current.amount += amount;
|
|
3140
|
-
byDate.set(point.date, current);
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
3144
|
-
date,
|
|
3145
|
-
apy: value.amount > 0 ? value.weighted / value.amount : 0,
|
|
3146
|
-
historicalBalance: { amount: value.amount, unit }
|
|
3147
|
-
}));
|
|
3148
|
-
}
|
|
3149
|
-
function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
|
|
3150
|
-
let weighted = 0;
|
|
3151
|
-
let totalUsd = 0;
|
|
3152
|
-
const byAsset = {};
|
|
3153
|
-
for (const context of contexts) {
|
|
3154
|
-
const valueUsd = usd(
|
|
3155
|
-
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
3156
|
-
context.snapshot.baseAssetDecimals,
|
|
3157
|
-
context.snapshot.baseAssetPriceUsd
|
|
3158
|
-
);
|
|
3159
|
-
const apy = apyForDays(context, days, now);
|
|
3160
|
-
if (apy === void 0) continue;
|
|
3161
|
-
weighted += apy * valueUsd;
|
|
3162
|
-
totalUsd += valueUsd;
|
|
3163
|
-
byAsset[context.asset] = apy;
|
|
3164
|
-
}
|
|
3165
|
-
const dayCount = Number(days.slice(0, -1));
|
|
3166
|
-
return {
|
|
3167
|
-
walletAddress,
|
|
3168
|
-
...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
|
|
3169
|
-
apyByChainAndAsset: { 8453: byAsset },
|
|
3170
|
-
history: aggregateHistory(contexts, dayCount, now)
|
|
3171
|
-
};
|
|
3172
|
-
}
|
|
3173
|
-
function actionType(value) {
|
|
3174
|
-
const normalized = value.toLowerCase();
|
|
3175
|
-
if (normalized.includes("deposit")) return "Deposit";
|
|
3176
|
-
if (normalized.includes("withdraw")) return "Withdraw";
|
|
3177
|
-
if (normalized.includes("yield") || normalized.includes("earn"))
|
|
3178
|
-
return "Earned";
|
|
3179
|
-
return "Rebalance";
|
|
3180
|
-
}
|
|
3181
|
-
function transactionHashes(details) {
|
|
3182
|
-
if (!details) return [];
|
|
3183
|
-
const values = [
|
|
3184
|
-
details.transactionHash,
|
|
3185
|
-
details.txHash,
|
|
3186
|
-
...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
|
|
3187
|
-
...Array.isArray(details.txHashes) ? details.txHashes : []
|
|
3188
|
-
];
|
|
3189
|
-
return values.filter(
|
|
3190
|
-
(value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
|
|
3191
|
-
).filter((value, index, all) => all.indexOf(value) === index);
|
|
3192
|
-
}
|
|
3193
|
-
function actionEntry(action) {
|
|
3194
|
-
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3195
|
-
return {
|
|
3196
|
-
agent: "yieldseeker",
|
|
3197
|
-
action: actionType(action.actionType),
|
|
3198
|
-
date: action.createdDate,
|
|
3199
|
-
oldApy: null,
|
|
3200
|
-
newApy: null,
|
|
3201
|
-
transactions: [
|
|
3202
|
-
{
|
|
3203
|
-
txHashes: transactionHashes(action.details),
|
|
3204
|
-
chainId: 8453
|
|
3205
|
-
}
|
|
3206
|
-
],
|
|
3207
|
-
rebalanceLog: []
|
|
3208
|
-
};
|
|
3209
|
-
}
|
|
3210
|
-
function depositDestination(context, movement) {
|
|
3211
|
-
const to = movement.toAddress.toLowerCase();
|
|
3212
|
-
const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
|
|
3213
|
-
if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
|
|
3214
|
-
const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
|
|
3215
|
-
const minted = receipt && context.historic?.movements.some((candidate) => candidate.chainId === movement.chainId && candidate.transactionHash.toLowerCase() === movement.transactionHash.toLowerCase() && candidate.assetAddress.toLowerCase() === receipt.address.toLowerCase() && candidate.fromAddress.toLowerCase() === "0x0000000000000000000000000000000000000000" && candidate.toAddress.toLowerCase() === context.wallet.walletAddress.toLowerCase() && raw(candidate.assetAmount, "historic position") > 0n);
|
|
3216
|
-
if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
|
|
3217
|
-
return void 0;
|
|
3218
|
-
}
|
|
3219
|
-
function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
|
|
3220
|
-
const from = movement.fromAddress.toLowerCase();
|
|
3221
|
-
const to = movement.toAddress.toLowerCase();
|
|
3222
|
-
const owner = ownerAddress.toLowerCase();
|
|
3223
|
-
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3224
|
-
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3225
|
-
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3226
|
-
return void 0;
|
|
3227
|
-
}
|
|
3228
|
-
let action;
|
|
3229
|
-
if (to === agentWallet && !vaultAddresses.has(from)) {
|
|
3230
|
-
action = "Top up";
|
|
3231
|
-
} else if (from === agentWallet && to === owner) {
|
|
3232
|
-
action = "Withdraw";
|
|
3233
|
-
} else if (from === agentWallet && destination) {
|
|
3234
|
-
action = "Deposit";
|
|
3235
|
-
}
|
|
3236
|
-
if (!action) return void 0;
|
|
3237
|
-
return {
|
|
3238
|
-
agent: "yieldseeker",
|
|
3239
|
-
action,
|
|
3240
|
-
...action === "Deposit" && destination ? { positions: [{
|
|
3241
|
-
...destination,
|
|
3242
|
-
amount: decimal(movement.assetAmount, YIELDSEEKER_ASSET_METADATA[asset].decimals, "historic position")
|
|
3243
|
-
}] } : {},
|
|
3244
|
-
date: movement.blockDate,
|
|
3245
|
-
oldApy: null,
|
|
3246
|
-
newApy: null,
|
|
3247
|
-
transactions: [
|
|
3248
|
-
{
|
|
3249
|
-
txHashes: [movement.transactionHash],
|
|
3250
|
-
chainId: agent.chainId,
|
|
3251
|
-
tokenSymbol: asset,
|
|
3252
|
-
amount: decimal(
|
|
3253
|
-
movement.assetAmount,
|
|
3254
|
-
YIELDSEEKER_ASSET_METADATA[asset].decimals,
|
|
3255
|
-
"historic position"
|
|
3256
|
-
)
|
|
3257
|
-
}
|
|
3258
|
-
],
|
|
3259
|
-
rebalanceLog: []
|
|
3260
|
-
};
|
|
3261
|
-
}
|
|
3262
|
-
function mapYieldseekerHistory(contexts, options) {
|
|
3263
|
-
const entries = contexts.flatMap((context) => {
|
|
3264
|
-
const seenMovements = /* @__PURE__ */ new Set();
|
|
3265
|
-
const movements = (context.historic?.movements ?? []).filter((movement) => {
|
|
3266
|
-
const key2 = `${movement.chainId}:${movement.transactionHash.toLowerCase()}:${movement.logIndex}`;
|
|
3267
|
-
if (seenMovements.has(key2)) return false;
|
|
3268
|
-
seenMovements.add(key2);
|
|
3269
|
-
return true;
|
|
3270
|
-
});
|
|
3271
|
-
return [
|
|
3272
|
-
...movements.map(
|
|
3273
|
-
(movement) => movementEntry(
|
|
3274
|
-
movement,
|
|
3275
|
-
context.wallet,
|
|
3276
|
-
context.agent,
|
|
3277
|
-
context.asset,
|
|
3278
|
-
options.ownerAddress,
|
|
3279
|
-
options.vaultAddresses,
|
|
3280
|
-
depositDestination(context, movement)
|
|
3281
|
-
)
|
|
3282
|
-
),
|
|
3283
|
-
...(context.actions ?? []).map(actionEntry)
|
|
3284
|
-
].filter((entry) => entry !== void 0);
|
|
3285
|
-
});
|
|
3286
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
3287
|
-
const ungrouped = [];
|
|
3288
|
-
for (const entry of entries) {
|
|
3289
|
-
const tx = entry.transactions[0];
|
|
3290
|
-
const hash = tx?.txHashes[0];
|
|
3291
|
-
if (!hash) {
|
|
3292
|
-
ungrouped.push(entry);
|
|
3293
|
-
continue;
|
|
3294
|
-
}
|
|
3295
|
-
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
|
|
3296
|
-
const previous = grouped.get(key2);
|
|
3297
|
-
if (!previous) {
|
|
3298
|
-
grouped.set(key2, entry);
|
|
3299
|
-
continue;
|
|
3300
|
-
}
|
|
3301
|
-
if (entry.action === "Deposit" && entry.positions?.length) {
|
|
3302
|
-
if (!previous.positions?.length) {
|
|
3303
|
-
grouped.set(key2, entry);
|
|
3304
|
-
continue;
|
|
3305
|
-
}
|
|
3306
|
-
previous.positions.push(...entry.positions);
|
|
3307
|
-
previous.transactions.push(...entry.transactions);
|
|
3308
|
-
}
|
|
3309
|
-
}
|
|
3310
|
-
const filtered = [...grouped.values(), ...ungrouped].filter((entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)).sort((left, right) => right.date.localeCompare(left.date));
|
|
3311
|
-
return {
|
|
3312
|
-
data: filtered.slice(0, options.limit),
|
|
3313
|
-
// v1 returns the whole action/movement collection and defines no cursor.
|
|
3314
|
-
// Report a terminal page so callers never loop over the same prefix.
|
|
3315
|
-
hasMore: false
|
|
3316
|
-
};
|
|
3317
|
-
}
|
|
3318
|
-
function mapYieldseekerProfile(address, contexts) {
|
|
3319
|
-
const protocols = /* @__PURE__ */ new Set();
|
|
3320
|
-
for (const context of contexts) {
|
|
3321
|
-
for (const current of context.positions) {
|
|
3322
|
-
if (current.yieldOption?.provider) {
|
|
3323
|
-
protocols.add(String(current.yieldOption.provider));
|
|
3324
|
-
}
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
return {
|
|
3328
|
-
address,
|
|
3329
|
-
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3330
|
-
chains: contexts.length > 0 ? [8453] : [],
|
|
3331
|
-
hasActiveSessionKey: contexts.some(
|
|
3332
|
-
(context) => context.wallet.initializedDate != null
|
|
3333
|
-
),
|
|
3334
|
-
protocols: [...protocols]
|
|
3335
|
-
};
|
|
3336
|
-
}
|
|
3337
|
-
function mapYieldseekerAgentApy(options, days) {
|
|
3338
|
-
const perAsset = {};
|
|
3339
|
-
const all = [];
|
|
3340
|
-
for (const entry of options) {
|
|
3341
|
-
const apys = entry.yieldOptions.map(
|
|
3342
|
-
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
3343
|
-
).filter(Number.isFinite);
|
|
3344
|
-
if (apys.length === 0) continue;
|
|
3345
|
-
const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
|
|
3346
|
-
perAsset[entry.asset] = average;
|
|
3347
|
-
all.push(average);
|
|
3348
|
-
}
|
|
3349
|
-
return {
|
|
3350
|
-
averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
|
|
3351
|
-
detailedApys: { apyPerAsset: { 8453: perAsset } }
|
|
3352
|
-
};
|
|
3353
|
-
}
|
|
3354
|
-
|
|
3355
|
-
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3356
|
-
var OWNEY_AGENT_NAME = "owney";
|
|
3357
|
-
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3358
|
-
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3359
|
-
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3360
|
-
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3361
|
-
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
3362
|
-
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
3363
|
-
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
3364
|
-
var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
3365
|
-
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
3366
|
-
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
3367
|
-
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3368
|
-
function generateYieldseekerUsername() {
|
|
3369
|
-
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3370
|
-
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
3371
|
-
}
|
|
3372
|
-
function isUsernameConflict(error) {
|
|
3373
|
-
if (!(error instanceof YieldseekerApiError)) return false;
|
|
3374
|
-
const code = error.providerCode.toUpperCase();
|
|
3375
|
-
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
3376
|
-
}
|
|
3377
|
-
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
3378
|
-
{
|
|
3379
|
-
type: "function",
|
|
3380
|
-
name: "withdrawAssetToUser",
|
|
3381
|
-
stateMutability: "nonpayable",
|
|
3382
|
-
inputs: [
|
|
3383
|
-
{ name: "recipient", type: "address" },
|
|
3384
|
-
{ name: "asset", type: "address" },
|
|
3385
|
-
{ name: "amount", type: "uint256" }
|
|
3386
|
-
],
|
|
3387
|
-
outputs: []
|
|
3388
|
-
},
|
|
3389
|
-
{
|
|
3390
|
-
type: "function",
|
|
3391
|
-
name: "withdrawAllAssetToUser",
|
|
3392
|
-
stateMutability: "nonpayable",
|
|
3393
|
-
inputs: [
|
|
3394
|
-
{ name: "recipient", type: "address" },
|
|
3395
|
-
{ name: "asset", type: "address" }
|
|
3396
|
-
],
|
|
3397
|
-
outputs: []
|
|
3398
|
-
}
|
|
3399
|
-
];
|
|
3400
|
-
function query(params) {
|
|
3401
|
-
const search = new URLSearchParams();
|
|
3402
|
-
for (const [key2, value] of Object.entries(params)) {
|
|
3403
|
-
if (value !== void 0) search.set(key2, String(value));
|
|
3404
|
-
}
|
|
3405
|
-
const encoded = search.toString();
|
|
3406
|
-
return encoded ? `?${encoded}` : "";
|
|
3407
|
-
}
|
|
3408
|
-
var YieldseekerAgent = class {
|
|
3409
|
-
id = "yieldseeker";
|
|
3410
|
-
balanceComposition = "tokens-plus-positions";
|
|
3411
|
-
withdrawalRequiresWalletApproval = true;
|
|
3412
|
-
supportedChainIds = [8453];
|
|
3413
|
-
supportedAssets = [
|
|
3414
|
-
{
|
|
3415
|
-
chainId: 8453,
|
|
3416
|
-
chain: "BASE",
|
|
3417
|
-
assets: [
|
|
3418
|
-
{ symbol: "USDC", minDepositAmount: "10000000" },
|
|
3419
|
-
{ symbol: "WETH", minDepositAmount: "1" }
|
|
3420
|
-
]
|
|
3421
|
-
}
|
|
3422
|
-
];
|
|
3423
|
-
api;
|
|
3424
|
-
auth;
|
|
3425
|
-
rpcUrls;
|
|
3426
|
-
receiptClient;
|
|
3427
|
-
transactionExecutor;
|
|
3428
|
-
unwindReceiptWaiter;
|
|
3429
|
-
agentContexts = /* @__PURE__ */ new Map();
|
|
3430
|
-
users = /* @__PURE__ */ new Map();
|
|
3431
|
-
pendingAgents = /* @__PURE__ */ new Map();
|
|
3432
|
-
pendingWalletContexts = /* @__PURE__ */ new Map();
|
|
3433
|
-
readCache = /* @__PURE__ */ new Map();
|
|
3434
|
-
pendingReads = /* @__PURE__ */ new Map();
|
|
3435
|
-
readGeneration = 0;
|
|
3436
|
-
portfolioVersions = /* @__PURE__ */ new Map();
|
|
3437
|
-
snapshotFailures = /* @__PURE__ */ new Map();
|
|
3438
|
-
standardReadFailures = /* @__PURE__ */ new Map();
|
|
3439
|
-
reconcileUntil = /* @__PURE__ */ new Map();
|
|
3440
|
-
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
3441
|
-
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
3442
|
-
// settlement; keep slow reconciliation after the fast window expires.
|
|
3443
|
-
snapshotMovements = /* @__PURE__ */ new Map();
|
|
3444
|
-
movementEpochs = /* @__PURE__ */ new Map();
|
|
3445
|
-
yieldOptions = /* @__PURE__ */ new Map();
|
|
3446
|
-
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3447
|
-
constructor(owneyApiKey, options = {}) {
|
|
3448
|
-
this.api = new YieldseekerApiClient(
|
|
3449
|
-
owneyApiKey,
|
|
3450
|
-
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
3451
|
-
options.fetchFn
|
|
3452
|
-
);
|
|
3453
|
-
this.auth = new YieldseekerAuth(options.auth);
|
|
3454
|
-
this.rpcUrls = options.rpcUrls;
|
|
3455
|
-
this.transactionExecutor = options.transactionExecutor;
|
|
3456
|
-
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3457
|
-
}
|
|
3458
|
-
getReceiptClient() {
|
|
3459
|
-
this.receiptClient ??= createPaidRpcClient(base3, this.rpcUrls);
|
|
3460
|
-
return this.receiptClient;
|
|
3461
|
-
}
|
|
3462
|
-
async disconnect() {
|
|
3463
|
-
this.readGeneration += 1;
|
|
3464
|
-
this.auth.clear();
|
|
3465
|
-
for (const key2 of this.users.keys()) {
|
|
3466
|
-
const [walletAddress, chainId] = key2.split(":");
|
|
3467
|
-
clearYieldseekerIdentity(walletAddress, Number(chainId));
|
|
3468
|
-
}
|
|
3469
|
-
this.users.clear();
|
|
3470
|
-
this.agentContexts.clear();
|
|
3471
|
-
this.pendingAgents.clear();
|
|
3472
|
-
this.pendingWalletContexts.clear();
|
|
3473
|
-
this.readCache.clear();
|
|
3474
|
-
this.pendingReads.clear();
|
|
3475
|
-
this.portfolioVersions.clear();
|
|
3476
|
-
this.snapshotFailures.clear();
|
|
3477
|
-
this.standardReadFailures.clear();
|
|
3478
|
-
this.reconcileUntil.clear();
|
|
3479
|
-
this.activityRefreshUntil.clear();
|
|
3480
|
-
this.snapshotMovements.clear();
|
|
3481
|
-
this.movementEpochs.clear();
|
|
3482
|
-
}
|
|
3483
|
-
async activateAgent(state, chainId, asset) {
|
|
3484
|
-
this.assertChain(chainId);
|
|
3485
|
-
const targetAsset = asset ?? "USDC";
|
|
3486
|
-
this.assertAsset(targetAsset);
|
|
3487
|
-
await this.ensureAgent(state, chainId, targetAsset);
|
|
3488
|
-
}
|
|
3489
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3490
|
-
this.assertChain(chainId);
|
|
3491
|
-
this.assertAsset(asset);
|
|
3492
|
-
if (BigInt(amount) <= 0n) {
|
|
3493
|
-
throw new OwneyError(
|
|
3494
|
-
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
3495
|
-
"Yieldseeker deposits must be greater than zero.",
|
|
3496
|
-
{ amount, minDepositAmount: "1" },
|
|
3497
|
-
this.id
|
|
3498
|
-
);
|
|
3499
|
-
}
|
|
3500
|
-
const context = await this.ensureAgent(state, chainId, asset);
|
|
3501
|
-
const generation = this.readGeneration;
|
|
3502
|
-
const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
|
|
3503
|
-
let txHash;
|
|
3504
|
-
if (depositCallback) {
|
|
3505
|
-
provideDepositVerificationContext(depositCallback, {
|
|
3506
|
-
agentId: "yieldseeker",
|
|
3507
|
-
signature: await this.auth.getToken(state, chainId),
|
|
3508
|
-
userId: context.user.userId,
|
|
3509
|
-
yieldseekerAgentId: context.agent.agentId
|
|
3510
|
-
});
|
|
3511
|
-
txHash = await depositCallback(
|
|
3512
|
-
context.wallet.walletAddress,
|
|
3513
|
-
chainId,
|
|
3514
|
-
amount
|
|
3515
|
-
);
|
|
3516
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3517
|
-
} else {
|
|
3518
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3519
|
-
from: getAddress2(state.walletAddress),
|
|
3520
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3521
|
-
data: encodeFunctionData({
|
|
3522
|
-
abi: erc20Abi,
|
|
3523
|
-
functionName: "transfer",
|
|
3524
|
-
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3525
|
-
}),
|
|
3526
|
-
value: "0",
|
|
3527
|
-
chainId
|
|
3528
|
-
});
|
|
3529
|
-
}
|
|
3530
|
-
if (this.readGeneration === generation) {
|
|
3531
|
-
await this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3532
|
-
}
|
|
3533
|
-
return {
|
|
3534
|
-
txHash,
|
|
3535
|
-
smartWallet: context.wallet.walletAddress,
|
|
3536
|
-
amount
|
|
3537
|
-
};
|
|
3538
|
-
}
|
|
3539
|
-
async withdraw(state, chainId, asset, amount) {
|
|
3540
|
-
this.assertChain(chainId);
|
|
3541
|
-
this.assertAsset(asset);
|
|
3542
|
-
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
3543
|
-
throw new OwneyError(
|
|
3544
|
-
"WITHDRAW_FAILED",
|
|
3545
|
-
"Yieldseeker withdrawals must be greater than zero.",
|
|
3546
|
-
{ amount },
|
|
3547
|
-
this.id
|
|
3548
|
-
);
|
|
3549
|
-
}
|
|
3550
|
-
const context = await this.findAgent(state, chainId, asset);
|
|
3551
|
-
if (!context) {
|
|
3552
|
-
throw new OwneyError(
|
|
3553
|
-
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3554
|
-
`No Yieldseeker ${asset} agent exists for this wallet.`,
|
|
3555
|
-
{ asset, available: "0" },
|
|
3556
|
-
this.id
|
|
3557
|
-
);
|
|
3558
|
-
}
|
|
3559
|
-
const generation = this.readGeneration;
|
|
3560
|
-
let confirmedMovement = false;
|
|
3561
|
-
let settlement = null;
|
|
3562
|
-
try {
|
|
3563
|
-
const portfolio = await this.loadPortfolioContext(
|
|
3564
|
-
state,
|
|
3565
|
-
chainId,
|
|
3566
|
-
context
|
|
3567
|
-
);
|
|
3568
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3569
|
-
const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
|
|
3570
|
-
([address]) => address.toLowerCase() === metadata.address.toLowerCase()
|
|
3571
|
-
);
|
|
3572
|
-
const idle = BigInt(idleEntry?.[1] ?? "0");
|
|
3573
|
-
const deployed = portfolio.positions.reduce(
|
|
3574
|
-
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3575
|
-
0n
|
|
3576
|
-
);
|
|
3577
|
-
const totalAvailable = idle + deployed;
|
|
3578
|
-
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3579
|
-
const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
|
|
3580
|
-
if (requested > totalAvailable) {
|
|
3581
|
-
throw new OwneyError(
|
|
3582
|
-
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3583
|
-
`Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
|
|
3584
|
-
{
|
|
3585
|
-
asset,
|
|
3586
|
-
requested: requested.toString(),
|
|
3587
|
-
available: totalAvailable.toString()
|
|
3588
|
-
},
|
|
3589
|
-
this.id
|
|
3590
|
-
);
|
|
3591
|
-
}
|
|
3592
|
-
let remaining = requested > idle ? requested - idle : 0n;
|
|
3593
|
-
for (const position2 of portfolio.positions) {
|
|
3594
|
-
if (remaining === 0n) break;
|
|
3595
|
-
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
3596
|
-
if (available <= 0n) continue;
|
|
3597
|
-
const assetsRaw = available < remaining ? available : remaining;
|
|
3598
|
-
const response = await this.walletRequest(
|
|
3599
|
-
state,
|
|
3600
|
-
chainId,
|
|
3601
|
-
this.agentPath(context, "withdraw-from-position"),
|
|
3602
|
-
{
|
|
3603
|
-
method: "POST",
|
|
3604
|
-
body: {
|
|
3605
|
-
chainId,
|
|
3606
|
-
vaultAddress: position2.yieldOption.address,
|
|
3607
|
-
assetsRaw: assetsRaw.toString()
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
);
|
|
3611
|
-
if (!this.isTransactionHash(response?.transactionHash)) {
|
|
3612
|
-
throw this.invalidResponse("position withdrawal");
|
|
3613
|
-
}
|
|
3614
|
-
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3615
|
-
confirmedMovement = true;
|
|
3616
|
-
remaining -= assetsRaw;
|
|
3617
|
-
}
|
|
3618
|
-
if (remaining > 0n) {
|
|
3619
|
-
throw this.invalidResponse("yield positions", {
|
|
3620
|
-
reason: "Withdrawable positions could not cover the request.",
|
|
3621
|
-
remaining: remaining.toString()
|
|
3622
|
-
});
|
|
3623
|
-
}
|
|
3624
|
-
const account = getAddress2(state.walletAddress);
|
|
3625
|
-
const txHash = await this.submitTransaction(state, chainId, {
|
|
3626
|
-
from: account,
|
|
3627
|
-
to: getAddress2(context.wallet.walletAddress),
|
|
3628
|
-
data: amount === void 0 ? encodeFunctionData({
|
|
3629
|
-
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3630
|
-
functionName: "withdrawAllAssetToUser",
|
|
3631
|
-
args: [account, metadata.address]
|
|
3632
|
-
}) : encodeFunctionData({
|
|
3633
|
-
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3634
|
-
functionName: "withdrawAssetToUser",
|
|
3635
|
-
args: [account, metadata.address, requested]
|
|
3636
|
-
}),
|
|
3637
|
-
value: "0",
|
|
3638
|
-
chainId
|
|
3639
|
-
});
|
|
3640
|
-
confirmedMovement = true;
|
|
3641
|
-
settlement = plannedMovement;
|
|
3642
|
-
return {
|
|
3643
|
-
txHash,
|
|
3644
|
-
type: amount === void 0 ? "full" : "partial",
|
|
3645
|
-
amount: requested.toString()
|
|
3646
|
-
};
|
|
3647
|
-
} finally {
|
|
3648
|
-
if (confirmedMovement && this.readGeneration === generation) {
|
|
3649
|
-
await this.refreshSnapshotAfterMovement(
|
|
3650
|
-
state,
|
|
3651
|
-
chainId,
|
|
3652
|
-
context,
|
|
3653
|
-
"withdrawal",
|
|
3654
|
-
settlement
|
|
3655
|
-
);
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
async getBalances(state, chainId) {
|
|
3660
|
-
this.assertChain(chainId);
|
|
3661
|
-
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
|
|
3662
|
-
}
|
|
3663
|
-
async getEarnings(state, chainId) {
|
|
3664
|
-
this.assertChain(chainId);
|
|
3665
|
-
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
3666
|
-
}
|
|
3667
|
-
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
3668
|
-
this.assertChain(chainId);
|
|
3669
|
-
const asset = tokenSymbol?.toUpperCase();
|
|
3670
|
-
if (asset !== void 0) this.assertAsset(asset);
|
|
3671
|
-
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3672
|
-
...asset ? { asset } : {},
|
|
3673
|
-
historic: true
|
|
3674
|
-
});
|
|
3675
|
-
return mapYieldseekerApy(state.walletAddress, contexts, days);
|
|
3676
|
-
}
|
|
3677
|
-
async getHistory(state, chainId, options) {
|
|
3678
|
-
this.assertChain(chainId);
|
|
3679
|
-
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3680
|
-
if (asset !== void 0) this.assertAsset(asset);
|
|
3681
|
-
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3682
|
-
...asset ? { asset } : {},
|
|
3683
|
-
historic: true,
|
|
3684
|
-
actions: true
|
|
3685
|
-
});
|
|
3686
|
-
const catalog = await Promise.all(
|
|
3687
|
-
[...new Set(contexts.map((context) => context.asset))].map(
|
|
3688
|
-
(contextAsset) => this.loadYieldOptions(contextAsset)
|
|
3689
|
-
)
|
|
3690
|
-
);
|
|
3691
|
-
const vaultAddresses = new Set(
|
|
3692
|
-
catalog.flat().filter(
|
|
3693
|
-
(yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
|
|
3694
|
-
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
3695
|
-
);
|
|
3696
|
-
return mapYieldseekerHistory(contexts, {
|
|
3697
|
-
limit: options?.limit ?? 10,
|
|
3698
|
-
ownerAddress: state.walletAddress,
|
|
3699
|
-
vaultAddresses,
|
|
3700
|
-
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
3701
|
-
...options?.toDate ? { toDate: options.toDate } : {}
|
|
3702
|
-
});
|
|
3703
|
-
}
|
|
3704
|
-
async getUserProfile(state, chainId) {
|
|
3705
|
-
this.assertChain(chainId);
|
|
3706
|
-
return mapYieldseekerProfile(
|
|
3707
|
-
state.walletAddress,
|
|
3708
|
-
await this.loadPortfolio(state, chainId, {})
|
|
3709
|
-
);
|
|
3710
|
-
}
|
|
3711
|
-
async getAgentApy(days, options) {
|
|
3712
|
-
this.assertOptionalChain(options?.chainId);
|
|
3713
|
-
const requested = options?.tokenSymbol?.toUpperCase();
|
|
3714
|
-
if (requested !== void 0) this.assertAsset(requested);
|
|
3715
|
-
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
3716
|
-
const values = await Promise.all(
|
|
3717
|
-
assets.map(async (asset) => {
|
|
3718
|
-
return { asset, yieldOptions: await this.loadYieldOptions(asset) };
|
|
3719
|
-
})
|
|
3720
|
-
);
|
|
3721
|
-
return mapYieldseekerAgentApy(values, days);
|
|
3722
|
-
}
|
|
3723
|
-
async loadYieldOptions(asset) {
|
|
3724
|
-
const cached = this.yieldOptions.get(asset);
|
|
3725
|
-
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
3726
|
-
const pending = this.pendingYieldOptions.get(asset);
|
|
3727
|
-
if (pending) return pending;
|
|
3728
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3729
|
-
const request = this.api.request(
|
|
3730
|
-
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3731
|
-
).then((response) => {
|
|
3732
|
-
if (!Array.isArray(response?.yieldOptions)) {
|
|
3733
|
-
throw this.invalidResponse("yield options");
|
|
3734
|
-
}
|
|
3735
|
-
this.yieldOptions.set(asset, {
|
|
3736
|
-
expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
|
|
3737
|
-
value: response.yieldOptions
|
|
3738
|
-
});
|
|
3739
|
-
return response.yieldOptions;
|
|
3740
|
-
}).finally(() => this.pendingYieldOptions.delete(asset));
|
|
3741
|
-
this.pendingYieldOptions.set(asset, request);
|
|
3742
|
-
return request;
|
|
3743
|
-
}
|
|
3744
|
-
userKey(state, chainId) {
|
|
3745
|
-
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
3746
|
-
}
|
|
3747
|
-
contextKey(state, chainId, asset) {
|
|
3748
|
-
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3749
|
-
}
|
|
3750
|
-
assertSession(generation, chainId, asset) {
|
|
3751
|
-
if (this.readGeneration !== generation) {
|
|
3752
|
-
throw new OwneyError(
|
|
3753
|
-
"NOT_CONNECTED",
|
|
3754
|
-
"Wallet session changed during the Yieldseeker request.",
|
|
3755
|
-
{ rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
|
|
3756
|
-
this.id
|
|
3757
|
-
);
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
cachedRead(key2, ttlMs, read2) {
|
|
3761
|
-
const cached = this.readCache.get(key2);
|
|
3762
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
3763
|
-
return Promise.resolve(cached.value);
|
|
3764
|
-
}
|
|
3765
|
-
const pending = this.pendingReads.get(key2);
|
|
3766
|
-
if (pending) return pending;
|
|
3767
|
-
const generation = this.readGeneration;
|
|
3768
|
-
const request = Promise.resolve().then(read2).then((value) => {
|
|
3769
|
-
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
3770
|
-
this.readCache.set(key2, {
|
|
3771
|
-
expiresAt: Date.now() + ttlMs,
|
|
3772
|
-
value
|
|
3773
|
-
});
|
|
3774
|
-
}
|
|
3775
|
-
return value;
|
|
3776
|
-
}).finally(() => {
|
|
3777
|
-
if (this.pendingReads.get(key2) === request) {
|
|
3778
|
-
this.pendingReads.delete(key2);
|
|
3779
|
-
}
|
|
3780
|
-
});
|
|
3781
|
-
this.pendingReads.set(key2, request);
|
|
3782
|
-
return request;
|
|
3783
|
-
}
|
|
3784
|
-
agentListKey(state, chainId) {
|
|
3785
|
-
return `agents:${this.userKey(state, chainId)}`;
|
|
3786
|
-
}
|
|
3787
|
-
async listAgents(state, chainId, user) {
|
|
3788
|
-
const response = await this.cachedRead(
|
|
3789
|
-
this.agentListKey(state, chainId),
|
|
3790
|
-
YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
3791
|
-
async () => {
|
|
3792
|
-
const response2 = await this.walletRequest(
|
|
3793
|
-
state,
|
|
3794
|
-
chainId,
|
|
3795
|
-
`/users/${user.userId}/agents`
|
|
3796
|
-
);
|
|
3797
|
-
if (!Array.isArray(response2?.agents)) {
|
|
3798
|
-
throw this.invalidResponse("agent list");
|
|
3799
|
-
}
|
|
3800
|
-
return response2;
|
|
3801
|
-
}
|
|
3802
|
-
);
|
|
3803
|
-
return response.agents;
|
|
3804
|
-
}
|
|
3805
|
-
async contextForAgent(state, chainId, user, agent, asset) {
|
|
3806
|
-
const key2 = this.contextKey(state, chainId, asset);
|
|
3807
|
-
const cached = this.agentContexts.get(key2);
|
|
3808
|
-
if (cached?.agent.agentId === agent.agentId) return cached;
|
|
3809
|
-
const pending = this.pendingWalletContexts.get(key2);
|
|
3810
|
-
if (pending) return pending;
|
|
3811
|
-
const generation = this.readGeneration;
|
|
3812
|
-
const request = this.walletRequest(
|
|
3813
|
-
state,
|
|
3814
|
-
chainId,
|
|
3815
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3816
|
-
).then((walletResponse) => {
|
|
3817
|
-
this.assertSession(generation, chainId, asset);
|
|
3818
|
-
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3819
|
-
throw this.invalidResponse("agent wallet");
|
|
3820
|
-
}
|
|
3821
|
-
const context = {
|
|
3822
|
-
user,
|
|
3823
|
-
agent,
|
|
3824
|
-
wallet: walletResponse.agentWallet,
|
|
3825
|
-
asset
|
|
3826
|
-
};
|
|
3827
|
-
this.agentContexts.set(key2, context);
|
|
3828
|
-
return context;
|
|
3829
|
-
});
|
|
3830
|
-
this.pendingWalletContexts.set(key2, request);
|
|
3831
|
-
try {
|
|
3832
|
-
return await request;
|
|
3833
|
-
} finally {
|
|
3834
|
-
if (this.pendingWalletContexts.get(key2) === request) {
|
|
3835
|
-
this.pendingWalletContexts.delete(key2);
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
|
-
async resolveUser(state, chainId) {
|
|
3840
|
-
const key2 = this.userKey(state, chainId);
|
|
3841
|
-
const inMemory = this.users.get(key2);
|
|
3842
|
-
if (inMemory) return inMemory;
|
|
3843
|
-
const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
|
|
3844
|
-
if (persisted) {
|
|
3845
|
-
this.users.set(key2, persisted);
|
|
3846
|
-
return persisted;
|
|
3847
|
-
}
|
|
3848
|
-
const walletAddress = getAddress2(state.walletAddress);
|
|
3849
|
-
const generation = this.readGeneration;
|
|
3850
|
-
let user = null;
|
|
3851
|
-
try {
|
|
3852
|
-
const login = await this.providerRequest(
|
|
3853
|
-
state,
|
|
3854
|
-
chainId,
|
|
3855
|
-
"/users/login-with-wallet",
|
|
3856
|
-
{ method: "POST", body: { walletAddress } }
|
|
3857
|
-
);
|
|
3858
|
-
user = login?.user ?? null;
|
|
3859
|
-
if (!user) {
|
|
3860
|
-
throw this.invalidResponse("wallet login", {
|
|
3861
|
-
reason: "A successful login returned no user."
|
|
3862
|
-
});
|
|
3863
|
-
}
|
|
3864
|
-
} catch (error) {
|
|
3865
|
-
if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
|
|
3866
|
-
if (error instanceof OwneyError) throw error;
|
|
3867
|
-
throw this.mapApiError(error);
|
|
3868
|
-
}
|
|
3869
|
-
let created;
|
|
3870
|
-
for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
|
|
3871
|
-
try {
|
|
3872
|
-
created = await this.providerRequest(
|
|
3873
|
-
state,
|
|
3874
|
-
chainId,
|
|
3875
|
-
"/users",
|
|
3876
|
-
{
|
|
3877
|
-
method: "POST",
|
|
3878
|
-
body: {
|
|
3879
|
-
walletAddress,
|
|
3880
|
-
username: generateYieldseekerUsername()
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
);
|
|
3884
|
-
break;
|
|
3885
|
-
} catch (createError) {
|
|
3886
|
-
const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
|
|
3887
|
-
if (canRetry) continue;
|
|
3888
|
-
throw this.mapApiError(createError);
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
user = created?.user ?? null;
|
|
3892
|
-
}
|
|
3893
|
-
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3894
|
-
throw this.invalidResponse("wallet identity");
|
|
3895
|
-
}
|
|
3896
|
-
this.assertSession(generation, chainId);
|
|
3897
|
-
const resolved = { userId: user.userId };
|
|
3898
|
-
this.users.set(key2, resolved);
|
|
3899
|
-
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
3900
|
-
return resolved;
|
|
3901
|
-
}
|
|
3902
|
-
forgetUser(state, chainId) {
|
|
3903
|
-
this.users.delete(this.userKey(state, chainId));
|
|
3904
|
-
clearYieldseekerIdentity(state.walletAddress, chainId);
|
|
3905
|
-
}
|
|
3906
|
-
async ensureAgent(state, chainId, asset) {
|
|
3907
|
-
const key2 = this.contextKey(state, chainId, asset);
|
|
3908
|
-
const cached = this.agentContexts.get(key2);
|
|
3909
|
-
if (cached) return cached;
|
|
3910
|
-
const pending = this.pendingAgents.get(key2);
|
|
3911
|
-
if (pending) return pending;
|
|
3912
|
-
const generation = this.readGeneration;
|
|
3913
|
-
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3914
|
-
async (context) => {
|
|
3915
|
-
this.assertSession(generation, chainId, asset);
|
|
3916
|
-
if (!context) throw this.invalidResponse("agent creation");
|
|
3917
|
-
await this.deployAgent(state, chainId, context);
|
|
3918
|
-
this.assertSession(generation, chainId, asset);
|
|
3919
|
-
this.agentContexts.set(key2, context);
|
|
3920
|
-
return context;
|
|
3921
|
-
}
|
|
3922
|
-
);
|
|
3923
|
-
this.pendingAgents.set(key2, request);
|
|
3924
|
-
try {
|
|
3925
|
-
return await request;
|
|
3926
|
-
} finally {
|
|
3927
|
-
if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
async findAgent(state, chainId, asset) {
|
|
3931
|
-
const key2 = this.contextKey(state, chainId, asset);
|
|
3932
|
-
const cached = this.agentContexts.get(key2);
|
|
3933
|
-
if (cached) return cached;
|
|
3934
|
-
const generation = this.readGeneration;
|
|
3935
|
-
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3936
|
-
this.assertSession(generation, chainId, asset);
|
|
3937
|
-
if (context) this.agentContexts.set(key2, context);
|
|
3938
|
-
return context;
|
|
3939
|
-
}
|
|
3940
|
-
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3941
|
-
const generation = this.readGeneration;
|
|
3942
|
-
const user = await this.resolveUser(state, chainId);
|
|
3943
|
-
this.assertSession(generation, chainId, asset);
|
|
3944
|
-
const agents = await this.listAgents(state, chainId, user);
|
|
3945
|
-
this.assertSession(generation, chainId, asset);
|
|
3946
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3947
|
-
let agent = agents.find(
|
|
3948
|
-
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3949
|
-
);
|
|
3950
|
-
if (!agent && createIfMissing) {
|
|
3951
|
-
const created = await this.walletRequest(
|
|
3952
|
-
state,
|
|
3953
|
-
chainId,
|
|
3954
|
-
`/users/${user.userId}/agents`,
|
|
3955
|
-
{
|
|
3956
|
-
method: "POST",
|
|
3957
|
-
body: {
|
|
3958
|
-
name: OWNEY_AGENT_NAME,
|
|
3959
|
-
emoji: "\u{1F989}",
|
|
3960
|
-
chainId,
|
|
3961
|
-
assetAddress: metadata.address,
|
|
3962
|
-
type: "vault",
|
|
3963
|
-
rulePreset: null
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
);
|
|
3967
|
-
this.assertSession(generation, chainId, asset);
|
|
3968
|
-
agent = created?.agent;
|
|
3969
|
-
if (agent) {
|
|
3970
|
-
this.readCache.set(this.agentListKey(state, chainId), {
|
|
3971
|
-
expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
3972
|
-
value: {
|
|
3973
|
-
agents: [...agents, agent]
|
|
3974
|
-
}
|
|
3975
|
-
});
|
|
3976
|
-
}
|
|
3977
|
-
}
|
|
3978
|
-
if (!agent) return null;
|
|
3979
|
-
this.assertAgent(agent);
|
|
3980
|
-
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3981
|
-
}
|
|
3982
|
-
async loadPortfolio(state, chainId, options) {
|
|
3983
|
-
const generation = this.readGeneration;
|
|
3984
|
-
const user = await this.resolveUser(state, chainId);
|
|
3985
|
-
this.assertSession(generation, chainId);
|
|
3986
|
-
const agents = await this.listAgents(state, chainId, user);
|
|
3987
|
-
this.assertSession(generation, chainId);
|
|
3988
|
-
const contexts = [];
|
|
3989
|
-
for (const agent of agents) {
|
|
3990
|
-
const asset = this.assetForAgent(agent);
|
|
3991
|
-
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
|
|
3992
|
-
continue;
|
|
3993
|
-
}
|
|
3994
|
-
this.assertAgent(agent);
|
|
3995
|
-
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
3996
|
-
}
|
|
3997
|
-
const resolvedContexts = await Promise.all(contexts);
|
|
3998
|
-
this.assertSession(generation, chainId);
|
|
3999
|
-
return Promise.all(
|
|
4000
|
-
resolvedContexts.map(
|
|
4001
|
-
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
4002
|
-
)
|
|
4003
|
-
);
|
|
4004
|
-
}
|
|
4005
|
-
portfolioVersion(contextKey) {
|
|
4006
|
-
return this.portfolioVersions.get(contextKey) ?? 0;
|
|
4007
|
-
}
|
|
4008
|
-
portfolioCacheMs(contextKey) {
|
|
4009
|
-
return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4010
|
-
}
|
|
4011
|
-
snapshotMovement(state, chainId, context, delta) {
|
|
4012
|
-
const key2 = this.contextKey(state, chainId, context.asset);
|
|
4013
|
-
const previous = this.snapshotMovements.get(key2);
|
|
4014
|
-
if (this.snapshotMovements.has(key2)) {
|
|
4015
|
-
if (!previous || previous.delta > 0n !== delta > 0n) return null;
|
|
4016
|
-
return {
|
|
4017
|
-
...previous,
|
|
4018
|
-
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4019
|
-
delta: previous.delta + delta,
|
|
4020
|
-
expectedBalance: previous.expectedBalance + delta,
|
|
4021
|
-
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4025
|
-
if (!cached || cached.expiresAt <= Date.now()) return null;
|
|
4026
|
-
const snapshot = cached.value.agentSnapshot;
|
|
4027
|
-
if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
|
|
4028
|
-
if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
|
|
4029
|
-
return {
|
|
4030
|
-
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4031
|
-
delta,
|
|
4032
|
-
decimals: snapshot.baseAssetDecimals,
|
|
4033
|
-
expectedBalance: BigInt(snapshot.totalValueBase) + delta,
|
|
4034
|
-
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4035
|
-
};
|
|
4036
|
-
}
|
|
4037
|
-
advancePortfolioVersion(contextKey) {
|
|
4038
|
-
const version = this.portfolioVersion(contextKey) + 1;
|
|
4039
|
-
this.portfolioVersions.set(contextKey, version);
|
|
4040
|
-
this.snapshotFailures.delete(contextKey);
|
|
4041
|
-
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4042
|
-
const key2 = `${kind}:${contextKey}`;
|
|
4043
|
-
this.readCache.delete(key2);
|
|
4044
|
-
this.pendingReads.delete(key2);
|
|
4045
|
-
}
|
|
4046
|
-
return version;
|
|
4047
|
-
}
|
|
4048
|
-
requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
|
|
4049
|
-
return this.walletRequest(
|
|
4050
|
-
state,
|
|
4051
|
-
chainId,
|
|
4052
|
-
`${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
|
|
4053
|
-
).then((response) => {
|
|
4054
|
-
if (!response?.agentSnapshot) {
|
|
4055
|
-
throw this.invalidResponse("agent snapshot");
|
|
4056
|
-
}
|
|
4057
|
-
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4058
|
-
this.snapshotFailures.delete(contextKey);
|
|
4059
|
-
const movement = this.snapshotMovements.get(contextKey);
|
|
4060
|
-
const snapshot = response.agentSnapshot;
|
|
4061
|
-
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4062
|
-
const balance = BigInt(snapshot.totalValueBase);
|
|
4063
|
-
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4064
|
-
const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
|
|
4065
|
-
if (reconciled) {
|
|
4066
|
-
this.snapshotMovements.delete(contextKey);
|
|
4067
|
-
this.reconcileUntil.delete(contextKey);
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
return response;
|
|
4072
|
-
}).catch((error) => {
|
|
4073
|
-
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4074
|
-
this.snapshotFailures.set(contextKey, {
|
|
4075
|
-
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
|
|
4076
|
-
error
|
|
4077
|
-
});
|
|
4078
|
-
}
|
|
4079
|
-
throw error;
|
|
4080
|
-
});
|
|
4081
|
-
}
|
|
4082
|
-
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
4083
|
-
const key2 = `snapshot:${contextKey}`;
|
|
4084
|
-
const cached = this.readCache.get(key2);
|
|
4085
|
-
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
4086
|
-
const failure = this.snapshotFailures.get(contextKey);
|
|
4087
|
-
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4088
|
-
this.advancePortfolioVersion(contextKey);
|
|
4089
|
-
}
|
|
4090
|
-
const version = this.portfolioVersion(contextKey);
|
|
4091
|
-
const generation = this.readGeneration;
|
|
4092
|
-
return {
|
|
4093
|
-
version,
|
|
4094
|
-
read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
|
|
4095
|
-
// Force only during the bounded post-transaction window. Keep unknown
|
|
4096
|
-
// or unreconciled movements for verification, but do not let them turn
|
|
4097
|
-
// routine polling into forced refreshes indefinitely.
|
|
4098
|
-
this.requestPortfolioSnapshot(
|
|
4099
|
-
state,
|
|
4100
|
-
chainId,
|
|
4101
|
-
context,
|
|
4102
|
-
contextKey,
|
|
4103
|
-
version,
|
|
4104
|
-
generation,
|
|
4105
|
-
this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
|
|
4106
|
-
)
|
|
4107
|
-
))
|
|
4108
|
-
};
|
|
4109
|
-
}
|
|
4110
|
-
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
4111
|
-
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4112
|
-
const generation = this.readGeneration;
|
|
4113
|
-
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4114
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4115
|
-
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4116
|
-
try {
|
|
4117
|
-
const snapshot = await read2;
|
|
4118
|
-
assertSession();
|
|
4119
|
-
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4120
|
-
const [positions, historic, actions] = await Promise.all([
|
|
4121
|
-
this.cachedRead(
|
|
4122
|
-
`positions:${contextKey}`,
|
|
4123
|
-
this.portfolioCacheMs(contextKey),
|
|
4124
|
-
async () => {
|
|
4125
|
-
const response = await this.walletRequest(
|
|
4126
|
-
state,
|
|
4127
|
-
chainId,
|
|
4128
|
-
this.agentPath(context, "yield-positions")
|
|
4129
|
-
);
|
|
4130
|
-
if (!Array.isArray(response?.yieldPositions)) {
|
|
4131
|
-
throw this.invalidResponse("yield positions");
|
|
4132
|
-
}
|
|
4133
|
-
return response;
|
|
4134
|
-
}
|
|
4135
|
-
),
|
|
4136
|
-
options.historic ? this.cachedRead(
|
|
4137
|
-
`historic:${contextKey}`,
|
|
4138
|
-
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4139
|
-
() => this.walletRequest(
|
|
4140
|
-
state,
|
|
4141
|
-
chainId,
|
|
4142
|
-
this.agentPath(context, "wallet/historic-position")
|
|
4143
|
-
)
|
|
4144
|
-
) : Promise.resolve(void 0),
|
|
4145
|
-
options.actions ? this.cachedRead(
|
|
4146
|
-
`actions:${contextKey}`,
|
|
4147
|
-
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4148
|
-
() => this.walletRequest(
|
|
4149
|
-
state,
|
|
4150
|
-
chainId,
|
|
4151
|
-
this.agentPath(context, "actions")
|
|
4152
|
-
)
|
|
4153
|
-
) : Promise.resolve(void 0)
|
|
4154
|
-
]);
|
|
4155
|
-
assertSession();
|
|
4156
|
-
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4157
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
4158
|
-
throw this.invalidResponse("yield positions");
|
|
4159
|
-
}
|
|
4160
|
-
return {
|
|
4161
|
-
...context,
|
|
4162
|
-
snapshot: snapshot.agentSnapshot,
|
|
4163
|
-
positions: positions.yieldPositions,
|
|
4164
|
-
...historic?.position ? { historic: historic.position } : {},
|
|
4165
|
-
...actions?.actions ? { actions: actions.actions } : {}
|
|
4166
|
-
};
|
|
4167
|
-
} catch (error) {
|
|
4168
|
-
assertSession();
|
|
4169
|
-
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4170
|
-
throw error;
|
|
4171
|
-
}
|
|
4172
|
-
}
|
|
4173
|
-
throw new OwneyError(
|
|
4174
|
-
"AGENT_API_ERROR",
|
|
4175
|
-
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4176
|
-
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4177
|
-
this.id
|
|
4178
|
-
);
|
|
4179
|
-
}
|
|
4180
|
-
async deployAgent(state, chainId, context) {
|
|
4181
|
-
if (context.wallet.initializedDate != null) return;
|
|
4182
|
-
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
4183
|
-
const deployed = await this.walletRequest(
|
|
4184
|
-
state,
|
|
4185
|
-
chainId,
|
|
4186
|
-
this.agentPath(context, "deploy"),
|
|
4187
|
-
{ method: "POST", body: {} }
|
|
4188
|
-
);
|
|
4189
|
-
if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
4190
|
-
throw this.invalidResponse("agent deployment", {
|
|
4191
|
-
reason: "Deploy did not return the expected Agent Wallet."
|
|
4192
|
-
});
|
|
4193
|
-
}
|
|
4194
|
-
context.wallet = deployed.agentWallet;
|
|
4195
|
-
}
|
|
4196
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4197
|
-
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4198
|
-
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4199
|
-
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4200
|
-
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4201
|
-
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4202
|
-
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4203
|
-
const version = this.advancePortfolioVersion(contextKey);
|
|
4204
|
-
const generation = this.readGeneration;
|
|
4205
|
-
try {
|
|
4206
|
-
await this.cachedRead(
|
|
4207
|
-
`snapshot:${contextKey}`,
|
|
4208
|
-
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4209
|
-
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
4210
|
-
);
|
|
4211
|
-
} catch (error) {
|
|
4212
|
-
console.warn(
|
|
4213
|
-
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
4214
|
-
error
|
|
4215
|
-
);
|
|
4216
|
-
}
|
|
4217
|
-
}
|
|
4218
|
-
agentPath(context, suffix) {
|
|
4219
|
-
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
4220
|
-
}
|
|
4221
|
-
async walletRequest(state, chainId, path, options = {}) {
|
|
4222
|
-
const read2 = (options.method ?? "GET") === "GET";
|
|
4223
|
-
const userKey = this.userKey(state, chainId);
|
|
4224
|
-
if (read2) {
|
|
4225
|
-
const failure = this.standardReadFailures.get(userKey);
|
|
4226
|
-
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4227
|
-
if (failure) this.standardReadFailures.delete(userKey);
|
|
4228
|
-
}
|
|
4229
|
-
const generation = this.readGeneration;
|
|
4230
|
-
try {
|
|
4231
|
-
return await this.providerRequest(state, chainId, path, options);
|
|
4232
|
-
} catch (error) {
|
|
4233
|
-
const mapped = this.mapApiError(error);
|
|
4234
|
-
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4235
|
-
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4236
|
-
this.standardReadFailures.set(userKey, {
|
|
4237
|
-
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4238
|
-
error: mapped
|
|
4239
|
-
});
|
|
4240
|
-
}
|
|
4241
|
-
throw mapped;
|
|
4242
|
-
}
|
|
4243
|
-
}
|
|
4244
|
-
async providerRequest(state, chainId, path, options = {}) {
|
|
4245
|
-
this.assertChain(chainId);
|
|
4246
|
-
const request = (signature2) => this.api.request(path, {
|
|
4247
|
-
...options,
|
|
4248
|
-
signature: signature2
|
|
4249
|
-
});
|
|
4250
|
-
let signature = await this.auth.getToken(state, chainId);
|
|
4251
|
-
try {
|
|
4252
|
-
return await request(signature);
|
|
4253
|
-
} catch (error) {
|
|
4254
|
-
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
4255
|
-
if (error.providerCode === "NO_USER") throw error;
|
|
4256
|
-
if (!error.isAuthenticationError) throw error;
|
|
4257
|
-
signature = await this.auth.refreshToken(state, chainId, signature);
|
|
4258
|
-
try {
|
|
4259
|
-
return await request(signature);
|
|
4260
|
-
} catch (retryError) {
|
|
4261
|
-
if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
|
|
4262
|
-
this.forgetUser(state, chainId);
|
|
4263
|
-
}
|
|
4264
|
-
throw retryError;
|
|
4265
|
-
}
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
mapApiError(error) {
|
|
4269
|
-
if (!(error instanceof YieldseekerApiError)) {
|
|
4270
|
-
return new OwneyError(
|
|
4271
|
-
"AGENT_API_ERROR",
|
|
4272
|
-
"Yieldseeker request failed.",
|
|
4273
|
-
{ cause: error instanceof Error ? error.message : String(error) },
|
|
4274
|
-
this.id
|
|
4275
|
-
);
|
|
4276
|
-
}
|
|
4277
|
-
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
4278
|
-
return new OwneyError(
|
|
4279
|
-
code,
|
|
4280
|
-
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
4281
|
-
{
|
|
4282
|
-
rpcSource: "agent-api",
|
|
4283
|
-
statusCode: error.status,
|
|
4284
|
-
providerCode: error.providerCode,
|
|
4285
|
-
...error.responseFields ? { fields: error.responseFields } : {}
|
|
4286
|
-
},
|
|
4287
|
-
this.id
|
|
4288
|
-
);
|
|
4289
|
-
}
|
|
4290
|
-
async submitTransaction(state, chainId, transaction) {
|
|
4291
|
-
if (this.transactionExecutor) {
|
|
4292
|
-
return this.transactionExecutor(state, chainId, transaction);
|
|
4293
|
-
}
|
|
4294
|
-
this.assertTransaction(transaction, state, chainId);
|
|
4295
|
-
const account = getAddress2(state.walletAddress);
|
|
4296
|
-
const walletClient = createWalletClient2({
|
|
4297
|
-
account,
|
|
4298
|
-
chain: base3,
|
|
4299
|
-
transport: custom2(state.provider)
|
|
4300
|
-
});
|
|
4301
|
-
const walletChainClient = createPublicClient3({
|
|
4302
|
-
chain: base3,
|
|
4303
|
-
transport: custom2(state.provider)
|
|
4304
|
-
});
|
|
4305
|
-
await ensureWalletOnChain(
|
|
4306
|
-
walletChainClient,
|
|
4307
|
-
walletClient,
|
|
4308
|
-
8453
|
|
4309
|
-
);
|
|
4310
|
-
const hash = await walletClient.sendTransaction({
|
|
4311
|
-
account,
|
|
4312
|
-
chain: base3,
|
|
4313
|
-
to: getAddress2(transaction.to),
|
|
4314
|
-
data: transaction.data,
|
|
4315
|
-
value: BigInt(transaction.value)
|
|
4316
|
-
});
|
|
4317
|
-
const receipt = await withPaidRpcDiagnostics(
|
|
4318
|
-
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4319
|
-
hash,
|
|
4320
|
-
confirmations: 1
|
|
4321
|
-
}),
|
|
4322
|
-
8453,
|
|
4323
|
-
"eth_getTransactionReceipt",
|
|
4324
|
-
this.id
|
|
4325
|
-
);
|
|
4326
|
-
if (receipt.status !== "success") {
|
|
4327
|
-
throw new OwneyError(
|
|
4328
|
-
"AGENT_TRANSACTION_REVERTED",
|
|
4329
|
-
`Yieldseeker transaction reverted (${hash}).`,
|
|
4330
|
-
{ transactionHash: hash },
|
|
4331
|
-
this.id
|
|
4332
|
-
);
|
|
4333
|
-
}
|
|
4334
|
-
return hash;
|
|
4335
|
-
}
|
|
4336
|
-
async waitForReceipt(state, chainId, transactionHash) {
|
|
4337
|
-
if (this.unwindReceiptWaiter) {
|
|
4338
|
-
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
4339
|
-
return;
|
|
4340
|
-
}
|
|
4341
|
-
const receipt = await withPaidRpcDiagnostics(
|
|
4342
|
-
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4343
|
-
hash: transactionHash,
|
|
4344
|
-
confirmations: 1
|
|
4345
|
-
}),
|
|
4346
|
-
8453,
|
|
4347
|
-
"eth_getTransactionReceipt",
|
|
4348
|
-
this.id
|
|
4349
|
-
);
|
|
4350
|
-
if (receipt.status !== "success") {
|
|
4351
|
-
throw new OwneyError(
|
|
4352
|
-
"AGENT_TRANSACTION_REVERTED",
|
|
4353
|
-
`Yieldseeker transaction reverted (${transactionHash}).`,
|
|
4354
|
-
{ transactionHash },
|
|
4355
|
-
this.id
|
|
4356
|
-
);
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
assertTransaction(transaction, state, chainId) {
|
|
4360
|
-
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)) {
|
|
4361
|
-
throw this.invalidResponse("transaction");
|
|
4362
|
-
}
|
|
4363
|
-
}
|
|
4364
|
-
assertAgent(agent) {
|
|
4365
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
|
|
4366
|
-
throw this.invalidResponse("agent");
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
isOwneyAgent(agent) {
|
|
4370
|
-
return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
|
|
4371
|
-
}
|
|
4372
|
-
assetForAgent(agent) {
|
|
4373
|
-
for (const asset of ["USDC", "WETH"]) {
|
|
4374
|
-
if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
|
|
4375
|
-
return asset;
|
|
4376
|
-
}
|
|
4377
|
-
}
|
|
4378
|
-
return null;
|
|
4379
|
-
}
|
|
4380
|
-
isTransactionHash(value) {
|
|
4381
|
-
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
4382
|
-
}
|
|
4383
|
-
assertChain(chainId) {
|
|
4384
|
-
if (chainId !== 8453) {
|
|
4385
|
-
throw new OwneyError(
|
|
4386
|
-
"CHAIN_UNSUPPORTED",
|
|
4387
|
-
`Yieldseeker does not support chain ${chainId}.`,
|
|
4388
|
-
{ chainId, supportedChainIds: [8453] },
|
|
4389
|
-
this.id
|
|
4390
|
-
);
|
|
4391
|
-
}
|
|
4392
|
-
}
|
|
4393
|
-
assertOptionalChain(chainId) {
|
|
4394
|
-
if (chainId !== void 0) this.assertChain(chainId);
|
|
4395
|
-
}
|
|
4396
|
-
assertAsset(asset) {
|
|
4397
|
-
if (asset !== "USDC" && asset !== "WETH") {
|
|
4398
|
-
throw new OwneyError(
|
|
4399
|
-
"ASSET_UNSUPPORTED",
|
|
4400
|
-
`Yieldseeker does not support asset ${asset} in the Owney rollout.`,
|
|
4401
|
-
{
|
|
4402
|
-
asset,
|
|
4403
|
-
supportedAssets: ["USDC", "WETH"],
|
|
4404
|
-
providerAlsoAdvertises: ["cbBTC"]
|
|
4405
|
-
},
|
|
4406
|
-
this.id
|
|
4407
|
-
);
|
|
4408
|
-
}
|
|
4409
|
-
}
|
|
4410
|
-
invalidResponse(operation, details = {}) {
|
|
4411
|
-
return new OwneyError(
|
|
4412
|
-
"AGENT_INVALID_RESPONSE",
|
|
4413
|
-
`Yieldseeker returned an invalid ${operation} response.`,
|
|
4414
|
-
details,
|
|
4415
|
-
this.id
|
|
4416
|
-
);
|
|
4417
|
-
}
|
|
4418
|
-
};
|
|
4419
|
-
|
|
4420
|
-
// src/lib/routing-api.ts
|
|
4421
|
-
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
4422
|
-
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4423
|
-
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
4424
|
-
try {
|
|
4425
|
-
const res = await fetch(url, {
|
|
4426
|
-
method: "GET",
|
|
4427
|
-
headers: {
|
|
4428
|
-
"Content-Type": "application/json",
|
|
4429
|
-
"x-owney-api-key": `${apiKey}`
|
|
4430
|
-
}
|
|
4431
|
-
});
|
|
4432
|
-
if (!res.ok) {
|
|
4433
|
-
if (res.status !== 404) {
|
|
4434
|
-
console.warn(
|
|
4435
|
-
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
4436
|
-
);
|
|
4437
|
-
}
|
|
4438
|
-
return null;
|
|
4439
|
-
}
|
|
4440
|
-
const json = await res.json();
|
|
4441
|
-
const policy = json.success ? json.data ?? null : null;
|
|
4442
|
-
debugLog(
|
|
4443
|
-
"owney-sdk",
|
|
4444
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
4445
|
-
policy ?? void 0
|
|
4446
|
-
);
|
|
4447
|
-
return policy;
|
|
4448
|
-
} catch (error) {
|
|
4449
|
-
console.warn(
|
|
4450
|
-
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
4451
|
-
error instanceof Error ? error.message : String(error)
|
|
4452
|
-
);
|
|
4453
|
-
return null;
|
|
4454
|
-
}
|
|
4455
|
-
}
|
|
4456
|
-
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4457
|
-
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
4458
|
-
const res = await fetch(url, {
|
|
4459
|
-
method: "GET",
|
|
4460
|
-
headers: {
|
|
4461
|
-
"Content-Type": "application/json",
|
|
4462
|
-
"x-owney-api-key": `${apiKey}`
|
|
4463
|
-
}
|
|
4464
|
-
});
|
|
4465
|
-
if (!res.ok) {
|
|
4466
|
-
const text = await res.text().catch(() => "");
|
|
4467
|
-
throw new OwneyError(
|
|
4468
|
-
"API_ROUTING_ERROR",
|
|
4469
|
-
`Routing API error ${res.status}: ${text}`,
|
|
4470
|
-
{ statusCode: res.status, responseBody: text }
|
|
4471
|
-
);
|
|
4472
|
-
}
|
|
4473
|
-
const json = await res.json();
|
|
4474
|
-
if (!json.success) {
|
|
4475
|
-
throw new OwneyError(
|
|
4476
|
-
"API_ROUTING_FAILED",
|
|
4477
|
-
`Routing API request failed: ${json.message}`,
|
|
4478
|
-
{ message: json.message }
|
|
4479
|
-
);
|
|
4480
|
-
}
|
|
4481
|
-
return json.data;
|
|
4482
|
-
}
|
|
4483
|
-
|
|
4484
|
-
// src/lib/health-report.ts
|
|
4485
|
-
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
4486
|
-
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
|
|
4487
|
-
try {
|
|
4488
|
-
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
4489
|
-
method: "POST",
|
|
4490
|
-
headers: {
|
|
4491
|
-
"Content-Type": "application/json",
|
|
4492
|
-
"x-owney-api-key": apiKey
|
|
4493
|
-
},
|
|
4494
|
-
body: JSON.stringify({
|
|
4495
|
-
agent_type: agentType,
|
|
4496
|
-
error_code: errorCode,
|
|
4497
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4498
|
-
})
|
|
4499
|
-
});
|
|
4500
|
-
} catch (err) {
|
|
4501
|
-
console.warn(
|
|
4502
|
-
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
4503
|
-
err instanceof Error ? err.message : err
|
|
4504
|
-
);
|
|
4505
|
-
}
|
|
4506
|
-
}
|
|
4507
|
-
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
4508
|
-
try {
|
|
4509
|
-
return await fn();
|
|
4510
|
-
} catch (err) {
|
|
4511
|
-
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
4512
|
-
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
4513
|
-
throw err;
|
|
2193
|
+
return await fn();
|
|
2194
|
+
} catch (err) {
|
|
2195
|
+
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
2196
|
+
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
2197
|
+
throw err;
|
|
4514
2198
|
}
|
|
4515
2199
|
}
|
|
4516
2200
|
|
|
@@ -4523,29 +2207,8 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4523
2207
|
const tokenBalance = agentBalance?.tokens.find(
|
|
4524
2208
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4525
2209
|
);
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
const chainNameById = {
|
|
4529
|
-
1: "ETHEREUM",
|
|
4530
|
-
8453: "BASE",
|
|
4531
|
-
42161: "ARBITRUM"
|
|
4532
|
-
};
|
|
4533
|
-
const targetChain = chainNameById[chainId];
|
|
4534
|
-
for (const position2 of agentBalance?.positions ?? []) {
|
|
4535
|
-
const positionChain = position2.chain.trim().toUpperCase();
|
|
4536
|
-
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4537
|
-
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4538
|
-
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits(position2.amount, decimals);
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
const assetTotal = agentBalance?.assetBalances?.find(
|
|
4542
|
-
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4543
|
-
);
|
|
4544
|
-
if (assetTotal) {
|
|
4545
|
-
const total = parseUnits(assetTotal.amount, decimals);
|
|
4546
|
-
if (total < balance) balance = total > 0n ? total : 0n;
|
|
4547
|
-
}
|
|
4548
|
-
return { agent, balance };
|
|
2210
|
+
if (!tokenBalance) return { agent, balance: 0n };
|
|
2211
|
+
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
4549
2212
|
});
|
|
4550
2213
|
}
|
|
4551
2214
|
function planProportionalShares(balances, requested, totalAvailable) {
|
|
@@ -4571,9 +2234,7 @@ function planProportionalShares(balances, requested, totalAvailable) {
|
|
|
4571
2234
|
return plans;
|
|
4572
2235
|
}
|
|
4573
2236
|
function planDisabledDrain(disabled, requested) {
|
|
4574
|
-
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
4575
|
-
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
4576
|
-
);
|
|
2237
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
4577
2238
|
const plans = [];
|
|
4578
2239
|
let remaining = requested;
|
|
4579
2240
|
for (const { agent, balance } of sorted) {
|
|
@@ -4624,13 +2285,6 @@ function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
|
4624
2285
|
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
4625
2286
|
}
|
|
4626
2287
|
const normalizedToken = tokenSymbol.toUpperCase();
|
|
4627
|
-
const snapshots = balance.assetBalances?.filter(
|
|
4628
|
-
(token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
|
|
4629
|
-
);
|
|
4630
|
-
if (snapshots?.length) {
|
|
4631
|
-
const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
|
|
4632
|
-
if (Number.isFinite(amount)) return Math.max(0, amount);
|
|
4633
|
-
}
|
|
4634
2288
|
return balance.tokens.reduce((total, token) => {
|
|
4635
2289
|
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
4636
2290
|
return total;
|
|
@@ -4702,386 +2356,441 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
4702
2356
|
|
|
4703
2357
|
// src/client.ts
|
|
4704
2358
|
import {
|
|
4705
|
-
createPublicClient as
|
|
4706
|
-
createWalletClient
|
|
4707
|
-
custom
|
|
4708
|
-
} from "viem";
|
|
4709
|
-
import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
4710
|
-
|
|
4711
|
-
// src/lib/sponsored-token-batch.ts
|
|
4712
|
-
import {
|
|
4713
|
-
isAddressEqual,
|
|
4714
|
-
keccak256,
|
|
4715
|
-
toBytes
|
|
2359
|
+
createPublicClient as createPublicClient2,
|
|
2360
|
+
createWalletClient,
|
|
2361
|
+
custom
|
|
4716
2362
|
} from "viem";
|
|
2363
|
+
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
4717
2364
|
|
|
4718
|
-
// src/lib/
|
|
4719
|
-
import {
|
|
4720
|
-
var
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
{ name: "owner", type: "address" },
|
|
4726
|
-
{ name: "spender", type: "address" },
|
|
4727
|
-
{ name: "value", type: "uint256" },
|
|
4728
|
-
{ name: "nonce", type: "uint256" },
|
|
4729
|
-
{ name: "deadline", type: "uint256" }
|
|
4730
|
-
]
|
|
4731
|
-
};
|
|
4732
|
-
var ERC2612_READ_ABI = parseAbi2([
|
|
4733
|
-
"function name() view returns (string)",
|
|
4734
|
-
"function version() view returns (string)",
|
|
4735
|
-
"function nonces(address owner) view returns (uint256)"
|
|
4736
|
-
]);
|
|
4737
|
-
function erc2612TypedData(input) {
|
|
2365
|
+
// src/lib/transfer-auth.ts
|
|
2366
|
+
import { bytesToHex } from "viem";
|
|
2367
|
+
var ERC20_META_ABI = [
|
|
2368
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2369
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2370
|
+
];
|
|
2371
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
4738
2372
|
return {
|
|
4739
|
-
domain: {
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
2373
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2374
|
+
types: {
|
|
2375
|
+
TransferWithAuthorization: [
|
|
2376
|
+
{ name: "from", type: "address" },
|
|
2377
|
+
{ name: "to", type: "address" },
|
|
2378
|
+
{ name: "value", type: "uint256" },
|
|
2379
|
+
{ name: "validAfter", type: "uint256" },
|
|
2380
|
+
{ name: "validBefore", type: "uint256" },
|
|
2381
|
+
{ name: "nonce", type: "bytes32" }
|
|
2382
|
+
]
|
|
4744
2383
|
},
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
message: {
|
|
4748
|
-
owner: input.owner,
|
|
4749
|
-
spender: BATCH_PERMIT2_ADDRESS,
|
|
4750
|
-
value: BigInt(input.permit.value),
|
|
4751
|
-
nonce: BigInt(input.permit.nonce),
|
|
4752
|
-
deadline: BigInt(input.permit.deadline)
|
|
4753
|
-
}
|
|
2384
|
+
primaryType: "TransferWithAuthorization",
|
|
2385
|
+
message: input.message
|
|
4754
2386
|
};
|
|
4755
2387
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
{
|
|
4759
|
-
{
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
var
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
})
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
2388
|
+
async function readTokenMeta(publicClient, token) {
|
|
2389
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
2390
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2391
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2392
|
+
]);
|
|
2393
|
+
return { tokenName, tokenVersion };
|
|
2394
|
+
}
|
|
2395
|
+
function randomAuthNonce() {
|
|
2396
|
+
const bytes = new Uint8Array(32);
|
|
2397
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2398
|
+
return bytesToHex(bytes);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
// src/lib/sponsor-client.ts
|
|
2402
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2403
|
+
async function postSponsorTransferAuth(input) {
|
|
2404
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2405
|
+
let res;
|
|
2406
|
+
try {
|
|
2407
|
+
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2408
|
+
method: "POST",
|
|
2409
|
+
headers: {
|
|
2410
|
+
"content-type": "application/json",
|
|
2411
|
+
"x-owney-api-key": input.apiKey
|
|
2412
|
+
},
|
|
2413
|
+
body: JSON.stringify(input.body)
|
|
2414
|
+
});
|
|
2415
|
+
} catch (networkError) {
|
|
2416
|
+
throw new OwneyError(
|
|
2417
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2418
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2419
|
+
{ cause: String(networkError) }
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
const text = await res.text();
|
|
2423
|
+
let parsed = null;
|
|
2424
|
+
try {
|
|
2425
|
+
parsed = JSON.parse(text);
|
|
2426
|
+
} catch {
|
|
2427
|
+
}
|
|
2428
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2429
|
+
throw new OwneyError(
|
|
2430
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2431
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2432
|
+
{
|
|
2433
|
+
statusCode: res.status,
|
|
2434
|
+
responseBody: text.slice(0, 500),
|
|
2435
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2436
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2437
|
+
safeToFallback: res.status === 503
|
|
2438
|
+
}
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
return parsed.data;
|
|
2442
|
+
}
|
|
2443
|
+
async function postSponsorPermit2Transfer(input) {
|
|
2444
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2445
|
+
let res;
|
|
2446
|
+
try {
|
|
2447
|
+
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
2448
|
+
method: "POST",
|
|
2449
|
+
headers: {
|
|
2450
|
+
"content-type": "application/json",
|
|
2451
|
+
"x-owney-api-key": input.apiKey
|
|
2452
|
+
},
|
|
2453
|
+
body: JSON.stringify(input.body)
|
|
2454
|
+
});
|
|
2455
|
+
} catch (networkError) {
|
|
2456
|
+
throw new OwneyError(
|
|
2457
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2458
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2459
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
const text = await res.text();
|
|
2463
|
+
let parsed = null;
|
|
2464
|
+
try {
|
|
2465
|
+
parsed = JSON.parse(text);
|
|
2466
|
+
} catch {
|
|
2467
|
+
}
|
|
2468
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2469
|
+
throw new OwneyError(
|
|
2470
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2471
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2472
|
+
{
|
|
2473
|
+
statusCode: res.status,
|
|
2474
|
+
responseBody: text.slice(0, 500),
|
|
2475
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2476
|
+
}
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2479
|
+
return parsed.data;
|
|
2480
|
+
}
|
|
2481
|
+
async function getSponsorRelayerAddress(input) {
|
|
2482
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2483
|
+
let res;
|
|
2484
|
+
try {
|
|
2485
|
+
res = await fetch(
|
|
2486
|
+
`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2487
|
+
{
|
|
2488
|
+
headers: { "x-owney-api-key": input.apiKey }
|
|
2489
|
+
}
|
|
2490
|
+
);
|
|
2491
|
+
} catch (networkError) {
|
|
2492
|
+
throw new OwneyError(
|
|
2493
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2494
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2495
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
const text = await res.text();
|
|
2499
|
+
let parsed = null;
|
|
2500
|
+
try {
|
|
2501
|
+
parsed = JSON.parse(text);
|
|
2502
|
+
} catch {
|
|
2503
|
+
}
|
|
2504
|
+
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
2505
|
+
throw new OwneyError(
|
|
2506
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2507
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2508
|
+
{
|
|
2509
|
+
statusCode: res.status,
|
|
2510
|
+
responseBody: text.slice(0, 500),
|
|
2511
|
+
safeToFallback: true
|
|
2512
|
+
}
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
return parsed.data.relayer;
|
|
4786
2516
|
}
|
|
4787
|
-
|
|
2517
|
+
|
|
2518
|
+
// src/lib/permit2.ts
|
|
2519
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2520
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2521
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2522
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
2523
|
+
{
|
|
2524
|
+
type: "function",
|
|
2525
|
+
name: "allowance",
|
|
2526
|
+
stateMutability: "view",
|
|
2527
|
+
inputs: [
|
|
2528
|
+
{ name: "owner", type: "address" },
|
|
2529
|
+
{ name: "spender", type: "address" }
|
|
2530
|
+
],
|
|
2531
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
type: "function",
|
|
2535
|
+
name: "approve",
|
|
2536
|
+
stateMutability: "nonpayable",
|
|
2537
|
+
inputs: [
|
|
2538
|
+
{ name: "spender", type: "address" },
|
|
2539
|
+
{ name: "amount", type: "uint256" }
|
|
2540
|
+
],
|
|
2541
|
+
outputs: [{ name: "", type: "bool" }]
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
type: "function",
|
|
2545
|
+
name: "balanceOf",
|
|
2546
|
+
stateMutability: "view",
|
|
2547
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2548
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2549
|
+
}
|
|
2550
|
+
];
|
|
2551
|
+
function buildPermitTransferFromTypedData(input) {
|
|
4788
2552
|
return {
|
|
4789
2553
|
domain: {
|
|
4790
2554
|
name: "Permit2",
|
|
4791
|
-
chainId:
|
|
4792
|
-
verifyingContract:
|
|
2555
|
+
chainId: input.chainId,
|
|
2556
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
4793
2557
|
},
|
|
4794
|
-
types:
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
2558
|
+
types: {
|
|
2559
|
+
PermitTransferFrom: [
|
|
2560
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
2561
|
+
{ name: "spender", type: "address" },
|
|
2562
|
+
{ name: "nonce", type: "uint256" },
|
|
2563
|
+
{ name: "deadline", type: "uint256" }
|
|
2564
|
+
],
|
|
2565
|
+
TokenPermissions: [
|
|
2566
|
+
{ name: "token", type: "address" },
|
|
2567
|
+
{ name: "amount", type: "uint256" }
|
|
2568
|
+
]
|
|
2569
|
+
},
|
|
2570
|
+
primaryType: "PermitTransferFrom",
|
|
2571
|
+
message: input.message
|
|
4801
2572
|
};
|
|
4802
2573
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
var planOf = (transfers) => JSON.stringify(
|
|
4808
|
-
transfers.map((t) => ({
|
|
4809
|
-
to: t.to.toLowerCase(),
|
|
4810
|
-
amount: BigInt(t.amount).toString()
|
|
4811
|
-
}))
|
|
4812
|
-
);
|
|
4813
|
-
function read(key2) {
|
|
4814
|
-
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
2574
|
+
function randomPermit2Nonce() {
|
|
2575
|
+
const bytes = new Uint8Array(32);
|
|
2576
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2577
|
+
return BigInt(bytesToHex2(bytes));
|
|
4815
2578
|
}
|
|
4816
|
-
function
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
2579
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2580
|
+
return publicClient.readContract({
|
|
2581
|
+
address: token,
|
|
2582
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2583
|
+
functionName: "allowance",
|
|
2584
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
4820
2585
|
});
|
|
4821
|
-
if (typeof window === "undefined") memory.set(key2, value);
|
|
4822
|
-
else window.localStorage.setItem(key2, value);
|
|
4823
2586
|
}
|
|
4824
|
-
function
|
|
4825
|
-
|
|
4826
|
-
|
|
2587
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2588
|
+
return publicClient.readContract({
|
|
2589
|
+
address: token,
|
|
2590
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2591
|
+
functionName: "balanceOf",
|
|
2592
|
+
args: [owner]
|
|
2593
|
+
});
|
|
4827
2594
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
)
|
|
4838
|
-
);
|
|
4839
|
-
return active.promise;
|
|
4840
|
-
}
|
|
4841
|
-
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
4842
|
-
inflight.set(key2, { plan, promise });
|
|
4843
|
-
return promise;
|
|
2595
|
+
|
|
2596
|
+
// src/lib/chain-guard.ts
|
|
2597
|
+
var CHAIN_NAMES = {
|
|
2598
|
+
1: "Ethereum",
|
|
2599
|
+
8453: "Base",
|
|
2600
|
+
42161: "Arbitrum"
|
|
2601
|
+
};
|
|
2602
|
+
function chainName(chainId) {
|
|
2603
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
4844
2604
|
}
|
|
4845
|
-
async function
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
let body = initial;
|
|
4852
|
-
save(key2, body);
|
|
4853
|
-
try {
|
|
4854
|
-
if (!body.serializedTransaction) {
|
|
4855
|
-
const prepared = await postSponsorBatchTransfer({
|
|
4856
|
-
apiKey: i.apiKey,
|
|
4857
|
-
baseUrl: i.baseUrl,
|
|
4858
|
-
body
|
|
4859
|
-
});
|
|
4860
|
-
if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
|
|
4861
|
-
throw new Error(
|
|
4862
|
-
"Sponsorship API did not return a valid prepared transaction."
|
|
4863
|
-
);
|
|
4864
|
-
body = {
|
|
4865
|
-
...body,
|
|
4866
|
-
serializedTransaction: prepared.serializedTransaction
|
|
4867
|
-
};
|
|
4868
|
-
save(key2, body);
|
|
4869
|
-
}
|
|
4870
|
-
const result = await postSponsorBatchTransfer({
|
|
4871
|
-
apiKey: i.apiKey,
|
|
4872
|
-
baseUrl: i.baseUrl,
|
|
4873
|
-
body
|
|
4874
|
-
});
|
|
4875
|
-
if (result.txHash !== keccak256(body.serializedTransaction))
|
|
4876
|
-
throw new Error(
|
|
4877
|
-
"Sponsorship receipt does not match the pending transaction."
|
|
4878
|
-
);
|
|
4879
|
-
clear(key2);
|
|
4880
|
-
return result.txHash;
|
|
4881
|
-
} catch (error) {
|
|
4882
|
-
if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
|
|
4883
|
-
clear(key2);
|
|
4884
|
-
throw error;
|
|
4885
|
-
}
|
|
4886
|
-
};
|
|
4887
|
-
const saved = read(key2);
|
|
4888
|
-
if (saved) {
|
|
4889
|
-
const previous = JSON.parse(saved);
|
|
4890
|
-
if (previous.chainId !== i.chainId || !isAddressEqual(previous.from, i.owner) || !isAddressEqual(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
4891
|
-
throw new Error(
|
|
4892
|
-
"Retry the previous token deposit and agent split first to reconcile its status."
|
|
4893
|
-
);
|
|
4894
|
-
i.onApproved?.();
|
|
4895
|
-
return send({ ...previous, transfers: i.transfers });
|
|
4896
|
-
}
|
|
4897
|
-
const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
|
|
4898
|
-
const [balance, allowance] = await Promise.all([
|
|
4899
|
-
readErc20Balance(i.pub, i.token, i.owner),
|
|
4900
|
-
readPermit2Allowance(i.pub, i.token, i.owner)
|
|
4901
|
-
]);
|
|
4902
|
-
if (balance < total)
|
|
2605
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2606
|
+
const actual = await pub.getChainId();
|
|
2607
|
+
if (actual === expected) return;
|
|
2608
|
+
try {
|
|
2609
|
+
await wallet.switchChain({ id: expected });
|
|
2610
|
+
} catch (error) {
|
|
4903
2611
|
throw new OwneyError(
|
|
4904
|
-
"
|
|
4905
|
-
|
|
2612
|
+
"CHAIN_MISMATCH",
|
|
2613
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2614
|
+
{
|
|
2615
|
+
expectedChainId: expected,
|
|
2616
|
+
actualChainId: actual,
|
|
2617
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2618
|
+
}
|
|
4906
2619
|
);
|
|
4907
|
-
|
|
2620
|
+
}
|
|
2621
|
+
const after = await pub.getChainId();
|
|
2622
|
+
if (after !== expected) {
|
|
4908
2623
|
throw new OwneyError(
|
|
4909
|
-
"
|
|
4910
|
-
|
|
2624
|
+
"CHAIN_MISMATCH",
|
|
2625
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2626
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
4911
2627
|
);
|
|
4912
|
-
const now = (await i.pub.getBlock()).timestamp;
|
|
4913
|
-
let erc2612Permit;
|
|
4914
|
-
if (allowance < total) {
|
|
4915
|
-
const [tokenName, tokenVersion, permitNonce] = await Promise.all([
|
|
4916
|
-
i.pub.readContract({
|
|
4917
|
-
address: i.token,
|
|
4918
|
-
abi: ERC2612_READ_ABI,
|
|
4919
|
-
functionName: "name"
|
|
4920
|
-
}),
|
|
4921
|
-
i.pub.readContract({
|
|
4922
|
-
address: i.token,
|
|
4923
|
-
abi: ERC2612_READ_ABI,
|
|
4924
|
-
functionName: "version"
|
|
4925
|
-
}),
|
|
4926
|
-
i.pub.readContract({
|
|
4927
|
-
address: i.token,
|
|
4928
|
-
abi: ERC2612_READ_ABI,
|
|
4929
|
-
functionName: "nonces",
|
|
4930
|
-
args: [i.owner]
|
|
4931
|
-
})
|
|
4932
|
-
]);
|
|
4933
|
-
const unsignedPermit = {
|
|
4934
|
-
value: MAX_UINT256.toString(),
|
|
4935
|
-
nonce: permitNonce.toString(),
|
|
4936
|
-
deadline: (now + 900n).toString(),
|
|
4937
|
-
tokenName,
|
|
4938
|
-
tokenVersion
|
|
4939
|
-
};
|
|
4940
|
-
const signature2 = await i.wallet.signTypedData({
|
|
4941
|
-
account: i.owner,
|
|
4942
|
-
...erc2612TypedData({
|
|
4943
|
-
chainId: 8453,
|
|
4944
|
-
token: i.token,
|
|
4945
|
-
owner: i.owner,
|
|
4946
|
-
permit: unsignedPermit
|
|
4947
|
-
})
|
|
4948
|
-
});
|
|
4949
|
-
erc2612Permit = { ...unsignedPermit, signature: signature2 };
|
|
4950
2628
|
}
|
|
4951
|
-
const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
|
|
4952
|
-
apiKey: i.apiKey,
|
|
4953
|
-
baseUrl: i.baseUrl,
|
|
4954
|
-
chainId: i.chainId
|
|
4955
|
-
});
|
|
4956
|
-
const unsigned = {
|
|
4957
|
-
chainId: i.chainId,
|
|
4958
|
-
token: i.token,
|
|
4959
|
-
from: i.owner,
|
|
4960
|
-
transfers: i.transfers,
|
|
4961
|
-
nonce: randomPermit2Nonce().toString(),
|
|
4962
|
-
deadline: (now + 900n).toString(),
|
|
4963
|
-
...erc2612Permit ? { erc2612Permit } : {}
|
|
4964
|
-
};
|
|
4965
|
-
const signature = await i.wallet.signTypedData({
|
|
4966
|
-
account: i.owner,
|
|
4967
|
-
...batchTypedData(unsigned, spender)
|
|
4968
|
-
});
|
|
4969
|
-
i.onApproved?.();
|
|
4970
|
-
return send({ ...unsigned, signature });
|
|
4971
2629
|
}
|
|
4972
2630
|
|
|
4973
|
-
// src/lib/sponsored-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
2631
|
+
// src/lib/sponsored-deposit.ts
|
|
2632
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2633
|
+
function makeSponsoredDepositCallback(deps) {
|
|
2634
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
2635
|
+
return async (smartWallet, chainId, amount) => {
|
|
2636
|
+
const cid = chainId;
|
|
2637
|
+
const token = deps.tokenAddressByChain[cid];
|
|
2638
|
+
if (!token) {
|
|
4977
2639
|
throw new OwneyError(
|
|
4978
2640
|
"CHAIN_UNSUPPORTED",
|
|
4979
2641
|
`No sponsored token configured for chain ${chainId}`
|
|
4980
2642
|
);
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
2643
|
+
}
|
|
2644
|
+
const pub = deps.getPublicClient(cid);
|
|
2645
|
+
const wallet = deps.getWalletClient(cid);
|
|
2646
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2647
|
+
try {
|
|
2648
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2649
|
+
if (balance < BigInt(amount)) {
|
|
2650
|
+
throw new OwneyError(
|
|
2651
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2652
|
+
"Insufficient balance for this deposit.",
|
|
2653
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
} catch (err) {
|
|
2657
|
+
if (err instanceof OwneyError) throw err;
|
|
2658
|
+
console.warn(
|
|
2659
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2660
|
+
err instanceof Error ? err.message : String(err)
|
|
4986
2661
|
);
|
|
4987
|
-
|
|
4988
|
-
await
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
2662
|
+
}
|
|
2663
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
2664
|
+
const validAfter = 0n;
|
|
2665
|
+
const validBefore = BigInt(
|
|
2666
|
+
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2667
|
+
);
|
|
2668
|
+
const nonce = randomAuthNonce();
|
|
2669
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
4993
2670
|
token,
|
|
4994
|
-
chainId,
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
2671
|
+
chainId: cid,
|
|
2672
|
+
tokenName,
|
|
2673
|
+
tokenVersion,
|
|
2674
|
+
message: {
|
|
2675
|
+
from: deps.ownerAddress,
|
|
2676
|
+
to: smartWallet,
|
|
2677
|
+
value: BigInt(amount),
|
|
2678
|
+
validAfter,
|
|
2679
|
+
validBefore,
|
|
2680
|
+
nonce
|
|
2681
|
+
}
|
|
2682
|
+
});
|
|
2683
|
+
const authSignature = await wallet.signTypedData({
|
|
2684
|
+
account: deps.ownerAddress,
|
|
2685
|
+
...typedData
|
|
2686
|
+
});
|
|
2687
|
+
deps.onApproved?.();
|
|
2688
|
+
const result = await post({
|
|
2689
|
+
baseUrl: deps.baseUrl,
|
|
2690
|
+
apiKey: deps.apiKey,
|
|
2691
|
+
body: {
|
|
2692
|
+
chainId: cid,
|
|
2693
|
+
token,
|
|
2694
|
+
from: deps.ownerAddress,
|
|
2695
|
+
to: smartWallet,
|
|
2696
|
+
value: amount,
|
|
2697
|
+
validAfter: validAfter.toString(),
|
|
2698
|
+
validBefore: validBefore.toString(),
|
|
2699
|
+
nonce,
|
|
2700
|
+
authSignature,
|
|
2701
|
+
tokenName,
|
|
2702
|
+
tokenVersion
|
|
2703
|
+
}
|
|
4999
2704
|
});
|
|
2705
|
+
return result.txHash;
|
|
5000
2706
|
};
|
|
5001
|
-
const callback = makeVerificationAwareDepositCallback(
|
|
5002
|
-
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
5003
|
-
);
|
|
5004
|
-
registerDepositBatch(callback, batch);
|
|
5005
|
-
return callback;
|
|
5006
2707
|
}
|
|
5007
2708
|
|
|
5008
|
-
// src/lib/
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
const funding = deferred();
|
|
5021
|
-
const tasks = [];
|
|
5022
|
-
const transfers = [];
|
|
5023
|
-
try {
|
|
5024
|
-
for (const leg of legs) {
|
|
5025
|
-
const ready = deferred();
|
|
5026
|
-
let entered = false;
|
|
5027
|
-
const callback = makeVerificationAwareDepositCallback(
|
|
5028
|
-
(to, cid, amount, verification) => {
|
|
5029
|
-
if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
|
|
5030
|
-
const error = new Error(
|
|
5031
|
-
"Agent changed its prepared deposit share."
|
|
5032
|
-
);
|
|
5033
|
-
ready.reject(error);
|
|
5034
|
-
throw error;
|
|
5035
|
-
}
|
|
5036
|
-
entered = true;
|
|
5037
|
-
ready.resolve(toBatchTransfer(to, amount, verification));
|
|
5038
|
-
return funding.promise;
|
|
5039
|
-
}
|
|
5040
|
-
);
|
|
5041
|
-
const task = Promise.resolve().then(() => leg.run(callback));
|
|
5042
|
-
tasks.push(task);
|
|
5043
|
-
void task.then(
|
|
5044
|
-
() => {
|
|
5045
|
-
if (!entered)
|
|
5046
|
-
ready.reject(
|
|
5047
|
-
new Error("Agent did not prepare a deposit transfer.")
|
|
5048
|
-
);
|
|
5049
|
-
},
|
|
5050
|
-
(error) => ready.reject(error)
|
|
2709
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
2710
|
+
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2711
|
+
function makeSponsoredWethCallback(deps) {
|
|
2712
|
+
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2713
|
+
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2714
|
+
return async (smartWallet, chainId, amount) => {
|
|
2715
|
+
const cid = chainId;
|
|
2716
|
+
const token = deps.tokenAddressByChain[cid];
|
|
2717
|
+
if (!token) {
|
|
2718
|
+
throw new OwneyError(
|
|
2719
|
+
"CHAIN_UNSUPPORTED",
|
|
2720
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
5051
2721
|
);
|
|
5052
|
-
transfers.push(await ready.promise);
|
|
5053
2722
|
}
|
|
5054
|
-
const
|
|
5055
|
-
|
|
5056
|
-
const
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
if (
|
|
5061
|
-
|
|
5062
|
-
|
|
2723
|
+
const amountWei = BigInt(amount);
|
|
2724
|
+
const pub = deps.getPublicClient(cid);
|
|
2725
|
+
const wallet = deps.getWalletClient(cid);
|
|
2726
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2727
|
+
try {
|
|
2728
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2729
|
+
if (balance < amountWei) {
|
|
2730
|
+
throw new OwneyError(
|
|
2731
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2732
|
+
"Insufficient WETH balance for this deposit.",
|
|
2733
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
} catch (err) {
|
|
2737
|
+
if (err instanceof OwneyError) throw err;
|
|
2738
|
+
console.warn(
|
|
2739
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2740
|
+
err instanceof Error ? err.message : String(err)
|
|
2741
|
+
);
|
|
5063
2742
|
}
|
|
5064
|
-
|
|
2743
|
+
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
2744
|
+
if (allowance < amountWei) {
|
|
5065
2745
|
throw new OwneyError(
|
|
5066
|
-
"
|
|
5067
|
-
"
|
|
5068
|
-
{
|
|
5069
|
-
txHash,
|
|
5070
|
-
fundsSubmitted: true,
|
|
5071
|
-
agentResults,
|
|
5072
|
-
failedAgentIds: failures
|
|
5073
|
-
}
|
|
2746
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
2747
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
2748
|
+
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
5074
2749
|
);
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
2750
|
+
}
|
|
2751
|
+
const relayer = await get({
|
|
2752
|
+
baseUrl: deps.baseUrl,
|
|
2753
|
+
apiKey: deps.apiKey,
|
|
2754
|
+
chainId: cid
|
|
2755
|
+
});
|
|
2756
|
+
const nonce = randomPermit2Nonce();
|
|
2757
|
+
const deadline = BigInt(
|
|
2758
|
+
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
2759
|
+
);
|
|
2760
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
2761
|
+
chainId: cid,
|
|
2762
|
+
message: {
|
|
2763
|
+
permitted: { token, amount: amountWei },
|
|
2764
|
+
spender: relayer,
|
|
2765
|
+
nonce,
|
|
2766
|
+
deadline
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
const signature = await wallet.signTypedData({
|
|
2770
|
+
account: deps.ownerAddress,
|
|
2771
|
+
...typedData
|
|
2772
|
+
});
|
|
2773
|
+
deps.onApproved?.();
|
|
2774
|
+
const result = await post({
|
|
2775
|
+
baseUrl: deps.baseUrl,
|
|
2776
|
+
apiKey: deps.apiKey,
|
|
2777
|
+
body: {
|
|
2778
|
+
chainId: cid,
|
|
2779
|
+
token,
|
|
2780
|
+
from: deps.ownerAddress,
|
|
2781
|
+
to: smartWallet,
|
|
2782
|
+
amount,
|
|
2783
|
+
nonce: nonce.toString(),
|
|
2784
|
+
deadline: deadline.toString(),
|
|
2785
|
+
signature
|
|
2786
|
+
}
|
|
2787
|
+
});
|
|
2788
|
+
return result.txHash;
|
|
2789
|
+
};
|
|
5081
2790
|
}
|
|
5082
2791
|
|
|
5083
2792
|
// src/lib/sponsored-calls-deposit.ts
|
|
5084
|
-
import { encodeFunctionData
|
|
2793
|
+
import { encodeFunctionData, erc20Abi, toHex } from "viem";
|
|
5085
2794
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
5086
2795
|
var DEFAULT_MAX_POLLS = 30;
|
|
5087
2796
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -5107,7 +2816,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5107
2816
|
}
|
|
5108
2817
|
return new URL(configured, origin).toString();
|
|
5109
2818
|
};
|
|
5110
|
-
|
|
2819
|
+
return async (smartWallet, chainId, amount) => {
|
|
5111
2820
|
const cid = chainId;
|
|
5112
2821
|
const token = deps.tokenAddressByChain[cid];
|
|
5113
2822
|
if (!token) {
|
|
@@ -5123,42 +2832,11 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5123
2832
|
{ chainId }
|
|
5124
2833
|
);
|
|
5125
2834
|
}
|
|
5126
|
-
const
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
functionName: "transfer",
|
|
5132
|
-
args: [transfer.to, BigInt(transfer.amount)]
|
|
5133
|
-
})
|
|
5134
|
-
}));
|
|
5135
|
-
let paymasterUrl = absolutePaymasterUrl();
|
|
5136
|
-
for (const transfer of transfers) {
|
|
5137
|
-
const verification = transfer.yieldseeker;
|
|
5138
|
-
if (!verification) continue;
|
|
5139
|
-
if (chainId !== 8453)
|
|
5140
|
-
throw new OwneyError(
|
|
5141
|
-
"CHAIN_UNSUPPORTED",
|
|
5142
|
-
`Yieldseeker sponsorship is not available on chain ${chainId}.`
|
|
5143
|
-
);
|
|
5144
|
-
const { intent } = await postPaymasterIntent({
|
|
5145
|
-
baseUrl: deps.routingApiBaseUrl,
|
|
5146
|
-
apiKey: deps.apiKey,
|
|
5147
|
-
yieldseekerSignature: verification.signature,
|
|
5148
|
-
body: {
|
|
5149
|
-
chainId,
|
|
5150
|
-
token,
|
|
5151
|
-
from: deps.ownerAddress,
|
|
5152
|
-
to: transfer.to,
|
|
5153
|
-
amount: transfer.amount,
|
|
5154
|
-
yieldseekerUserId: verification.userId,
|
|
5155
|
-
yieldseekerAgentId: verification.agentId
|
|
5156
|
-
}
|
|
5157
|
-
});
|
|
5158
|
-
const url = new URL(paymasterUrl);
|
|
5159
|
-
url.searchParams.append("owneyIntent", intent);
|
|
5160
|
-
paymasterUrl = url.toString();
|
|
5161
|
-
}
|
|
2835
|
+
const data = encodeFunctionData({
|
|
2836
|
+
abi: erc20Abi,
|
|
2837
|
+
functionName: "transfer",
|
|
2838
|
+
args: [smartWallet, BigInt(amount)]
|
|
2839
|
+
});
|
|
5162
2840
|
const sendResult = await deps.provider.request({
|
|
5163
2841
|
method: "wallet_sendCalls",
|
|
5164
2842
|
params: [
|
|
@@ -5166,10 +2844,10 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5166
2844
|
version: "2.0.0",
|
|
5167
2845
|
from: deps.ownerAddress,
|
|
5168
2846
|
chainId: toHex(chainId),
|
|
5169
|
-
atomicRequired:
|
|
5170
|
-
calls,
|
|
2847
|
+
atomicRequired: false,
|
|
2848
|
+
calls: [{ to: token, value: "0x0", data }],
|
|
5171
2849
|
capabilities: {
|
|
5172
|
-
paymasterService: { url:
|
|
2850
|
+
paymasterService: { url: absolutePaymasterUrl() }
|
|
5173
2851
|
}
|
|
5174
2852
|
}
|
|
5175
2853
|
]
|
|
@@ -5189,24 +2867,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5189
2867
|
params: [callsId]
|
|
5190
2868
|
});
|
|
5191
2869
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
5192
|
-
if (
|
|
5193
|
-
throw new OwneyError(
|
|
5194
|
-
"SPONSOR_REQUEST_FAILED",
|
|
5195
|
-
"The sponsored deposit did not complete successfully.",
|
|
5196
|
-
{ chainId, callsId, safeToFallback: false }
|
|
5197
|
-
);
|
|
5198
|
-
}
|
|
5199
|
-
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
5200
|
-
if (status?.receipts?.some(
|
|
5201
|
-
(receipt) => receipt.transactionHash !== txHash
|
|
5202
|
-
))
|
|
5203
|
-
throw new OwneyError(
|
|
5204
|
-
"SPONSORED_CALLS_NO_RECEIPT",
|
|
5205
|
-
"The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
|
|
5206
|
-
{ chainId, callsId }
|
|
5207
|
-
);
|
|
5208
|
-
return txHash;
|
|
5209
|
-
}
|
|
2870
|
+
if (txHash) return txHash;
|
|
5210
2871
|
if (pollIntervalMs > 0) {
|
|
5211
2872
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
5212
2873
|
}
|
|
@@ -5217,16 +2878,9 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
5217
2878
|
{ chainId, callsId }
|
|
5218
2879
|
);
|
|
5219
2880
|
};
|
|
5220
|
-
const callback = makeVerificationAwareDepositCallback(
|
|
5221
|
-
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
5222
|
-
);
|
|
5223
|
-
registerDepositBatch(callback, batch);
|
|
5224
|
-
return callback;
|
|
5225
2881
|
}
|
|
5226
2882
|
|
|
5227
2883
|
// src/client.ts
|
|
5228
|
-
var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
|
|
5229
|
-
var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
|
|
5230
2884
|
function encodeMultiAgentCursor(map) {
|
|
5231
2885
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
5232
2886
|
}
|
|
@@ -5253,7 +2907,7 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
5253
2907
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
5254
2908
|
};
|
|
5255
2909
|
var VIEM_CHAIN2 = {
|
|
5256
|
-
8453:
|
|
2910
|
+
8453: base2,
|
|
5257
2911
|
42161: arbitrum2,
|
|
5258
2912
|
1: mainnet2
|
|
5259
2913
|
};
|
|
@@ -5262,11 +2916,6 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
5262
2916
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
5263
2917
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
5264
2918
|
};
|
|
5265
|
-
var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
|
|
5266
|
-
function sponsoredTokensFor(asset) {
|
|
5267
|
-
if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
|
|
5268
|
-
return SPONSORED_TOKENS_BY_ASSET[asset];
|
|
5269
|
-
}
|
|
5270
2919
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
5271
2920
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
5272
2921
|
}
|
|
@@ -5287,10 +2936,7 @@ var OwneySDK = class {
|
|
|
5287
2936
|
// leave every user's agent profile alone.
|
|
5288
2937
|
orgAgentConfig;
|
|
5289
2938
|
orgAgentConfigPromise = null;
|
|
5290
|
-
rpcUrls;
|
|
5291
2939
|
zyfaiRpcUrls;
|
|
5292
|
-
yieldseekerApiBaseUrl;
|
|
5293
|
-
yieldseekerSiweOrigin;
|
|
5294
2940
|
routingApiBaseUrl;
|
|
5295
2941
|
referralSource;
|
|
5296
2942
|
cachedSponsoredCallback = null;
|
|
@@ -5312,14 +2958,7 @@ var OwneySDK = class {
|
|
|
5312
2958
|
constructor(config) {
|
|
5313
2959
|
this.apiKey = config.apiKey;
|
|
5314
2960
|
if (config.debug) setOwneyDebug(true);
|
|
5315
|
-
this.
|
|
5316
|
-
config.rpcUrls,
|
|
5317
|
-
config.apiKey,
|
|
5318
|
-
config.routingApiBaseUrl
|
|
5319
|
-
);
|
|
5320
|
-
this.zyfaiRpcUrls = config.rpcUrls ? void 0 : config.zyfaiRpcUrls;
|
|
5321
|
-
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
5322
|
-
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
2961
|
+
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
5323
2962
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
5324
2963
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
5325
2964
|
this.referralSource = config.referralSource;
|
|
@@ -5353,7 +2992,6 @@ var OwneySDK = class {
|
|
|
5353
2992
|
* After calling this, `connect()` must be called again before using agent methods.
|
|
5354
2993
|
*/
|
|
5355
2994
|
async disconnect() {
|
|
5356
|
-
this.state = null;
|
|
5357
2995
|
for (const agent of this.agents.values()) {
|
|
5358
2996
|
await agent.disconnect();
|
|
5359
2997
|
}
|
|
@@ -5407,16 +3045,18 @@ var OwneySDK = class {
|
|
|
5407
3045
|
}
|
|
5408
3046
|
return this.state.provider;
|
|
5409
3047
|
}
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
3048
|
+
/**
|
|
3049
|
+
* Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
|
|
3050
|
+
* used when the caller omits `depositCallback`. Wraps the connected EIP-1193
|
|
3051
|
+
* provider with viem `custom(provider)` to read token meta and sign the
|
|
3052
|
+
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
3053
|
+
*/
|
|
5414
3054
|
getDefaultSponsoredCallback(onApproved) {
|
|
5415
3055
|
if (!onApproved && this.cachedSponsoredCallback)
|
|
5416
3056
|
return this.cachedSponsoredCallback;
|
|
5417
3057
|
const provider = this.requireConnectedProvider();
|
|
5418
3058
|
const owner = this.state.walletAddress;
|
|
5419
|
-
const callback =
|
|
3059
|
+
const callback = makeSponsoredDepositCallback({
|
|
5420
3060
|
apiKey: this.apiKey,
|
|
5421
3061
|
baseUrl: this.routingApiBaseUrl,
|
|
5422
3062
|
ownerAddress: owner,
|
|
@@ -5425,33 +3065,35 @@ var OwneySDK = class {
|
|
|
5425
3065
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
5426
3066
|
// PublicClient/WalletClient param types — structurally identical at
|
|
5427
3067
|
// runtime, but the two share a name TS treats as unrelated.
|
|
5428
|
-
getPublicClient: (cid) =>
|
|
5429
|
-
getWalletChainClient: (cid) => createPublicClient4({
|
|
3068
|
+
getPublicClient: (cid) => createPublicClient2({
|
|
5430
3069
|
chain: VIEM_CHAIN2[cid],
|
|
5431
|
-
transport:
|
|
3070
|
+
transport: custom(provider)
|
|
5432
3071
|
}),
|
|
5433
|
-
getWalletClient: (cid) =>
|
|
3072
|
+
getWalletClient: (cid) => createWalletClient({
|
|
5434
3073
|
account: owner,
|
|
5435
3074
|
chain: VIEM_CHAIN2[cid],
|
|
5436
|
-
transport:
|
|
3075
|
+
transport: custom(provider)
|
|
5437
3076
|
})
|
|
5438
3077
|
});
|
|
5439
3078
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
5440
3079
|
return callback;
|
|
5441
3080
|
}
|
|
5442
|
-
/**
|
|
3081
|
+
/**
|
|
3082
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
3083
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
3084
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
3085
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
3086
|
+
*/
|
|
5443
3087
|
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
5444
3088
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
5445
3089
|
if (!onApproved && cached) return cached;
|
|
5446
3090
|
const provider = this.requireConnectedProvider();
|
|
5447
3091
|
const callback = makeSponsoredCallsCallback({
|
|
5448
|
-
apiKey: this.apiKey,
|
|
5449
|
-
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
5450
3092
|
provider,
|
|
5451
3093
|
ownerAddress: this.state.walletAddress,
|
|
5452
3094
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
5453
3095
|
onApproved,
|
|
5454
|
-
tokenAddressByChain:
|
|
3096
|
+
tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
|
|
5455
3097
|
});
|
|
5456
3098
|
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
5457
3099
|
return callback;
|
|
@@ -5460,14 +3102,14 @@ var OwneySDK = class {
|
|
|
5460
3102
|
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
5461
3103
|
* callback used when the caller omits `depositCallback` for a WETH deposit.
|
|
5462
3104
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
5463
|
-
*
|
|
3105
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
5464
3106
|
*/
|
|
5465
3107
|
getDefaultWethSponsoredCallback(onApproved) {
|
|
5466
3108
|
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
5467
3109
|
return this.cachedWethSponsoredCallback;
|
|
5468
3110
|
const provider = this.requireConnectedProvider();
|
|
5469
3111
|
const owner = this.state.walletAddress;
|
|
5470
|
-
const callback =
|
|
3112
|
+
const callback = makeSponsoredWethCallback({
|
|
5471
3113
|
apiKey: this.apiKey,
|
|
5472
3114
|
baseUrl: this.routingApiBaseUrl,
|
|
5473
3115
|
ownerAddress: owner,
|
|
@@ -5476,15 +3118,14 @@ var OwneySDK = class {
|
|
|
5476
3118
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
5477
3119
|
// PublicClient/WalletClient param types — structurally identical at
|
|
5478
3120
|
// runtime, but the two share a name TS treats as unrelated.
|
|
5479
|
-
getPublicClient: (cid) =>
|
|
5480
|
-
getWalletChainClient: (cid) => createPublicClient4({
|
|
3121
|
+
getPublicClient: (cid) => createPublicClient2({
|
|
5481
3122
|
chain: VIEM_CHAIN2[cid],
|
|
5482
|
-
transport:
|
|
3123
|
+
transport: custom(provider)
|
|
5483
3124
|
}),
|
|
5484
|
-
getWalletClient: (cid) =>
|
|
3125
|
+
getWalletClient: (cid) => createWalletClient({
|
|
5485
3126
|
account: owner,
|
|
5486
3127
|
chain: VIEM_CHAIN2[cid],
|
|
5487
|
-
transport:
|
|
3128
|
+
transport: custom(provider)
|
|
5488
3129
|
})
|
|
5489
3130
|
});
|
|
5490
3131
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -5515,10 +3156,12 @@ var OwneySDK = class {
|
|
|
5515
3156
|
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
5516
3157
|
this.apiKey,
|
|
5517
3158
|
this.routingApiBaseUrl
|
|
5518
|
-
).then(
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
3159
|
+
).then(
|
|
3160
|
+
(config) => {
|
|
3161
|
+
this.orgAgentConfig = config;
|
|
3162
|
+
return config;
|
|
3163
|
+
}
|
|
3164
|
+
);
|
|
5522
3165
|
}
|
|
5523
3166
|
return this.orgAgentConfigPromise;
|
|
5524
3167
|
}
|
|
@@ -5558,14 +3201,7 @@ var OwneySDK = class {
|
|
|
5558
3201
|
this.routingApiBaseUrl
|
|
5559
3202
|
);
|
|
5560
3203
|
this.disabledAgents.clear();
|
|
5561
|
-
for (const {
|
|
5562
|
-
key: key2,
|
|
5563
|
-
agent_type,
|
|
5564
|
-
is_enabled,
|
|
5565
|
-
is_configured
|
|
5566
|
-
} of agentKeys) {
|
|
5567
|
-
const configured = is_configured ?? Boolean(key2);
|
|
5568
|
-
if (!configured) continue;
|
|
3204
|
+
for (const { key: key2, agent_type, is_enabled } of agentKeys) {
|
|
5569
3205
|
const agent = this.createAgent(agent_type, key2);
|
|
5570
3206
|
if (!agent) continue;
|
|
5571
3207
|
this.agents.set(agent_type, agent);
|
|
@@ -5589,23 +3225,7 @@ var OwneySDK = class {
|
|
|
5589
3225
|
}
|
|
5590
3226
|
createAgent(agentId, key2) {
|
|
5591
3227
|
if (agentId === "zyfai") {
|
|
5592
|
-
|
|
5593
|
-
return new ZyfaiAgent(
|
|
5594
|
-
key2,
|
|
5595
|
-
this.zyfaiRpcUrls ? resolveRpcUrls(
|
|
5596
|
-
this.zyfaiRpcUrls,
|
|
5597
|
-
this.apiKey,
|
|
5598
|
-
this.routingApiBaseUrl
|
|
5599
|
-
) : this.rpcUrls,
|
|
5600
|
-
this.referralSource
|
|
5601
|
-
);
|
|
5602
|
-
}
|
|
5603
|
-
if (agentId === "yieldseeker") {
|
|
5604
|
-
return new YieldseekerAgent(this.apiKey, {
|
|
5605
|
-
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5606
|
-
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
|
|
5607
|
-
rpcUrls: this.rpcUrls
|
|
5608
|
-
});
|
|
3228
|
+
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
5609
3229
|
}
|
|
5610
3230
|
return null;
|
|
5611
3231
|
}
|
|
@@ -5651,10 +3271,9 @@ var OwneySDK = class {
|
|
|
5651
3271
|
* If provided, ALL specified agents must support the chainId or the call
|
|
5652
3272
|
* throws before activating any agent.
|
|
5653
3273
|
*/
|
|
5654
|
-
async activateAgent(chainId, agentId
|
|
3274
|
+
async activateAgent(chainId, agentId) {
|
|
5655
3275
|
const state = this.requireState();
|
|
5656
3276
|
await this.ensureAgentsInitialized();
|
|
5657
|
-
this.assertActivationSession(state);
|
|
5658
3277
|
if (agentId !== void 0) {
|
|
5659
3278
|
if (agentId.length === 0) {
|
|
5660
3279
|
throw new OwneyError(
|
|
@@ -5688,7 +3307,7 @@ var OwneySDK = class {
|
|
|
5688
3307
|
this.activeAgents.add(id);
|
|
5689
3308
|
}
|
|
5690
3309
|
state.chainId = chainId;
|
|
5691
|
-
await this.activateAgentsInTurn(agents, state, chainId
|
|
3310
|
+
await this.activateAgentsInTurn(agents, state, chainId);
|
|
5692
3311
|
return;
|
|
5693
3312
|
}
|
|
5694
3313
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -5709,12 +3328,7 @@ var OwneySDK = class {
|
|
|
5709
3328
|
const enabledCompatible = compatible.filter(
|
|
5710
3329
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
5711
3330
|
);
|
|
5712
|
-
await this.activateAgentsInTurn(enabledCompatible, state, chainId
|
|
5713
|
-
}
|
|
5714
|
-
assertActivationSession(state) {
|
|
5715
|
-
if (this.state !== state) {
|
|
5716
|
-
throw new OwneyError("NOT_CONNECTED", "Wallet connection changed. Please try again.");
|
|
5717
|
-
}
|
|
3331
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId);
|
|
5718
3332
|
}
|
|
5719
3333
|
/**
|
|
5720
3334
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -5729,51 +3343,26 @@ var OwneySDK = class {
|
|
|
5729
3343
|
* Serializing costs no real wall-clock: the user can only approve one prompt
|
|
5730
3344
|
* at a time anyway.
|
|
5731
3345
|
*
|
|
5732
|
-
*
|
|
5733
|
-
*
|
|
5734
|
-
*
|
|
3346
|
+
* Every agent is attempted even if an earlier one fails, so one declined
|
|
3347
|
+
* signature can't deny the remaining agents their turn. The first failure is
|
|
3348
|
+
* rethrown (matching the previous `Promise.all` rejection) once all agents
|
|
3349
|
+
* have had a chance to activate.
|
|
5735
3350
|
*/
|
|
5736
|
-
async activateAgentsInTurn(agents, state, chainId
|
|
3351
|
+
async activateAgentsInTurn(agents, state, chainId) {
|
|
5737
3352
|
let firstError = null;
|
|
5738
|
-
const activatedAgentIds = [];
|
|
5739
|
-
const failedAgents = [];
|
|
5740
3353
|
for (const agent of agents) {
|
|
5741
|
-
this.assertActivationSession(state);
|
|
5742
3354
|
try {
|
|
5743
|
-
await agent.activateAgent(state, chainId
|
|
5744
|
-
this.assertActivationSession(state);
|
|
3355
|
+
await agent.activateAgent(state, chainId);
|
|
5745
3356
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
5746
|
-
this.assertActivationSession(state);
|
|
5747
|
-
activatedAgentIds.push(agent.id);
|
|
5748
3357
|
} catch (error) {
|
|
5749
|
-
this.assertActivationSession(state);
|
|
5750
|
-
const message = error !== null && typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : typeof error === "string" ? error : "No activation error message was returned.";
|
|
5751
|
-
failedAgents.push({
|
|
5752
|
-
agentId: agent.id,
|
|
5753
|
-
code: error instanceof OwneyError ? error.code : void 0,
|
|
5754
|
-
message,
|
|
5755
|
-
...error instanceof OwneyError && error.details ? { details: error.details } : {}
|
|
5756
|
-
});
|
|
5757
3358
|
if (firstError === null) {
|
|
5758
3359
|
firstError = error;
|
|
5759
3360
|
} else {
|
|
5760
3361
|
console.error(`activateAgent(${agent.id}) failed:`, error);
|
|
5761
3362
|
}
|
|
5762
|
-
break;
|
|
5763
3363
|
}
|
|
5764
3364
|
}
|
|
5765
|
-
if (firstError
|
|
5766
|
-
if (activatedAgentIds.length === 0) throw firstError;
|
|
5767
|
-
const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
|
|
5768
|
-
const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
|
|
5769
|
-
const failureMessages = failedAgents.map(
|
|
5770
|
-
({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
|
|
5771
|
-
).join(" ");
|
|
5772
|
-
throw new OwneyError(
|
|
5773
|
-
"AGENT_ACTIVATION_PARTIAL_FAILURE",
|
|
5774
|
-
`${activeNames} activated. ${failureMessages}`,
|
|
5775
|
-
{ activatedAgentIds, failedAgentIds, failures: failedAgents }
|
|
5776
|
-
);
|
|
3365
|
+
if (firstError !== null) throw firstError;
|
|
5777
3366
|
}
|
|
5778
3367
|
/**
|
|
5779
3368
|
* Deposit into a specific agent, or distribute across all eligible agents.
|
|
@@ -5783,8 +3372,7 @@ var OwneySDK = class {
|
|
|
5783
3372
|
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
5784
3373
|
* @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
|
|
5785
3374
|
* When agentId is omitted, this callback is invoked once per eligible agent with that agent's
|
|
5786
|
-
* split amount and smart wallet address
|
|
5787
|
-
* all shares into one signature; custom callbacks still run once per agent.
|
|
3375
|
+
* split amount and smart wallet address — expect multiple wallet prompts.
|
|
5788
3376
|
* @param options.agentId - Optional explicit target. Otherwise split equally,
|
|
5789
3377
|
* or fund remaining agents when a recovery deposit cannot meet every minimum.
|
|
5790
3378
|
* @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
|
|
@@ -5869,40 +3457,6 @@ var OwneySDK = class {
|
|
|
5869
3457
|
}
|
|
5870
3458
|
);
|
|
5871
3459
|
}
|
|
5872
|
-
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
5873
|
-
if (!depositCallback && batchTransfer) {
|
|
5874
|
-
return runAgentDepositBatch(
|
|
5875
|
-
chainId,
|
|
5876
|
-
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
5877
|
-
id: agent.id,
|
|
5878
|
-
amount: amount2,
|
|
5879
|
-
run: (callback) => withFailureReporting(
|
|
5880
|
-
this.apiKey,
|
|
5881
|
-
agent.id,
|
|
5882
|
-
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
5883
|
-
this.routingApiBaseUrl
|
|
5884
|
-
)
|
|
5885
|
-
})),
|
|
5886
|
-
async (cid, transfers) => {
|
|
5887
|
-
try {
|
|
5888
|
-
return await batchTransfer(cid, transfers);
|
|
5889
|
-
} catch (error) {
|
|
5890
|
-
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
5891
|
-
throw error;
|
|
5892
|
-
const requiredAmount = transfers.reduce(
|
|
5893
|
-
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
5894
|
-
0n
|
|
5895
|
-
);
|
|
5896
|
-
await this.approvePermit2(
|
|
5897
|
-
asset,
|
|
5898
|
-
requiredAmount,
|
|
5899
|
-
cid
|
|
5900
|
-
);
|
|
5901
|
-
return batchTransfer(cid, transfers);
|
|
5902
|
-
}
|
|
5903
|
-
}
|
|
5904
|
-
);
|
|
5905
|
-
}
|
|
5906
3460
|
const agentResults = {};
|
|
5907
3461
|
for (const [
|
|
5908
3462
|
index,
|
|
@@ -5940,12 +3494,10 @@ var OwneySDK = class {
|
|
|
5940
3494
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
5941
3495
|
* two independent auto-recovery mechanisms:
|
|
5942
3496
|
*
|
|
5943
|
-
* 1. Missing Permit2 allowance
|
|
5944
|
-
*
|
|
5945
|
-
*
|
|
5946
|
-
*
|
|
5947
|
-
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
5948
|
-
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
3497
|
+
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
3498
|
+
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
3499
|
+
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
3500
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
5949
3501
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5950
3502
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
5951
3503
|
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
@@ -5981,16 +3533,12 @@ var OwneySDK = class {
|
|
|
5981
3533
|
try {
|
|
5982
3534
|
return await attempt(effectiveCallback);
|
|
5983
3535
|
} catch (error) {
|
|
5984
|
-
if (!approvalAttempted && appCallback === void 0 &&
|
|
3536
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
5985
3537
|
approvalAttempted = true;
|
|
5986
3538
|
console.warn(
|
|
5987
|
-
"[owney-sdk] First
|
|
5988
|
-
);
|
|
5989
|
-
await this.approvePermit2(
|
|
5990
|
-
asset,
|
|
5991
|
-
BigInt(amount),
|
|
5992
|
-
chainId
|
|
3539
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
5993
3540
|
);
|
|
3541
|
+
await this.approvePermit2();
|
|
5994
3542
|
continue;
|
|
5995
3543
|
}
|
|
5996
3544
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -6028,10 +3576,10 @@ var OwneySDK = class {
|
|
|
6028
3576
|
agent,
|
|
6029
3577
|
amount: i === agents.length - 1 ? perAgent + remainder : perAgent
|
|
6030
3578
|
}));
|
|
6031
|
-
const
|
|
3579
|
+
const valid = splits.filter(
|
|
6032
3580
|
(s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
|
|
6033
3581
|
);
|
|
6034
|
-
if (
|
|
3582
|
+
if (valid.length === agents.length) {
|
|
6035
3583
|
return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
|
|
6036
3584
|
}
|
|
6037
3585
|
const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
|
|
@@ -6050,11 +3598,6 @@ var OwneySDK = class {
|
|
|
6050
3598
|
)
|
|
6051
3599
|
}));
|
|
6052
3600
|
}
|
|
6053
|
-
formatAgentName(agentId) {
|
|
6054
|
-
if (agentId === "zyfai") return "Zyfai";
|
|
6055
|
-
if (agentId === "yieldseeker") return "Yieldseeker";
|
|
6056
|
-
return agentId;
|
|
6057
|
-
}
|
|
6058
3601
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
6059
3602
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
6060
3603
|
const parsedAmount = BigInt(amount);
|
|
@@ -6086,12 +3629,12 @@ var OwneySDK = class {
|
|
|
6086
3629
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
6087
3630
|
);
|
|
6088
3631
|
const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
|
|
6089
|
-
const
|
|
3632
|
+
const position = (balance.positions ?? []).find((p) => {
|
|
6090
3633
|
const positionChain = p.chain.trim().toUpperCase();
|
|
6091
3634
|
const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
|
|
6092
3635
|
return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
|
|
6093
3636
|
});
|
|
6094
|
-
return !!token && Number(token.amount) > 0 || !!
|
|
3637
|
+
return !!token && Number(token.amount) > 0 || !!position;
|
|
6095
3638
|
} catch (error) {
|
|
6096
3639
|
if (requireReliableRead) {
|
|
6097
3640
|
throw new OwneyError(
|
|
@@ -6136,32 +3679,6 @@ var OwneySDK = class {
|
|
|
6136
3679
|
}
|
|
6137
3680
|
return eligible;
|
|
6138
3681
|
}
|
|
6139
|
-
/**
|
|
6140
|
-
* Run owner-approved withdrawals before relayer-only withdrawals. Wallet
|
|
6141
|
-
* approval is the only point at which the user can cancel the aggregate
|
|
6142
|
-
* operation, so no relayer leg should commit before it has completed.
|
|
6143
|
-
*/
|
|
6144
|
-
orderAgentsForWithdrawal(agents) {
|
|
6145
|
-
return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
|
|
6146
|
-
const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
|
|
6147
|
-
return approvalOrder || left.index - right.index;
|
|
6148
|
-
}).map(({ agent }) => agent);
|
|
6149
|
-
}
|
|
6150
|
-
isUserRejectedWithdrawal(error) {
|
|
6151
|
-
let current = error;
|
|
6152
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6153
|
-
while (current && typeof current === "object" && !seen.has(current)) {
|
|
6154
|
-
seen.add(current);
|
|
6155
|
-
const candidate = current;
|
|
6156
|
-
if (candidate.code === 4001 || candidate.code === "4001") return true;
|
|
6157
|
-
const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
|
|
6158
|
-
if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
|
|
6159
|
-
return true;
|
|
6160
|
-
}
|
|
6161
|
-
current = candidate.cause;
|
|
6162
|
-
}
|
|
6163
|
-
return false;
|
|
6164
|
-
}
|
|
6165
3682
|
// --- Fund operations ---
|
|
6166
3683
|
/**
|
|
6167
3684
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -6173,14 +3690,7 @@ var OwneySDK = class {
|
|
|
6173
3690
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
6174
3691
|
*/
|
|
6175
3692
|
async withdraw(options) {
|
|
6176
|
-
const { asset, amount, agentId
|
|
6177
|
-
const notifyAgentResult = (id, result) => {
|
|
6178
|
-
try {
|
|
6179
|
-
onAgentResult?.(id, result);
|
|
6180
|
-
} catch (error) {
|
|
6181
|
-
console.warn("Withdrawal result observer failed:", error);
|
|
6182
|
-
}
|
|
6183
|
-
};
|
|
3693
|
+
const { asset, amount, agentId } = options;
|
|
6184
3694
|
const state = this.requireState();
|
|
6185
3695
|
const chainId = this.requireChainId();
|
|
6186
3696
|
const token = asset;
|
|
@@ -6196,26 +3706,21 @@ var OwneySDK = class {
|
|
|
6196
3706
|
if (agentId) {
|
|
6197
3707
|
const agent = this.getAgent(agentId);
|
|
6198
3708
|
this.validateAssetSupport(agent, chainId, asset);
|
|
6199
|
-
|
|
3709
|
+
return withFailureReporting(
|
|
6200
3710
|
this.apiKey,
|
|
6201
3711
|
agent.id,
|
|
6202
3712
|
() => agent.withdraw(state, chainId, token, amount),
|
|
6203
3713
|
this.routingApiBaseUrl
|
|
6204
3714
|
);
|
|
6205
|
-
notifyAgentResult(agent.id, result);
|
|
6206
|
-
return result;
|
|
6207
3715
|
}
|
|
6208
3716
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6209
|
-
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
6210
3717
|
if (!amount) {
|
|
6211
3718
|
const results2 = {};
|
|
6212
3719
|
const agentErrors2 = {};
|
|
6213
|
-
for (const agent of
|
|
3720
|
+
for (const agent of eligibleAgents) {
|
|
6214
3721
|
try {
|
|
6215
3722
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
6216
|
-
notifyAgentResult(agent.id, results2[agent.id]);
|
|
6217
3723
|
} catch (err) {
|
|
6218
|
-
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6219
3724
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
6220
3725
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
6221
3726
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -6244,10 +3749,6 @@ var OwneySDK = class {
|
|
|
6244
3749
|
}
|
|
6245
3750
|
const requested = BigInt(amount);
|
|
6246
3751
|
const aggregated = await this.getBalances();
|
|
6247
|
-
const unavailableAgents = eligibleAgents.filter(
|
|
6248
|
-
(agent) => !(agent.id in aggregated.agentBalances)
|
|
6249
|
-
);
|
|
6250
|
-
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
6251
3752
|
const balances = projectAgentBalancesForAsset(
|
|
6252
3753
|
eligibleAgents,
|
|
6253
3754
|
aggregated.agentBalances,
|
|
@@ -6256,18 +3757,7 @@ var OwneySDK = class {
|
|
|
6256
3757
|
assetInfo.decimals
|
|
6257
3758
|
);
|
|
6258
3759
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
6259
|
-
if (totalAvailable
|
|
6260
|
-
throw new OwneyError(
|
|
6261
|
-
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
6262
|
-
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
6263
|
-
{
|
|
6264
|
-
asset,
|
|
6265
|
-
unavailableAgents: unavailableAgentIds,
|
|
6266
|
-
agentErrors: aggregated.agentErrors
|
|
6267
|
-
}
|
|
6268
|
-
);
|
|
6269
|
-
}
|
|
6270
|
-
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
3760
|
+
if (totalAvailable < requested) {
|
|
6271
3761
|
throw new OwneyError(
|
|
6272
3762
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
6273
3763
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -6278,7 +3768,6 @@ var OwneySDK = class {
|
|
|
6278
3768
|
}
|
|
6279
3769
|
);
|
|
6280
3770
|
}
|
|
6281
|
-
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
6282
3771
|
const disabledBalances = balances.filter(
|
|
6283
3772
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
6284
3773
|
);
|
|
@@ -6287,7 +3776,7 @@ var OwneySDK = class {
|
|
|
6287
3776
|
);
|
|
6288
3777
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
6289
3778
|
disabledBalances,
|
|
6290
|
-
|
|
3779
|
+
requested
|
|
6291
3780
|
);
|
|
6292
3781
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
6293
3782
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -6296,13 +3785,10 @@ var OwneySDK = class {
|
|
|
6296
3785
|
planned: 0n
|
|
6297
3786
|
}));
|
|
6298
3787
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
6299
|
-
const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
|
|
6300
3788
|
const results = {};
|
|
6301
|
-
const agentErrors = {
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
for (let i = 0; i < orderedPlans.length; i++) {
|
|
6305
|
-
const p = orderedPlans[i];
|
|
3789
|
+
const agentErrors = {};
|
|
3790
|
+
for (let i = 0; i < plans.length; i++) {
|
|
3791
|
+
const p = plans[i];
|
|
6306
3792
|
if (p.planned === 0n) continue;
|
|
6307
3793
|
try {
|
|
6308
3794
|
results[p.agent.id] = await p.agent.withdraw(
|
|
@@ -6311,9 +3797,7 @@ var OwneySDK = class {
|
|
|
6311
3797
|
token,
|
|
6312
3798
|
p.planned.toString()
|
|
6313
3799
|
);
|
|
6314
|
-
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
6315
3800
|
} catch (err) {
|
|
6316
|
-
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6317
3801
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
6318
3802
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
6319
3803
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -6325,7 +3809,7 @@ var OwneySDK = class {
|
|
|
6325
3809
|
);
|
|
6326
3810
|
const failedAmount = p.planned;
|
|
6327
3811
|
p.planned = 0n;
|
|
6328
|
-
redistributeShare(
|
|
3812
|
+
redistributeShare(plans, i, failedAmount);
|
|
6329
3813
|
}
|
|
6330
3814
|
}
|
|
6331
3815
|
if (Object.keys(results).length === 0) {
|
|
@@ -6349,8 +3833,7 @@ var OwneySDK = class {
|
|
|
6349
3833
|
requested: amount,
|
|
6350
3834
|
withdrawn: withdrawn.toString(),
|
|
6351
3835
|
partialResults: results,
|
|
6352
|
-
agentErrors
|
|
6353
|
-
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
3836
|
+
agentErrors
|
|
6354
3837
|
}
|
|
6355
3838
|
);
|
|
6356
3839
|
}
|
|
@@ -6368,10 +3851,7 @@ var OwneySDK = class {
|
|
|
6368
3851
|
if (agentId) {
|
|
6369
3852
|
const agent = this.getAgent(agentId);
|
|
6370
3853
|
const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
6371
|
-
return
|
|
6372
|
-
...result,
|
|
6373
|
-
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
6374
|
-
};
|
|
3854
|
+
return result;
|
|
6375
3855
|
}
|
|
6376
3856
|
let totalBalance = 0;
|
|
6377
3857
|
const results = {};
|
|
@@ -6379,13 +3859,7 @@ var OwneySDK = class {
|
|
|
6379
3859
|
const balanceResults = await Promise.allSettled(
|
|
6380
3860
|
entries.map(async ([id, agent]) => {
|
|
6381
3861
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
6382
|
-
return [
|
|
6383
|
-
id,
|
|
6384
|
-
{
|
|
6385
|
-
...b,
|
|
6386
|
-
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
6387
|
-
}
|
|
6388
|
-
];
|
|
3862
|
+
return [id, b];
|
|
6389
3863
|
})
|
|
6390
3864
|
);
|
|
6391
3865
|
let successCount = 0;
|
|
@@ -6407,7 +3881,6 @@ var OwneySDK = class {
|
|
|
6407
3881
|
const retryDelay = rateLimitDelay(reason);
|
|
6408
3882
|
if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
|
|
6409
3883
|
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
6410
|
-
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
|
|
6411
3884
|
}
|
|
6412
3885
|
if (successCount === 0) {
|
|
6413
3886
|
throw new OwneyError(
|
|
@@ -6586,10 +4059,7 @@ var OwneySDK = class {
|
|
|
6586
4059
|
Promise.all(
|
|
6587
4060
|
entries.map(async ([id, agent]) => {
|
|
6588
4061
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
6589
|
-
return [
|
|
6590
|
-
id,
|
|
6591
|
-
balanceForApyScope(b, chainId, tokenSymbol)
|
|
6592
|
-
];
|
|
4062
|
+
return [id, balanceForApyScope(b, chainId, tokenSymbol)];
|
|
6593
4063
|
})
|
|
6594
4064
|
)
|
|
6595
4065
|
]);
|
|
@@ -6750,95 +4220,51 @@ var OwneySDK = class {
|
|
|
6750
4220
|
return pending;
|
|
6751
4221
|
}
|
|
6752
4222
|
/**
|
|
6753
|
-
*
|
|
6754
|
-
*
|
|
6755
|
-
*
|
|
6756
|
-
*
|
|
6757
|
-
*
|
|
6758
|
-
*
|
|
6759
|
-
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
6760
|
-
* @param expectedChainId Chain captured by the deposit that requested approval.
|
|
4223
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
4224
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
4225
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
4226
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
4227
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
6761
4228
|
* @returns the approval transaction hash.
|
|
6762
4229
|
*/
|
|
6763
|
-
async approvePermit2(asset = "WETH"
|
|
4230
|
+
async approvePermit2(asset = "WETH") {
|
|
4231
|
+
void asset;
|
|
6764
4232
|
const state = this.requireState();
|
|
6765
|
-
const chainId =
|
|
6766
|
-
this.
|
|
6767
|
-
const token =
|
|
4233
|
+
const chainId = this.requireChainId();
|
|
4234
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
4235
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
6768
4236
|
if (!token) {
|
|
6769
4237
|
throw new OwneyError(
|
|
6770
4238
|
"CHAIN_UNSUPPORTED",
|
|
6771
|
-
`No sponsored
|
|
4239
|
+
`No sponsored WETH on chain ${chainId}`
|
|
6772
4240
|
);
|
|
6773
4241
|
}
|
|
6774
4242
|
const provider = this.requireConnectedProvider();
|
|
6775
|
-
const
|
|
6776
|
-
chain: VIEM_CHAIN2[chainId],
|
|
6777
|
-
transport: custom3(provider)
|
|
6778
|
-
});
|
|
6779
|
-
const publicClient = this.getPaidRpcClient(chainId);
|
|
6780
|
-
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6781
|
-
const wallet = createWalletClient3({
|
|
4243
|
+
const wallet = createWalletClient({
|
|
6782
4244
|
account: state.walletAddress,
|
|
6783
4245
|
chain: VIEM_CHAIN2[chainId],
|
|
6784
|
-
transport:
|
|
4246
|
+
transport: custom(provider)
|
|
6785
4247
|
});
|
|
6786
|
-
await ensureWalletOnChain(walletChainClient, wallet, chainId);
|
|
6787
4248
|
const hash = await wallet.writeContract({
|
|
6788
4249
|
address: token,
|
|
6789
4250
|
abi: ERC20_ALLOWANCE_ABI,
|
|
6790
4251
|
functionName: "approve",
|
|
6791
|
-
args: [PERMIT2_ADDRESS,
|
|
4252
|
+
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
6792
4253
|
account: state.walletAddress,
|
|
6793
4254
|
chain: VIEM_CHAIN2[chainId]
|
|
6794
4255
|
});
|
|
6795
|
-
const
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
);
|
|
4256
|
+
const publicClient = createPublicClient2({
|
|
4257
|
+
chain: VIEM_CHAIN2[chainId],
|
|
4258
|
+
transport: custom(provider)
|
|
4259
|
+
});
|
|
4260
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4261
|
+
hash,
|
|
4262
|
+
confirmations: 1
|
|
4263
|
+
});
|
|
6803
4264
|
if (receipt.status !== "success") {
|
|
6804
4265
|
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
6805
4266
|
}
|
|
6806
|
-
|
|
6807
|
-
let verificationError;
|
|
6808
|
-
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
|
|
6809
|
-
try {
|
|
6810
|
-
observedAllowance = await readPermit2Allowance(
|
|
6811
|
-
publicClient,
|
|
6812
|
-
token,
|
|
6813
|
-
state.walletAddress,
|
|
6814
|
-
attempt === 0 ? receipt.blockNumber : void 0
|
|
6815
|
-
);
|
|
6816
|
-
verificationError = void 0;
|
|
6817
|
-
if (observedAllowance >= requiredAmount) return hash;
|
|
6818
|
-
} catch (error) {
|
|
6819
|
-
verificationError = error;
|
|
6820
|
-
}
|
|
6821
|
-
if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
|
|
6822
|
-
await new Promise(
|
|
6823
|
-
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
|
|
6824
|
-
);
|
|
6825
|
-
}
|
|
6826
|
-
}
|
|
6827
|
-
throw new OwneyError(
|
|
6828
|
-
"PERMIT2_APPROVAL_REQUIRED",
|
|
6829
|
-
"Permit2 approval was confirmed, but the required token allowance was not observable.",
|
|
6830
|
-
{
|
|
6831
|
-
approvalConfirmed: true,
|
|
6832
|
-
approvalTxHash: hash,
|
|
6833
|
-
owner: state.walletAddress,
|
|
6834
|
-
token,
|
|
6835
|
-
spender: PERMIT2_ADDRESS,
|
|
6836
|
-
chainId,
|
|
6837
|
-
requiredAmount: requiredAmount.toString(),
|
|
6838
|
-
observedAllowance: observedAllowance.toString(),
|
|
6839
|
-
...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
|
|
6840
|
-
}
|
|
6841
|
-
);
|
|
4267
|
+
return hash;
|
|
6842
4268
|
}
|
|
6843
4269
|
// --- Discovery (no wallet required) ---
|
|
6844
4270
|
/**
|
|
@@ -6866,9 +4292,7 @@ var OwneySDK = class {
|
|
|
6866
4292
|
return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
6867
4293
|
}
|
|
6868
4294
|
const results = {};
|
|
6869
|
-
const agentEntries = [...this.agents.entries()]
|
|
6870
|
-
([id]) => !this.isAgentDisabled(id)
|
|
6871
|
-
);
|
|
4295
|
+
const agentEntries = [...this.agents.entries()];
|
|
6872
4296
|
const apyResults = await Promise.all(
|
|
6873
4297
|
agentEntries.map(async ([id, agent]) => {
|
|
6874
4298
|
const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
@@ -6943,13 +4367,13 @@ var OwneySDK = class {
|
|
|
6943
4367
|
};
|
|
6944
4368
|
|
|
6945
4369
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
6946
|
-
import { getAddress
|
|
6947
|
-
import { SiweMessage
|
|
4370
|
+
import { getAddress } from "viem";
|
|
4371
|
+
import { SiweMessage } from "siwe";
|
|
6948
4372
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
6949
4373
|
|
|
6950
4374
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
6951
|
-
var
|
|
6952
|
-
var
|
|
4375
|
+
var KEY_PREFIX2 = "owney.siwx.session";
|
|
4376
|
+
var storage2 = () => {
|
|
6953
4377
|
if (typeof window === "undefined") return null;
|
|
6954
4378
|
try {
|
|
6955
4379
|
return window.localStorage;
|
|
@@ -6957,8 +4381,8 @@ var storage4 = () => {
|
|
|
6957
4381
|
return null;
|
|
6958
4382
|
}
|
|
6959
4383
|
};
|
|
6960
|
-
var
|
|
6961
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
4384
|
+
var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
|
|
4385
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
|
|
6962
4386
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
6963
4387
|
var readLegacySiwxSession = (store, address) => {
|
|
6964
4388
|
if (!store) return null;
|
|
@@ -6989,17 +4413,17 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
6989
4413
|
};
|
|
6990
4414
|
var readSiwxSession = (address, chainId) => {
|
|
6991
4415
|
if (typeof window === "undefined") return null;
|
|
6992
|
-
const key2 =
|
|
6993
|
-
const store =
|
|
6994
|
-
let
|
|
4416
|
+
const key2 = buildKey2(address);
|
|
4417
|
+
const store = storage2();
|
|
4418
|
+
let raw = null;
|
|
6995
4419
|
try {
|
|
6996
|
-
|
|
4420
|
+
raw = store?.getItem(key2) ?? null;
|
|
6997
4421
|
} catch {
|
|
6998
|
-
|
|
4422
|
+
raw = null;
|
|
6999
4423
|
}
|
|
7000
|
-
if (
|
|
4424
|
+
if (raw) {
|
|
7001
4425
|
try {
|
|
7002
|
-
return JSON.parse(
|
|
4426
|
+
return JSON.parse(raw);
|
|
7003
4427
|
} catch {
|
|
7004
4428
|
memorySiwxSessions.delete(key2);
|
|
7005
4429
|
try {
|
|
@@ -7018,18 +4442,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
7018
4442
|
};
|
|
7019
4443
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
7020
4444
|
if (typeof window === "undefined") return;
|
|
7021
|
-
const key2 =
|
|
4445
|
+
const key2 = buildKey2(address);
|
|
7022
4446
|
memorySiwxSessions.set(key2, session);
|
|
7023
|
-
const store =
|
|
4447
|
+
const store = storage2();
|
|
7024
4448
|
try {
|
|
7025
4449
|
store?.setItem(key2, JSON.stringify(session));
|
|
7026
4450
|
} catch {
|
|
7027
4451
|
}
|
|
7028
4452
|
};
|
|
7029
4453
|
var clearSiwxSession = (address, _chainId) => {
|
|
7030
|
-
const key2 =
|
|
4454
|
+
const key2 = buildKey2(address);
|
|
7031
4455
|
memorySiwxSessions.delete(key2);
|
|
7032
|
-
const store =
|
|
4456
|
+
const store = storage2();
|
|
7033
4457
|
try {
|
|
7034
4458
|
store?.removeItem(key2);
|
|
7035
4459
|
} catch {
|
|
@@ -7069,8 +4493,8 @@ function buildSIWXConfig(deps) {
|
|
|
7069
4493
|
statement: STATEMENT,
|
|
7070
4494
|
issuedAt,
|
|
7071
4495
|
toString() {
|
|
7072
|
-
return new
|
|
7073
|
-
address:
|
|
4496
|
+
return new SiweMessage({
|
|
4497
|
+
address: getAddress(accountAddress),
|
|
7074
4498
|
chainId: numericChainId(chainId),
|
|
7075
4499
|
domain,
|
|
7076
4500
|
uri,
|
|
@@ -7112,7 +4536,7 @@ function buildSIWXConfig(deps) {
|
|
|
7112
4536
|
const persistSession = async (session) => {
|
|
7113
4537
|
const address = session.data.accountAddress;
|
|
7114
4538
|
const id = numericChainId(session.data.chainId);
|
|
7115
|
-
const message = new
|
|
4539
|
+
const message = new SiweMessage(session.message);
|
|
7116
4540
|
const login = await post("/auth/login", {
|
|
7117
4541
|
message,
|
|
7118
4542
|
signature: session.signature,
|
|
@@ -7161,7 +4585,6 @@ export {
|
|
|
7161
4585
|
NotConnectedError,
|
|
7162
4586
|
OwneyError,
|
|
7163
4587
|
OwneySDK,
|
|
7164
|
-
YieldseekerAgent,
|
|
7165
4588
|
createOwneySIWX,
|
|
7166
4589
|
setOwneyDebug
|
|
7167
4590
|
};
|