@owney/sdk 0.7.25-beta.1 → 0.7.25-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -14
- package/dist/index.cjs +971 -290
- package/dist/index.d.cts +228 -157
- package/dist/index.d.ts +228 -157
- package/dist/index.js +917 -233
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -125,6 +125,34 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// 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
|
+
}
|
|
128
156
|
var AgentReads = class {
|
|
129
157
|
inFlight = /* @__PURE__ */ new Map();
|
|
130
158
|
cooldowns = /* @__PURE__ */ new Map();
|
|
@@ -132,14 +160,15 @@ var AgentReads = class {
|
|
|
132
160
|
clearInFlight() {
|
|
133
161
|
this.inFlight.clear();
|
|
134
162
|
}
|
|
135
|
-
limited(agentId, until) {
|
|
163
|
+
limited(agentId, until, diagnostics) {
|
|
136
164
|
return new OwneyError(
|
|
137
165
|
"AGENT_RATE_LIMITED",
|
|
138
166
|
"Too many requests. Please wait before trying again.",
|
|
139
167
|
{
|
|
140
168
|
statusCode: 429,
|
|
141
169
|
retryAt: until,
|
|
142
|
-
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
|
|
170
|
+
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3)),
|
|
171
|
+
...diagnostics
|
|
143
172
|
},
|
|
144
173
|
agentId
|
|
145
174
|
);
|
|
@@ -147,7 +176,9 @@ var AgentReads = class {
|
|
|
147
176
|
run(agentId, key2, fetch2) {
|
|
148
177
|
const cooldown = this.cooldowns.get(agentId);
|
|
149
178
|
if (cooldown && cooldown.until > Date.now()) {
|
|
150
|
-
return Promise.reject(
|
|
179
|
+
return Promise.reject(
|
|
180
|
+
this.limited(agentId, cooldown.until, cooldown.diagnostics)
|
|
181
|
+
);
|
|
151
182
|
}
|
|
152
183
|
const requestKey = JSON.stringify([agentId, key2]);
|
|
153
184
|
const existing = this.inFlight.get(requestKey);
|
|
@@ -168,8 +199,9 @@ var AgentReads = class {
|
|
|
168
199
|
Math.min(3e4 * 2 ** (failures - 1), 3e5)
|
|
169
200
|
);
|
|
170
201
|
const until = Math.max(previous?.until ?? 0, Date.now() + delay);
|
|
171
|
-
|
|
172
|
-
|
|
202
|
+
const diagnostics = rateLimitDiagnostics(error);
|
|
203
|
+
this.cooldowns.set(agentId, { until, failures, diagnostics });
|
|
204
|
+
throw this.limited(agentId, until, diagnostics);
|
|
173
205
|
}
|
|
174
206
|
).finally(() => {
|
|
175
207
|
if (this.inFlight.get(requestKey) === promise)
|
|
@@ -182,7 +214,7 @@ var AgentReads = class {
|
|
|
182
214
|
|
|
183
215
|
// src/agents/zyfai/zyfai.agent.ts
|
|
184
216
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
185
|
-
import {
|
|
217
|
+
import { parseAbi } from "viem";
|
|
186
218
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
187
219
|
|
|
188
220
|
// src/types/config.ts
|
|
@@ -1063,15 +1095,148 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
1063
1095
|
return !protocolListsEqual(current.protocols, desiredProtocols);
|
|
1064
1096
|
}
|
|
1065
1097
|
|
|
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
|
+
|
|
1066
1236
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1067
1237
|
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
|
|
1068
1238
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1069
1239
|
]);
|
|
1070
|
-
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
1071
|
-
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1072
|
-
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1073
|
-
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
1074
|
-
};
|
|
1075
1240
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
1076
1241
|
8453: "0x4200000000000000000000000000000000000006",
|
|
1077
1242
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
@@ -1137,7 +1302,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1137
1302
|
earningsSnapshot = null;
|
|
1138
1303
|
earningsGeneration = 0;
|
|
1139
1304
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1140
|
-
this.rpcUrls = rpcUrls
|
|
1305
|
+
this.rpcUrls = resolveRpcUrls(rpcUrls);
|
|
1141
1306
|
this.sdk = new ZyfaiSDK({
|
|
1142
1307
|
apiKey,
|
|
1143
1308
|
rpcUrls: this.rpcUrls,
|
|
@@ -1151,10 +1316,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1151
1316
|
getPublicClient(chainId) {
|
|
1152
1317
|
const cached = this.publicClients.get(chainId);
|
|
1153
1318
|
if (cached) return cached;
|
|
1154
|
-
const client =
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1319
|
+
const client = createPaidRpcClient(
|
|
1320
|
+
VIEM_CHAIN[chainId],
|
|
1321
|
+
this.rpcUrls
|
|
1322
|
+
);
|
|
1158
1323
|
this.publicClients.set(chainId, client);
|
|
1159
1324
|
return client;
|
|
1160
1325
|
}
|
|
@@ -2287,11 +2452,12 @@ async function postSponsorBatchTransfer(input) {
|
|
|
2287
2452
|
// src/lib/permit2.ts
|
|
2288
2453
|
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2289
2454
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2455
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2290
2456
|
function permit2ApprovalAmount(requiredAmount) {
|
|
2291
2457
|
if (requiredAmount <= 0n) {
|
|
2292
2458
|
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2293
2459
|
}
|
|
2294
|
-
return
|
|
2460
|
+
return MAX_UINT256;
|
|
2295
2461
|
}
|
|
2296
2462
|
var ERC20_ALLOWANCE_ABI = [
|
|
2297
2463
|
{
|
|
@@ -2327,12 +2493,13 @@ function randomPermit2Nonce() {
|
|
|
2327
2493
|
globalThis.crypto.getRandomValues(bytes);
|
|
2328
2494
|
return BigInt(bytesToHex2(bytes));
|
|
2329
2495
|
}
|
|
2330
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2496
|
+
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
2331
2497
|
return publicClient.readContract({
|
|
2332
2498
|
address: token,
|
|
2333
2499
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2334
2500
|
functionName: "allowance",
|
|
2335
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2501
|
+
args: [owner, PERMIT2_ADDRESS],
|
|
2502
|
+
...blockNumber === void 0 ? {} : { blockNumber }
|
|
2336
2503
|
});
|
|
2337
2504
|
}
|
|
2338
2505
|
async function readErc20Balance(publicClient, token, owner) {
|
|
@@ -2720,10 +2887,11 @@ var YieldseekerApiClient = class {
|
|
|
2720
2887
|
const payload = await response.json().catch(() => null);
|
|
2721
2888
|
if (!response.ok) {
|
|
2722
2889
|
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2890
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
2723
2891
|
throw new YieldseekerApiError(
|
|
2724
2892
|
response.status,
|
|
2725
2893
|
error.code,
|
|
2726
|
-
error.fields
|
|
2894
|
+
retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
|
|
2727
2895
|
);
|
|
2728
2896
|
}
|
|
2729
2897
|
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
@@ -2854,13 +3022,14 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
2854
3022
|
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2855
3023
|
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2856
3024
|
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2857
|
-
// share quantity
|
|
3025
|
+
// share quantity separate from the withdrawable underlying asset amount.
|
|
2858
3026
|
amount: decimal(
|
|
2859
3027
|
value.assetsBase,
|
|
2860
3028
|
baseAssetDecimals,
|
|
2861
3029
|
"yield positions"
|
|
2862
3030
|
),
|
|
2863
3031
|
amountRaw: String(value.assetsRaw),
|
|
3032
|
+
withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
|
|
2864
3033
|
apy: percent(option.riskAdjustedApy),
|
|
2865
3034
|
tvl: Number(option.totalDepositsUsd),
|
|
2866
3035
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -3185,10 +3354,18 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
3185
3354
|
|
|
3186
3355
|
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3187
3356
|
var OWNEY_AGENT_NAME = "owney";
|
|
3357
|
+
var OWNEY_AGENT_RULE_PRESET = "explorative";
|
|
3188
3358
|
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3189
3359
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3190
3360
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3191
3361
|
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3362
|
+
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
3363
|
+
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
3364
|
+
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
3365
|
+
var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
3366
|
+
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
3367
|
+
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
3368
|
+
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3192
3369
|
function generateYieldseekerUsername() {
|
|
3193
3370
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3194
3371
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -3232,6 +3409,7 @@ function query(params) {
|
|
|
3232
3409
|
var YieldseekerAgent = class {
|
|
3233
3410
|
id = "yieldseeker";
|
|
3234
3411
|
balanceComposition = "tokens-plus-positions";
|
|
3412
|
+
withdrawalRequiresWalletApproval = true;
|
|
3235
3413
|
supportedChainIds = [8453];
|
|
3236
3414
|
supportedAssets = [
|
|
3237
3415
|
{
|
|
@@ -3245,11 +3423,26 @@ var YieldseekerAgent = class {
|
|
|
3245
3423
|
];
|
|
3246
3424
|
api;
|
|
3247
3425
|
auth;
|
|
3426
|
+
rpcUrls;
|
|
3427
|
+
receiptClient;
|
|
3248
3428
|
transactionExecutor;
|
|
3249
3429
|
unwindReceiptWaiter;
|
|
3250
3430
|
agentContexts = /* @__PURE__ */ new Map();
|
|
3251
3431
|
users = /* @__PURE__ */ new Map();
|
|
3252
3432
|
pendingAgents = /* @__PURE__ */ new Map();
|
|
3433
|
+
pendingWalletContexts = /* @__PURE__ */ new Map();
|
|
3434
|
+
readCache = /* @__PURE__ */ new Map();
|
|
3435
|
+
pendingReads = /* @__PURE__ */ new Map();
|
|
3436
|
+
readGeneration = 0;
|
|
3437
|
+
portfolioVersions = /* @__PURE__ */ new Map();
|
|
3438
|
+
snapshotFailures = /* @__PURE__ */ new Map();
|
|
3439
|
+
standardReadFailures = /* @__PURE__ */ new Map();
|
|
3440
|
+
reconcileUntil = /* @__PURE__ */ new Map();
|
|
3441
|
+
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
3442
|
+
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
3443
|
+
// settlement; keep slow reconciliation after the fast window expires.
|
|
3444
|
+
snapshotMovements = /* @__PURE__ */ new Map();
|
|
3445
|
+
movementEpochs = /* @__PURE__ */ new Map();
|
|
3253
3446
|
yieldOptions = /* @__PURE__ */ new Map();
|
|
3254
3447
|
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3255
3448
|
constructor(owneyApiKey, options = {}) {
|
|
@@ -3259,10 +3452,16 @@ var YieldseekerAgent = class {
|
|
|
3259
3452
|
options.fetchFn
|
|
3260
3453
|
);
|
|
3261
3454
|
this.auth = new YieldseekerAuth(options.auth);
|
|
3455
|
+
this.rpcUrls = options.rpcUrls;
|
|
3262
3456
|
this.transactionExecutor = options.transactionExecutor;
|
|
3263
3457
|
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3264
3458
|
}
|
|
3459
|
+
getReceiptClient() {
|
|
3460
|
+
this.receiptClient ??= createPaidRpcClient(base3, this.rpcUrls);
|
|
3461
|
+
return this.receiptClient;
|
|
3462
|
+
}
|
|
3265
3463
|
async disconnect() {
|
|
3464
|
+
this.readGeneration += 1;
|
|
3266
3465
|
this.auth.clear();
|
|
3267
3466
|
for (const key2 of this.users.keys()) {
|
|
3268
3467
|
const [walletAddress, chainId] = key2.split(":");
|
|
@@ -3271,6 +3470,16 @@ var YieldseekerAgent = class {
|
|
|
3271
3470
|
this.users.clear();
|
|
3272
3471
|
this.agentContexts.clear();
|
|
3273
3472
|
this.pendingAgents.clear();
|
|
3473
|
+
this.pendingWalletContexts.clear();
|
|
3474
|
+
this.readCache.clear();
|
|
3475
|
+
this.pendingReads.clear();
|
|
3476
|
+
this.portfolioVersions.clear();
|
|
3477
|
+
this.snapshotFailures.clear();
|
|
3478
|
+
this.standardReadFailures.clear();
|
|
3479
|
+
this.reconcileUntil.clear();
|
|
3480
|
+
this.activityRefreshUntil.clear();
|
|
3481
|
+
this.snapshotMovements.clear();
|
|
3482
|
+
this.movementEpochs.clear();
|
|
3274
3483
|
}
|
|
3275
3484
|
async activateAgent(state, chainId, asset) {
|
|
3276
3485
|
this.assertChain(chainId);
|
|
@@ -3290,41 +3499,37 @@ var YieldseekerAgent = class {
|
|
|
3290
3499
|
);
|
|
3291
3500
|
}
|
|
3292
3501
|
const context = await this.ensureAgent(state, chainId, asset);
|
|
3502
|
+
const generation = this.readGeneration;
|
|
3503
|
+
const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
|
|
3293
3504
|
let txHash;
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
context.wallet.walletAddress,
|
|
3304
|
-
chainId,
|
|
3305
|
-
amount
|
|
3306
|
-
);
|
|
3307
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3308
|
-
} else {
|
|
3309
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3310
|
-
from: getAddress2(state.walletAddress),
|
|
3311
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3312
|
-
data: encodeFunctionData({
|
|
3313
|
-
abi: erc20Abi,
|
|
3314
|
-
functionName: "transfer",
|
|
3315
|
-
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3316
|
-
}),
|
|
3317
|
-
value: "0",
|
|
3318
|
-
chainId
|
|
3319
|
-
});
|
|
3320
|
-
}
|
|
3321
|
-
} finally {
|
|
3322
|
-
await this.refreshSnapshotAfterMovement(
|
|
3323
|
-
state,
|
|
3505
|
+
if (depositCallback) {
|
|
3506
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3507
|
+
agentId: "yieldseeker",
|
|
3508
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3509
|
+
userId: context.user.userId,
|
|
3510
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3511
|
+
});
|
|
3512
|
+
txHash = await depositCallback(
|
|
3513
|
+
context.wallet.walletAddress,
|
|
3324
3514
|
chainId,
|
|
3325
|
-
|
|
3326
|
-
"deposit"
|
|
3515
|
+
amount
|
|
3327
3516
|
);
|
|
3517
|
+
await this.waitForReceipt(state, chainId, txHash);
|
|
3518
|
+
} else {
|
|
3519
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3520
|
+
from: getAddress2(state.walletAddress),
|
|
3521
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3522
|
+
data: encodeFunctionData({
|
|
3523
|
+
abi: erc20Abi,
|
|
3524
|
+
functionName: "transfer",
|
|
3525
|
+
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3526
|
+
}),
|
|
3527
|
+
value: "0",
|
|
3528
|
+
chainId
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3531
|
+
if (this.readGeneration === generation) {
|
|
3532
|
+
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3328
3533
|
}
|
|
3329
3534
|
return {
|
|
3330
3535
|
txHash,
|
|
@@ -3352,6 +3557,9 @@ var YieldseekerAgent = class {
|
|
|
3352
3557
|
this.id
|
|
3353
3558
|
);
|
|
3354
3559
|
}
|
|
3560
|
+
const generation = this.readGeneration;
|
|
3561
|
+
let confirmedMovement = false;
|
|
3562
|
+
let settlement = null;
|
|
3355
3563
|
try {
|
|
3356
3564
|
const portfolio = await this.loadPortfolioContext(
|
|
3357
3565
|
state,
|
|
@@ -3369,6 +3577,7 @@ var YieldseekerAgent = class {
|
|
|
3369
3577
|
);
|
|
3370
3578
|
const totalAvailable = idle + deployed;
|
|
3371
3579
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3580
|
+
const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
|
|
3372
3581
|
if (requested > totalAvailable) {
|
|
3373
3582
|
throw new OwneyError(
|
|
3374
3583
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -3404,6 +3613,7 @@ var YieldseekerAgent = class {
|
|
|
3404
3613
|
throw this.invalidResponse("position withdrawal");
|
|
3405
3614
|
}
|
|
3406
3615
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3616
|
+
confirmedMovement = true;
|
|
3407
3617
|
remaining -= assetsRaw;
|
|
3408
3618
|
}
|
|
3409
3619
|
if (remaining > 0n) {
|
|
@@ -3428,18 +3638,23 @@ var YieldseekerAgent = class {
|
|
|
3428
3638
|
value: "0",
|
|
3429
3639
|
chainId
|
|
3430
3640
|
});
|
|
3641
|
+
confirmedMovement = true;
|
|
3642
|
+
settlement = plannedMovement;
|
|
3431
3643
|
return {
|
|
3432
3644
|
txHash,
|
|
3433
3645
|
type: amount === void 0 ? "full" : "partial",
|
|
3434
3646
|
amount: requested.toString()
|
|
3435
3647
|
};
|
|
3436
3648
|
} finally {
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3649
|
+
if (confirmedMovement && this.readGeneration === generation) {
|
|
3650
|
+
void this.refreshSnapshotAfterMovement(
|
|
3651
|
+
state,
|
|
3652
|
+
chainId,
|
|
3653
|
+
context,
|
|
3654
|
+
"withdrawal",
|
|
3655
|
+
settlement
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3443
3658
|
}
|
|
3444
3659
|
}
|
|
3445
3660
|
async getBalances(state, chainId) {
|
|
@@ -3464,7 +3679,7 @@ var YieldseekerAgent = class {
|
|
|
3464
3679
|
this.assertChain(chainId);
|
|
3465
3680
|
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3466
3681
|
if (asset !== void 0) this.assertAsset(asset);
|
|
3467
|
-
const contexts = await this.
|
|
3682
|
+
const contexts = await this.loadActivityPortfolio(state, chainId, {
|
|
3468
3683
|
...asset ? { asset } : {},
|
|
3469
3684
|
historic: true,
|
|
3470
3685
|
actions: true
|
|
@@ -3491,7 +3706,7 @@ var YieldseekerAgent = class {
|
|
|
3491
3706
|
this.assertChain(chainId);
|
|
3492
3707
|
return mapYieldseekerProfile(
|
|
3493
3708
|
state.walletAddress,
|
|
3494
|
-
await this.
|
|
3709
|
+
await this.loadActivityPortfolio(state, chainId, {})
|
|
3495
3710
|
);
|
|
3496
3711
|
}
|
|
3497
3712
|
async getAgentApy(days, options) {
|
|
@@ -3533,6 +3748,95 @@ var YieldseekerAgent = class {
|
|
|
3533
3748
|
contextKey(state, chainId, asset) {
|
|
3534
3749
|
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3535
3750
|
}
|
|
3751
|
+
assertSession(generation, chainId, asset) {
|
|
3752
|
+
if (this.readGeneration !== generation) {
|
|
3753
|
+
throw new OwneyError(
|
|
3754
|
+
"NOT_CONNECTED",
|
|
3755
|
+
"Wallet session changed during the Yieldseeker request.",
|
|
3756
|
+
{ rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
|
|
3757
|
+
this.id
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
cachedRead(key2, ttlMs, read2) {
|
|
3762
|
+
const cached = this.readCache.get(key2);
|
|
3763
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
3764
|
+
return Promise.resolve(cached.value);
|
|
3765
|
+
}
|
|
3766
|
+
const pending = this.pendingReads.get(key2);
|
|
3767
|
+
if (pending) return pending;
|
|
3768
|
+
const generation = this.readGeneration;
|
|
3769
|
+
const request = Promise.resolve().then(read2).then((value) => {
|
|
3770
|
+
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
3771
|
+
this.readCache.set(key2, {
|
|
3772
|
+
expiresAt: Date.now() + ttlMs,
|
|
3773
|
+
value
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
return value;
|
|
3777
|
+
}).finally(() => {
|
|
3778
|
+
if (this.pendingReads.get(key2) === request) {
|
|
3779
|
+
this.pendingReads.delete(key2);
|
|
3780
|
+
}
|
|
3781
|
+
});
|
|
3782
|
+
this.pendingReads.set(key2, request);
|
|
3783
|
+
return request;
|
|
3784
|
+
}
|
|
3785
|
+
agentListKey(state, chainId) {
|
|
3786
|
+
return `agents:${this.userKey(state, chainId)}`;
|
|
3787
|
+
}
|
|
3788
|
+
async listAgents(state, chainId, user) {
|
|
3789
|
+
const response = await this.cachedRead(
|
|
3790
|
+
this.agentListKey(state, chainId),
|
|
3791
|
+
YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
3792
|
+
async () => {
|
|
3793
|
+
const response2 = await this.walletRequest(
|
|
3794
|
+
state,
|
|
3795
|
+
chainId,
|
|
3796
|
+
`/users/${user.userId}/agents`
|
|
3797
|
+
);
|
|
3798
|
+
if (!Array.isArray(response2?.agents)) {
|
|
3799
|
+
throw this.invalidResponse("agent list");
|
|
3800
|
+
}
|
|
3801
|
+
return response2;
|
|
3802
|
+
}
|
|
3803
|
+
);
|
|
3804
|
+
return response.agents;
|
|
3805
|
+
}
|
|
3806
|
+
async contextForAgent(state, chainId, user, agent, asset) {
|
|
3807
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3808
|
+
const cached = this.agentContexts.get(key2);
|
|
3809
|
+
if (cached?.agent.agentId === agent.agentId) return cached;
|
|
3810
|
+
const pending = this.pendingWalletContexts.get(key2);
|
|
3811
|
+
if (pending) return pending;
|
|
3812
|
+
const generation = this.readGeneration;
|
|
3813
|
+
const request = this.walletRequest(
|
|
3814
|
+
state,
|
|
3815
|
+
chainId,
|
|
3816
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3817
|
+
).then((walletResponse) => {
|
|
3818
|
+
this.assertSession(generation, chainId, asset);
|
|
3819
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3820
|
+
throw this.invalidResponse("agent wallet");
|
|
3821
|
+
}
|
|
3822
|
+
const context = {
|
|
3823
|
+
user,
|
|
3824
|
+
agent,
|
|
3825
|
+
wallet: walletResponse.agentWallet,
|
|
3826
|
+
asset
|
|
3827
|
+
};
|
|
3828
|
+
this.agentContexts.set(key2, context);
|
|
3829
|
+
return context;
|
|
3830
|
+
});
|
|
3831
|
+
this.pendingWalletContexts.set(key2, request);
|
|
3832
|
+
try {
|
|
3833
|
+
return await request;
|
|
3834
|
+
} finally {
|
|
3835
|
+
if (this.pendingWalletContexts.get(key2) === request) {
|
|
3836
|
+
this.pendingWalletContexts.delete(key2);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3536
3840
|
async resolveUser(state, chainId) {
|
|
3537
3841
|
const key2 = this.userKey(state, chainId);
|
|
3538
3842
|
const inMemory = this.users.get(key2);
|
|
@@ -3543,6 +3847,7 @@ var YieldseekerAgent = class {
|
|
|
3543
3847
|
return persisted;
|
|
3544
3848
|
}
|
|
3545
3849
|
const walletAddress = getAddress2(state.walletAddress);
|
|
3850
|
+
const generation = this.readGeneration;
|
|
3546
3851
|
let user = null;
|
|
3547
3852
|
try {
|
|
3548
3853
|
const login = await this.providerRequest(
|
|
@@ -3589,6 +3894,7 @@ var YieldseekerAgent = class {
|
|
|
3589
3894
|
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3590
3895
|
throw this.invalidResponse("wallet identity");
|
|
3591
3896
|
}
|
|
3897
|
+
this.assertSession(generation, chainId);
|
|
3592
3898
|
const resolved = { userId: user.userId };
|
|
3593
3899
|
this.users.set(key2, resolved);
|
|
3594
3900
|
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
@@ -3604,10 +3910,13 @@ var YieldseekerAgent = class {
|
|
|
3604
3910
|
if (cached) return cached;
|
|
3605
3911
|
const pending = this.pendingAgents.get(key2);
|
|
3606
3912
|
if (pending) return pending;
|
|
3913
|
+
const generation = this.readGeneration;
|
|
3607
3914
|
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3608
3915
|
async (context) => {
|
|
3916
|
+
this.assertSession(generation, chainId, asset);
|
|
3609
3917
|
if (!context) throw this.invalidResponse("agent creation");
|
|
3610
3918
|
await this.deployAgent(state, chainId, context);
|
|
3919
|
+
this.assertSession(generation, chainId, asset);
|
|
3611
3920
|
this.agentContexts.set(key2, context);
|
|
3612
3921
|
return context;
|
|
3613
3922
|
}
|
|
@@ -3616,29 +3925,27 @@ var YieldseekerAgent = class {
|
|
|
3616
3925
|
try {
|
|
3617
3926
|
return await request;
|
|
3618
3927
|
} finally {
|
|
3619
|
-
this.pendingAgents.delete(key2);
|
|
3928
|
+
if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
|
|
3620
3929
|
}
|
|
3621
3930
|
}
|
|
3622
3931
|
async findAgent(state, chainId, asset) {
|
|
3623
3932
|
const key2 = this.contextKey(state, chainId, asset);
|
|
3624
3933
|
const cached = this.agentContexts.get(key2);
|
|
3625
3934
|
if (cached) return cached;
|
|
3935
|
+
const generation = this.readGeneration;
|
|
3626
3936
|
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3937
|
+
this.assertSession(generation, chainId, asset);
|
|
3627
3938
|
if (context) this.agentContexts.set(key2, context);
|
|
3628
3939
|
return context;
|
|
3629
3940
|
}
|
|
3630
3941
|
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3942
|
+
const generation = this.readGeneration;
|
|
3631
3943
|
const user = await this.resolveUser(state, chainId);
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
`/users/${user.userId}/agents`
|
|
3636
|
-
);
|
|
3637
|
-
if (!Array.isArray(response?.agents)) {
|
|
3638
|
-
throw this.invalidResponse("agent list");
|
|
3639
|
-
}
|
|
3944
|
+
this.assertSession(generation, chainId, asset);
|
|
3945
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
3946
|
+
this.assertSession(generation, chainId, asset);
|
|
3640
3947
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3641
|
-
let agent =
|
|
3948
|
+
let agent = agents.find(
|
|
3642
3949
|
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3643
3950
|
);
|
|
3644
3951
|
if (!agent && createIfMissing) {
|
|
@@ -3654,99 +3961,254 @@ var YieldseekerAgent = class {
|
|
|
3654
3961
|
chainId,
|
|
3655
3962
|
assetAddress: metadata.address,
|
|
3656
3963
|
type: "vault",
|
|
3657
|
-
rulePreset:
|
|
3964
|
+
rulePreset: OWNEY_AGENT_RULE_PRESET
|
|
3658
3965
|
}
|
|
3659
3966
|
}
|
|
3660
3967
|
);
|
|
3968
|
+
this.assertSession(generation, chainId, asset);
|
|
3661
3969
|
agent = created?.agent;
|
|
3970
|
+
if (agent) {
|
|
3971
|
+
this.readCache.set(this.agentListKey(state, chainId), {
|
|
3972
|
+
expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
3973
|
+
value: {
|
|
3974
|
+
agents: [...agents, agent]
|
|
3975
|
+
}
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3662
3978
|
}
|
|
3663
3979
|
if (!agent) return null;
|
|
3664
3980
|
this.assertAgent(agent);
|
|
3665
|
-
|
|
3666
|
-
state,
|
|
3667
|
-
chainId,
|
|
3668
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3669
|
-
);
|
|
3670
|
-
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3671
|
-
throw this.invalidResponse("agent wallet");
|
|
3672
|
-
}
|
|
3673
|
-
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
3981
|
+
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3674
3982
|
}
|
|
3675
3983
|
async loadPortfolio(state, chainId, options) {
|
|
3676
|
-
const
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3984
|
+
const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
|
|
3985
|
+
return Promise.all(
|
|
3986
|
+
contexts.map(
|
|
3987
|
+
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3988
|
+
)
|
|
3681
3989
|
);
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3990
|
+
}
|
|
3991
|
+
async loadActivityPortfolio(state, chainId, options) {
|
|
3992
|
+
const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
|
|
3993
|
+
return Promise.all(
|
|
3994
|
+
contexts.map(
|
|
3995
|
+
(context) => this.loadActivityContext(state, chainId, context, options)
|
|
3996
|
+
)
|
|
3997
|
+
);
|
|
3998
|
+
}
|
|
3999
|
+
async resolvePortfolioContexts(state, chainId, assetFilter) {
|
|
4000
|
+
const generation = this.readGeneration;
|
|
4001
|
+
const user = await this.resolveUser(state, chainId);
|
|
4002
|
+
this.assertSession(generation, chainId);
|
|
4003
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
4004
|
+
this.assertSession(generation, chainId);
|
|
3685
4005
|
const contexts = [];
|
|
3686
|
-
for (const agent of
|
|
4006
|
+
for (const agent of agents) {
|
|
3687
4007
|
const asset = this.assetForAgent(agent);
|
|
3688
|
-
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" ||
|
|
4008
|
+
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || assetFilter && assetFilter !== asset) {
|
|
3689
4009
|
continue;
|
|
3690
4010
|
}
|
|
3691
4011
|
this.assertAgent(agent);
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
4012
|
+
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
4013
|
+
}
|
|
4014
|
+
const resolvedContexts = await Promise.all(contexts);
|
|
4015
|
+
this.assertSession(generation, chainId);
|
|
4016
|
+
return resolvedContexts;
|
|
4017
|
+
}
|
|
4018
|
+
portfolioVersion(contextKey) {
|
|
4019
|
+
return this.portfolioVersions.get(contextKey) ?? 0;
|
|
4020
|
+
}
|
|
4021
|
+
portfolioCacheMs(contextKey) {
|
|
4022
|
+
return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4023
|
+
}
|
|
4024
|
+
snapshotMovement(state, chainId, context, delta) {
|
|
4025
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
4026
|
+
const previous = this.snapshotMovements.get(key2);
|
|
4027
|
+
if (this.snapshotMovements.has(key2)) {
|
|
4028
|
+
if (!previous || previous.delta > 0n !== delta > 0n) return null;
|
|
4029
|
+
return {
|
|
4030
|
+
...previous,
|
|
4031
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4032
|
+
delta: previous.delta + delta,
|
|
4033
|
+
expectedBalance: previous.expectedBalance + delta,
|
|
4034
|
+
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
3705
4035
|
};
|
|
3706
|
-
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3707
|
-
contexts.push(context);
|
|
3708
4036
|
}
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
);
|
|
4037
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4038
|
+
if (!cached || cached.expiresAt <= Date.now()) return null;
|
|
4039
|
+
const snapshot = cached.value.agentSnapshot;
|
|
4040
|
+
if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
|
|
4041
|
+
if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
|
|
4042
|
+
return {
|
|
4043
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4044
|
+
delta,
|
|
4045
|
+
decimals: snapshot.baseAssetDecimals,
|
|
4046
|
+
expectedBalance: BigInt(snapshot.totalValueBase) + delta,
|
|
4047
|
+
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4048
|
+
};
|
|
4049
|
+
}
|
|
4050
|
+
advancePortfolioVersion(contextKey) {
|
|
4051
|
+
const version = this.portfolioVersion(contextKey) + 1;
|
|
4052
|
+
this.portfolioVersions.set(contextKey, version);
|
|
4053
|
+
this.snapshotFailures.delete(contextKey);
|
|
4054
|
+
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4055
|
+
const key2 = `${kind}:${contextKey}`;
|
|
4056
|
+
this.readCache.delete(key2);
|
|
4057
|
+
this.pendingReads.delete(key2);
|
|
4058
|
+
}
|
|
4059
|
+
return version;
|
|
4060
|
+
}
|
|
4061
|
+
requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
|
|
4062
|
+
return this.walletRequest(
|
|
4063
|
+
state,
|
|
4064
|
+
chainId,
|
|
4065
|
+
`${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
|
|
4066
|
+
).then((response) => {
|
|
4067
|
+
if (!response?.agentSnapshot) {
|
|
4068
|
+
throw this.invalidResponse("agent snapshot");
|
|
4069
|
+
}
|
|
4070
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4071
|
+
this.snapshotFailures.delete(contextKey);
|
|
4072
|
+
const movement = this.snapshotMovements.get(contextKey);
|
|
4073
|
+
const snapshot = response.agentSnapshot;
|
|
4074
|
+
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4075
|
+
const balance = BigInt(snapshot.totalValueBase);
|
|
4076
|
+
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4077
|
+
const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
|
|
4078
|
+
if (reconciled) {
|
|
4079
|
+
this.snapshotMovements.delete(contextKey);
|
|
4080
|
+
this.reconcileUntil.delete(contextKey);
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
return response;
|
|
4085
|
+
}).catch((error) => {
|
|
4086
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4087
|
+
this.snapshotFailures.set(contextKey, {
|
|
4088
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
|
|
4089
|
+
error
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
throw error;
|
|
4093
|
+
});
|
|
4094
|
+
}
|
|
4095
|
+
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
4096
|
+
const key2 = `snapshot:${contextKey}`;
|
|
4097
|
+
const cached = this.readCache.get(key2);
|
|
4098
|
+
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
4099
|
+
const failure = this.snapshotFailures.get(contextKey);
|
|
4100
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4101
|
+
this.advancePortfolioVersion(contextKey);
|
|
4102
|
+
}
|
|
4103
|
+
const version = this.portfolioVersion(contextKey);
|
|
4104
|
+
const generation = this.readGeneration;
|
|
4105
|
+
return {
|
|
4106
|
+
version,
|
|
4107
|
+
read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
|
|
4108
|
+
// Force only during the bounded post-transaction window. Keep unknown
|
|
4109
|
+
// or unreconciled movements for verification, but do not let them turn
|
|
4110
|
+
// routine polling into forced refreshes indefinitely.
|
|
4111
|
+
this.requestPortfolioSnapshot(
|
|
4112
|
+
state,
|
|
4113
|
+
chainId,
|
|
4114
|
+
context,
|
|
4115
|
+
contextKey,
|
|
4116
|
+
version,
|
|
4117
|
+
generation,
|
|
4118
|
+
this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
|
|
4119
|
+
)
|
|
4120
|
+
))
|
|
4121
|
+
};
|
|
3714
4122
|
}
|
|
3715
4123
|
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3716
|
-
const
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4124
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4125
|
+
const generation = this.readGeneration;
|
|
4126
|
+
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4127
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4128
|
+
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4129
|
+
const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4130
|
+
try {
|
|
4131
|
+
const [snapshot, portfolioParts] = await Promise.all([read2, parts]);
|
|
4132
|
+
assertSession();
|
|
4133
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4134
|
+
return {
|
|
4135
|
+
...context,
|
|
4136
|
+
snapshot: snapshot.agentSnapshot,
|
|
4137
|
+
...portfolioParts
|
|
4138
|
+
};
|
|
4139
|
+
} catch (error) {
|
|
4140
|
+
assertSession();
|
|
4141
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4142
|
+
throw error;
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
throw new OwneyError(
|
|
4146
|
+
"AGENT_API_ERROR",
|
|
4147
|
+
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4148
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4149
|
+
this.id
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4152
|
+
async loadActivityContext(state, chainId, context, options) {
|
|
4153
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4154
|
+
const generation = this.readGeneration;
|
|
4155
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4156
|
+
const version = this.portfolioVersion(contextKey);
|
|
4157
|
+
try {
|
|
4158
|
+
const parts = await this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4159
|
+
this.assertSession(generation, chainId, context.asset);
|
|
4160
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4161
|
+
return { ...context, ...parts };
|
|
4162
|
+
} catch (error) {
|
|
4163
|
+
this.assertSession(generation, chainId, context.asset);
|
|
4164
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4165
|
+
throw error;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
throw new OwneyError(
|
|
4169
|
+
"AGENT_API_ERROR",
|
|
4170
|
+
"Yieldseeker activity changed during the read. Please retry.",
|
|
4171
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4172
|
+
this.id
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4175
|
+
async loadPortfolioParts(state, chainId, context, options, contextKey) {
|
|
4176
|
+
const [positions, historic, actions] = await Promise.all([
|
|
4177
|
+
this.cachedRead(
|
|
4178
|
+
`positions:${contextKey}`,
|
|
4179
|
+
this.portfolioCacheMs(contextKey),
|
|
4180
|
+
async () => {
|
|
4181
|
+
const response = await this.walletRequest(
|
|
4182
|
+
state,
|
|
4183
|
+
chainId,
|
|
4184
|
+
this.agentPath(context, "yield-positions")
|
|
4185
|
+
);
|
|
4186
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
4187
|
+
throw this.invalidResponse("yield positions");
|
|
4188
|
+
}
|
|
4189
|
+
return response;
|
|
4190
|
+
}
|
|
3729
4191
|
),
|
|
3730
|
-
options.historic ? this.
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
this.
|
|
4192
|
+
options.historic ? this.cachedRead(
|
|
4193
|
+
`historic:${contextKey}`,
|
|
4194
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4195
|
+
() => this.walletRequest(
|
|
4196
|
+
state,
|
|
4197
|
+
chainId,
|
|
4198
|
+
this.agentPath(context, "wallet/historic-position")
|
|
4199
|
+
)
|
|
3734
4200
|
) : Promise.resolve(void 0),
|
|
3735
|
-
options.actions ? this.
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
this.
|
|
4201
|
+
options.actions ? this.cachedRead(
|
|
4202
|
+
`actions:${contextKey}`,
|
|
4203
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4204
|
+
() => this.walletRequest(
|
|
4205
|
+
state,
|
|
4206
|
+
chainId,
|
|
4207
|
+
this.agentPath(context, "actions")
|
|
4208
|
+
)
|
|
3739
4209
|
) : Promise.resolve(void 0)
|
|
3740
4210
|
]);
|
|
3741
|
-
if (!snapshot?.agentSnapshot) {
|
|
3742
|
-
throw this.invalidResponse("agent snapshot");
|
|
3743
|
-
}
|
|
3744
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3745
|
-
throw this.invalidResponse("yield positions");
|
|
3746
|
-
}
|
|
3747
4211
|
return {
|
|
3748
|
-
...context,
|
|
3749
|
-
snapshot: snapshot.agentSnapshot,
|
|
3750
4212
|
positions: positions.yieldPositions,
|
|
3751
4213
|
...historic?.position ? { historic: historic.position } : {},
|
|
3752
4214
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
@@ -3768,18 +4230,21 @@ var YieldseekerAgent = class {
|
|
|
3768
4230
|
}
|
|
3769
4231
|
context.wallet = deployed.agentWallet;
|
|
3770
4232
|
}
|
|
3771
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4233
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4234
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4235
|
+
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4236
|
+
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4237
|
+
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4238
|
+
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4239
|
+
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4240
|
+
const version = this.advancePortfolioVersion(contextKey);
|
|
4241
|
+
const generation = this.readGeneration;
|
|
3772
4242
|
try {
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
shouldForceRefresh: true
|
|
3778
|
-
})}`
|
|
4243
|
+
await this.cachedRead(
|
|
4244
|
+
`snapshot:${contextKey}`,
|
|
4245
|
+
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4246
|
+
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
3779
4247
|
);
|
|
3780
|
-
if (!response?.agentSnapshot) {
|
|
3781
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
3782
|
-
}
|
|
3783
4248
|
} catch (error) {
|
|
3784
4249
|
console.warn(
|
|
3785
4250
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
@@ -3791,10 +4256,26 @@ var YieldseekerAgent = class {
|
|
|
3791
4256
|
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3792
4257
|
}
|
|
3793
4258
|
async walletRequest(state, chainId, path, options = {}) {
|
|
4259
|
+
const read2 = (options.method ?? "GET") === "GET";
|
|
4260
|
+
const userKey = this.userKey(state, chainId);
|
|
4261
|
+
if (read2) {
|
|
4262
|
+
const failure = this.standardReadFailures.get(userKey);
|
|
4263
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4264
|
+
if (failure) this.standardReadFailures.delete(userKey);
|
|
4265
|
+
}
|
|
4266
|
+
const generation = this.readGeneration;
|
|
3794
4267
|
try {
|
|
3795
4268
|
return await this.providerRequest(state, chainId, path, options);
|
|
3796
4269
|
} catch (error) {
|
|
3797
|
-
|
|
4270
|
+
const mapped = this.mapApiError(error);
|
|
4271
|
+
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4272
|
+
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4273
|
+
this.standardReadFailures.set(userKey, {
|
|
4274
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4275
|
+
error: mapped
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
throw mapped;
|
|
3798
4279
|
}
|
|
3799
4280
|
}
|
|
3800
4281
|
async providerRequest(state, chainId, path, options = {}) {
|
|
@@ -3835,6 +4316,7 @@ var YieldseekerAgent = class {
|
|
|
3835
4316
|
code,
|
|
3836
4317
|
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3837
4318
|
{
|
|
4319
|
+
rpcSource: "agent-api",
|
|
3838
4320
|
statusCode: error.status,
|
|
3839
4321
|
providerCode: error.providerCode,
|
|
3840
4322
|
...error.responseFields ? { fields: error.responseFields } : {}
|
|
@@ -3853,12 +4335,12 @@ var YieldseekerAgent = class {
|
|
|
3853
4335
|
chain: base3,
|
|
3854
4336
|
transport: custom2(state.provider)
|
|
3855
4337
|
});
|
|
3856
|
-
const
|
|
4338
|
+
const walletChainClient = createPublicClient3({
|
|
3857
4339
|
chain: base3,
|
|
3858
4340
|
transport: custom2(state.provider)
|
|
3859
4341
|
});
|
|
3860
4342
|
await ensureWalletOnChain(
|
|
3861
|
-
|
|
4343
|
+
walletChainClient,
|
|
3862
4344
|
walletClient,
|
|
3863
4345
|
8453
|
|
3864
4346
|
);
|
|
@@ -3869,10 +4351,15 @@ var YieldseekerAgent = class {
|
|
|
3869
4351
|
data: transaction.data,
|
|
3870
4352
|
value: BigInt(transaction.value)
|
|
3871
4353
|
});
|
|
3872
|
-
const receipt = await
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
4354
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4355
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4356
|
+
hash,
|
|
4357
|
+
confirmations: 1
|
|
4358
|
+
}),
|
|
4359
|
+
8453,
|
|
4360
|
+
"eth_getTransactionReceipt",
|
|
4361
|
+
this.id
|
|
4362
|
+
);
|
|
3876
4363
|
if (receipt.status !== "success") {
|
|
3877
4364
|
throw new OwneyError(
|
|
3878
4365
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3888,14 +4375,15 @@ var YieldseekerAgent = class {
|
|
|
3888
4375
|
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3889
4376
|
return;
|
|
3890
4377
|
}
|
|
3891
|
-
const
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
4378
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4379
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4380
|
+
hash: transactionHash,
|
|
4381
|
+
confirmations: 1
|
|
4382
|
+
}),
|
|
4383
|
+
8453,
|
|
4384
|
+
"eth_getTransactionReceipt",
|
|
4385
|
+
this.id
|
|
4386
|
+
);
|
|
3899
4387
|
if (receipt.status !== "success") {
|
|
3900
4388
|
throw new OwneyError(
|
|
3901
4389
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -4084,16 +4572,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4084
4572
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
4085
4573
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4086
4574
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4087
|
-
|
|
4088
|
-
try {
|
|
4089
|
-
balance += BigInt(position2.amountRaw);
|
|
4090
|
-
continue;
|
|
4091
|
-
} catch {
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
balance += parseUnits(position2.amount, decimals);
|
|
4575
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits(position2.amount, decimals);
|
|
4095
4576
|
}
|
|
4096
4577
|
}
|
|
4578
|
+
const assetTotal = agentBalance?.assetBalances?.find(
|
|
4579
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4580
|
+
);
|
|
4581
|
+
if (assetTotal) {
|
|
4582
|
+
const total = parseUnits(assetTotal.amount, decimals);
|
|
4583
|
+
if (total < balance) balance = total > 0n ? total : 0n;
|
|
4584
|
+
}
|
|
4097
4585
|
return { agent, balance };
|
|
4098
4586
|
});
|
|
4099
4587
|
}
|
|
@@ -4267,6 +4755,41 @@ import {
|
|
|
4267
4755
|
// src/lib/permit2-batch.ts
|
|
4268
4756
|
import { parseAbi as parseAbi2, hashStruct } from "viem";
|
|
4269
4757
|
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
4758
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
4759
|
+
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
4760
|
+
var ERC2612_PERMIT_TYPES = {
|
|
4761
|
+
Permit: [
|
|
4762
|
+
{ name: "owner", type: "address" },
|
|
4763
|
+
{ name: "spender", type: "address" },
|
|
4764
|
+
{ name: "value", type: "uint256" },
|
|
4765
|
+
{ name: "nonce", type: "uint256" },
|
|
4766
|
+
{ name: "deadline", type: "uint256" }
|
|
4767
|
+
]
|
|
4768
|
+
};
|
|
4769
|
+
var ERC2612_READ_ABI = parseAbi2([
|
|
4770
|
+
"function name() view returns (string)",
|
|
4771
|
+
"function version() view returns (string)",
|
|
4772
|
+
"function nonces(address owner) view returns (uint256)"
|
|
4773
|
+
]);
|
|
4774
|
+
function erc2612TypedData(input) {
|
|
4775
|
+
return {
|
|
4776
|
+
domain: {
|
|
4777
|
+
name: input.permit.tokenName,
|
|
4778
|
+
version: input.permit.tokenVersion,
|
|
4779
|
+
chainId: input.chainId,
|
|
4780
|
+
verifyingContract: input.token
|
|
4781
|
+
},
|
|
4782
|
+
types: ERC2612_PERMIT_TYPES,
|
|
4783
|
+
primaryType: "Permit",
|
|
4784
|
+
message: {
|
|
4785
|
+
owner: input.owner,
|
|
4786
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
4787
|
+
value: BigInt(input.permit.value),
|
|
4788
|
+
nonce: BigInt(input.permit.nonce),
|
|
4789
|
+
deadline: BigInt(input.permit.deadline)
|
|
4790
|
+
}
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4270
4793
|
var PERMIT_BATCH_TYPES = {
|
|
4271
4794
|
PermitBatchWitnessTransferFrom: [
|
|
4272
4795
|
{ name: "permitted", type: "TokenPermissions[]" },
|
|
@@ -4418,28 +4941,67 @@ async function execute(i, key2, plan) {
|
|
|
4418
4941
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4419
4942
|
"Insufficient token balance for this deposit."
|
|
4420
4943
|
);
|
|
4421
|
-
if (allowance < total)
|
|
4944
|
+
if (allowance < total && (i.chainId !== 8453 || !isAddressEqual(i.token, BASE_USDC_ADDRESS)))
|
|
4422
4945
|
throw new OwneyError(
|
|
4423
4946
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
4424
4947
|
"token deposits need a one-time Permit2 approval."
|
|
4425
4948
|
);
|
|
4426
|
-
const
|
|
4949
|
+
const now = (await i.pub.getBlock()).timestamp;
|
|
4950
|
+
let erc2612Permit;
|
|
4951
|
+
if (allowance < total) {
|
|
4952
|
+
const [tokenName, tokenVersion, permitNonce] = await Promise.all([
|
|
4953
|
+
i.pub.readContract({
|
|
4954
|
+
address: i.token,
|
|
4955
|
+
abi: ERC2612_READ_ABI,
|
|
4956
|
+
functionName: "name"
|
|
4957
|
+
}),
|
|
4958
|
+
i.pub.readContract({
|
|
4959
|
+
address: i.token,
|
|
4960
|
+
abi: ERC2612_READ_ABI,
|
|
4961
|
+
functionName: "version"
|
|
4962
|
+
}),
|
|
4963
|
+
i.pub.readContract({
|
|
4964
|
+
address: i.token,
|
|
4965
|
+
abi: ERC2612_READ_ABI,
|
|
4966
|
+
functionName: "nonces",
|
|
4967
|
+
args: [i.owner]
|
|
4968
|
+
})
|
|
4969
|
+
]);
|
|
4970
|
+
const unsignedPermit = {
|
|
4971
|
+
value: MAX_UINT256.toString(),
|
|
4972
|
+
nonce: permitNonce.toString(),
|
|
4973
|
+
deadline: (now + 900n).toString(),
|
|
4974
|
+
tokenName,
|
|
4975
|
+
tokenVersion
|
|
4976
|
+
};
|
|
4977
|
+
const signature2 = await i.wallet.signTypedData({
|
|
4978
|
+
account: i.owner,
|
|
4979
|
+
...erc2612TypedData({
|
|
4980
|
+
chainId: 8453,
|
|
4981
|
+
token: i.token,
|
|
4982
|
+
owner: i.owner,
|
|
4983
|
+
permit: unsignedPermit
|
|
4984
|
+
})
|
|
4985
|
+
});
|
|
4986
|
+
erc2612Permit = { ...unsignedPermit, signature: signature2 };
|
|
4987
|
+
}
|
|
4988
|
+
const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
|
|
4427
4989
|
apiKey: i.apiKey,
|
|
4428
4990
|
baseUrl: i.baseUrl,
|
|
4429
4991
|
chainId: i.chainId
|
|
4430
4992
|
});
|
|
4431
|
-
const now = (await i.pub.getBlock()).timestamp;
|
|
4432
4993
|
const unsigned = {
|
|
4433
4994
|
chainId: i.chainId,
|
|
4434
4995
|
token: i.token,
|
|
4435
4996
|
from: i.owner,
|
|
4436
4997
|
transfers: i.transfers,
|
|
4437
4998
|
nonce: randomPermit2Nonce().toString(),
|
|
4438
|
-
deadline: (now + 900n).toString()
|
|
4999
|
+
deadline: (now + 900n).toString(),
|
|
5000
|
+
...erc2612Permit ? { erc2612Permit } : {}
|
|
4439
5001
|
};
|
|
4440
5002
|
const signature = await i.wallet.signTypedData({
|
|
4441
5003
|
account: i.owner,
|
|
4442
|
-
...batchTypedData(unsigned,
|
|
5004
|
+
...batchTypedData(unsigned, spender)
|
|
4443
5005
|
});
|
|
4444
5006
|
i.onApproved?.();
|
|
4445
5007
|
return send({ ...unsigned, signature });
|
|
@@ -4459,8 +5021,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
4459
5021
|
"CHAIN_UNSUPPORTED",
|
|
4460
5022
|
`No sponsored token configured for chain ${chainId}`
|
|
4461
5023
|
);
|
|
4462
|
-
const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
|
|
4463
|
-
await ensureWalletOnChain(
|
|
5024
|
+
const pub = deps.getPublicClient(chainId), walletChain = deps.getWalletChainClient?.(chainId) ?? pub, wallet = deps.getWalletClient(chainId);
|
|
5025
|
+
await ensureWalletOnChain(walletChain, wallet, chainId);
|
|
4464
5026
|
return sponsorTokenBatch({
|
|
4465
5027
|
apiKey: deps.apiKey,
|
|
4466
5028
|
baseUrl: deps.baseUrl,
|
|
@@ -4700,6 +5262,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4700
5262
|
}
|
|
4701
5263
|
|
|
4702
5264
|
// src/client.ts
|
|
5265
|
+
var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
|
|
5266
|
+
var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
|
|
4703
5267
|
function encodeMultiAgentCursor(map) {
|
|
4704
5268
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
4705
5269
|
}
|
|
@@ -4760,6 +5324,7 @@ var OwneySDK = class {
|
|
|
4760
5324
|
// leave every user's agent profile alone.
|
|
4761
5325
|
orgAgentConfig;
|
|
4762
5326
|
orgAgentConfigPromise = null;
|
|
5327
|
+
rpcUrls;
|
|
4763
5328
|
zyfaiRpcUrls;
|
|
4764
5329
|
yieldseekerApiBaseUrl;
|
|
4765
5330
|
yieldseekerSiweOrigin;
|
|
@@ -4784,7 +5349,12 @@ var OwneySDK = class {
|
|
|
4784
5349
|
constructor(config) {
|
|
4785
5350
|
this.apiKey = config.apiKey;
|
|
4786
5351
|
if (config.debug) setOwneyDebug(true);
|
|
4787
|
-
this.
|
|
5352
|
+
this.rpcUrls = resolveRpcUrls(
|
|
5353
|
+
config.rpcUrls,
|
|
5354
|
+
config.apiKey,
|
|
5355
|
+
config.routingApiBaseUrl
|
|
5356
|
+
);
|
|
5357
|
+
this.zyfaiRpcUrls = config.rpcUrls ? void 0 : config.zyfaiRpcUrls;
|
|
4788
5358
|
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4789
5359
|
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
4790
5360
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
@@ -4874,6 +5444,9 @@ var OwneySDK = class {
|
|
|
4874
5444
|
}
|
|
4875
5445
|
return this.state.provider;
|
|
4876
5446
|
}
|
|
5447
|
+
getPaidRpcClient(chainId) {
|
|
5448
|
+
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
5449
|
+
}
|
|
4877
5450
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
4878
5451
|
getDefaultSponsoredCallback(onApproved) {
|
|
4879
5452
|
if (!onApproved && this.cachedSponsoredCallback)
|
|
@@ -4889,7 +5462,8 @@ var OwneySDK = class {
|
|
|
4889
5462
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4890
5463
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4891
5464
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4892
|
-
getPublicClient: (cid) =>
|
|
5465
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
5466
|
+
getWalletChainClient: (cid) => createPublicClient4({
|
|
4893
5467
|
chain: VIEM_CHAIN2[cid],
|
|
4894
5468
|
transport: custom3(provider)
|
|
4895
5469
|
}),
|
|
@@ -4939,7 +5513,8 @@ var OwneySDK = class {
|
|
|
4939
5513
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4940
5514
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4941
5515
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4942
|
-
getPublicClient: (cid) =>
|
|
5516
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
5517
|
+
getWalletChainClient: (cid) => createPublicClient4({
|
|
4943
5518
|
chain: VIEM_CHAIN2[cid],
|
|
4944
5519
|
transport: custom3(provider)
|
|
4945
5520
|
}),
|
|
@@ -5052,12 +5627,21 @@ var OwneySDK = class {
|
|
|
5052
5627
|
createAgent(agentId, key2) {
|
|
5053
5628
|
if (agentId === "zyfai") {
|
|
5054
5629
|
if (!key2) return null;
|
|
5055
|
-
return new ZyfaiAgent(
|
|
5630
|
+
return new ZyfaiAgent(
|
|
5631
|
+
key2,
|
|
5632
|
+
this.zyfaiRpcUrls ? resolveRpcUrls(
|
|
5633
|
+
this.zyfaiRpcUrls,
|
|
5634
|
+
this.apiKey,
|
|
5635
|
+
this.routingApiBaseUrl
|
|
5636
|
+
) : this.rpcUrls,
|
|
5637
|
+
this.referralSource
|
|
5638
|
+
);
|
|
5056
5639
|
}
|
|
5057
5640
|
if (agentId === "yieldseeker") {
|
|
5058
5641
|
return new YieldseekerAgent(this.apiKey, {
|
|
5059
5642
|
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5060
|
-
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
5643
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
|
|
5644
|
+
rpcUrls: this.rpcUrls
|
|
5061
5645
|
});
|
|
5062
5646
|
}
|
|
5063
5647
|
return null;
|
|
@@ -5348,7 +5932,8 @@ var OwneySDK = class {
|
|
|
5348
5932
|
);
|
|
5349
5933
|
await this.approvePermit2(
|
|
5350
5934
|
asset,
|
|
5351
|
-
requiredAmount
|
|
5935
|
+
requiredAmount,
|
|
5936
|
+
cid
|
|
5352
5937
|
);
|
|
5353
5938
|
return batchTransfer(cid, transfers);
|
|
5354
5939
|
}
|
|
@@ -5392,10 +5977,12 @@ var OwneySDK = class {
|
|
|
5392
5977
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
5393
5978
|
* two independent auto-recovery mechanisms:
|
|
5394
5979
|
*
|
|
5395
|
-
* 1. Missing Permit2 allowance
|
|
5396
|
-
* callback and the attempt fails with
|
|
5397
|
-
*
|
|
5398
|
-
*
|
|
5980
|
+
* 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
|
|
5981
|
+
* app did not supply its own callback and the attempt fails with
|
|
5982
|
+
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
5983
|
+
* for that token. Base USDC bundles a gasless ERC-2612 approval inside
|
|
5984
|
+
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
5985
|
+
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
5399
5986
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5400
5987
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
5401
5988
|
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
@@ -5438,7 +6025,8 @@ var OwneySDK = class {
|
|
|
5438
6025
|
);
|
|
5439
6026
|
await this.approvePermit2(
|
|
5440
6027
|
asset,
|
|
5441
|
-
BigInt(amount)
|
|
6028
|
+
BigInt(amount),
|
|
6029
|
+
chainId
|
|
5442
6030
|
);
|
|
5443
6031
|
continue;
|
|
5444
6032
|
}
|
|
@@ -5585,6 +6173,32 @@ var OwneySDK = class {
|
|
|
5585
6173
|
}
|
|
5586
6174
|
return eligible;
|
|
5587
6175
|
}
|
|
6176
|
+
/**
|
|
6177
|
+
* Run owner-approved withdrawals before relayer-only withdrawals. Wallet
|
|
6178
|
+
* approval is the only point at which the user can cancel the aggregate
|
|
6179
|
+
* operation, so no relayer leg should commit before it has completed.
|
|
6180
|
+
*/
|
|
6181
|
+
orderAgentsForWithdrawal(agents) {
|
|
6182
|
+
return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
|
|
6183
|
+
const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
|
|
6184
|
+
return approvalOrder || left.index - right.index;
|
|
6185
|
+
}).map(({ agent }) => agent);
|
|
6186
|
+
}
|
|
6187
|
+
isUserRejectedWithdrawal(error) {
|
|
6188
|
+
let current = error;
|
|
6189
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6190
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
6191
|
+
seen.add(current);
|
|
6192
|
+
const candidate = current;
|
|
6193
|
+
if (candidate.code === 4001 || candidate.code === "4001") return true;
|
|
6194
|
+
const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
|
|
6195
|
+
if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
|
|
6196
|
+
return true;
|
|
6197
|
+
}
|
|
6198
|
+
current = candidate.cause;
|
|
6199
|
+
}
|
|
6200
|
+
return false;
|
|
6201
|
+
}
|
|
5588
6202
|
// --- Fund operations ---
|
|
5589
6203
|
/**
|
|
5590
6204
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -5596,7 +6210,14 @@ var OwneySDK = class {
|
|
|
5596
6210
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
5597
6211
|
*/
|
|
5598
6212
|
async withdraw(options) {
|
|
5599
|
-
const { asset, amount, agentId } = options;
|
|
6213
|
+
const { asset, amount, agentId, onAgentResult } = options;
|
|
6214
|
+
const notifyAgentResult = (id, result) => {
|
|
6215
|
+
try {
|
|
6216
|
+
onAgentResult?.(id, result);
|
|
6217
|
+
} catch (error) {
|
|
6218
|
+
console.warn("Withdrawal result observer failed:", error);
|
|
6219
|
+
}
|
|
6220
|
+
};
|
|
5600
6221
|
const state = this.requireState();
|
|
5601
6222
|
const chainId = this.requireChainId();
|
|
5602
6223
|
const token = asset;
|
|
@@ -5612,21 +6233,26 @@ var OwneySDK = class {
|
|
|
5612
6233
|
if (agentId) {
|
|
5613
6234
|
const agent = this.getAgent(agentId);
|
|
5614
6235
|
this.validateAssetSupport(agent, chainId, asset);
|
|
5615
|
-
|
|
6236
|
+
const result = await withFailureReporting(
|
|
5616
6237
|
this.apiKey,
|
|
5617
6238
|
agent.id,
|
|
5618
6239
|
() => agent.withdraw(state, chainId, token, amount),
|
|
5619
6240
|
this.routingApiBaseUrl
|
|
5620
6241
|
);
|
|
6242
|
+
notifyAgentResult(agent.id, result);
|
|
6243
|
+
return result;
|
|
5621
6244
|
}
|
|
5622
6245
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6246
|
+
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
5623
6247
|
if (!amount) {
|
|
5624
6248
|
const results2 = {};
|
|
5625
6249
|
const agentErrors2 = {};
|
|
5626
|
-
for (const agent of
|
|
6250
|
+
for (const agent of withdrawalAgents) {
|
|
5627
6251
|
try {
|
|
5628
6252
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
6253
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
5629
6254
|
} catch (err) {
|
|
6255
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
5630
6256
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
5631
6257
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
5632
6258
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5707,12 +6333,13 @@ var OwneySDK = class {
|
|
|
5707
6333
|
planned: 0n
|
|
5708
6334
|
}));
|
|
5709
6335
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
6336
|
+
const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
|
|
5710
6337
|
const results = {};
|
|
5711
6338
|
const agentErrors = {
|
|
5712
6339
|
...aggregated.agentErrors ?? {}
|
|
5713
6340
|
};
|
|
5714
|
-
for (let i = 0; i <
|
|
5715
|
-
const p =
|
|
6341
|
+
for (let i = 0; i < orderedPlans.length; i++) {
|
|
6342
|
+
const p = orderedPlans[i];
|
|
5716
6343
|
if (p.planned === 0n) continue;
|
|
5717
6344
|
try {
|
|
5718
6345
|
results[p.agent.id] = await p.agent.withdraw(
|
|
@@ -5721,7 +6348,9 @@ var OwneySDK = class {
|
|
|
5721
6348
|
token,
|
|
5722
6349
|
p.planned.toString()
|
|
5723
6350
|
);
|
|
6351
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
5724
6352
|
} catch (err) {
|
|
6353
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
5725
6354
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
5726
6355
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
5727
6356
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5733,7 +6362,7 @@ var OwneySDK = class {
|
|
|
5733
6362
|
);
|
|
5734
6363
|
const failedAmount = p.planned;
|
|
5735
6364
|
p.planned = 0n;
|
|
5736
|
-
redistributeShare(
|
|
6365
|
+
redistributeShare(orderedPlans, i, failedAmount);
|
|
5737
6366
|
}
|
|
5738
6367
|
}
|
|
5739
6368
|
if (Object.keys(results).length === 0) {
|
|
@@ -5843,23 +6472,11 @@ var OwneySDK = class {
|
|
|
5843
6472
|
const agent = this.getAgent(agentId);
|
|
5844
6473
|
return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
|
|
5845
6474
|
}
|
|
5846
|
-
let totalEarnings = 0;
|
|
5847
|
-
const results = {};
|
|
5848
6475
|
const entries = [...this.getActiveAgents().entries()];
|
|
5849
|
-
|
|
5850
|
-
entries
|
|
5851
|
-
|
|
5852
|
-
return [id, e];
|
|
5853
|
-
})
|
|
6476
|
+
return this.aggregateEarnings(
|
|
6477
|
+
entries,
|
|
6478
|
+
(agent) => this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId))
|
|
5854
6479
|
);
|
|
5855
|
-
for (const [id, e] of earningsResults) {
|
|
5856
|
-
results[id] = e;
|
|
5857
|
-
totalEarnings += Number(e.lifetimeEarnings);
|
|
5858
|
-
}
|
|
5859
|
-
return {
|
|
5860
|
-
totalEarnings: String(totalEarnings),
|
|
5861
|
-
agentEarnings: results
|
|
5862
|
-
};
|
|
5863
6480
|
}
|
|
5864
6481
|
/**
|
|
5865
6482
|
* Return refreshed earnings while letting each agent own its refresh policy.
|
|
@@ -5874,18 +6491,42 @@ var OwneySDK = class {
|
|
|
5874
6491
|
() => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
|
|
5875
6492
|
);
|
|
5876
6493
|
if (agentId) return refreshAgent(this.getAgent(agentId));
|
|
5877
|
-
|
|
6494
|
+
return this.aggregateEarnings([...this.getActiveAgents().entries()], refreshAgent);
|
|
6495
|
+
}
|
|
6496
|
+
async aggregateEarnings(entries, read2) {
|
|
6497
|
+
const settled = await Promise.allSettled(entries.map(([, agent]) => read2(agent)));
|
|
5878
6498
|
const agentEarnings = {};
|
|
5879
|
-
const
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
)
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
6499
|
+
const agentErrors = {};
|
|
6500
|
+
const agentRetryAt = {};
|
|
6501
|
+
const failures = [];
|
|
6502
|
+
let totalEarnings = 0;
|
|
6503
|
+
for (let i = 0; i < settled.length; i++) {
|
|
6504
|
+
const [id] = entries[i];
|
|
6505
|
+
const result = settled[i];
|
|
6506
|
+
if (result.status === "fulfilled") {
|
|
6507
|
+
agentEarnings[id] = result.value;
|
|
6508
|
+
totalEarnings += Number(result.value.lifetimeEarnings);
|
|
6509
|
+
continue;
|
|
6510
|
+
}
|
|
6511
|
+
const reason = result.reason;
|
|
6512
|
+
failures.push(reason);
|
|
6513
|
+
const retryDelay = rateLimitDelay(reason);
|
|
6514
|
+
if (retryDelay !== void 0) agentRetryAt[id] = Date.now() + retryDelay;
|
|
6515
|
+
agentErrors[id] = reason instanceof Error ? reason.message : String(reason);
|
|
6516
|
+
console.error(`[owney-sdk] Earnings fetch failed for agent "${id}":`, reason);
|
|
5887
6517
|
}
|
|
5888
|
-
|
|
6518
|
+
if (entries.length > 0 && Object.keys(agentEarnings).length === 0) {
|
|
6519
|
+
throw new OwneyError(
|
|
6520
|
+
"EARNINGS_ALL_FAILED",
|
|
6521
|
+
"Failed to fetch earnings for all active agents.",
|
|
6522
|
+
{ agentErrors, failures }
|
|
6523
|
+
);
|
|
6524
|
+
}
|
|
6525
|
+
return {
|
|
6526
|
+
totalEarnings: String(totalEarnings),
|
|
6527
|
+
agentEarnings,
|
|
6528
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
|
|
6529
|
+
};
|
|
5889
6530
|
}
|
|
5890
6531
|
/**
|
|
5891
6532
|
* Get the weighted APY for the user's account over a time period.
|
|
@@ -6159,16 +6800,18 @@ var OwneySDK = class {
|
|
|
6159
6800
|
}
|
|
6160
6801
|
/**
|
|
6161
6802
|
* User-paid approval of Permit2 on the selected token for the active chain.
|
|
6162
|
-
*
|
|
6163
|
-
*
|
|
6164
|
-
*
|
|
6803
|
+
* Grants the maximum ERC20 allowance so later deposits do not require another
|
|
6804
|
+
* approval. Resolves after one confirmation so the subsequent deposit attempt
|
|
6805
|
+
* sees the new allowance. Deposit retries pass their captured chain id so a
|
|
6806
|
+
* concurrent activation cannot redirect the approval to another network.
|
|
6165
6807
|
*
|
|
6166
6808
|
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
6809
|
+
* @param expectedChainId Chain captured by the deposit that requested approval.
|
|
6167
6810
|
* @returns the approval transaction hash.
|
|
6168
6811
|
*/
|
|
6169
|
-
async approvePermit2(asset = "WETH", requiredAmount = 0n) {
|
|
6812
|
+
async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId) {
|
|
6170
6813
|
const state = this.requireState();
|
|
6171
|
-
const chainId = this.requireChainId();
|
|
6814
|
+
const chainId = expectedChainId ?? this.requireChainId();
|
|
6172
6815
|
this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
|
|
6173
6816
|
const token = sponsoredTokensFor(asset)[chainId];
|
|
6174
6817
|
if (!token) {
|
|
@@ -6178,16 +6821,18 @@ var OwneySDK = class {
|
|
|
6178
6821
|
);
|
|
6179
6822
|
}
|
|
6180
6823
|
const provider = this.requireConnectedProvider();
|
|
6181
|
-
const
|
|
6824
|
+
const walletChainClient = createPublicClient4({
|
|
6182
6825
|
chain: VIEM_CHAIN2[chainId],
|
|
6183
6826
|
transport: custom3(provider)
|
|
6184
6827
|
});
|
|
6828
|
+
const publicClient = this.getPaidRpcClient(chainId);
|
|
6185
6829
|
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6186
6830
|
const wallet = createWalletClient3({
|
|
6187
6831
|
account: state.walletAddress,
|
|
6188
6832
|
chain: VIEM_CHAIN2[chainId],
|
|
6189
6833
|
transport: custom3(provider)
|
|
6190
6834
|
});
|
|
6835
|
+
await ensureWalletOnChain(walletChainClient, wallet, chainId);
|
|
6191
6836
|
const hash = await wallet.writeContract({
|
|
6192
6837
|
address: token,
|
|
6193
6838
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -6196,14 +6841,53 @@ var OwneySDK = class {
|
|
|
6196
6841
|
account: state.walletAddress,
|
|
6197
6842
|
chain: VIEM_CHAIN2[chainId]
|
|
6198
6843
|
});
|
|
6199
|
-
const receipt = await
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6844
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
6845
|
+
() => publicClient.waitForTransactionReceipt({
|
|
6846
|
+
hash,
|
|
6847
|
+
confirmations: 1
|
|
6848
|
+
}),
|
|
6849
|
+
chainId,
|
|
6850
|
+
"eth_getTransactionReceipt"
|
|
6851
|
+
);
|
|
6203
6852
|
if (receipt.status !== "success") {
|
|
6204
6853
|
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
6205
6854
|
}
|
|
6206
|
-
|
|
6855
|
+
let observedAllowance = 0n;
|
|
6856
|
+
let verificationError;
|
|
6857
|
+
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
|
|
6858
|
+
try {
|
|
6859
|
+
observedAllowance = await readPermit2Allowance(
|
|
6860
|
+
publicClient,
|
|
6861
|
+
token,
|
|
6862
|
+
state.walletAddress,
|
|
6863
|
+
attempt === 0 ? receipt.blockNumber : void 0
|
|
6864
|
+
);
|
|
6865
|
+
verificationError = void 0;
|
|
6866
|
+
if (observedAllowance >= requiredAmount) return hash;
|
|
6867
|
+
} catch (error) {
|
|
6868
|
+
verificationError = error;
|
|
6869
|
+
}
|
|
6870
|
+
if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
|
|
6871
|
+
await new Promise(
|
|
6872
|
+
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
|
|
6873
|
+
);
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
throw new OwneyError(
|
|
6877
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
6878
|
+
"Permit2 approval was confirmed, but the required token allowance was not observable.",
|
|
6879
|
+
{
|
|
6880
|
+
approvalConfirmed: true,
|
|
6881
|
+
approvalTxHash: hash,
|
|
6882
|
+
owner: state.walletAddress,
|
|
6883
|
+
token,
|
|
6884
|
+
spender: PERMIT2_ADDRESS,
|
|
6885
|
+
chainId,
|
|
6886
|
+
requiredAmount: requiredAmount.toString(),
|
|
6887
|
+
observedAllowance: observedAllowance.toString(),
|
|
6888
|
+
...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
|
|
6889
|
+
}
|
|
6890
|
+
);
|
|
6207
6891
|
}
|
|
6208
6892
|
// --- Discovery (no wallet required) ---
|
|
6209
6893
|
/**
|