@owney/sdk 0.7.25-beta.3 → 0.7.25-beta.6
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 +72 -13
- package/dist/index.cjs +614 -187
- package/dist/index.d.cts +51 -8
- package/dist/index.d.ts +51 -8
- package/dist/index.js +555 -125
- 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,131 @@ 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
|
+
var DEFAULT_RPC_URLS = {
|
|
1106
|
+
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1107
|
+
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1108
|
+
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
1109
|
+
};
|
|
1110
|
+
function resolveRpcUrl(rpcUrls, chainId) {
|
|
1111
|
+
const url = rpcUrls?.[chainId]?.trim();
|
|
1112
|
+
if (url) return url;
|
|
1113
|
+
return DEFAULT_RPC_URLS[chainId];
|
|
1114
|
+
}
|
|
1115
|
+
function resolveRpcUrls(rpcUrls) {
|
|
1116
|
+
return {
|
|
1117
|
+
1: resolveRpcUrl(rpcUrls, 1),
|
|
1118
|
+
8453: resolveRpcUrl(rpcUrls, 8453),
|
|
1119
|
+
42161: resolveRpcUrl(rpcUrls, 42161)
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
function createPaidRpcClient(chain, rpcUrls) {
|
|
1123
|
+
const url = resolveRpcUrl(
|
|
1124
|
+
rpcUrls,
|
|
1125
|
+
chain.id
|
|
1126
|
+
);
|
|
1127
|
+
return createPublicClient({
|
|
1128
|
+
chain,
|
|
1129
|
+
transport: http(url, {
|
|
1130
|
+
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1131
|
+
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
1132
|
+
})
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
function headerValue(error, name) {
|
|
1136
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1137
|
+
let value;
|
|
1138
|
+
function visit(candidate, depth = 0) {
|
|
1139
|
+
if (value || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1140
|
+
return;
|
|
1141
|
+
if (seen.has(candidate)) return;
|
|
1142
|
+
seen.add(candidate);
|
|
1143
|
+
const record = candidate;
|
|
1144
|
+
const headers = record.headers;
|
|
1145
|
+
const found = typeof headers?.get === "function" ? headers.get(name) : headers?.[name] ?? headers?.[name.toLowerCase()];
|
|
1146
|
+
if (typeof found === "string" && found) {
|
|
1147
|
+
value = found;
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
for (const key2 of ["cause", "details", "response", "error"])
|
|
1151
|
+
visit(record[key2], depth + 1);
|
|
1152
|
+
}
|
|
1153
|
+
visit(error);
|
|
1154
|
+
return value;
|
|
1155
|
+
}
|
|
1156
|
+
function statusCode(error) {
|
|
1157
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1158
|
+
let status;
|
|
1159
|
+
function visit(candidate, depth = 0) {
|
|
1160
|
+
if (status || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1161
|
+
return;
|
|
1162
|
+
if (seen.has(candidate)) return;
|
|
1163
|
+
seen.add(candidate);
|
|
1164
|
+
const record = candidate;
|
|
1165
|
+
for (const key2 of ["status", "statusCode"]) {
|
|
1166
|
+
const parsed = Number(record[key2]);
|
|
1167
|
+
if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1168
|
+
status = parsed;
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
for (const key2 of ["cause", "details", "response", "error"])
|
|
1173
|
+
visit(record[key2], depth + 1);
|
|
1174
|
+
}
|
|
1175
|
+
visit(error);
|
|
1176
|
+
return status;
|
|
1177
|
+
}
|
|
1178
|
+
function paidRpcError(error, chainId, rpcMethod, agentId) {
|
|
1179
|
+
const delay = rateLimitDelay(error);
|
|
1180
|
+
const providerRequestId = headerValue(error, "x-alchemy-request-id") ?? headerValue(error, "x-request-id");
|
|
1181
|
+
if (delay === void 0) {
|
|
1182
|
+
return new OwneyError(
|
|
1183
|
+
"AGENT_API_ERROR",
|
|
1184
|
+
"The blockchain RPC request failed.",
|
|
1185
|
+
{
|
|
1186
|
+
rpcSource: "paid-rpc",
|
|
1187
|
+
chainId,
|
|
1188
|
+
rpcMethod,
|
|
1189
|
+
...statusCode(error) ? { statusCode: statusCode(error) } : {},
|
|
1190
|
+
...providerRequestId ? { providerRequestId } : {}
|
|
1191
|
+
},
|
|
1192
|
+
agentId
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
const retryAt = Date.now() + delay;
|
|
1196
|
+
return new OwneyError(
|
|
1197
|
+
"AGENT_RATE_LIMITED",
|
|
1198
|
+
"The blockchain RPC is rate limited. Please wait before trying again.",
|
|
1199
|
+
{
|
|
1200
|
+
rpcSource: "paid-rpc",
|
|
1201
|
+
chainId,
|
|
1202
|
+
rpcMethod,
|
|
1203
|
+
statusCode: 429,
|
|
1204
|
+
retryAt,
|
|
1205
|
+
retryAfterSeconds: Math.max(0, Math.ceil(delay / 1e3)),
|
|
1206
|
+
...providerRequestId ? { providerRequestId } : {}
|
|
1207
|
+
},
|
|
1208
|
+
agentId
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
1212
|
+
try {
|
|
1213
|
+
return await operation();
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
throw paidRpcError(error, chainId, rpcMethod, agentId);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1066
1219
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1067
1220
|
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
|
|
1068
1221
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1069
1222
|
]);
|
|
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
1223
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
1076
1224
|
8453: "0x4200000000000000000000000000000000000006",
|
|
1077
1225
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
@@ -1137,7 +1285,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1137
1285
|
earningsSnapshot = null;
|
|
1138
1286
|
earningsGeneration = 0;
|
|
1139
1287
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1140
|
-
this.rpcUrls = rpcUrls
|
|
1288
|
+
this.rpcUrls = resolveRpcUrls(rpcUrls);
|
|
1141
1289
|
this.sdk = new ZyfaiSDK({
|
|
1142
1290
|
apiKey,
|
|
1143
1291
|
rpcUrls: this.rpcUrls,
|
|
@@ -1151,10 +1299,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1151
1299
|
getPublicClient(chainId) {
|
|
1152
1300
|
const cached = this.publicClients.get(chainId);
|
|
1153
1301
|
if (cached) return cached;
|
|
1154
|
-
const client =
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1302
|
+
const client = createPaidRpcClient(
|
|
1303
|
+
VIEM_CHAIN[chainId],
|
|
1304
|
+
this.rpcUrls
|
|
1305
|
+
);
|
|
1158
1306
|
this.publicClients.set(chainId, client);
|
|
1159
1307
|
return client;
|
|
1160
1308
|
}
|
|
@@ -2328,12 +2476,13 @@ function randomPermit2Nonce() {
|
|
|
2328
2476
|
globalThis.crypto.getRandomValues(bytes);
|
|
2329
2477
|
return BigInt(bytesToHex2(bytes));
|
|
2330
2478
|
}
|
|
2331
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2479
|
+
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
2332
2480
|
return publicClient.readContract({
|
|
2333
2481
|
address: token,
|
|
2334
2482
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2335
2483
|
functionName: "allowance",
|
|
2336
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2484
|
+
args: [owner, PERMIT2_ADDRESS],
|
|
2485
|
+
...blockNumber === void 0 ? {} : { blockNumber }
|
|
2337
2486
|
});
|
|
2338
2487
|
}
|
|
2339
2488
|
async function readErc20Balance(publicClient, token, owner) {
|
|
@@ -3190,6 +3339,9 @@ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
|
3190
3339
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3191
3340
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3192
3341
|
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3342
|
+
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 6e4;
|
|
3343
|
+
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 3e4;
|
|
3344
|
+
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3193
3345
|
function generateYieldseekerUsername() {
|
|
3194
3346
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3195
3347
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -3233,6 +3385,7 @@ function query(params) {
|
|
|
3233
3385
|
var YieldseekerAgent = class {
|
|
3234
3386
|
id = "yieldseeker";
|
|
3235
3387
|
balanceComposition = "tokens-plus-positions";
|
|
3388
|
+
withdrawalRequiresWalletApproval = true;
|
|
3236
3389
|
supportedChainIds = [8453];
|
|
3237
3390
|
supportedAssets = [
|
|
3238
3391
|
{
|
|
@@ -3246,11 +3399,17 @@ var YieldseekerAgent = class {
|
|
|
3246
3399
|
];
|
|
3247
3400
|
api;
|
|
3248
3401
|
auth;
|
|
3402
|
+
rpcUrls;
|
|
3403
|
+
receiptClient;
|
|
3249
3404
|
transactionExecutor;
|
|
3250
3405
|
unwindReceiptWaiter;
|
|
3251
3406
|
agentContexts = /* @__PURE__ */ new Map();
|
|
3252
3407
|
users = /* @__PURE__ */ new Map();
|
|
3253
3408
|
pendingAgents = /* @__PURE__ */ new Map();
|
|
3409
|
+
pendingWalletContexts = /* @__PURE__ */ new Map();
|
|
3410
|
+
readCache = /* @__PURE__ */ new Map();
|
|
3411
|
+
pendingReads = /* @__PURE__ */ new Map();
|
|
3412
|
+
readGeneration = 0;
|
|
3254
3413
|
yieldOptions = /* @__PURE__ */ new Map();
|
|
3255
3414
|
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3256
3415
|
constructor(owneyApiKey, options = {}) {
|
|
@@ -3260,10 +3419,16 @@ var YieldseekerAgent = class {
|
|
|
3260
3419
|
options.fetchFn
|
|
3261
3420
|
);
|
|
3262
3421
|
this.auth = new YieldseekerAuth(options.auth);
|
|
3422
|
+
this.rpcUrls = options.rpcUrls;
|
|
3263
3423
|
this.transactionExecutor = options.transactionExecutor;
|
|
3264
3424
|
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3265
3425
|
}
|
|
3426
|
+
getReceiptClient() {
|
|
3427
|
+
this.receiptClient ??= createPaidRpcClient(base3, this.rpcUrls);
|
|
3428
|
+
return this.receiptClient;
|
|
3429
|
+
}
|
|
3266
3430
|
async disconnect() {
|
|
3431
|
+
this.readGeneration += 1;
|
|
3267
3432
|
this.auth.clear();
|
|
3268
3433
|
for (const key2 of this.users.keys()) {
|
|
3269
3434
|
const [walletAddress, chainId] = key2.split(":");
|
|
@@ -3272,6 +3437,9 @@ var YieldseekerAgent = class {
|
|
|
3272
3437
|
this.users.clear();
|
|
3273
3438
|
this.agentContexts.clear();
|
|
3274
3439
|
this.pendingAgents.clear();
|
|
3440
|
+
this.pendingWalletContexts.clear();
|
|
3441
|
+
this.readCache.clear();
|
|
3442
|
+
this.pendingReads.clear();
|
|
3275
3443
|
}
|
|
3276
3444
|
async activateAgent(state, chainId, asset) {
|
|
3277
3445
|
this.assertChain(chainId);
|
|
@@ -3534,6 +3702,86 @@ var YieldseekerAgent = class {
|
|
|
3534
3702
|
contextKey(state, chainId, asset) {
|
|
3535
3703
|
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3536
3704
|
}
|
|
3705
|
+
cachedRead(key2, ttlMs, read2) {
|
|
3706
|
+
const cached = this.readCache.get(key2);
|
|
3707
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
3708
|
+
return Promise.resolve(cached.value);
|
|
3709
|
+
}
|
|
3710
|
+
const pending = this.pendingReads.get(key2);
|
|
3711
|
+
if (pending) return pending;
|
|
3712
|
+
const generation = this.readGeneration;
|
|
3713
|
+
const request = Promise.resolve().then(read2).then((value) => {
|
|
3714
|
+
if (this.readGeneration === generation) {
|
|
3715
|
+
this.readCache.set(key2, {
|
|
3716
|
+
expiresAt: Date.now() + ttlMs,
|
|
3717
|
+
value
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
return value;
|
|
3721
|
+
}).finally(() => {
|
|
3722
|
+
if (this.pendingReads.get(key2) === request) {
|
|
3723
|
+
this.pendingReads.delete(key2);
|
|
3724
|
+
}
|
|
3725
|
+
});
|
|
3726
|
+
this.pendingReads.set(key2, request);
|
|
3727
|
+
return request;
|
|
3728
|
+
}
|
|
3729
|
+
agentListKey(state, chainId) {
|
|
3730
|
+
return `agents:${this.userKey(state, chainId)}`;
|
|
3731
|
+
}
|
|
3732
|
+
async listAgents(state, chainId, user) {
|
|
3733
|
+
const response = await this.cachedRead(
|
|
3734
|
+
this.agentListKey(state, chainId),
|
|
3735
|
+
YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
3736
|
+
async () => {
|
|
3737
|
+
const response2 = await this.walletRequest(
|
|
3738
|
+
state,
|
|
3739
|
+
chainId,
|
|
3740
|
+
`/users/${user.userId}/agents`
|
|
3741
|
+
);
|
|
3742
|
+
if (!Array.isArray(response2?.agents)) {
|
|
3743
|
+
throw this.invalidResponse("agent list");
|
|
3744
|
+
}
|
|
3745
|
+
return response2;
|
|
3746
|
+
}
|
|
3747
|
+
);
|
|
3748
|
+
return response.agents;
|
|
3749
|
+
}
|
|
3750
|
+
async contextForAgent(state, chainId, user, agent, asset) {
|
|
3751
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3752
|
+
const cached = this.agentContexts.get(key2);
|
|
3753
|
+
if (cached?.agent.agentId === agent.agentId) return cached;
|
|
3754
|
+
const pending = this.pendingWalletContexts.get(key2);
|
|
3755
|
+
if (pending) return pending;
|
|
3756
|
+
const generation = this.readGeneration;
|
|
3757
|
+
const request = this.walletRequest(
|
|
3758
|
+
state,
|
|
3759
|
+
chainId,
|
|
3760
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3761
|
+
).then((walletResponse) => {
|
|
3762
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3763
|
+
throw this.invalidResponse("agent wallet");
|
|
3764
|
+
}
|
|
3765
|
+
const context = {
|
|
3766
|
+
user,
|
|
3767
|
+
agent,
|
|
3768
|
+
wallet: walletResponse.agentWallet,
|
|
3769
|
+
asset
|
|
3770
|
+
};
|
|
3771
|
+
if (this.readGeneration === generation) {
|
|
3772
|
+
this.agentContexts.set(key2, context);
|
|
3773
|
+
}
|
|
3774
|
+
return context;
|
|
3775
|
+
});
|
|
3776
|
+
this.pendingWalletContexts.set(key2, request);
|
|
3777
|
+
try {
|
|
3778
|
+
return await request;
|
|
3779
|
+
} finally {
|
|
3780
|
+
if (this.pendingWalletContexts.get(key2) === request) {
|
|
3781
|
+
this.pendingWalletContexts.delete(key2);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3537
3785
|
async resolveUser(state, chainId) {
|
|
3538
3786
|
const key2 = this.userKey(state, chainId);
|
|
3539
3787
|
const inMemory = this.users.get(key2);
|
|
@@ -3630,16 +3878,9 @@ var YieldseekerAgent = class {
|
|
|
3630
3878
|
}
|
|
3631
3879
|
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3632
3880
|
const user = await this.resolveUser(state, chainId);
|
|
3633
|
-
const
|
|
3634
|
-
state,
|
|
3635
|
-
chainId,
|
|
3636
|
-
`/users/${user.userId}/agents`
|
|
3637
|
-
);
|
|
3638
|
-
if (!Array.isArray(response?.agents)) {
|
|
3639
|
-
throw this.invalidResponse("agent list");
|
|
3640
|
-
}
|
|
3881
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
3641
3882
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3642
|
-
let agent =
|
|
3883
|
+
let agent = agents.find(
|
|
3643
3884
|
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3644
3885
|
);
|
|
3645
3886
|
if (!agent && createIfMissing) {
|
|
@@ -3660,83 +3901,91 @@ var YieldseekerAgent = class {
|
|
|
3660
3901
|
}
|
|
3661
3902
|
);
|
|
3662
3903
|
agent = created?.agent;
|
|
3904
|
+
if (agent) {
|
|
3905
|
+
this.readCache.set(this.agentListKey(state, chainId), {
|
|
3906
|
+
expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
3907
|
+
value: {
|
|
3908
|
+
agents: [...agents, agent]
|
|
3909
|
+
}
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3663
3912
|
}
|
|
3664
3913
|
if (!agent) return null;
|
|
3665
3914
|
this.assertAgent(agent);
|
|
3666
|
-
|
|
3667
|
-
state,
|
|
3668
|
-
chainId,
|
|
3669
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3670
|
-
);
|
|
3671
|
-
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3672
|
-
throw this.invalidResponse("agent wallet");
|
|
3673
|
-
}
|
|
3674
|
-
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
3915
|
+
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3675
3916
|
}
|
|
3676
3917
|
async loadPortfolio(state, chainId, options) {
|
|
3677
3918
|
const user = await this.resolveUser(state, chainId);
|
|
3678
|
-
const
|
|
3679
|
-
state,
|
|
3680
|
-
chainId,
|
|
3681
|
-
`/users/${user.userId}/agents`
|
|
3682
|
-
);
|
|
3683
|
-
if (!Array.isArray(response?.agents)) {
|
|
3684
|
-
throw this.invalidResponse("agent list");
|
|
3685
|
-
}
|
|
3919
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
3686
3920
|
const contexts = [];
|
|
3687
|
-
for (const agent of
|
|
3921
|
+
for (const agent of agents) {
|
|
3688
3922
|
const asset = this.assetForAgent(agent);
|
|
3689
3923
|
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
|
|
3690
3924
|
continue;
|
|
3691
3925
|
}
|
|
3692
3926
|
this.assertAgent(agent);
|
|
3693
|
-
|
|
3694
|
-
state,
|
|
3695
|
-
chainId,
|
|
3696
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3697
|
-
);
|
|
3698
|
-
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3699
|
-
throw this.invalidResponse("agent wallet");
|
|
3700
|
-
}
|
|
3701
|
-
const context = {
|
|
3702
|
-
user,
|
|
3703
|
-
agent,
|
|
3704
|
-
wallet: walletResponse.agentWallet,
|
|
3705
|
-
asset
|
|
3706
|
-
};
|
|
3707
|
-
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3708
|
-
contexts.push(context);
|
|
3927
|
+
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
3709
3928
|
}
|
|
3929
|
+
const resolvedContexts = await Promise.all(contexts);
|
|
3710
3930
|
return Promise.all(
|
|
3711
|
-
|
|
3931
|
+
resolvedContexts.map(
|
|
3712
3932
|
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3713
3933
|
)
|
|
3714
3934
|
);
|
|
3715
3935
|
}
|
|
3716
3936
|
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3937
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
3717
3938
|
const [snapshot, positions, historic, actions] = await Promise.all([
|
|
3718
|
-
this.
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3939
|
+
this.cachedRead(
|
|
3940
|
+
`snapshot:${contextKey}`,
|
|
3941
|
+
YIELDSEEKER_PORTFOLIO_CACHE_MS,
|
|
3942
|
+
async () => {
|
|
3943
|
+
const response = await this.walletRequest(
|
|
3944
|
+
state,
|
|
3945
|
+
chainId,
|
|
3946
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3947
|
+
shouldOnlyUseRecentValue: true,
|
|
3948
|
+
shouldAllowStaleOnError: true
|
|
3949
|
+
})}`
|
|
3950
|
+
);
|
|
3951
|
+
if (!response?.agentSnapshot) {
|
|
3952
|
+
throw this.invalidResponse("agent snapshot");
|
|
3953
|
+
}
|
|
3954
|
+
return response;
|
|
3955
|
+
}
|
|
3725
3956
|
),
|
|
3726
|
-
this.
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3957
|
+
this.cachedRead(
|
|
3958
|
+
`positions:${contextKey}`,
|
|
3959
|
+
YIELDSEEKER_PORTFOLIO_CACHE_MS,
|
|
3960
|
+
async () => {
|
|
3961
|
+
const response = await this.walletRequest(
|
|
3962
|
+
state,
|
|
3963
|
+
chainId,
|
|
3964
|
+
this.agentPath(context, "yield-positions")
|
|
3965
|
+
);
|
|
3966
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
3967
|
+
throw this.invalidResponse("yield positions");
|
|
3968
|
+
}
|
|
3969
|
+
return response;
|
|
3970
|
+
}
|
|
3730
3971
|
),
|
|
3731
|
-
options.historic ? this.
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
this.
|
|
3972
|
+
options.historic ? this.cachedRead(
|
|
3973
|
+
`historic:${contextKey}`,
|
|
3974
|
+
YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
3975
|
+
() => this.walletRequest(
|
|
3976
|
+
state,
|
|
3977
|
+
chainId,
|
|
3978
|
+
this.agentPath(context, "wallet/historic-position")
|
|
3979
|
+
)
|
|
3735
3980
|
) : Promise.resolve(void 0),
|
|
3736
|
-
options.actions ? this.
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
this.
|
|
3981
|
+
options.actions ? this.cachedRead(
|
|
3982
|
+
`actions:${contextKey}`,
|
|
3983
|
+
YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
3984
|
+
() => this.walletRequest(
|
|
3985
|
+
state,
|
|
3986
|
+
chainId,
|
|
3987
|
+
this.agentPath(context, "actions")
|
|
3988
|
+
)
|
|
3740
3989
|
) : Promise.resolve(void 0)
|
|
3741
3990
|
]);
|
|
3742
3991
|
if (!snapshot?.agentSnapshot) {
|
|
@@ -3770,6 +4019,13 @@ var YieldseekerAgent = class {
|
|
|
3770
4019
|
context.wallet = deployed.agentWallet;
|
|
3771
4020
|
}
|
|
3772
4021
|
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4022
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4023
|
+
const invalidatePortfolio = () => {
|
|
4024
|
+
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4025
|
+
this.readCache.delete(`${kind}:${contextKey}`);
|
|
4026
|
+
}
|
|
4027
|
+
};
|
|
4028
|
+
invalidatePortfolio();
|
|
3773
4029
|
try {
|
|
3774
4030
|
const response = await this.walletRequest(
|
|
3775
4031
|
state,
|
|
@@ -3786,6 +4042,8 @@ var YieldseekerAgent = class {
|
|
|
3786
4042
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
3787
4043
|
error
|
|
3788
4044
|
);
|
|
4045
|
+
} finally {
|
|
4046
|
+
invalidatePortfolio();
|
|
3789
4047
|
}
|
|
3790
4048
|
}
|
|
3791
4049
|
agentPath(context, suffix) {
|
|
@@ -3836,6 +4094,7 @@ var YieldseekerAgent = class {
|
|
|
3836
4094
|
code,
|
|
3837
4095
|
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3838
4096
|
{
|
|
4097
|
+
rpcSource: "agent-api",
|
|
3839
4098
|
statusCode: error.status,
|
|
3840
4099
|
providerCode: error.providerCode,
|
|
3841
4100
|
...error.responseFields ? { fields: error.responseFields } : {}
|
|
@@ -3854,12 +4113,12 @@ var YieldseekerAgent = class {
|
|
|
3854
4113
|
chain: base3,
|
|
3855
4114
|
transport: custom2(state.provider)
|
|
3856
4115
|
});
|
|
3857
|
-
const
|
|
4116
|
+
const walletChainClient = createPublicClient3({
|
|
3858
4117
|
chain: base3,
|
|
3859
4118
|
transport: custom2(state.provider)
|
|
3860
4119
|
});
|
|
3861
4120
|
await ensureWalletOnChain(
|
|
3862
|
-
|
|
4121
|
+
walletChainClient,
|
|
3863
4122
|
walletClient,
|
|
3864
4123
|
8453
|
|
3865
4124
|
);
|
|
@@ -3870,10 +4129,15 @@ var YieldseekerAgent = class {
|
|
|
3870
4129
|
data: transaction.data,
|
|
3871
4130
|
value: BigInt(transaction.value)
|
|
3872
4131
|
});
|
|
3873
|
-
const receipt = await
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
4132
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4133
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4134
|
+
hash,
|
|
4135
|
+
confirmations: 1
|
|
4136
|
+
}),
|
|
4137
|
+
8453,
|
|
4138
|
+
"eth_getTransactionReceipt",
|
|
4139
|
+
this.id
|
|
4140
|
+
);
|
|
3877
4141
|
if (receipt.status !== "success") {
|
|
3878
4142
|
throw new OwneyError(
|
|
3879
4143
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3889,14 +4153,15 @@ var YieldseekerAgent = class {
|
|
|
3889
4153
|
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3890
4154
|
return;
|
|
3891
4155
|
}
|
|
3892
|
-
const
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
4156
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4157
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4158
|
+
hash: transactionHash,
|
|
4159
|
+
confirmations: 1
|
|
4160
|
+
}),
|
|
4161
|
+
8453,
|
|
4162
|
+
"eth_getTransactionReceipt",
|
|
4163
|
+
this.id
|
|
4164
|
+
);
|
|
3900
4165
|
if (receipt.status !== "success") {
|
|
3901
4166
|
throw new OwneyError(
|
|
3902
4167
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -4268,6 +4533,41 @@ import {
|
|
|
4268
4533
|
// src/lib/permit2-batch.ts
|
|
4269
4534
|
import { parseAbi as parseAbi2, hashStruct } from "viem";
|
|
4270
4535
|
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
4536
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
4537
|
+
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
4538
|
+
var ERC2612_PERMIT_TYPES = {
|
|
4539
|
+
Permit: [
|
|
4540
|
+
{ name: "owner", type: "address" },
|
|
4541
|
+
{ name: "spender", type: "address" },
|
|
4542
|
+
{ name: "value", type: "uint256" },
|
|
4543
|
+
{ name: "nonce", type: "uint256" },
|
|
4544
|
+
{ name: "deadline", type: "uint256" }
|
|
4545
|
+
]
|
|
4546
|
+
};
|
|
4547
|
+
var ERC2612_READ_ABI = parseAbi2([
|
|
4548
|
+
"function name() view returns (string)",
|
|
4549
|
+
"function version() view returns (string)",
|
|
4550
|
+
"function nonces(address owner) view returns (uint256)"
|
|
4551
|
+
]);
|
|
4552
|
+
function erc2612TypedData(input) {
|
|
4553
|
+
return {
|
|
4554
|
+
domain: {
|
|
4555
|
+
name: input.permit.tokenName,
|
|
4556
|
+
version: input.permit.tokenVersion,
|
|
4557
|
+
chainId: input.chainId,
|
|
4558
|
+
verifyingContract: input.token
|
|
4559
|
+
},
|
|
4560
|
+
types: ERC2612_PERMIT_TYPES,
|
|
4561
|
+
primaryType: "Permit",
|
|
4562
|
+
message: {
|
|
4563
|
+
owner: input.owner,
|
|
4564
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
4565
|
+
value: BigInt(input.permit.value),
|
|
4566
|
+
nonce: BigInt(input.permit.nonce),
|
|
4567
|
+
deadline: BigInt(input.permit.deadline)
|
|
4568
|
+
}
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4271
4571
|
var PERMIT_BATCH_TYPES = {
|
|
4272
4572
|
PermitBatchWitnessTransferFrom: [
|
|
4273
4573
|
{ name: "permitted", type: "TokenPermissions[]" },
|
|
@@ -4419,28 +4719,67 @@ async function execute(i, key2, plan) {
|
|
|
4419
4719
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4420
4720
|
"Insufficient token balance for this deposit."
|
|
4421
4721
|
);
|
|
4422
|
-
if (allowance < total)
|
|
4722
|
+
if (allowance < total && (i.chainId !== 8453 || !isAddressEqual(i.token, BASE_USDC_ADDRESS)))
|
|
4423
4723
|
throw new OwneyError(
|
|
4424
4724
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
4425
4725
|
"token deposits need a one-time Permit2 approval."
|
|
4426
4726
|
);
|
|
4427
|
-
const
|
|
4727
|
+
const now = (await i.pub.getBlock()).timestamp;
|
|
4728
|
+
let erc2612Permit;
|
|
4729
|
+
if (allowance < total) {
|
|
4730
|
+
const [tokenName, tokenVersion, permitNonce] = await Promise.all([
|
|
4731
|
+
i.pub.readContract({
|
|
4732
|
+
address: i.token,
|
|
4733
|
+
abi: ERC2612_READ_ABI,
|
|
4734
|
+
functionName: "name"
|
|
4735
|
+
}),
|
|
4736
|
+
i.pub.readContract({
|
|
4737
|
+
address: i.token,
|
|
4738
|
+
abi: ERC2612_READ_ABI,
|
|
4739
|
+
functionName: "version"
|
|
4740
|
+
}),
|
|
4741
|
+
i.pub.readContract({
|
|
4742
|
+
address: i.token,
|
|
4743
|
+
abi: ERC2612_READ_ABI,
|
|
4744
|
+
functionName: "nonces",
|
|
4745
|
+
args: [i.owner]
|
|
4746
|
+
})
|
|
4747
|
+
]);
|
|
4748
|
+
const unsignedPermit = {
|
|
4749
|
+
value: MAX_UINT256.toString(),
|
|
4750
|
+
nonce: permitNonce.toString(),
|
|
4751
|
+
deadline: (now + 900n).toString(),
|
|
4752
|
+
tokenName,
|
|
4753
|
+
tokenVersion
|
|
4754
|
+
};
|
|
4755
|
+
const signature2 = await i.wallet.signTypedData({
|
|
4756
|
+
account: i.owner,
|
|
4757
|
+
...erc2612TypedData({
|
|
4758
|
+
chainId: 8453,
|
|
4759
|
+
token: i.token,
|
|
4760
|
+
owner: i.owner,
|
|
4761
|
+
permit: unsignedPermit
|
|
4762
|
+
})
|
|
4763
|
+
});
|
|
4764
|
+
erc2612Permit = { ...unsignedPermit, signature: signature2 };
|
|
4765
|
+
}
|
|
4766
|
+
const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
|
|
4428
4767
|
apiKey: i.apiKey,
|
|
4429
4768
|
baseUrl: i.baseUrl,
|
|
4430
4769
|
chainId: i.chainId
|
|
4431
4770
|
});
|
|
4432
|
-
const now = (await i.pub.getBlock()).timestamp;
|
|
4433
4771
|
const unsigned = {
|
|
4434
4772
|
chainId: i.chainId,
|
|
4435
4773
|
token: i.token,
|
|
4436
4774
|
from: i.owner,
|
|
4437
4775
|
transfers: i.transfers,
|
|
4438
4776
|
nonce: randomPermit2Nonce().toString(),
|
|
4439
|
-
deadline: (now + 900n).toString()
|
|
4777
|
+
deadline: (now + 900n).toString(),
|
|
4778
|
+
...erc2612Permit ? { erc2612Permit } : {}
|
|
4440
4779
|
};
|
|
4441
4780
|
const signature = await i.wallet.signTypedData({
|
|
4442
4781
|
account: i.owner,
|
|
4443
|
-
...batchTypedData(unsigned,
|
|
4782
|
+
...batchTypedData(unsigned, spender)
|
|
4444
4783
|
});
|
|
4445
4784
|
i.onApproved?.();
|
|
4446
4785
|
return send({ ...unsigned, signature });
|
|
@@ -4460,8 +4799,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
4460
4799
|
"CHAIN_UNSUPPORTED",
|
|
4461
4800
|
`No sponsored token configured for chain ${chainId}`
|
|
4462
4801
|
);
|
|
4463
|
-
const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
|
|
4464
|
-
await ensureWalletOnChain(
|
|
4802
|
+
const pub = deps.getPublicClient(chainId), walletChain = deps.getWalletChainClient?.(chainId) ?? pub, wallet = deps.getWalletClient(chainId);
|
|
4803
|
+
await ensureWalletOnChain(walletChain, wallet, chainId);
|
|
4465
4804
|
return sponsorTokenBatch({
|
|
4466
4805
|
apiKey: deps.apiKey,
|
|
4467
4806
|
baseUrl: deps.baseUrl,
|
|
@@ -4701,6 +5040,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4701
5040
|
}
|
|
4702
5041
|
|
|
4703
5042
|
// src/client.ts
|
|
5043
|
+
var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
|
|
5044
|
+
var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
|
|
4704
5045
|
function encodeMultiAgentCursor(map) {
|
|
4705
5046
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
4706
5047
|
}
|
|
@@ -4761,6 +5102,7 @@ var OwneySDK = class {
|
|
|
4761
5102
|
// leave every user's agent profile alone.
|
|
4762
5103
|
orgAgentConfig;
|
|
4763
5104
|
orgAgentConfigPromise = null;
|
|
5105
|
+
rpcUrls;
|
|
4764
5106
|
zyfaiRpcUrls;
|
|
4765
5107
|
yieldseekerApiBaseUrl;
|
|
4766
5108
|
yieldseekerSiweOrigin;
|
|
@@ -4785,6 +5127,7 @@ var OwneySDK = class {
|
|
|
4785
5127
|
constructor(config) {
|
|
4786
5128
|
this.apiKey = config.apiKey;
|
|
4787
5129
|
if (config.debug) setOwneyDebug(true);
|
|
5130
|
+
this.rpcUrls = config.rpcUrls;
|
|
4788
5131
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
4789
5132
|
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4790
5133
|
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
@@ -4875,6 +5218,9 @@ var OwneySDK = class {
|
|
|
4875
5218
|
}
|
|
4876
5219
|
return this.state.provider;
|
|
4877
5220
|
}
|
|
5221
|
+
getPaidRpcClient(chainId) {
|
|
5222
|
+
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
5223
|
+
}
|
|
4878
5224
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
4879
5225
|
getDefaultSponsoredCallback(onApproved) {
|
|
4880
5226
|
if (!onApproved && this.cachedSponsoredCallback)
|
|
@@ -4890,7 +5236,8 @@ var OwneySDK = class {
|
|
|
4890
5236
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4891
5237
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4892
5238
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4893
|
-
getPublicClient: (cid) =>
|
|
5239
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
5240
|
+
getWalletChainClient: (cid) => createPublicClient4({
|
|
4894
5241
|
chain: VIEM_CHAIN2[cid],
|
|
4895
5242
|
transport: custom3(provider)
|
|
4896
5243
|
}),
|
|
@@ -4940,7 +5287,8 @@ var OwneySDK = class {
|
|
|
4940
5287
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4941
5288
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4942
5289
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4943
|
-
getPublicClient: (cid) =>
|
|
5290
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
5291
|
+
getWalletChainClient: (cid) => createPublicClient4({
|
|
4944
5292
|
chain: VIEM_CHAIN2[cid],
|
|
4945
5293
|
transport: custom3(provider)
|
|
4946
5294
|
}),
|
|
@@ -5053,12 +5401,17 @@ var OwneySDK = class {
|
|
|
5053
5401
|
createAgent(agentId, key2) {
|
|
5054
5402
|
if (agentId === "zyfai") {
|
|
5055
5403
|
if (!key2) return null;
|
|
5056
|
-
return new ZyfaiAgent(
|
|
5404
|
+
return new ZyfaiAgent(
|
|
5405
|
+
key2,
|
|
5406
|
+
this.rpcUrls ?? this.zyfaiRpcUrls,
|
|
5407
|
+
this.referralSource
|
|
5408
|
+
);
|
|
5057
5409
|
}
|
|
5058
5410
|
if (agentId === "yieldseeker") {
|
|
5059
5411
|
return new YieldseekerAgent(this.apiKey, {
|
|
5060
5412
|
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5061
|
-
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
5413
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
|
|
5414
|
+
rpcUrls: this.rpcUrls
|
|
5062
5415
|
});
|
|
5063
5416
|
}
|
|
5064
5417
|
return null;
|
|
@@ -5349,7 +5702,8 @@ var OwneySDK = class {
|
|
|
5349
5702
|
);
|
|
5350
5703
|
await this.approvePermit2(
|
|
5351
5704
|
asset,
|
|
5352
|
-
requiredAmount
|
|
5705
|
+
requiredAmount,
|
|
5706
|
+
cid
|
|
5353
5707
|
);
|
|
5354
5708
|
return batchTransfer(cid, transfers);
|
|
5355
5709
|
}
|
|
@@ -5393,10 +5747,12 @@ var OwneySDK = class {
|
|
|
5393
5747
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
5394
5748
|
* two independent auto-recovery mechanisms:
|
|
5395
5749
|
*
|
|
5396
|
-
* 1. Missing Permit2 allowance
|
|
5397
|
-
* callback and the attempt fails with
|
|
5398
|
-
*
|
|
5399
|
-
*
|
|
5750
|
+
* 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
|
|
5751
|
+
* app did not supply its own callback and the attempt fails with
|
|
5752
|
+
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
5753
|
+
* for that token. Base USDC bundles a gasless ERC-2612 approval inside
|
|
5754
|
+
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
5755
|
+
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
5400
5756
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5401
5757
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
5402
5758
|
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
@@ -5439,7 +5795,8 @@ var OwneySDK = class {
|
|
|
5439
5795
|
);
|
|
5440
5796
|
await this.approvePermit2(
|
|
5441
5797
|
asset,
|
|
5442
|
-
BigInt(amount)
|
|
5798
|
+
BigInt(amount),
|
|
5799
|
+
chainId
|
|
5443
5800
|
);
|
|
5444
5801
|
continue;
|
|
5445
5802
|
}
|
|
@@ -5586,6 +5943,32 @@ var OwneySDK = class {
|
|
|
5586
5943
|
}
|
|
5587
5944
|
return eligible;
|
|
5588
5945
|
}
|
|
5946
|
+
/**
|
|
5947
|
+
* Run owner-approved withdrawals before relayer-only withdrawals. Wallet
|
|
5948
|
+
* approval is the only point at which the user can cancel the aggregate
|
|
5949
|
+
* operation, so no relayer leg should commit before it has completed.
|
|
5950
|
+
*/
|
|
5951
|
+
orderAgentsForWithdrawal(agents) {
|
|
5952
|
+
return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
|
|
5953
|
+
const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
|
|
5954
|
+
return approvalOrder || left.index - right.index;
|
|
5955
|
+
}).map(({ agent }) => agent);
|
|
5956
|
+
}
|
|
5957
|
+
isUserRejectedWithdrawal(error) {
|
|
5958
|
+
let current = error;
|
|
5959
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5960
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
5961
|
+
seen.add(current);
|
|
5962
|
+
const candidate = current;
|
|
5963
|
+
if (candidate.code === 4001 || candidate.code === "4001") return true;
|
|
5964
|
+
const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
|
|
5965
|
+
if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
|
|
5966
|
+
return true;
|
|
5967
|
+
}
|
|
5968
|
+
current = candidate.cause;
|
|
5969
|
+
}
|
|
5970
|
+
return false;
|
|
5971
|
+
}
|
|
5589
5972
|
// --- Fund operations ---
|
|
5590
5973
|
/**
|
|
5591
5974
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -5621,13 +6004,15 @@ var OwneySDK = class {
|
|
|
5621
6004
|
);
|
|
5622
6005
|
}
|
|
5623
6006
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6007
|
+
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
5624
6008
|
if (!amount) {
|
|
5625
6009
|
const results2 = {};
|
|
5626
6010
|
const agentErrors2 = {};
|
|
5627
|
-
for (const agent of
|
|
6011
|
+
for (const agent of withdrawalAgents) {
|
|
5628
6012
|
try {
|
|
5629
6013
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
5630
6014
|
} catch (err) {
|
|
6015
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
5631
6016
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
5632
6017
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
5633
6018
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5708,12 +6093,13 @@ var OwneySDK = class {
|
|
|
5708
6093
|
planned: 0n
|
|
5709
6094
|
}));
|
|
5710
6095
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
6096
|
+
const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
|
|
5711
6097
|
const results = {};
|
|
5712
6098
|
const agentErrors = {
|
|
5713
6099
|
...aggregated.agentErrors ?? {}
|
|
5714
6100
|
};
|
|
5715
|
-
for (let i = 0; i <
|
|
5716
|
-
const p =
|
|
6101
|
+
for (let i = 0; i < orderedPlans.length; i++) {
|
|
6102
|
+
const p = orderedPlans[i];
|
|
5717
6103
|
if (p.planned === 0n) continue;
|
|
5718
6104
|
try {
|
|
5719
6105
|
results[p.agent.id] = await p.agent.withdraw(
|
|
@@ -5723,6 +6109,7 @@ var OwneySDK = class {
|
|
|
5723
6109
|
p.planned.toString()
|
|
5724
6110
|
);
|
|
5725
6111
|
} catch (err) {
|
|
6112
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
5726
6113
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
5727
6114
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
5728
6115
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5734,7 +6121,7 @@ var OwneySDK = class {
|
|
|
5734
6121
|
);
|
|
5735
6122
|
const failedAmount = p.planned;
|
|
5736
6123
|
p.planned = 0n;
|
|
5737
|
-
redistributeShare(
|
|
6124
|
+
redistributeShare(orderedPlans, i, failedAmount);
|
|
5738
6125
|
}
|
|
5739
6126
|
}
|
|
5740
6127
|
if (Object.keys(results).length === 0) {
|
|
@@ -6162,14 +6549,16 @@ var OwneySDK = class {
|
|
|
6162
6549
|
* User-paid approval of Permit2 on the selected token for the active chain.
|
|
6163
6550
|
* Grants the maximum ERC20 allowance so later deposits do not require another
|
|
6164
6551
|
* approval. Resolves after one confirmation so the subsequent deposit attempt
|
|
6165
|
-
* sees the new allowance.
|
|
6552
|
+
* sees the new allowance. Deposit retries pass their captured chain id so a
|
|
6553
|
+
* concurrent activation cannot redirect the approval to another network.
|
|
6166
6554
|
*
|
|
6167
6555
|
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
6556
|
+
* @param expectedChainId Chain captured by the deposit that requested approval.
|
|
6168
6557
|
* @returns the approval transaction hash.
|
|
6169
6558
|
*/
|
|
6170
|
-
async approvePermit2(asset = "WETH", requiredAmount = 0n) {
|
|
6559
|
+
async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId) {
|
|
6171
6560
|
const state = this.requireState();
|
|
6172
|
-
const chainId = this.requireChainId();
|
|
6561
|
+
const chainId = expectedChainId ?? this.requireChainId();
|
|
6173
6562
|
this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
|
|
6174
6563
|
const token = sponsoredTokensFor(asset)[chainId];
|
|
6175
6564
|
if (!token) {
|
|
@@ -6179,16 +6568,18 @@ var OwneySDK = class {
|
|
|
6179
6568
|
);
|
|
6180
6569
|
}
|
|
6181
6570
|
const provider = this.requireConnectedProvider();
|
|
6182
|
-
const
|
|
6571
|
+
const walletChainClient = createPublicClient4({
|
|
6183
6572
|
chain: VIEM_CHAIN2[chainId],
|
|
6184
6573
|
transport: custom3(provider)
|
|
6185
6574
|
});
|
|
6575
|
+
const publicClient = this.getPaidRpcClient(chainId);
|
|
6186
6576
|
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6187
6577
|
const wallet = createWalletClient3({
|
|
6188
6578
|
account: state.walletAddress,
|
|
6189
6579
|
chain: VIEM_CHAIN2[chainId],
|
|
6190
6580
|
transport: custom3(provider)
|
|
6191
6581
|
});
|
|
6582
|
+
await ensureWalletOnChain(walletChainClient, wallet, chainId);
|
|
6192
6583
|
const hash = await wallet.writeContract({
|
|
6193
6584
|
address: token,
|
|
6194
6585
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -6197,14 +6588,53 @@ var OwneySDK = class {
|
|
|
6197
6588
|
account: state.walletAddress,
|
|
6198
6589
|
chain: VIEM_CHAIN2[chainId]
|
|
6199
6590
|
});
|
|
6200
|
-
const receipt = await
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6591
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
6592
|
+
() => publicClient.waitForTransactionReceipt({
|
|
6593
|
+
hash,
|
|
6594
|
+
confirmations: 1
|
|
6595
|
+
}),
|
|
6596
|
+
chainId,
|
|
6597
|
+
"eth_getTransactionReceipt"
|
|
6598
|
+
);
|
|
6204
6599
|
if (receipt.status !== "success") {
|
|
6205
6600
|
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
6206
6601
|
}
|
|
6207
|
-
|
|
6602
|
+
let observedAllowance = 0n;
|
|
6603
|
+
let verificationError;
|
|
6604
|
+
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
|
|
6605
|
+
try {
|
|
6606
|
+
observedAllowance = await readPermit2Allowance(
|
|
6607
|
+
publicClient,
|
|
6608
|
+
token,
|
|
6609
|
+
state.walletAddress,
|
|
6610
|
+
attempt === 0 ? receipt.blockNumber : void 0
|
|
6611
|
+
);
|
|
6612
|
+
verificationError = void 0;
|
|
6613
|
+
if (observedAllowance >= requiredAmount) return hash;
|
|
6614
|
+
} catch (error) {
|
|
6615
|
+
verificationError = error;
|
|
6616
|
+
}
|
|
6617
|
+
if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
|
|
6618
|
+
await new Promise(
|
|
6619
|
+
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
|
|
6620
|
+
);
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
throw new OwneyError(
|
|
6624
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
6625
|
+
"Permit2 approval was confirmed, but the required token allowance was not observable.",
|
|
6626
|
+
{
|
|
6627
|
+
approvalConfirmed: true,
|
|
6628
|
+
approvalTxHash: hash,
|
|
6629
|
+
owner: state.walletAddress,
|
|
6630
|
+
token,
|
|
6631
|
+
spender: PERMIT2_ADDRESS,
|
|
6632
|
+
chainId,
|
|
6633
|
+
requiredAmount: requiredAmount.toString(),
|
|
6634
|
+
observedAllowance: observedAllowance.toString(),
|
|
6635
|
+
...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
|
|
6636
|
+
}
|
|
6637
|
+
);
|
|
6208
6638
|
}
|
|
6209
6639
|
// --- Discovery (no wallet required) ---
|
|
6210
6640
|
/**
|