@owney/sdk 0.7.25-beta.15 → 0.7.25-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +910 -235
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +851 -174
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -370,20 +370,44 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
370
370
|
};
|
|
371
371
|
const notify = (outcome) => {
|
|
372
372
|
if (outcome === "rejected" && matching.length !== 1) return;
|
|
373
|
+
const evidence = matching.length === 1 ? { ...request2 } : {
|
|
374
|
+
attempted: request2.attempted,
|
|
375
|
+
method: request2.method,
|
|
376
|
+
path: request2.path,
|
|
377
|
+
...request2.elapsedMs !== void 0 ? { elapsedMs: request2.elapsedMs } : {}
|
|
378
|
+
};
|
|
373
379
|
for (const observer of matching) {
|
|
374
380
|
try {
|
|
375
|
-
observer.notify(outcome,
|
|
381
|
+
observer.notify(outcome, evidence);
|
|
376
382
|
} catch {
|
|
377
383
|
}
|
|
378
384
|
}
|
|
379
385
|
};
|
|
380
386
|
notify("attempted");
|
|
387
|
+
let callArgs = args;
|
|
388
|
+
if (withdrawal && client.client?.defaults) {
|
|
389
|
+
const configIndex = method === "get" ? 1 : 2;
|
|
390
|
+
const config = args[configIndex];
|
|
391
|
+
const transforms = config?.transformResponse !== void 0 ? config.transformResponse : client.client.defaults.transformResponse;
|
|
392
|
+
const captureStatus = (data, _headers, status) => {
|
|
393
|
+
if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599) request2.httpStatus = status;
|
|
394
|
+
return data;
|
|
395
|
+
};
|
|
396
|
+
callArgs = [...args];
|
|
397
|
+
callArgs[configIndex] = { ...config, transformResponse: [
|
|
398
|
+
...Array.isArray(transforms) ? transforms : typeof transforms === "function" ? [transforms] : [],
|
|
399
|
+
captureStatus
|
|
400
|
+
] };
|
|
401
|
+
}
|
|
381
402
|
try {
|
|
382
|
-
return await original.apply(this,
|
|
403
|
+
return await original.apply(this, callArgs);
|
|
383
404
|
} catch (error) {
|
|
384
405
|
const message = error instanceof Error ? error.message : "";
|
|
385
|
-
|
|
386
|
-
if (
|
|
406
|
+
const status = error?.response?.status;
|
|
407
|
+
if (request2.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
|
|
408
|
+
request2.httpStatus = status;
|
|
409
|
+
request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : /^Idle balance \d+(?:\.\d+)? (USDC|WETH|ETH|EURC) is below the minimum withdrawal of \d+(?:\.\d+)? \1\.?$/.test(message) ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request2.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
410
|
+
if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing", "withdrawal-in-progress"].includes(request2.failure) && (request2.httpStatus === void 0 || request2.httpStatus >= 400 && request2.httpStatus < 500)) notify("rejected");
|
|
387
411
|
throw error;
|
|
388
412
|
} finally {
|
|
389
413
|
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
@@ -1464,6 +1488,7 @@ var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
|
|
|
1464
1488
|
var SPLITTING_ASSETS = ["USDC", "WETH"];
|
|
1465
1489
|
var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
|
|
1466
1490
|
var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1491
|
+
var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
|
|
1467
1492
|
var ZyfaiAgent = class _ZyfaiAgent {
|
|
1468
1493
|
id = "zyfai";
|
|
1469
1494
|
supportedChainIds = [8453, 42161, 1];
|
|
@@ -1518,6 +1543,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1518
1543
|
earningsRefreshes = /* @__PURE__ */ new Map();
|
|
1519
1544
|
earningsSnapshot = null;
|
|
1520
1545
|
earningsGeneration = 0;
|
|
1546
|
+
balanceSnapshots = /* @__PURE__ */ new WeakMap();
|
|
1521
1547
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1522
1548
|
this.rpcUrls = resolveRpcUrls(rpcUrls);
|
|
1523
1549
|
this.sdk = new ZyfaiSDK({
|
|
@@ -1721,7 +1747,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1721
1747
|
* chain/account switch fires the provider's `accountsChanged` →
|
|
1722
1748
|
* `disconnectAccount` (nulls walletClient + authenticatedUserId) while our
|
|
1723
1749
|
* own `connectAuth` guard still believes this chain is connected and
|
|
1724
|
-
* early-returns. Without this, authenticated calls (
|
|
1750
|
+
* early-returns. Without this, authenticated calls (sendDeposit,
|
|
1725
1751
|
* withdrawFunds) fail with "No account connected".
|
|
1726
1752
|
*/
|
|
1727
1753
|
async ensureConnected(state, chainId) {
|
|
@@ -2343,7 +2369,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2343
2369
|
return { txHash, smartWallet, amount, evidence: evidence2 };
|
|
2344
2370
|
}
|
|
2345
2371
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
2346
|
-
const raw2 = await this.sdk.
|
|
2372
|
+
const raw2 = await this.sdk.sendDeposit(
|
|
2347
2373
|
this.getAddress(),
|
|
2348
2374
|
validChainId,
|
|
2349
2375
|
amount,
|
|
@@ -2359,7 +2385,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2359
2385
|
intendedAmount: amount,
|
|
2360
2386
|
txHash: raw2.txHash,
|
|
2361
2387
|
transfer: "unknown",
|
|
2362
|
-
registration: "unknown",
|
|
2388
|
+
registration: raw2.registration ? "confirmed" : "unknown",
|
|
2363
2389
|
delivery: "not-applicable"
|
|
2364
2390
|
};
|
|
2365
2391
|
notifyFunding(onProgress, evidence);
|
|
@@ -2378,9 +2404,74 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2378
2404
|
}
|
|
2379
2405
|
}
|
|
2380
2406
|
// --- IAgent: Fund operations ---
|
|
2407
|
+
/** Only after a definitive endpoint rejection, combine a new provider
|
|
2408
|
+
* snapshot with the Safe's exact idle balance. Never parse its rounded error
|
|
2409
|
+
* amount as proof of zero, or use this to skip sending a new MAX request. */
|
|
2410
|
+
async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner) {
|
|
2411
|
+
const internals = this.sdk;
|
|
2412
|
+
const generation = this.earningsGeneration;
|
|
2413
|
+
let expired = false;
|
|
2414
|
+
const unavailable = () => new OwneyError(
|
|
2415
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
2416
|
+
"Could not verify Zyfai's balance after its withdrawal rejection.",
|
|
2417
|
+
{ chainId, token },
|
|
2418
|
+
this.id
|
|
2419
|
+
);
|
|
2420
|
+
const assertScope = () => {
|
|
2421
|
+
if (expired) throw unavailable();
|
|
2422
|
+
const account = internals.walletClient?.account?.address;
|
|
2423
|
+
if (generation !== this.earningsGeneration || state.chainId !== chainId || state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
|
|
2424
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2425
|
+
};
|
|
2426
|
+
const read2 = async () => {
|
|
2427
|
+
assertScope();
|
|
2428
|
+
const [wallet, registered, portfolio] = await Promise.all([
|
|
2429
|
+
this.sdk.getSmartWalletAddress(owner, chainId),
|
|
2430
|
+
this.sdk.getSmartWalletByEOA(owner),
|
|
2431
|
+
this.sdk.getPortfolio(owner).catch(() => void 0)
|
|
2432
|
+
]);
|
|
2433
|
+
assertScope();
|
|
2434
|
+
if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
|
|
2435
|
+
throw new OwneyError(
|
|
2436
|
+
"AGENT_AUTH_FAILED",
|
|
2437
|
+
"Could not verify Zyfai's withdrawal wallet.",
|
|
2438
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2439
|
+
this.id
|
|
2440
|
+
);
|
|
2441
|
+
if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
|
|
2442
|
+
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
2443
|
+
const idle = asset ? await this.getPublicClient(chainId).readContract({
|
|
2444
|
+
address: asset.address,
|
|
2445
|
+
abi: erc20Abi2,
|
|
2446
|
+
functionName: "balanceOf",
|
|
2447
|
+
args: [wallet.address]
|
|
2448
|
+
}).catch(() => void 0) : void 0;
|
|
2449
|
+
assertScope();
|
|
2450
|
+
if (idle === 0n) throw new OwneyError(
|
|
2451
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2452
|
+
"No funds remain in Zyfai for this asset.",
|
|
2453
|
+
{ available: "0", chainId, token },
|
|
2454
|
+
this.id
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2458
|
+
let timer;
|
|
2459
|
+
try {
|
|
2460
|
+
await Promise.race([read2(), new Promise((_, reject) => {
|
|
2461
|
+
timer = setTimeout(() => {
|
|
2462
|
+
expired = true;
|
|
2463
|
+
reject(unavailable());
|
|
2464
|
+
}, WITHDRAW_BALANCE_TIMEOUT_MS);
|
|
2465
|
+
})]);
|
|
2466
|
+
} finally {
|
|
2467
|
+
expired = true;
|
|
2468
|
+
if (timer) clearTimeout(timer);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2381
2471
|
/** Called only after the execution endpoint explicitly rejected a request.
|
|
2382
2472
|
* A timeout or an accepted request never authorizes repair and resubmission. */
|
|
2383
2473
|
async recoverWithdrawalSession(state, chainId, token, owner) {
|
|
2474
|
+
await this.assertNotEmptyRejectedWithdrawal(state, chainId, token, owner);
|
|
2384
2475
|
const internals = this.sdk;
|
|
2385
2476
|
const assertScope = () => {
|
|
2386
2477
|
const account = internals.walletClient?.account?.address;
|
|
@@ -2388,35 +2479,6 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2388
2479
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2389
2480
|
};
|
|
2390
2481
|
assertScope();
|
|
2391
|
-
const [wallet, registered, portfolio] = await Promise.all([
|
|
2392
|
-
this.sdk.getSmartWalletAddress(owner, chainId),
|
|
2393
|
-
this.sdk.getSmartWalletByEOA(owner),
|
|
2394
|
-
this.sdk.getPortfolio(owner).catch(() => void 0)
|
|
2395
|
-
]);
|
|
2396
|
-
assertScope();
|
|
2397
|
-
if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
|
|
2398
|
-
throw new OwneyError(
|
|
2399
|
-
"AGENT_AUTH_FAILED",
|
|
2400
|
-
"Could not verify Zyfai's withdrawal wallet.",
|
|
2401
|
-
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2402
|
-
this.id
|
|
2403
|
-
);
|
|
2404
|
-
if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
|
|
2405
|
-
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
2406
|
-
const idle = asset ? await this.getPublicClient(chainId).readContract({
|
|
2407
|
-
address: asset.address,
|
|
2408
|
-
abi: erc20Abi2,
|
|
2409
|
-
functionName: "balanceOf",
|
|
2410
|
-
args: [wallet.address]
|
|
2411
|
-
}).catch(() => void 0) : void 0;
|
|
2412
|
-
assertScope();
|
|
2413
|
-
if (idle === 0n) throw new OwneyError(
|
|
2414
|
-
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2415
|
-
"No funds remain in Zyfai for this asset.",
|
|
2416
|
-
{ available: "0", chainId, token },
|
|
2417
|
-
this.id
|
|
2418
|
-
);
|
|
2419
|
-
}
|
|
2420
2482
|
const walletState = await this.readWalletState(owner);
|
|
2421
2483
|
assertScope();
|
|
2422
2484
|
const unavailable = () => new OwneyError(
|
|
@@ -2468,6 +2530,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2468
2530
|
raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
|
|
2469
2531
|
break;
|
|
2470
2532
|
} catch (error) {
|
|
2533
|
+
if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
|
|
2534
|
+
try {
|
|
2535
|
+
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
|
|
2536
|
+
} catch (proofError) {
|
|
2537
|
+
if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2471
2540
|
if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
|
|
2472
2541
|
if (attempt === 1)
|
|
2473
2542
|
throw new OwneyError(
|
|
@@ -2561,12 +2630,100 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2561
2630
|
async getFundingWallet(state, chainId) {
|
|
2562
2631
|
return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
|
|
2563
2632
|
}
|
|
2633
|
+
/** A positive Safe balance is a withdrawal planning floor when the portfolio
|
|
2634
|
+
* index reports zero. Idle zero cannot prove that Zyfai's protocol positions
|
|
2635
|
+
* are empty: its SDK does not expose a complete contract catalog for them. */
|
|
2636
|
+
async getWithdrawableBalance(state, chainId, token) {
|
|
2637
|
+
const validChainId = isValidChainId(chainId);
|
|
2638
|
+
const asset = SupportedAssets.find((value) => value.chainId === validChainId && value.symbol === token);
|
|
2639
|
+
if (!asset) throw new OwneyError("WITHDRAW_NO_PERMITTED_TOKENS", "Asset selected is not supported", void 0, this.id);
|
|
2640
|
+
const owner = state.walletAddress;
|
|
2641
|
+
const generation = this.earningsGeneration;
|
|
2642
|
+
const internals = this.sdk;
|
|
2643
|
+
let expired = false;
|
|
2644
|
+
const unavailable = () => new OwneyError(
|
|
2645
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
2646
|
+
"Could not verify Zyfai's withdrawal balance. Its vault positions may still contain funds.",
|
|
2647
|
+
{ reason: "ZYFAI_VAULT_BALANCE_UNVERIFIED", chainId, token },
|
|
2648
|
+
this.id
|
|
2649
|
+
);
|
|
2650
|
+
const assertScope = () => {
|
|
2651
|
+
if (expired) throw unavailable();
|
|
2652
|
+
const account = internals.walletClient?.account?.address;
|
|
2653
|
+
if (generation !== this.earningsGeneration || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== validChainId || state.walletAddress.toLowerCase() !== owner.toLowerCase() || state.chainId !== validChainId || account && account.toLowerCase() !== owner.toLowerCase())
|
|
2654
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while checking Zyfai's withdrawal balance.", void 0, this.id);
|
|
2655
|
+
};
|
|
2656
|
+
let timer;
|
|
2657
|
+
const read2 = async () => {
|
|
2658
|
+
assertScope();
|
|
2659
|
+
const [wallet, registered] = await Promise.all([
|
|
2660
|
+
this.sdk.getSmartWalletAddress(owner, validChainId),
|
|
2661
|
+
this.sdk.getSmartWalletByEOA(owner)
|
|
2662
|
+
]);
|
|
2663
|
+
assertScope();
|
|
2664
|
+
if (!isAddress(wallet.address) || wallet.isDeployed !== true || wallet.isOwner !== true || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
|
|
2665
|
+
const client = this.getPublicClient(validChainId);
|
|
2666
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
2667
|
+
assertScope();
|
|
2668
|
+
const idle = await client.readContract({
|
|
2669
|
+
address: asset.address,
|
|
2670
|
+
abi: erc20Abi2,
|
|
2671
|
+
functionName: "balanceOf",
|
|
2672
|
+
args: [wallet.address],
|
|
2673
|
+
blockNumber
|
|
2674
|
+
});
|
|
2675
|
+
assertScope();
|
|
2676
|
+
if (typeof idle !== "bigint" || idle <= 0n) throw unavailable();
|
|
2677
|
+
return idle;
|
|
2678
|
+
};
|
|
2679
|
+
try {
|
|
2680
|
+
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
2681
|
+
timer = setTimeout(() => {
|
|
2682
|
+
expired = true;
|
|
2683
|
+
reject(unavailable());
|
|
2684
|
+
}, WITHDRAW_BALANCE_TIMEOUT_MS);
|
|
2685
|
+
})]);
|
|
2686
|
+
} catch (error) {
|
|
2687
|
+
if (error instanceof OwneyError) throw error;
|
|
2688
|
+
throw unavailable();
|
|
2689
|
+
} finally {
|
|
2690
|
+
expired = true;
|
|
2691
|
+
if (timer) clearTimeout(timer);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2564
2694
|
async getBalances(state, chainId) {
|
|
2565
2695
|
const requestedAt = Date.now();
|
|
2696
|
+
const generation = this.earningsGeneration;
|
|
2566
2697
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
2567
2698
|
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
2568
2699
|
const balance = mapBalances(raw2, validChainId, smartWallet);
|
|
2569
|
-
|
|
2700
|
+
const reads = balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) ?? [];
|
|
2701
|
+
for (const asset of SupportedAssets.filter((item) => item.chainId === chainId)) {
|
|
2702
|
+
if (!reads.some((read2) => read2.chainId === chainId && read2.asset === asset.symbol) && hasEmptyWithdrawalSnapshot(raw2, chainId, asset.symbol)) {
|
|
2703
|
+
reads.push({ chainId, asset: asset.symbol, requestedAt, fetchedAt: Date.now(), positionsStatus: "available" });
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
const result = { ...balance, readMetadata: reads };
|
|
2707
|
+
for (const read2 of reads) this.balanceSnapshots.set(read2, { raw: raw2, generation, owner: state.walletAddress.toLowerCase() });
|
|
2708
|
+
return result;
|
|
2709
|
+
}
|
|
2710
|
+
async verifyWithdrawalBalance(state, evidence, balance) {
|
|
2711
|
+
const read2 = balance.readMetadata?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
|
|
2712
|
+
const snapshot = read2 && this.balanceSnapshots.get(read2);
|
|
2713
|
+
const asset = SupportedAssets.find((item) => item.chainId === evidence.chainId && item.symbol === evidence.token);
|
|
2714
|
+
if (!snapshot || snapshot.generation !== this.earningsGeneration || snapshot.owner !== state.walletAddress.toLowerCase() || !read2 || !asset || !evidence.txHash || !evidence.blockNumber || !balance.smartWallet || balance.smartWallet.toLowerCase() !== evidence.agentWallet?.toLowerCase() || !hasEmptyWithdrawalSnapshot(snapshot.raw, evidence.chainId, evidence.token)) return void 0;
|
|
2715
|
+
const client = this.getPublicClient(evidence.chainId);
|
|
2716
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
2717
|
+
if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
|
|
2718
|
+
const idle = await client.readContract({
|
|
2719
|
+
address: asset.address,
|
|
2720
|
+
abi: erc20Abi2,
|
|
2721
|
+
functionName: "balanceOf",
|
|
2722
|
+
args: [balance.smartWallet],
|
|
2723
|
+
blockNumber
|
|
2724
|
+
});
|
|
2725
|
+
if (idle !== 0n || snapshot.generation !== this.earningsGeneration || this.connectedAddress?.toLowerCase() !== snapshot.owner || this.connectedChainId !== evidence.chainId) return void 0;
|
|
2726
|
+
return { ...read2, withdrawalCheck: { txHash: evidence.txHash, blockNumber: blockNumber.toString() } };
|
|
2570
2727
|
}
|
|
2571
2728
|
earningsKey(state, chainId, smartWallet) {
|
|
2572
2729
|
return JSON.stringify([
|
|
@@ -2772,10 +2929,11 @@ import {
|
|
|
2772
2929
|
decodeEventLog as decodeEventLog2,
|
|
2773
2930
|
encodeEventTopics,
|
|
2774
2931
|
encodeFunctionData,
|
|
2775
|
-
erc20Abi as
|
|
2776
|
-
erc4626Abi,
|
|
2777
|
-
getAddress as
|
|
2778
|
-
isAddress as isAddress3
|
|
2932
|
+
erc20Abi as erc20Abi4,
|
|
2933
|
+
erc4626Abi as erc4626Abi2,
|
|
2934
|
+
getAddress as getAddress5,
|
|
2935
|
+
isAddress as isAddress3,
|
|
2936
|
+
parseUnits
|
|
2779
2937
|
} from "viem";
|
|
2780
2938
|
import { base as base4 } from "viem/chains";
|
|
2781
2939
|
|
|
@@ -3565,6 +3723,7 @@ function mapYieldseekerBalances(contexts) {
|
|
|
3565
3723
|
chainId: context.agent.chainId,
|
|
3566
3724
|
asset: context.asset,
|
|
3567
3725
|
fetchedAt: context.fetchedAt,
|
|
3726
|
+
requestedAt: context.requestedAt,
|
|
3568
3727
|
readVersion: context.readVersion,
|
|
3569
3728
|
providerUpdatedAt: context.snapshot.updatedDate,
|
|
3570
3729
|
snapshotId: String(context.snapshot.agentWalletSnapshotId),
|
|
@@ -3942,9 +4101,55 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
3942
4101
|
};
|
|
3943
4102
|
}
|
|
3944
4103
|
|
|
4104
|
+
// src/lib/remaining-vault-balance.ts
|
|
4105
|
+
import { erc20Abi as erc20Abi3, erc4626Abi, getAddress as getAddress2 } from "viem";
|
|
4106
|
+
async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber) {
|
|
4107
|
+
const addresses = [...new Set(vaults.map((value) => value.toLowerCase()))];
|
|
4108
|
+
if (addresses.length > 64) return void 0;
|
|
4109
|
+
const batches = [];
|
|
4110
|
+
for (let index = 0; index < addresses.length; index += 10) batches.push(addresses.slice(index, index + 10));
|
|
4111
|
+
const values = await Promise.all(batches.map(async (batch) => {
|
|
4112
|
+
const balances = await client.multicall({
|
|
4113
|
+
blockNumber,
|
|
4114
|
+
allowFailure: true,
|
|
4115
|
+
contracts: batch.map((address) => ({
|
|
4116
|
+
address: getAddress2(address),
|
|
4117
|
+
abi: erc20Abi3,
|
|
4118
|
+
functionName: "balanceOf",
|
|
4119
|
+
args: [wallet]
|
|
4120
|
+
}))
|
|
4121
|
+
});
|
|
4122
|
+
if (balances.length !== batch.length) return void 0;
|
|
4123
|
+
const funded = [];
|
|
4124
|
+
for (let index = 0; index < batch.length; index++) {
|
|
4125
|
+
const result = balances[index];
|
|
4126
|
+
if (result?.status !== "success" || typeof result.result !== "bigint" || result.result < 0n) return void 0;
|
|
4127
|
+
if (result.result > 0n) funded.push({ address: getAddress2(batch[index]), shares: result.result });
|
|
4128
|
+
}
|
|
4129
|
+
if (!funded.length) return 0n;
|
|
4130
|
+
const valuations = await client.multicall({
|
|
4131
|
+
blockNumber,
|
|
4132
|
+
allowFailure: true,
|
|
4133
|
+
contracts: funded.flatMap(({ address, shares }) => [
|
|
4134
|
+
{ address, abi: erc4626Abi, functionName: "asset" },
|
|
4135
|
+
{ address, abi: erc4626Abi, functionName: "convertToAssets", args: [shares] }
|
|
4136
|
+
])
|
|
4137
|
+
});
|
|
4138
|
+
if (valuations.length !== funded.length * 2) return void 0;
|
|
4139
|
+
let total = 0n;
|
|
4140
|
+
for (let index = 0; index < funded.length; index++) {
|
|
4141
|
+
const underlying = valuations[index * 2], value = valuations[index * 2 + 1];
|
|
4142
|
+
if (underlying?.status !== "success" || typeof underlying.result !== "string" || underlying.result.toLowerCase() !== asset.toLowerCase() || value?.status !== "success" || typeof value.result !== "bigint" || value.result < 0n) return void 0;
|
|
4143
|
+
total += value.result;
|
|
4144
|
+
}
|
|
4145
|
+
return total;
|
|
4146
|
+
}));
|
|
4147
|
+
return values.some((value) => value === void 0) ? void 0 : values.reduce((sum, value) => sum + value, 0n);
|
|
4148
|
+
}
|
|
4149
|
+
|
|
3945
4150
|
// src/agents/yieldseeker/yieldseeker.wallet-version.ts
|
|
3946
4151
|
import {
|
|
3947
|
-
getAddress as
|
|
4152
|
+
getAddress as getAddress3,
|
|
3948
4153
|
isAddressEqual,
|
|
3949
4154
|
parseAbi as parseAbi3
|
|
3950
4155
|
} from "viem";
|
|
@@ -3968,9 +4173,9 @@ function yieldseekerOwnerPath(code) {
|
|
|
3968
4173
|
return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
|
|
3969
4174
|
}
|
|
3970
4175
|
async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
|
|
3971
|
-
const agentWallet =
|
|
3972
|
-
const expectedOwner =
|
|
3973
|
-
const expectedAsset =
|
|
4176
|
+
const agentWallet = getAddress3(wallet);
|
|
4177
|
+
const expectedOwner = getAddress3(owner);
|
|
4178
|
+
const expectedAsset = getAddress3(asset);
|
|
3974
4179
|
const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
|
|
3975
4180
|
client.getBytecode({ address: agentWallet, blockNumber }),
|
|
3976
4181
|
client.getBytecode({ address: expectedOwner, blockNumber }),
|
|
@@ -3984,7 +4189,7 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
|
|
|
3984
4189
|
if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
|
|
3985
4190
|
throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
|
|
3986
4191
|
}
|
|
3987
|
-
const implementation =
|
|
4192
|
+
const implementation = getAddress3(`0x${slot.slice(-40)}`);
|
|
3988
4193
|
const mapped = await client.readContract({
|
|
3989
4194
|
address: YIELDSEEKER_FACTORY,
|
|
3990
4195
|
abi: factoryAbi,
|
|
@@ -4011,7 +4216,7 @@ var YIELDSEEKER_UPGRADE_ABI = walletAbi;
|
|
|
4011
4216
|
import {
|
|
4012
4217
|
createWalletClient as createWalletClient2,
|
|
4013
4218
|
custom as custom2,
|
|
4014
|
-
getAddress as
|
|
4219
|
+
getAddress as getAddress4,
|
|
4015
4220
|
toHex
|
|
4016
4221
|
} from "viem";
|
|
4017
4222
|
import { getUserOperationHash as getUserOperationHash2 } from "viem/account-abstraction";
|
|
@@ -4520,7 +4725,7 @@ async function submitSmart(input) {
|
|
|
4520
4725
|
);
|
|
4521
4726
|
}
|
|
4522
4727
|
async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
4523
|
-
const owner =
|
|
4728
|
+
const owner = getAddress4(input.state.walletAddress);
|
|
4524
4729
|
const { intent, rpcUrl } = await requestIntent({
|
|
4525
4730
|
apiKey: input.apiKey,
|
|
4526
4731
|
routingApiBaseUrl: input.routingApiBaseUrl,
|
|
@@ -4538,7 +4743,7 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
|
4538
4743
|
return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
|
|
4539
4744
|
}
|
|
4540
4745
|
async function submitYieldseekerSponsoredUpgrade(input) {
|
|
4541
|
-
const owner =
|
|
4746
|
+
const owner = getAddress4(input.state.walletAddress);
|
|
4542
4747
|
const { intent, rpcUrl } = await requestIntent({
|
|
4543
4748
|
apiKey: input.apiKey,
|
|
4544
4749
|
routingApiBaseUrl: input.routingApiBaseUrl,
|
|
@@ -4573,6 +4778,8 @@ var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
|
4573
4778
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4574
4779
|
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4575
4780
|
var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
|
|
4781
|
+
var YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT = 64;
|
|
4782
|
+
var YIELDSEEKER_VAULTS_PER_RPC_BATCH = 10;
|
|
4576
4783
|
function generateYieldseekerUsername() {
|
|
4577
4784
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4578
4785
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4653,6 +4860,8 @@ var YieldseekerAgent = class {
|
|
|
4653
4860
|
transactionExecutor;
|
|
4654
4861
|
unwindReceiptWaiter;
|
|
4655
4862
|
agentContexts = /* @__PURE__ */ new Map();
|
|
4863
|
+
balanceContexts = /* @__PURE__ */ new WeakMap();
|
|
4864
|
+
balanceVaults = /* @__PURE__ */ new Map();
|
|
4656
4865
|
users = /* @__PURE__ */ new Map();
|
|
4657
4866
|
pendingAgents = /* @__PURE__ */ new Map();
|
|
4658
4867
|
pendingWalletContexts = /* @__PURE__ */ new Map();
|
|
@@ -4698,8 +4907,8 @@ var YieldseekerAgent = class {
|
|
|
4698
4907
|
if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
|
|
4699
4908
|
const status = await readYieldseekerWalletStatus(
|
|
4700
4909
|
this.getReceiptClient(),
|
|
4701
|
-
|
|
4702
|
-
|
|
4910
|
+
getAddress5(context.wallet.walletAddress),
|
|
4911
|
+
getAddress5(state.walletAddress),
|
|
4703
4912
|
YIELDSEEKER_ASSET_METADATA[asset].address
|
|
4704
4913
|
);
|
|
4705
4914
|
this.rememberWalletStatus(key2, status);
|
|
@@ -4895,7 +5104,7 @@ var YieldseekerAgent = class {
|
|
|
4895
5104
|
}
|
|
4896
5105
|
} else {
|
|
4897
5106
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4898
|
-
from:
|
|
5107
|
+
from: getAddress5(state.walletAddress),
|
|
4899
5108
|
to: before.agentWallet,
|
|
4900
5109
|
data,
|
|
4901
5110
|
value: "0",
|
|
@@ -4918,6 +5127,7 @@ var YieldseekerAgent = class {
|
|
|
4918
5127
|
}
|
|
4919
5128
|
this.users.clear();
|
|
4920
5129
|
this.agentContexts.clear();
|
|
5130
|
+
this.balanceVaults.clear();
|
|
4921
5131
|
this.pendingAgents.clear();
|
|
4922
5132
|
this.pendingWalletContexts.clear();
|
|
4923
5133
|
this.readCache.clear();
|
|
@@ -4981,12 +5191,12 @@ var YieldseekerAgent = class {
|
|
|
4981
5191
|
} else {
|
|
4982
5192
|
try {
|
|
4983
5193
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4984
|
-
from:
|
|
5194
|
+
from: getAddress5(state.walletAddress),
|
|
4985
5195
|
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
4986
5196
|
data: encodeFunctionData({
|
|
4987
|
-
abi:
|
|
5197
|
+
abi: erc20Abi4,
|
|
4988
5198
|
functionName: "transfer",
|
|
4989
|
-
args: [
|
|
5199
|
+
args: [getAddress5(context.wallet.walletAddress), BigInt(amount)]
|
|
4990
5200
|
}),
|
|
4991
5201
|
value: "0",
|
|
4992
5202
|
chainId
|
|
@@ -5096,80 +5306,98 @@ var YieldseekerAgent = class {
|
|
|
5096
5306
|
let settlement = null;
|
|
5097
5307
|
try {
|
|
5098
5308
|
const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
|
|
5099
|
-
this.
|
|
5100
|
-
const
|
|
5309
|
+
const contextKey = this.contextKey(state, chainId, asset);
|
|
5310
|
+
const priorPositions = this.withdrawalVaultHints(state, chainId, context);
|
|
5311
|
+
this.advancePortfolioVersion(contextKey);
|
|
5312
|
+
const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5313
|
+
let timer;
|
|
5314
|
+
try {
|
|
5315
|
+
return await Promise.race([read2, new Promise((resolve) => {
|
|
5316
|
+
timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
|
|
5317
|
+
})]);
|
|
5318
|
+
} finally {
|
|
5319
|
+
if (timer) clearTimeout(timer);
|
|
5320
|
+
}
|
|
5321
|
+
};
|
|
5322
|
+
const feeRateRead = amount === void 0 ? Promise.resolve(0) : boundedVerification(
|
|
5323
|
+
(() => {
|
|
5324
|
+
try {
|
|
5325
|
+
return this.portfolioSnapshot(state, chainId, context, contextKey).read.then((value) => value.agentSnapshot.feeRateBps);
|
|
5326
|
+
} catch (error) {
|
|
5327
|
+
return Promise.reject(error);
|
|
5328
|
+
}
|
|
5329
|
+
})().catch(() => void 0)
|
|
5330
|
+
);
|
|
5331
|
+
const positionsRead = this.readWithdrawalPositions(state, chainId, context);
|
|
5101
5332
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
5102
|
-
const agentWallet =
|
|
5103
|
-
const
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5333
|
+
const agentWallet = getAddress5(context.wallet.walletAddress);
|
|
5334
|
+
const readOwnerFunds = async (blockNumber) => {
|
|
5335
|
+
const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5336
|
+
address: agentWallet,
|
|
5337
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5338
|
+
functionName: "feeTracker",
|
|
5339
|
+
blockNumber
|
|
5340
|
+
}), chainId, "eth_call", this.id);
|
|
5110
5341
|
this.assertSession(generation, chainId, asset);
|
|
5111
5342
|
if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
|
|
5112
5343
|
throw this.invalidResponse("wallet fee tracker");
|
|
5113
5344
|
const [idle2, fees] = await Promise.all([
|
|
5114
5345
|
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5115
5346
|
address: metadata.address,
|
|
5116
|
-
abi:
|
|
5347
|
+
abi: erc20Abi4,
|
|
5117
5348
|
functionName: "balanceOf",
|
|
5118
|
-
args: [agentWallet]
|
|
5349
|
+
args: [agentWallet],
|
|
5350
|
+
blockNumber
|
|
5119
5351
|
}), chainId, "eth_call", this.id),
|
|
5120
5352
|
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5121
|
-
address:
|
|
5353
|
+
address: getAddress5(feeTracker),
|
|
5122
5354
|
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5123
5355
|
functionName: "getFeesOwed",
|
|
5124
|
-
args: [agentWallet]
|
|
5356
|
+
args: [agentWallet],
|
|
5357
|
+
blockNumber
|
|
5125
5358
|
}), chainId, "eth_call", this.id)
|
|
5126
5359
|
]);
|
|
5127
5360
|
this.assertSession(generation, chainId, asset);
|
|
5128
5361
|
if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
|
|
5129
5362
|
throw this.invalidResponse("wallet withdrawal balance");
|
|
5130
|
-
return { idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
|
|
5363
|
+
return { blockNumber, idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
|
|
5131
5364
|
};
|
|
5132
|
-
const [
|
|
5365
|
+
const [funds, feeRateBps] = await Promise.all([
|
|
5366
|
+
positionsRead.then((positions2) => this.resolveWithdrawalLiquidity(
|
|
5367
|
+
state,
|
|
5368
|
+
chainId,
|
|
5369
|
+
context,
|
|
5370
|
+
positions2,
|
|
5371
|
+
priorPositions,
|
|
5372
|
+
amount === void 0 ? void 0 : BigInt(amount)
|
|
5373
|
+
)),
|
|
5374
|
+
feeRateRead
|
|
5375
|
+
]);
|
|
5376
|
+
const initialFunds = funds;
|
|
5133
5377
|
const idle = initialFunds.idle;
|
|
5134
|
-
|
|
5378
|
+
const maxLiquidity = amount === void 0 ? funds : void 0;
|
|
5379
|
+
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()))];
|
|
5135
5380
|
let positionsStatus = "empty";
|
|
5136
|
-
const validatePositions = (values) =>
|
|
5137
|
-
|
|
5138
|
-
for (const value of values) {
|
|
5139
|
-
const address = value?.yieldOption?.address;
|
|
5140
|
-
if (typeof address !== "string" || !isAddress3(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
|
|
5141
|
-
throw this.invalidResponse("yield positions");
|
|
5142
|
-
seen.add(address.toLowerCase());
|
|
5143
|
-
}
|
|
5144
|
-
return values;
|
|
5145
|
-
};
|
|
5381
|
+
const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
|
|
5382
|
+
validatePositions(positions);
|
|
5146
5383
|
const zeroValuePositions = /* @__PURE__ */ new WeakSet();
|
|
5147
5384
|
const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
|
|
5148
5385
|
const knownPositions = /* @__PURE__ */ new Map();
|
|
5149
5386
|
const rememberPositions = (values) => {
|
|
5150
5387
|
for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
|
|
5151
5388
|
};
|
|
5152
|
-
const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5153
|
-
let timer;
|
|
5154
|
-
try {
|
|
5155
|
-
return await Promise.race([read2, new Promise((resolve) => {
|
|
5156
|
-
timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
|
|
5157
|
-
})]);
|
|
5158
|
-
} finally {
|
|
5159
|
-
if (timer) clearTimeout(timer);
|
|
5160
|
-
}
|
|
5161
|
-
};
|
|
5162
5389
|
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5163
5390
|
const verifiedPositions = [];
|
|
5164
5391
|
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
5165
|
-
|
|
5166
|
-
|
|
5392
|
+
let active = true;
|
|
5393
|
+
const checks = this.readVaultLiquidity(values.map((position2) => position2.yieldOption), agentWallet, blockNumber).catch(() => []);
|
|
5394
|
+
const reads = values.map(async (position2, index) => {
|
|
5395
|
+
const address = getAddress5(position2.yieldOption.address);
|
|
5167
5396
|
try {
|
|
5168
|
-
const
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
]);
|
|
5397
|
+
const group = (await checks).slice(index * 3, index * 3 + 3);
|
|
5398
|
+
if (!active || group.length !== 3 || group.some((value) => value.status !== "success")) return null;
|
|
5399
|
+
this.assertSession(generation, chainId, asset);
|
|
5400
|
+
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
5173
5401
|
if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
|
|
5174
5402
|
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
|
|
5175
5403
|
if (shares > 0n && withdrawable === 0n) {
|
|
@@ -5178,7 +5406,7 @@ var YieldseekerAgent = class {
|
|
|
5178
5406
|
try {
|
|
5179
5407
|
const value = await this.getReceiptClient().readContract({
|
|
5180
5408
|
address,
|
|
5181
|
-
abi:
|
|
5409
|
+
abi: erc4626Abi2,
|
|
5182
5410
|
functionName,
|
|
5183
5411
|
args: [shares],
|
|
5184
5412
|
blockNumber
|
|
@@ -5204,6 +5432,7 @@ var YieldseekerAgent = class {
|
|
|
5204
5432
|
}
|
|
5205
5433
|
});
|
|
5206
5434
|
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5435
|
+
active = false;
|
|
5207
5436
|
this.assertSession(generation, chainId, asset);
|
|
5208
5437
|
return {
|
|
5209
5438
|
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
@@ -5222,7 +5451,7 @@ var YieldseekerAgent = class {
|
|
|
5222
5451
|
return validatePositions(response.yieldPositions);
|
|
5223
5452
|
};
|
|
5224
5453
|
if (amount === void 0) {
|
|
5225
|
-
|
|
5454
|
+
rememberPositions(funds.indexedPositions);
|
|
5226
5455
|
rememberPositions(positions);
|
|
5227
5456
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5228
5457
|
}
|
|
@@ -5238,8 +5467,8 @@ var YieldseekerAgent = class {
|
|
|
5238
5467
|
const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5239
5468
|
if (amount === void 0 && zeroPayoutCandidates.length) {
|
|
5240
5469
|
const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5241
|
-
const blockNumber = await boundedVerification(
|
|
5242
|
-
Promise.resolve().then(() => this.getReceiptClient().getBlockNumber()).catch(() => void 0)
|
|
5470
|
+
const blockNumber = initialFunds.blockNumber ?? await boundedVerification(
|
|
5471
|
+
Promise.resolve().then(() => this.getReceiptClient().getBlockNumber({ cacheTime: 0 })).catch(() => void 0)
|
|
5243
5472
|
);
|
|
5244
5473
|
const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
|
|
5245
5474
|
positions = proof.positions;
|
|
@@ -5363,8 +5592,9 @@ var YieldseekerAgent = class {
|
|
|
5363
5592
|
remaining: remaining.toString()
|
|
5364
5593
|
});
|
|
5365
5594
|
}
|
|
5366
|
-
const
|
|
5367
|
-
const
|
|
5595
|
+
const ownerFunds = confirmedMovement ? await readOwnerFunds(latestUnwindBlock) : initialFunds;
|
|
5596
|
+
const availableForOwner = ownerFunds.available;
|
|
5597
|
+
const feeRate = feeRateBps ?? 0;
|
|
5368
5598
|
const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
|
|
5369
5599
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
5370
5600
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
@@ -5389,7 +5619,7 @@ var YieldseekerAgent = class {
|
|
|
5389
5619
|
delta: maxSettlementBasis.delta - payout,
|
|
5390
5620
|
expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
|
|
5391
5621
|
} : this.snapshotMovement(state, chainId, context, -payout);
|
|
5392
|
-
const account =
|
|
5622
|
+
const account = getAddress5(state.walletAddress);
|
|
5393
5623
|
let receipt;
|
|
5394
5624
|
const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount === void 0 ? "MAX" : payout.toString(), transfer: "pending", registration: "not-applicable", delivery: "pending" };
|
|
5395
5625
|
const data = amount === void 0 ? encodeFunctionData({
|
|
@@ -5412,7 +5642,7 @@ var YieldseekerAgent = class {
|
|
|
5412
5642
|
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
5413
5643
|
userId: context.user.userId,
|
|
5414
5644
|
agentId: context.agent.agentId,
|
|
5415
|
-
agentWallet:
|
|
5645
|
+
agentWallet: getAddress5(context.wallet.walletAddress),
|
|
5416
5646
|
asset: metadata.address,
|
|
5417
5647
|
callData: data,
|
|
5418
5648
|
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
|
|
@@ -5427,7 +5657,7 @@ var YieldseekerAgent = class {
|
|
|
5427
5657
|
onOwnerTransferAttempted?.();
|
|
5428
5658
|
txHash = await this.submitTransaction(state, chainId, {
|
|
5429
5659
|
from: account,
|
|
5430
|
-
to:
|
|
5660
|
+
to: getAddress5(context.wallet.walletAddress),
|
|
5431
5661
|
data,
|
|
5432
5662
|
value: "0",
|
|
5433
5663
|
chainId
|
|
@@ -5439,7 +5669,7 @@ var YieldseekerAgent = class {
|
|
|
5439
5669
|
settlement = plannedMovement;
|
|
5440
5670
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
5441
5671
|
notifyFunding(onProgress, evidence);
|
|
5442
|
-
let completion;
|
|
5672
|
+
let completion = maxLiquidity && !maxLiquidity.complete ? "unverified" : void 0;
|
|
5443
5673
|
if (amount === void 0) {
|
|
5444
5674
|
const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5445
5675
|
const knownAtPayout = new Set(knownPositions.keys());
|
|
@@ -5486,10 +5716,14 @@ var YieldseekerAgent = class {
|
|
|
5486
5716
|
{ asset, available: "0", txHash },
|
|
5487
5717
|
this.id
|
|
5488
5718
|
);
|
|
5719
|
+
const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
|
|
5720
|
+
const feeIncrease = ownerFunds.fees - initialFunds.fees;
|
|
5721
|
+
const feeAdjustment = amount !== void 0 && receipt && initialFunds.blockNumber !== void 0 && latestUnwindBlock !== void 0 && initialFunds.blockNumber <= latestUnwindBlock && receipt.blockNumber >= latestUnwindBlock && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && confirmedAmount === payout && feeIncrease > 0n && feeIncrease <= maxFee && requested - payout === feeIncrease ? { grossAmount: requested.toString(), feeAmount: feeIncrease.toString() } : void 0;
|
|
5489
5722
|
return {
|
|
5490
5723
|
txHash,
|
|
5491
5724
|
type: amount === void 0 && !completion ? "full" : "partial",
|
|
5492
5725
|
...completion ? { completion } : {},
|
|
5726
|
+
...feeAdjustment ? { feeAdjustment } : {},
|
|
5493
5727
|
amount: evidence.confirmedAmount ?? payout.toString(),
|
|
5494
5728
|
evidence
|
|
5495
5729
|
};
|
|
@@ -5505,10 +5739,298 @@ var YieldseekerAgent = class {
|
|
|
5505
5739
|
}
|
|
5506
5740
|
}
|
|
5507
5741
|
}
|
|
5742
|
+
/** A withdrawal-only liquidity floor; display snapshots never authorize it. */
|
|
5743
|
+
async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
|
|
5744
|
+
this.assertChain(chainId);
|
|
5745
|
+
this.assertAsset(asset);
|
|
5746
|
+
const generation = this.readGeneration;
|
|
5747
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
5748
|
+
this.assertSession(generation, chainId, asset);
|
|
5749
|
+
if (!context) throw new OwneyError(
|
|
5750
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5751
|
+
"The Yieldseeker withdrawal wallet could not be verified.",
|
|
5752
|
+
{ asset },
|
|
5753
|
+
this.id
|
|
5754
|
+
);
|
|
5755
|
+
const hints = this.withdrawalVaultHints(state, chainId, context);
|
|
5756
|
+
const positions = await this.readWithdrawalPositions(state, chainId, context);
|
|
5757
|
+
const proof = await this.resolveWithdrawalLiquidity(state, chainId, context, positions, hints, minimumAmount);
|
|
5758
|
+
this.assertSession(generation, chainId, asset);
|
|
5759
|
+
if (proof.available === 0n && !proof.complete) throw new OwneyError(
|
|
5760
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5761
|
+
"Yieldseeker withdrawal liquidity could not be verified yet.",
|
|
5762
|
+
{ asset },
|
|
5763
|
+
this.id
|
|
5764
|
+
);
|
|
5765
|
+
return proof.available;
|
|
5766
|
+
}
|
|
5767
|
+
async readVaultLiquidity(options, wallet, blockNumber) {
|
|
5768
|
+
const owner = getAddress5(wallet);
|
|
5769
|
+
const batches = Array.from({ length: Math.ceil(options.length / YIELDSEEKER_VAULTS_PER_RPC_BATCH) }, (_, index) => options.slice(index * YIELDSEEKER_VAULTS_PER_RPC_BATCH, (index + 1) * YIELDSEEKER_VAULTS_PER_RPC_BATCH));
|
|
5770
|
+
const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
|
|
5771
|
+
blockNumber,
|
|
5772
|
+
allowFailure: true,
|
|
5773
|
+
contracts: batch.flatMap((option) => {
|
|
5774
|
+
const address = getAddress5(option.address);
|
|
5775
|
+
return [
|
|
5776
|
+
{ address, abi: erc4626Abi2, functionName: "asset" },
|
|
5777
|
+
{ address, abi: erc20Abi4, functionName: "balanceOf", args: [owner] },
|
|
5778
|
+
{ address, abi: erc4626Abi2, functionName: "maxWithdraw", args: [owner] }
|
|
5779
|
+
];
|
|
5780
|
+
})
|
|
5781
|
+
})));
|
|
5782
|
+
return results.flat();
|
|
5783
|
+
}
|
|
5784
|
+
validateWithdrawalPositions(values, chainId, asset) {
|
|
5785
|
+
const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
|
|
5786
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5787
|
+
for (const value of values) {
|
|
5788
|
+
const option = value?.yieldOption;
|
|
5789
|
+
if (!option || typeof option.address !== "string" || !isAddress3(option.address) || seen.has(option.address.toLowerCase()) || option.chainId !== void 0 && option.chainId !== chainId || option.assetAddress !== void 0 && (typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== underlying) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
|
|
5790
|
+
throw this.invalidResponse("yield positions");
|
|
5791
|
+
seen.add(option.address.toLowerCase());
|
|
5792
|
+
}
|
|
5793
|
+
return values;
|
|
5794
|
+
}
|
|
5795
|
+
withdrawalVaultHints(state, chainId, context) {
|
|
5796
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
5797
|
+
const positions = this.readCache.get(`positions:${key2}`)?.value?.yieldPositions ?? [];
|
|
5798
|
+
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
|
|
5799
|
+
const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
|
|
5800
|
+
const receivedAssets = new Set((historic?.movements ?? []).filter((movement) => movement.chainId === chainId && movement.agentWalletId === context.wallet.agentWalletId && movement.toAddress?.toLowerCase() === context.wallet.walletAddress.toLowerCase()).filter((movement) => typeof movement.assetAddress === "string").map((movement) => movement.assetAddress.toLowerCase()));
|
|
5801
|
+
for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
|
|
5802
|
+
if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !isAddress3(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
|
|
5803
|
+
hints.set(option.address.toLowerCase(), {
|
|
5804
|
+
yieldOption: option,
|
|
5805
|
+
assetsRaw: "0",
|
|
5806
|
+
assetsBase: "0",
|
|
5807
|
+
assetsUsd: 0,
|
|
5808
|
+
withdrawableAssetsRaw: "0",
|
|
5809
|
+
withdrawableAssetsBase: "0"
|
|
5810
|
+
});
|
|
5811
|
+
}
|
|
5812
|
+
return [...hints.values()];
|
|
5813
|
+
}
|
|
5814
|
+
async readWithdrawalPositions(state, chainId, context) {
|
|
5815
|
+
const generation = this.readGeneration;
|
|
5816
|
+
let timer;
|
|
5817
|
+
try {
|
|
5818
|
+
const positions = await Promise.race([
|
|
5819
|
+
this.loadActivityContext(state, chainId, context, {}).then((value) => value.positions).catch((error) => {
|
|
5820
|
+
if (!(error instanceof OwneyError) || !["AGENT_API_ERROR", "AGENT_TIMEOUT"].includes(error.code)) throw error;
|
|
5821
|
+
return void 0;
|
|
5822
|
+
}),
|
|
5823
|
+
new Promise((resolve) => {
|
|
5824
|
+
timer = setTimeout(() => resolve(void 0), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
|
|
5825
|
+
})
|
|
5826
|
+
]);
|
|
5827
|
+
this.assertSession(generation, chainId, context.asset);
|
|
5828
|
+
return positions === void 0 ? void 0 : this.validateWithdrawalPositions(positions, chainId, context.asset);
|
|
5829
|
+
} finally {
|
|
5830
|
+
if (timer) clearTimeout(timer);
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
|
|
5834
|
+
const generation = this.readGeneration;
|
|
5835
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
5836
|
+
const version = this.portfolioVersion(contextKey);
|
|
5837
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
5838
|
+
const unavailable = () => new OwneyError(
|
|
5839
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5840
|
+
"Yieldseeker withdrawal liquidity could not be verified yet.",
|
|
5841
|
+
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
5842
|
+
this.id
|
|
5843
|
+
);
|
|
5844
|
+
let active = true;
|
|
5845
|
+
let knownProof;
|
|
5846
|
+
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5847
|
+
const assertReadCurrent = () => {
|
|
5848
|
+
this.assertSession(generation, chainId, context.asset);
|
|
5849
|
+
if (!active || this.portfolioVersion(contextKey) !== version) throw unavailable();
|
|
5850
|
+
};
|
|
5851
|
+
const read2 = async () => {
|
|
5852
|
+
const client = this.getReceiptClient();
|
|
5853
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
5854
|
+
const wallet = getAddress5(context.wallet.walletAddress);
|
|
5855
|
+
assertReadCurrent();
|
|
5856
|
+
if (typeof blockNumber !== "bigint") throw unavailable();
|
|
5857
|
+
const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
|
|
5858
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
5859
|
+
for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
|
|
5860
|
+
candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
|
|
5861
|
+
for (const position2 of indexedPositions) candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
|
|
5862
|
+
const options = [...candidates.values()];
|
|
5863
|
+
if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
5864
|
+
const [checks, idle, feeTracker] = await Promise.all([
|
|
5865
|
+
this.readVaultLiquidity(options, wallet, blockNumber),
|
|
5866
|
+
client.readContract({
|
|
5867
|
+
address: metadata.address,
|
|
5868
|
+
abi: erc20Abi4,
|
|
5869
|
+
functionName: "balanceOf",
|
|
5870
|
+
args: [wallet],
|
|
5871
|
+
blockNumber
|
|
5872
|
+
}),
|
|
5873
|
+
client.readContract({
|
|
5874
|
+
address: wallet,
|
|
5875
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5876
|
+
functionName: "feeTracker",
|
|
5877
|
+
blockNumber
|
|
5878
|
+
})
|
|
5879
|
+
]);
|
|
5880
|
+
if (typeof idle !== "bigint" || idle < 0n || !isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
|
|
5881
|
+
assertReadCurrent();
|
|
5882
|
+
const fees = await client.readContract({
|
|
5883
|
+
address: getAddress5(feeTracker),
|
|
5884
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5885
|
+
functionName: "getFeesOwed",
|
|
5886
|
+
args: [wallet],
|
|
5887
|
+
blockNumber
|
|
5888
|
+
});
|
|
5889
|
+
if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
|
|
5890
|
+
const verified = [];
|
|
5891
|
+
const verifiedVaults = [];
|
|
5892
|
+
let deployed = 0n;
|
|
5893
|
+
const collect = (options2, results) => {
|
|
5894
|
+
assertReadCurrent();
|
|
5895
|
+
const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
|
|
5896
|
+
this.balanceVaults.set(contextKey, known);
|
|
5897
|
+
let valid2 = true;
|
|
5898
|
+
options2.forEach((option, index) => {
|
|
5899
|
+
const group = results.slice(index * 3, index * 3 + 3);
|
|
5900
|
+
const shareProbe = group[1];
|
|
5901
|
+
if (shareProbe?.status === "success" && typeof shareProbe.result === "bigint" && shareProbe.result > 0n)
|
|
5902
|
+
known.add(option.address.toLowerCase());
|
|
5903
|
+
if (group.length !== 3 || group.some((value) => value.status !== "success")) {
|
|
5904
|
+
valid2 = false;
|
|
5905
|
+
return;
|
|
5906
|
+
}
|
|
5907
|
+
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
5908
|
+
if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
|
|
5909
|
+
valid2 = false;
|
|
5910
|
+
return;
|
|
5911
|
+
}
|
|
5912
|
+
verifiedVaults.push(option.address.toLowerCase());
|
|
5913
|
+
deployed += withdrawable;
|
|
5914
|
+
verified.push({
|
|
5915
|
+
yieldOption: option,
|
|
5916
|
+
assetsRaw: shares.toString(),
|
|
5917
|
+
assetsBase: "0",
|
|
5918
|
+
assetsUsd: 0,
|
|
5919
|
+
withdrawableAssetsRaw: withdrawable.toString(),
|
|
5920
|
+
withdrawableAssetsBase: withdrawable.toString()
|
|
5921
|
+
});
|
|
5922
|
+
});
|
|
5923
|
+
return valid2;
|
|
5924
|
+
};
|
|
5925
|
+
const allKnownVerified = collect(options, checks);
|
|
5926
|
+
const proof = (complete) => ({
|
|
5927
|
+
blockNumber,
|
|
5928
|
+
idle,
|
|
5929
|
+
fees,
|
|
5930
|
+
available: idle + deployed > fees ? idle + deployed - fees : 0n,
|
|
5931
|
+
positions: [...verified],
|
|
5932
|
+
verifiedVaults: [...verifiedVaults],
|
|
5933
|
+
indexedPositions,
|
|
5934
|
+
complete
|
|
5935
|
+
});
|
|
5936
|
+
knownProof = proof(false);
|
|
5937
|
+
const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
|
|
5938
|
+
const discrepancy = verified.some((position2) => {
|
|
5939
|
+
const row = indexed.get(position2.yieldOption.address.toLowerCase());
|
|
5940
|
+
return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
|
|
5941
|
+
});
|
|
5942
|
+
const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
|
|
5943
|
+
const expand = positions === void 0 || indexedPositions.length === 0 && !typedCoveredByIdle || !allKnownVerified || discrepancy || knownProof.available === 0n || requested !== void 0 && requested > knownProof.available;
|
|
5944
|
+
if (!expand) {
|
|
5945
|
+
assertReadCurrent();
|
|
5946
|
+
return proof(true);
|
|
5947
|
+
}
|
|
5948
|
+
const catalog = await this.loadYieldOptions(context.asset).catch(() => void 0);
|
|
5949
|
+
assertReadCurrent();
|
|
5950
|
+
const eligible = /* @__PURE__ */ new Map();
|
|
5951
|
+
for (const option of catalog ?? []) {
|
|
5952
|
+
if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && isAddress3(option.address))
|
|
5953
|
+
eligible.set(option.address.toLowerCase(), option);
|
|
5954
|
+
}
|
|
5955
|
+
const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
|
|
5956
|
+
if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
5957
|
+
const allExtraVerified = collect(extra, await this.readVaultLiquidity(extra, wallet, blockNumber));
|
|
5958
|
+
assertReadCurrent();
|
|
5959
|
+
const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
|
|
5960
|
+
if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
|
|
5961
|
+
return result;
|
|
5962
|
+
};
|
|
5963
|
+
let timer;
|
|
5964
|
+
try {
|
|
5965
|
+
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
5966
|
+
timer = setTimeout(() => reject(unavailable()), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
|
|
5967
|
+
})]);
|
|
5968
|
+
} catch (error) {
|
|
5969
|
+
assertReadCurrent();
|
|
5970
|
+
if (knownProof && (knownProof.available > 0n || hasUnverifiedIndexedFunds(knownProof)) && (!(error instanceof OwneyError) || error.code === "WITHDRAW_BALANCE_UNAVAILABLE")) return knownProof;
|
|
5971
|
+
if (error instanceof OwneyError) throw error;
|
|
5972
|
+
throw unavailable();
|
|
5973
|
+
} finally {
|
|
5974
|
+
active = false;
|
|
5975
|
+
if (timer) clearTimeout(timer);
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5508
5978
|
async getBalances(state, chainId, asset) {
|
|
5509
5979
|
this.assertChain(chainId);
|
|
5510
5980
|
if (asset !== void 0) this.assertAsset(asset);
|
|
5511
|
-
|
|
5981
|
+
const contexts = await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true });
|
|
5982
|
+
const balance = mapYieldseekerBalances(contexts);
|
|
5983
|
+
for (const context of contexts) {
|
|
5984
|
+
const read2 = balance.readMetadata.find((item) => item.asset === context.asset);
|
|
5985
|
+
this.balanceContexts.set(read2, { context, generation: this.readGeneration });
|
|
5986
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
5987
|
+
const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
|
|
5988
|
+
for (const position2 of context.positions) known.add(position2.yieldOption.address.toLowerCase());
|
|
5989
|
+
this.balanceVaults.set(key2, known);
|
|
5990
|
+
}
|
|
5991
|
+
return balance;
|
|
5992
|
+
}
|
|
5993
|
+
async verifyWithdrawalBalance(state, evidence, balance) {
|
|
5994
|
+
this.assertAsset(evidence.token);
|
|
5995
|
+
this.assertChain(evidence.chainId);
|
|
5996
|
+
const read2 = balance.readMetadata?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
|
|
5997
|
+
const cached = read2 && this.balanceContexts.get(read2);
|
|
5998
|
+
const context = cached?.generation === this.readGeneration ? cached.context : void 0;
|
|
5999
|
+
if (!context || context.positionsUnavailable || !read2 || !evidence.blockNumber || !evidence.txHash || context.wallet.walletAddress.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return void 0;
|
|
6000
|
+
const key2 = this.contextKey(state, evidence.chainId, evidence.token);
|
|
6001
|
+
const generation = this.readGeneration, version = this.portfolioVersion(key2);
|
|
6002
|
+
if (read2.readVersion !== version) return void 0;
|
|
6003
|
+
const wallet = getAddress5(context.wallet.walletAddress);
|
|
6004
|
+
const asset = YIELDSEEKER_ASSET_METADATA[evidence.token];
|
|
6005
|
+
const client = this.getReceiptClient();
|
|
6006
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
6007
|
+
if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
|
|
6008
|
+
const vaults = [...this.balanceVaults.get(key2) ?? []];
|
|
6009
|
+
const [invested, idle, tracker] = await Promise.all([
|
|
6010
|
+
remainingVaultBalance(client, wallet, asset.address, vaults, blockNumber),
|
|
6011
|
+
client.readContract({ address: asset.address, abi: erc20Abi4, functionName: "balanceOf", args: [wallet], blockNumber }),
|
|
6012
|
+
client.readContract({ address: wallet, abi: YIELDSEEKER_AGENT_WALLET_ABI, functionName: "feeTracker", blockNumber })
|
|
6013
|
+
]);
|
|
6014
|
+
if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !isAddress3(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
|
|
6015
|
+
const fees = await client.readContract({
|
|
6016
|
+
address: tracker,
|
|
6017
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
6018
|
+
functionName: "getFeesOwed",
|
|
6019
|
+
args: [wallet],
|
|
6020
|
+
blockNumber
|
|
6021
|
+
});
|
|
6022
|
+
this.assertSession(generation, evidence.chainId, evidence.token);
|
|
6023
|
+
if (version !== this.portfolioVersion(key2) || typeof fees !== "bigint" || fees < 0n) return void 0;
|
|
6024
|
+
const total = idle + invested > fees ? idle + invested - fees : 0n;
|
|
6025
|
+
const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
|
|
6026
|
+
if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
|
|
6027
|
+
const amount = parseUnits(displayed.amount, asset.decimals);
|
|
6028
|
+
if (amount < 0n) return void 0;
|
|
6029
|
+
const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
|
|
6030
|
+
const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
|
|
6031
|
+
const tolerance = movementUnit < displayUnit ? movementUnit : displayUnit;
|
|
6032
|
+
if ((total > amount ? total - amount : amount - total) > tolerance) return void 0;
|
|
6033
|
+
return { ...read2, withdrawalCheck: { txHash: evidence.txHash, blockNumber: blockNumber.toString() } };
|
|
5512
6034
|
}
|
|
5513
6035
|
invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS) {
|
|
5514
6036
|
this.assertAsset(asset);
|
|
@@ -5548,9 +6070,9 @@ var YieldseekerAgent = class {
|
|
|
5548
6070
|
if (!context) return void 0;
|
|
5549
6071
|
const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5550
6072
|
address: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5551
|
-
abi:
|
|
6073
|
+
abi: erc20Abi4,
|
|
5552
6074
|
functionName: "balanceOf",
|
|
5553
|
-
args: [
|
|
6075
|
+
args: [getAddress5(context.wallet.walletAddress)]
|
|
5554
6076
|
}), chainId, "eth_call", this.id);
|
|
5555
6077
|
this.assertSession(generation, chainId, asset);
|
|
5556
6078
|
if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
|
|
@@ -5662,11 +6184,13 @@ var YieldseekerAgent = class {
|
|
|
5662
6184
|
const pending = this.pendingReads.get(key2);
|
|
5663
6185
|
if (pending) return pending;
|
|
5664
6186
|
const generation = this.readGeneration;
|
|
6187
|
+
const requestedAt = Date.now();
|
|
5665
6188
|
const request = Promise.resolve().then(read2).then((value) => {
|
|
5666
6189
|
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
5667
6190
|
this.readCache.set(key2, {
|
|
5668
6191
|
expiresAt: Date.now() + ttlMs,
|
|
5669
6192
|
fetchedAt: Date.now(),
|
|
6193
|
+
requestedAt,
|
|
5670
6194
|
value
|
|
5671
6195
|
});
|
|
5672
6196
|
}
|
|
@@ -5743,7 +6267,7 @@ var YieldseekerAgent = class {
|
|
|
5743
6267
|
this.users.set(key2, persisted);
|
|
5744
6268
|
return persisted;
|
|
5745
6269
|
}
|
|
5746
|
-
const walletAddress =
|
|
6270
|
+
const walletAddress = getAddress5(state.walletAddress);
|
|
5747
6271
|
const generation = this.readGeneration;
|
|
5748
6272
|
let user = null;
|
|
5749
6273
|
try {
|
|
@@ -6035,6 +6559,7 @@ var YieldseekerAgent = class {
|
|
|
6035
6559
|
...context,
|
|
6036
6560
|
snapshot: snapshot.agentSnapshot,
|
|
6037
6561
|
fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
|
|
6562
|
+
requestedAt: this.readCache.get(`snapshot:${contextKey}`)?.requestedAt,
|
|
6038
6563
|
readVersion: version,
|
|
6039
6564
|
positionsUnavailable,
|
|
6040
6565
|
...portfolioParts
|
|
@@ -6234,7 +6759,7 @@ var YieldseekerAgent = class {
|
|
|
6234
6759
|
return hash2;
|
|
6235
6760
|
}
|
|
6236
6761
|
this.assertTransaction(transaction, state, chainId);
|
|
6237
|
-
const account =
|
|
6762
|
+
const account = getAddress5(state.walletAddress);
|
|
6238
6763
|
const walletClient = createWalletClient3({
|
|
6239
6764
|
account,
|
|
6240
6765
|
chain: base4,
|
|
@@ -6252,7 +6777,7 @@ var YieldseekerAgent = class {
|
|
|
6252
6777
|
const hash = await walletClient.sendTransaction({
|
|
6253
6778
|
account,
|
|
6254
6779
|
chain: base4,
|
|
6255
|
-
to:
|
|
6780
|
+
to: getAddress5(transaction.to),
|
|
6256
6781
|
data: transaction.data,
|
|
6257
6782
|
value: BigInt(transaction.value)
|
|
6258
6783
|
});
|
|
@@ -6356,7 +6881,7 @@ var YieldseekerAgent = class {
|
|
|
6356
6881
|
return receipt;
|
|
6357
6882
|
}
|
|
6358
6883
|
assertTransaction(transaction, state, chainId) {
|
|
6359
|
-
if (!transaction || typeof transaction.from !== "string" || !isAddress3(transaction.from) || typeof transaction.to !== "string" || !isAddress3(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId ||
|
|
6884
|
+
if (!transaction || typeof transaction.from !== "string" || !isAddress3(transaction.from) || typeof transaction.to !== "string" || !isAddress3(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress5(transaction.from) !== getAddress5(state.walletAddress)) {
|
|
6360
6885
|
throw this.invalidResponse("transaction");
|
|
6361
6886
|
}
|
|
6362
6887
|
}
|
|
@@ -6515,7 +7040,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
6515
7040
|
}
|
|
6516
7041
|
|
|
6517
7042
|
// src/lib/helpers/withdraw-helper.ts
|
|
6518
|
-
import { parseUnits } from "viem";
|
|
7043
|
+
import { parseUnits as parseUnits2 } from "viem";
|
|
6519
7044
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
6520
7045
|
const target = asset.toUpperCase();
|
|
6521
7046
|
return agents.map((agent) => {
|
|
@@ -6523,7 +7048,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
6523
7048
|
const tokenBalance = agentBalance?.tokens.find(
|
|
6524
7049
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
6525
7050
|
);
|
|
6526
|
-
let balance = tokenBalance ?
|
|
7051
|
+
let balance = tokenBalance ? parseUnits2(tokenBalance.amount, decimals) : 0n;
|
|
6527
7052
|
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
6528
7053
|
const chainNameById = {
|
|
6529
7054
|
1: "ETHEREUM",
|
|
@@ -6535,14 +7060,14 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
6535
7060
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
6536
7061
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
6537
7062
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
6538
|
-
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) :
|
|
7063
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits2(position2.amount, decimals);
|
|
6539
7064
|
}
|
|
6540
7065
|
}
|
|
6541
7066
|
const assetTotal = agentBalance?.assetBalances?.find(
|
|
6542
7067
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
6543
7068
|
);
|
|
6544
7069
|
if (assetTotal) {
|
|
6545
|
-
const total =
|
|
7070
|
+
const total = parseUnits2(assetTotal.amount, decimals);
|
|
6546
7071
|
if (total < balance) balance = total > 0n ? total : 0n;
|
|
6547
7072
|
}
|
|
6548
7073
|
return { agent, balance };
|
|
@@ -6702,6 +7227,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
6702
7227
|
|
|
6703
7228
|
// src/client.ts
|
|
6704
7229
|
import {
|
|
7230
|
+
parseUnits as parseUnits3,
|
|
6705
7231
|
createPublicClient as createPublicClient4,
|
|
6706
7232
|
createWalletClient as createWalletClient4,
|
|
6707
7233
|
custom as custom4
|
|
@@ -6861,7 +7387,40 @@ async function execute(i, key2, plan) {
|
|
|
6861
7387
|
const send = async (initial) => {
|
|
6862
7388
|
let body = initial;
|
|
6863
7389
|
save(key2, body);
|
|
7390
|
+
const rejectedPreparation = (error) => !body.serializedTransaction && error instanceof OwneyError && typeof error.details?.statusCode === "number" && error.details.statusCode >= 400 && error.details.statusCode < 500 && ![408, 429].includes(error.details.statusCode);
|
|
6864
7391
|
try {
|
|
7392
|
+
const post = () => postSponsorBatchTransfer({ apiKey: i.apiKey, baseUrl: i.baseUrl, body });
|
|
7393
|
+
const pendingError = (phase, txHash) => new OwneyError(
|
|
7394
|
+
"DEPOSIT_IN_PROGRESS",
|
|
7395
|
+
phase === "approval" ? "Deposit approval is awaiting confirmation. Retry the same amount to continue it." : "Deposit is awaiting confirmation. Its transaction is still being checked.",
|
|
7396
|
+
{ sponsorPending: true, phase, txHash, safeToFallback: false }
|
|
7397
|
+
);
|
|
7398
|
+
const assertReceipt = (receipt, phase) => {
|
|
7399
|
+
if (receipt.status !== "success") throw new OwneyError(
|
|
7400
|
+
"SPONSOR_REQUEST_FAILED",
|
|
7401
|
+
`Sponsored ${phase} transaction reverted. Please retry.`,
|
|
7402
|
+
{ notSubmitted: true, safeToFallback: false }
|
|
7403
|
+
);
|
|
7404
|
+
};
|
|
7405
|
+
const waitForConfirmation = async (phase, hash2) => {
|
|
7406
|
+
let receipt;
|
|
7407
|
+
try {
|
|
7408
|
+
receipt = await i.pub.waitForTransactionReceipt({ hash: hash2, confirmations: 1, timeout: 9e4, checkReplacement: false });
|
|
7409
|
+
} catch {
|
|
7410
|
+
receipt = await i.pub.getTransactionReceipt({ hash: hash2 }).catch(() => void 0);
|
|
7411
|
+
}
|
|
7412
|
+
if (!receipt) throw pendingError(phase, hash2);
|
|
7413
|
+
assertReceipt(receipt, phase);
|
|
7414
|
+
};
|
|
7415
|
+
const postSigned = async (phase, hash2) => {
|
|
7416
|
+
try {
|
|
7417
|
+
return await post();
|
|
7418
|
+
} catch (error) {
|
|
7419
|
+
if (rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true) throw error;
|
|
7420
|
+
await waitForConfirmation(phase, hash2);
|
|
7421
|
+
return void 0;
|
|
7422
|
+
}
|
|
7423
|
+
};
|
|
6865
7424
|
if (body.erc2612Permit && !body.serializedPermitTransaction) {
|
|
6866
7425
|
const preparedPermit = await postSponsorBatchTransfer({
|
|
6867
7426
|
apiKey: i.apiKey,
|
|
@@ -6879,11 +7438,17 @@ async function execute(i, key2, plan) {
|
|
|
6879
7438
|
save(key2, body);
|
|
6880
7439
|
}
|
|
6881
7440
|
if (!body.serializedTransaction) {
|
|
6882
|
-
const
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
7441
|
+
const permitHash = body.serializedPermitTransaction ? keccak256(body.serializedPermitTransaction) : void 0;
|
|
7442
|
+
let prepared = permitHash ? await postSigned("approval", permitHash) : await post();
|
|
7443
|
+
if (prepared?.status === "pending") {
|
|
7444
|
+
if (!permitHash || prepared.phase !== "approval" || prepared.txHash !== permitHash)
|
|
7445
|
+
throw new Error("Sponsorship approval does not match the pending transaction.");
|
|
7446
|
+
await waitForConfirmation("approval", permitHash);
|
|
7447
|
+
prepared = void 0;
|
|
7448
|
+
}
|
|
7449
|
+
if (!prepared) prepared = await post();
|
|
7450
|
+
if (prepared.status === "pending" && permitHash && prepared.phase === "approval" && prepared.txHash === permitHash)
|
|
7451
|
+
throw pendingError("approval", permitHash);
|
|
6887
7452
|
if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
|
|
6888
7453
|
throw new Error(
|
|
6889
7454
|
"Sponsorship API did not return a valid prepared transaction."
|
|
@@ -6898,19 +7463,30 @@ async function execute(i, key2, plan) {
|
|
|
6898
7463
|
i.onSubmitted?.(keccak256(body.serializedTransaction));
|
|
6899
7464
|
} catch {
|
|
6900
7465
|
}
|
|
6901
|
-
const
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
7466
|
+
const hash = keccak256(body.serializedTransaction);
|
|
7467
|
+
const existing = await i.pub.getTransactionReceipt({ hash }).catch(() => void 0);
|
|
7468
|
+
if (existing) {
|
|
7469
|
+
assertReceipt(existing, "deposit");
|
|
7470
|
+
void post().catch(() => void 0);
|
|
7471
|
+
clear(key2);
|
|
7472
|
+
return hash;
|
|
7473
|
+
}
|
|
7474
|
+
let result = await postSigned("deposit", hash);
|
|
7475
|
+
if (result?.status === "pending" && result.phase === "approval" && body.serializedPermitTransaction && result.txHash === keccak256(body.serializedPermitTransaction)) {
|
|
7476
|
+
await waitForConfirmation("approval", result.txHash);
|
|
7477
|
+
result = await postSigned("deposit", hash);
|
|
7478
|
+
if (result?.status === "pending" && result.phase === "approval" && result.txHash === keccak256(body.serializedPermitTransaction)) throw pendingError("approval", result.txHash);
|
|
7479
|
+
}
|
|
7480
|
+
if (result) {
|
|
7481
|
+
if (result.txHash !== hash || result.status === "pending" && result.phase !== "deposit")
|
|
7482
|
+
throw new Error("Sponsorship receipt does not match the pending transaction.");
|
|
7483
|
+
if (result.status === "pending") await waitForConfirmation("deposit", hash);
|
|
7484
|
+
}
|
|
7485
|
+
if (!result || result.status === "pending") void post().catch(() => void 0);
|
|
6910
7486
|
clear(key2);
|
|
6911
|
-
return
|
|
7487
|
+
return hash;
|
|
6912
7488
|
} catch (error) {
|
|
6913
|
-
if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
|
|
7489
|
+
if (!body.serializedTransaction && !body.serializedPermitTransaction || rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true)
|
|
6914
7490
|
clear(key2);
|
|
6915
7491
|
throw error;
|
|
6916
7492
|
}
|
|
@@ -7116,7 +7692,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
7116
7692
|
}
|
|
7117
7693
|
|
|
7118
7694
|
// src/lib/sponsored-calls-deposit.ts
|
|
7119
|
-
import { encodeFunctionData as encodeFunctionData2, erc20Abi as
|
|
7695
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi5, toHex as toHex2 } from "viem";
|
|
7120
7696
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
7121
7697
|
var DEFAULT_MAX_POLLS = 30;
|
|
7122
7698
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -7162,7 +7738,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
7162
7738
|
to: token,
|
|
7163
7739
|
value: "0x0",
|
|
7164
7740
|
data: encodeFunctionData2({
|
|
7165
|
-
abi:
|
|
7741
|
+
abi: erc20Abi5,
|
|
7166
7742
|
functionName: "transfer",
|
|
7167
7743
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
7168
7744
|
})
|
|
@@ -7363,6 +7939,7 @@ var OwneySDK = class {
|
|
|
7363
7939
|
cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
|
|
7364
7940
|
agentReads = new AgentReads();
|
|
7365
7941
|
recoveredFundingReads = /* @__PURE__ */ new Set();
|
|
7942
|
+
fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
|
|
7366
7943
|
activationChecks = /* @__PURE__ */ new Map();
|
|
7367
7944
|
readAgent(agent, method, fetch2, params = null) {
|
|
7368
7945
|
const key2 = JSON.stringify([
|
|
@@ -7408,6 +7985,7 @@ var OwneySDK = class {
|
|
|
7408
7985
|
}
|
|
7409
7986
|
this.agentReads.clearInFlight();
|
|
7410
7987
|
this.activationChecks.clear();
|
|
7988
|
+
this.fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
|
|
7411
7989
|
this.state = { provider, walletAddress, chainId: null };
|
|
7412
7990
|
this.cachedSponsoredCallback = null;
|
|
7413
7991
|
this.cachedWethSponsoredCallback = null;
|
|
@@ -7419,6 +7997,7 @@ var OwneySDK = class {
|
|
|
7419
7997
|
*/
|
|
7420
7998
|
async disconnect() {
|
|
7421
7999
|
this.state = null;
|
|
8000
|
+
this.fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
|
|
7422
8001
|
for (const agent of this.agents.values()) {
|
|
7423
8002
|
await agent.disconnect();
|
|
7424
8003
|
}
|
|
@@ -8319,9 +8898,8 @@ var OwneySDK = class {
|
|
|
8319
8898
|
verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
|
|
8320
8899
|
}
|
|
8321
8900
|
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
8322
|
-
if ((verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
|
|
8323
|
-
|
|
8324
|
-
agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
|
|
8901
|
+
if (options.refreshBalance !== false && (verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
|
|
8902
|
+
agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
|
|
8325
8903
|
this.recoveredFundingReads.add(key2);
|
|
8326
8904
|
if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
|
|
8327
8905
|
}
|
|
@@ -8429,6 +9007,44 @@ var OwneySDK = class {
|
|
|
8429
9007
|
assertCurrent();
|
|
8430
9008
|
return true;
|
|
8431
9009
|
}
|
|
9010
|
+
/** Optional post-payout display proof; receipt verification and the provider
|
|
9011
|
+
* adapter must agree before attaching it to this exact balance object. */
|
|
9012
|
+
async verifyWithdrawalBalance(evidence, balance) {
|
|
9013
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
9014
|
+
const agent = this.getAgent(evidence.agentId);
|
|
9015
|
+
if (!agent.verifyWithdrawalBalance || !evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return balance;
|
|
9016
|
+
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}:${evidence.intendedAmount}`;
|
|
9017
|
+
const checks = this.fundingBalanceChecks.get(balance) ?? /* @__PURE__ */ new Map();
|
|
9018
|
+
this.fundingBalanceChecks.set(balance, checks);
|
|
9019
|
+
const previous = checks.get(key2);
|
|
9020
|
+
if (previous) {
|
|
9021
|
+
const result2 = await previous;
|
|
9022
|
+
assertCurrent();
|
|
9023
|
+
return result2;
|
|
9024
|
+
}
|
|
9025
|
+
let timer;
|
|
9026
|
+
const read2 = async () => {
|
|
9027
|
+
const verified = await this.inspectFunding(evidence, "withdraw", { refreshBalance: false });
|
|
9028
|
+
assertCurrent();
|
|
9029
|
+
if (verified.transfer !== "confirmed" || verified.delivery !== "confirmed" || !verified.blockNumber) return balance;
|
|
9030
|
+
const metadata = await agent.verifyWithdrawalBalance(state, verified, balance);
|
|
9031
|
+
assertCurrent();
|
|
9032
|
+
if (!metadata || metadata.chainId !== chainId || metadata.asset.toUpperCase() !== evidence.token || !metadata.withdrawalCheck || metadata.withdrawalCheck.txHash.toLowerCase() !== verified.txHash?.toLowerCase() || BigInt(metadata.withdrawalCheck.blockNumber) < BigInt(verified.blockNumber)) return balance;
|
|
9033
|
+
return { ...balance, readMetadata: [
|
|
9034
|
+
...(balance.readMetadata ?? []).filter((item) => !(item.chainId === chainId && item.asset.toUpperCase() === evidence.token)),
|
|
9035
|
+
metadata
|
|
9036
|
+
] };
|
|
9037
|
+
};
|
|
9038
|
+
const pending = Promise.race([read2(), new Promise((resolve) => {
|
|
9039
|
+
timer = setTimeout(() => resolve(balance), 2e3);
|
|
9040
|
+
})]).catch(() => balance).finally(() => {
|
|
9041
|
+
if (timer) clearTimeout(timer);
|
|
9042
|
+
});
|
|
9043
|
+
checks.set(key2, pending);
|
|
9044
|
+
const result = await pending;
|
|
9045
|
+
assertCurrent();
|
|
9046
|
+
return result;
|
|
9047
|
+
}
|
|
8432
9048
|
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
8433
9049
|
endFundingBalanceRefresh(evidence, read2) {
|
|
8434
9050
|
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
@@ -8674,11 +9290,10 @@ var OwneySDK = class {
|
|
|
8674
9290
|
};
|
|
8675
9291
|
}
|
|
8676
9292
|
const requested = BigInt(amount);
|
|
8677
|
-
const aggregated = await this.
|
|
9293
|
+
const { balances: aggregated, failures: balanceFailures } = await this.readAggregatedBalances({ chainId, asset: token });
|
|
8678
9294
|
const unavailableAgents = eligibleAgents.filter(
|
|
8679
9295
|
(agent) => !(agent.id in aggregated.agentBalances)
|
|
8680
9296
|
);
|
|
8681
|
-
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
8682
9297
|
const balances = projectAgentBalancesForAsset(
|
|
8683
9298
|
eligibleAgents,
|
|
8684
9299
|
aggregated.agentBalances,
|
|
@@ -8686,6 +9301,44 @@ var OwneySDK = class {
|
|
|
8686
9301
|
asset,
|
|
8687
9302
|
assetInfo.decimals
|
|
8688
9303
|
);
|
|
9304
|
+
const indexedAvailable = balances.reduce((sum, entry) => sum + entry.balance, 0n);
|
|
9305
|
+
await Promise.all(balances.map(async (entry) => {
|
|
9306
|
+
if (entry.balance !== 0n && indexedAvailable >= requested || !entry.agent.getWithdrawableBalance) return;
|
|
9307
|
+
const originalFailure = balanceFailures.get(entry.agent.id);
|
|
9308
|
+
if (originalFailure instanceof OwneyError && ![
|
|
9309
|
+
"AGENT_API_ERROR",
|
|
9310
|
+
"AGENT_TIMEOUT",
|
|
9311
|
+
"AGENT_RATE_LIMITED"
|
|
9312
|
+
].includes(originalFailure.code)) return;
|
|
9313
|
+
if (this.isUserRejectedWithdrawal(originalFailure)) throw originalFailure;
|
|
9314
|
+
if (entry.balance !== 0n && entry.agent.id !== "yieldseeker") {
|
|
9315
|
+
const total = aggregated.agentBalances[entry.agent.id]?.assetBalances?.find((value) => value.chainId === chainId && value.asset.toUpperCase() === token);
|
|
9316
|
+
if (!total || parseUnits3(total.amount, assetInfo.decimals) <= entry.balance) return;
|
|
9317
|
+
}
|
|
9318
|
+
try {
|
|
9319
|
+
const otherAvailable = indexedAvailable - entry.balance;
|
|
9320
|
+
const minimumAmount = requested > otherAvailable ? requested - otherAvailable : void 0;
|
|
9321
|
+
const verified = await entry.agent.getWithdrawableBalance(state, chainId, token, minimumAmount);
|
|
9322
|
+
if (typeof verified !== "bigint" || verified < 0n)
|
|
9323
|
+
throw new OwneyError("WITHDRAW_BALANCE_UNAVAILABLE", "Invalid withdrawal balance proof.", {}, entry.agent.id);
|
|
9324
|
+
if (this.state !== state || this.state.walletAddress !== state.walletAddress || this.state.chainId !== chainId)
|
|
9325
|
+
throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
|
|
9326
|
+
entry.balance = verified;
|
|
9327
|
+
const unavailableIndex = unavailableAgents.indexOf(entry.agent);
|
|
9328
|
+
if (unavailableIndex !== -1) unavailableAgents.splice(unavailableIndex, 1);
|
|
9329
|
+
if (aggregated.agentErrors) delete aggregated.agentErrors[entry.agent.id];
|
|
9330
|
+
} catch (error) {
|
|
9331
|
+
entry.balance = 0n;
|
|
9332
|
+
if (!unavailableAgents.includes(entry.agent)) unavailableAgents.push(entry.agent);
|
|
9333
|
+
aggregated.agentErrors = {
|
|
9334
|
+
...aggregated.agentErrors,
|
|
9335
|
+
[entry.agent.id]: error instanceof Error ? error.message : String(error)
|
|
9336
|
+
};
|
|
9337
|
+
}
|
|
9338
|
+
}));
|
|
9339
|
+
if (this.state !== state || this.state.chainId !== chainId)
|
|
9340
|
+
throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
|
|
9341
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
8689
9342
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
8690
9343
|
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
8691
9344
|
throw new OwneyError(
|
|
@@ -8770,7 +9423,18 @@ var OwneySDK = class {
|
|
|
8770
9423
|
);
|
|
8771
9424
|
}
|
|
8772
9425
|
const withdrawn = sumWithdrawnAmount(results);
|
|
8773
|
-
|
|
9426
|
+
const verifiedFeeDeductions = orderedPlans.reduce((total, plan) => {
|
|
9427
|
+
const result = results[plan.agent.id];
|
|
9428
|
+
const adjustment = result?.feeAdjustment;
|
|
9429
|
+
const evidence = result?.evidence;
|
|
9430
|
+
const submitted = submissionEvidence.get(plan.agent.id);
|
|
9431
|
+
if (plan.agent.id !== "yieldseeker" || !adjustment || !evidence || !submitted || typeof result.amount !== "string" || !/^\d+$/.test(result.amount) || typeof adjustment.grossAmount !== "string" || !/^\d+$/.test(adjustment.grossAmount) || typeof adjustment.feeAmount !== "string" || !/^\d+$/.test(adjustment.feeAmount) || evidence.agentId !== plan.agent.id || evidence.chainId !== chainId || evidence.token !== token || typeof evidence.walletAddress !== "string" || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || typeof evidence.agentWallet !== "string" || !/^0x[\da-f]{40}$/i.test(evidence.agentWallet) || typeof result.txHash !== "string" || !/^0x[\da-f]{64}$/i.test(result.txHash) || typeof evidence.txHash !== "string" || typeof submitted.txHash !== "string" || evidence.txHash?.toLowerCase() !== result.txHash.toLowerCase() || submitted.txHash?.toLowerCase() !== result.txHash.toLowerCase() || submitted.agentId !== evidence.agentId || submitted.chainId !== chainId || submitted.token !== token || typeof submitted.walletAddress !== "string" || typeof submitted.agentWallet !== "string" || submitted.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || submitted.agentWallet?.toLowerCase() !== evidence.agentWallet.toLowerCase() || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || submitted.transfer !== "confirmed" || submitted.delivery !== "confirmed" || evidence.confirmedAmount !== result.amount || submitted.confirmedAmount !== result.amount || evidence.intendedAmount !== result.amount || submitted.intendedAmount !== result.amount) return total;
|
|
9432
|
+
const fee = BigInt(adjustment.feeAmount);
|
|
9433
|
+
const net = BigInt(result.amount);
|
|
9434
|
+
const gross = BigInt(adjustment.grossAmount);
|
|
9435
|
+
return fee > 0n && net > 0n && gross === plan.planned && net + fee === gross ? total + fee : total;
|
|
9436
|
+
}, 0n);
|
|
9437
|
+
if (withdrawn + verifiedFeeDeductions < requested) {
|
|
8774
9438
|
throw new OwneyError(
|
|
8775
9439
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
8776
9440
|
`Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
|
|
@@ -8794,23 +9458,42 @@ var OwneySDK = class {
|
|
|
8794
9458
|
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
8795
9459
|
*/
|
|
8796
9460
|
async getBalances(agentId, options) {
|
|
8797
|
-
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
8798
|
-
const asset = options?.asset;
|
|
8799
|
-
const readBalance = (agent) => this.readAgent(
|
|
8800
|
-
agent,
|
|
8801
|
-
"balances",
|
|
8802
|
-
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
8803
|
-
asset ? { asset } : null
|
|
8804
|
-
);
|
|
8805
9461
|
if (agentId) {
|
|
9462
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
9463
|
+
const asset = options?.asset;
|
|
8806
9464
|
const agent = this.getAgent(agentId);
|
|
8807
|
-
const result = await
|
|
9465
|
+
const result = await this.readAgent(
|
|
9466
|
+
agent,
|
|
9467
|
+
"balances",
|
|
9468
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
9469
|
+
asset ? { asset } : null
|
|
9470
|
+
);
|
|
8808
9471
|
assertCurrent();
|
|
8809
9472
|
return {
|
|
8810
9473
|
...result,
|
|
8811
9474
|
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
8812
9475
|
};
|
|
8813
9476
|
}
|
|
9477
|
+
const { balances, failures } = await this.readAggregatedBalances(options);
|
|
9478
|
+
if (Object.keys(balances.agentBalances).length === 0) {
|
|
9479
|
+
throw new OwneyError(
|
|
9480
|
+
"BALANCE_ALL_FAILED",
|
|
9481
|
+
"Failed to fetch balances for all active agents.",
|
|
9482
|
+
{ agentErrors: balances.agentErrors ?? {}, failures: [...failures.values()] }
|
|
9483
|
+
);
|
|
9484
|
+
}
|
|
9485
|
+
return balances;
|
|
9486
|
+
}
|
|
9487
|
+
/** Preserve failure objects for withdrawal recovery without changing public balance results. */
|
|
9488
|
+
async readAggregatedBalances(options) {
|
|
9489
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
9490
|
+
const asset = options?.asset;
|
|
9491
|
+
const readBalance = (agent) => this.readAgent(
|
|
9492
|
+
agent,
|
|
9493
|
+
"balances",
|
|
9494
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
9495
|
+
asset ? { asset } : null
|
|
9496
|
+
);
|
|
8814
9497
|
let totalBalance = 0;
|
|
8815
9498
|
const results = {};
|
|
8816
9499
|
const entries = [...this.getActiveAgents().entries()];
|
|
@@ -8827,39 +9510,33 @@ var OwneySDK = class {
|
|
|
8827
9510
|
})
|
|
8828
9511
|
);
|
|
8829
9512
|
assertCurrent();
|
|
8830
|
-
let successCount = 0;
|
|
8831
9513
|
const agentErrors = {};
|
|
8832
9514
|
const agentRetryAt = {};
|
|
8833
|
-
const agentFailures =
|
|
9515
|
+
const agentFailures = /* @__PURE__ */ new Map();
|
|
8834
9516
|
for (let i = 0; i < balanceResults.length; i++) {
|
|
8835
9517
|
const settledResult = balanceResults[i];
|
|
8836
|
-
const [
|
|
9518
|
+
const [agentId] = entries[i];
|
|
8837
9519
|
if (settledResult.status === "fulfilled") {
|
|
8838
9520
|
const [id, b] = settledResult.value;
|
|
8839
9521
|
results[id] = b;
|
|
8840
9522
|
totalBalance += Number(b.totalBalance);
|
|
8841
|
-
successCount += 1;
|
|
8842
9523
|
continue;
|
|
8843
9524
|
}
|
|
8844
9525
|
const reason = settledResult.reason;
|
|
8845
|
-
agentFailures.
|
|
9526
|
+
agentFailures.set(agentId, reason);
|
|
8846
9527
|
const retryDelay = rateLimitDelay(reason);
|
|
8847
|
-
if (retryDelay !== void 0) agentRetryAt[
|
|
8848
|
-
agentErrors[
|
|
8849
|
-
console.error(`[owney-sdk] Balance fetch failed for agent "${
|
|
8850
|
-
}
|
|
8851
|
-
if (successCount === 0) {
|
|
8852
|
-
throw new OwneyError(
|
|
8853
|
-
"BALANCE_ALL_FAILED",
|
|
8854
|
-
"Failed to fetch balances for all active agents.",
|
|
8855
|
-
{ agentErrors, failures: agentFailures }
|
|
8856
|
-
);
|
|
9528
|
+
if (retryDelay !== void 0) agentRetryAt[agentId] = Date.now() + retryDelay;
|
|
9529
|
+
agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
|
|
9530
|
+
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId}":`, reason);
|
|
8857
9531
|
}
|
|
8858
9532
|
return {
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
9533
|
+
balances: {
|
|
9534
|
+
totalBalance: String(totalBalance),
|
|
9535
|
+
totalBalanceAsset: "usdc",
|
|
9536
|
+
agentBalances: results,
|
|
9537
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
|
|
9538
|
+
},
|
|
9539
|
+
failures: agentFailures
|
|
8863
9540
|
};
|
|
8864
9541
|
}
|
|
8865
9542
|
/**
|
|
@@ -9411,7 +10088,7 @@ var OwneySDK = class {
|
|
|
9411
10088
|
};
|
|
9412
10089
|
|
|
9413
10090
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
9414
|
-
import { getAddress as
|
|
10091
|
+
import { getAddress as getAddress6 } from "viem";
|
|
9415
10092
|
import { SiweMessage as SiweMessage2 } from "siwe";
|
|
9416
10093
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
9417
10094
|
|
|
@@ -9538,7 +10215,7 @@ function buildSIWXConfig(deps) {
|
|
|
9538
10215
|
issuedAt,
|
|
9539
10216
|
toString() {
|
|
9540
10217
|
return new SiweMessage2({
|
|
9541
|
-
address:
|
|
10218
|
+
address: getAddress6(accountAddress),
|
|
9542
10219
|
chainId: numericChainId(chainId),
|
|
9543
10220
|
domain,
|
|
9544
10221
|
uri,
|