@owney/sdk 0.7.25-beta.14 → 0.7.25-beta.16
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 +1853 -567
- package/dist/index.d.cts +64 -6
- package/dist/index.d.ts +64 -6
- package/dist/index.js +1784 -494
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -68,6 +68,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
68
68
|
const attributed = /* @__PURE__ */ new Map();
|
|
69
69
|
const agent = evidence.agentWallet?.toLowerCase();
|
|
70
70
|
const owner = evidence.walletAddress.toLowerCase();
|
|
71
|
+
const source = direction === "deposit" ? owner : agent;
|
|
72
|
+
const destination = direction === "deposit" ? agent : owner;
|
|
71
73
|
const logIndexes = [];
|
|
72
74
|
const transferLogs = [];
|
|
73
75
|
const zeroTransferLogs = [];
|
|
@@ -82,17 +84,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
82
84
|
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
83
85
|
zeroTransferLogs.push(log.logIndex);
|
|
84
86
|
let matched = 0n;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (sender !== agent) attributed.set(sender, available - carried);
|
|
93
|
-
if (recipient === owner) matched = carried;
|
|
94
|
-
else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
95
|
-
}
|
|
87
|
+
const sender = from.toLowerCase();
|
|
88
|
+
const recipient = to.toLowerCase();
|
|
89
|
+
const available = sender === source ? value : attributed.get(sender) ?? 0n;
|
|
90
|
+
const carried = available < value ? available : value;
|
|
91
|
+
if (sender !== source) attributed.set(sender, available - carried);
|
|
92
|
+
if (recipient === destination) matched = carried;
|
|
93
|
+
else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
96
94
|
if (matched === 0n) continue;
|
|
97
95
|
amount += matched;
|
|
98
96
|
transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
|
|
@@ -372,20 +370,44 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
372
370
|
};
|
|
373
371
|
const notify = (outcome) => {
|
|
374
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
|
+
};
|
|
375
379
|
for (const observer of matching) {
|
|
376
380
|
try {
|
|
377
|
-
observer.notify(outcome,
|
|
381
|
+
observer.notify(outcome, evidence);
|
|
378
382
|
} catch {
|
|
379
383
|
}
|
|
380
384
|
}
|
|
381
385
|
};
|
|
382
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
|
+
}
|
|
383
402
|
try {
|
|
384
|
-
return await original.apply(this,
|
|
403
|
+
return await original.apply(this, callArgs);
|
|
385
404
|
} catch (error) {
|
|
386
405
|
const message = error instanceof Error ? error.message : "";
|
|
387
|
-
|
|
388
|
-
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");
|
|
389
411
|
throw error;
|
|
390
412
|
} finally {
|
|
391
413
|
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
@@ -408,9 +430,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
408
430
|
} };
|
|
409
431
|
}
|
|
410
432
|
|
|
433
|
+
// src/agents/zyfai/zyfai.withdraw-empty.ts
|
|
434
|
+
function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
435
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
436
|
+
const response = record(raw2);
|
|
437
|
+
const portfolio = record(response?.portfolio);
|
|
438
|
+
const chains = record(portfolio?.portfolioByChain);
|
|
439
|
+
if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
|
|
440
|
+
if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
|
|
441
|
+
const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
|
|
442
|
+
const chain = (value) => {
|
|
443
|
+
if (typeof value === "number") return value;
|
|
444
|
+
if (typeof value !== "string") return void 0;
|
|
445
|
+
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
446
|
+
};
|
|
447
|
+
const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
448
|
+
for (const [id, assets] of Object.entries(chains)) {
|
|
449
|
+
const idNumber = chain(id);
|
|
450
|
+
if (idNumber === void 0) return false;
|
|
451
|
+
if (idNumber !== chainId) continue;
|
|
452
|
+
const entries = record(assets);
|
|
453
|
+
if (!entries) return false;
|
|
454
|
+
for (const [asset, value] of Object.entries(entries)) {
|
|
455
|
+
if (symbol(asset) !== token) continue;
|
|
456
|
+
const balance = record(value);
|
|
457
|
+
if (!balance || !zero(balance.balance)) return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
|
|
461
|
+
const entry = record(value);
|
|
462
|
+
if (!entry) return false;
|
|
463
|
+
const id = chain(entry.chainId ?? entry.chain);
|
|
464
|
+
const asset = symbol(entry.tokenSymbol ?? entry.token_symbol);
|
|
465
|
+
if (id === void 0 || asset === void 0) return false;
|
|
466
|
+
if (id !== chainId || asset !== token) continue;
|
|
467
|
+
const amounts = [entry.balance, entry.amount, entry.underlyingAmount].filter((value2) => value2 !== void 0);
|
|
468
|
+
if (!amounts.length || !amounts.every(zero)) return false;
|
|
469
|
+
}
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
411
473
|
// src/agents/zyfai/zyfai.agent.ts
|
|
412
474
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
413
|
-
import { formatUnits, parseAbi as parseAbi2 } from "viem";
|
|
475
|
+
import { erc20Abi as erc20Abi2, formatUnits as formatUnits2, isAddress, parseAbi as parseAbi2 } from "viem";
|
|
414
476
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
415
477
|
|
|
416
478
|
// src/types/config.ts
|
|
@@ -497,14 +559,6 @@ function hexToDecimal(hex, decimals = 6) {
|
|
|
497
559
|
const remainder = parsed % divisor;
|
|
498
560
|
return Number(whole) + Number(remainder) / Number(divisor);
|
|
499
561
|
}
|
|
500
|
-
function normalizeDecimalPlace(value, decimals = 6) {
|
|
501
|
-
const parsed = BigInt(value);
|
|
502
|
-
if (decimals <= 0) return Number(parsed);
|
|
503
|
-
const divisor = BigInt(10 ** decimals);
|
|
504
|
-
const whole = parsed / divisor;
|
|
505
|
-
const remainder = parsed % divisor;
|
|
506
|
-
return Number(whole) + Number(remainder) / Number(divisor);
|
|
507
|
-
}
|
|
508
562
|
var DayFilterMapping = {
|
|
509
563
|
"30D": 30,
|
|
510
564
|
"14D": 14,
|
|
@@ -512,6 +566,8 @@ var DayFilterMapping = {
|
|
|
512
566
|
};
|
|
513
567
|
|
|
514
568
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
569
|
+
import { formatUnits } from "viem";
|
|
570
|
+
var exactHexAmount = (raw2, decimals) => formatUnits(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
|
|
515
571
|
var DECIMALS_BY_SYMBOL = {
|
|
516
572
|
usdc: 6,
|
|
517
573
|
weth: 18,
|
|
@@ -566,15 +622,15 @@ function mapPendingAllocations(raw2) {
|
|
|
566
622
|
if (typeof e.balance !== "string") continue;
|
|
567
623
|
let amount;
|
|
568
624
|
try {
|
|
569
|
-
|
|
625
|
+
if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
|
|
626
|
+
amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
|
|
570
627
|
} catch {
|
|
571
628
|
continue;
|
|
572
629
|
}
|
|
573
|
-
if (!Number.isFinite(amount) || amount <= 0) continue;
|
|
574
630
|
pending.push({
|
|
575
631
|
chainId: e.chainId,
|
|
576
632
|
asset: e.tokenSymbol,
|
|
577
|
-
amount
|
|
633
|
+
amount,
|
|
578
634
|
isPending: e.isPending === true,
|
|
579
635
|
...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
|
|
580
636
|
});
|
|
@@ -597,13 +653,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
597
653
|
{ balance, balanceWithFee, decimals }
|
|
598
654
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
599
655
|
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
600
|
-
const amount =
|
|
601
|
-
totalBalance += amount;
|
|
656
|
+
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
|
|
657
|
+
totalBalance += Number(amount);
|
|
602
658
|
tokens.push({
|
|
603
659
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
604
660
|
chainId: positionChainId,
|
|
605
661
|
asset: symbol,
|
|
606
|
-
amount
|
|
662
|
+
amount
|
|
607
663
|
});
|
|
608
664
|
}
|
|
609
665
|
}
|
|
@@ -617,19 +673,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
617
673
|
// legacy `amount` fallback is hex. Runtime
|
|
618
674
|
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
619
675
|
// when present, otherwise fall back to the per-symbol lookup.
|
|
620
|
-
amount:
|
|
621
|
-
p.underlyingAmountWithFee ?? p.underlyingAmount
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
p.decimals
|
|
632
|
-
)
|
|
676
|
+
amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? formatUnits(
|
|
677
|
+
BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
|
|
678
|
+
tokenDecimals(
|
|
679
|
+
p.token_symbol,
|
|
680
|
+
p.decimals
|
|
681
|
+
)
|
|
682
|
+
) : exactHexAmount(
|
|
683
|
+
p.amount || "0",
|
|
684
|
+
tokenDecimals(
|
|
685
|
+
p.token_symbol,
|
|
686
|
+
p.decimals
|
|
633
687
|
)
|
|
634
688
|
),
|
|
635
689
|
apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
|
|
@@ -1434,6 +1488,7 @@ var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
|
|
|
1434
1488
|
var SPLITTING_ASSETS = ["USDC", "WETH"];
|
|
1435
1489
|
var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
|
|
1436
1490
|
var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1491
|
+
var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
|
|
1437
1492
|
var ZyfaiAgent = class _ZyfaiAgent {
|
|
1438
1493
|
id = "zyfai";
|
|
1439
1494
|
supportedChainIds = [8453, 42161, 1];
|
|
@@ -1573,7 +1628,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1573
1628
|
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
1574
1629
|
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
1575
1630
|
Object.assign(tx, {
|
|
1576
|
-
amount:
|
|
1631
|
+
amount: formatUnits2(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
1577
1632
|
amountRaw: evidence.confirmedAmount,
|
|
1578
1633
|
blockNumber: evidence.blockNumber,
|
|
1579
1634
|
logIndexes: evidence.logIndexes,
|
|
@@ -2300,6 +2355,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2300
2355
|
evidence2 = { ...evidence2, transfer: "unknown" };
|
|
2301
2356
|
}
|
|
2302
2357
|
notifyFunding(onProgress, evidence2);
|
|
2358
|
+
if (evidence2.transfer === "failed")
|
|
2359
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
|
|
2303
2360
|
try {
|
|
2304
2361
|
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
2305
2362
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
@@ -2338,12 +2395,113 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2338
2395
|
} catch {
|
|
2339
2396
|
}
|
|
2340
2397
|
notifyFunding(onProgress, evidence);
|
|
2398
|
+
if (evidence.transfer === "failed")
|
|
2399
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
|
|
2341
2400
|
return { ...mapDeposit(raw2), evidence };
|
|
2342
2401
|
} catch (error) {
|
|
2343
2402
|
throw error;
|
|
2344
2403
|
}
|
|
2345
2404
|
}
|
|
2346
2405
|
// --- IAgent: Fund operations ---
|
|
2406
|
+
/** Only after a definitive endpoint rejection, combine a new provider
|
|
2407
|
+
* snapshot with the Safe's exact idle balance. Never parse its rounded error
|
|
2408
|
+
* amount as proof of zero, or use this to skip sending a new MAX request. */
|
|
2409
|
+
async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner) {
|
|
2410
|
+
const internals = this.sdk;
|
|
2411
|
+
const generation = this.earningsGeneration;
|
|
2412
|
+
let expired = false;
|
|
2413
|
+
const unavailable = () => new OwneyError(
|
|
2414
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
2415
|
+
"Could not verify Zyfai's balance after its withdrawal rejection.",
|
|
2416
|
+
{ chainId, token },
|
|
2417
|
+
this.id
|
|
2418
|
+
);
|
|
2419
|
+
const assertScope = () => {
|
|
2420
|
+
if (expired) throw unavailable();
|
|
2421
|
+
const account = internals.walletClient?.account?.address;
|
|
2422
|
+
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())
|
|
2423
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2424
|
+
};
|
|
2425
|
+
const read2 = async () => {
|
|
2426
|
+
assertScope();
|
|
2427
|
+
const [wallet, registered, portfolio] = await Promise.all([
|
|
2428
|
+
this.sdk.getSmartWalletAddress(owner, chainId),
|
|
2429
|
+
this.sdk.getSmartWalletByEOA(owner),
|
|
2430
|
+
this.sdk.getPortfolio(owner).catch(() => void 0)
|
|
2431
|
+
]);
|
|
2432
|
+
assertScope();
|
|
2433
|
+
if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
|
|
2434
|
+
throw new OwneyError(
|
|
2435
|
+
"AGENT_AUTH_FAILED",
|
|
2436
|
+
"Could not verify Zyfai's withdrawal wallet.",
|
|
2437
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2438
|
+
this.id
|
|
2439
|
+
);
|
|
2440
|
+
if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
|
|
2441
|
+
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
2442
|
+
const idle = asset ? await this.getPublicClient(chainId).readContract({
|
|
2443
|
+
address: asset.address,
|
|
2444
|
+
abi: erc20Abi2,
|
|
2445
|
+
functionName: "balanceOf",
|
|
2446
|
+
args: [wallet.address]
|
|
2447
|
+
}).catch(() => void 0) : void 0;
|
|
2448
|
+
assertScope();
|
|
2449
|
+
if (idle === 0n) throw new OwneyError(
|
|
2450
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2451
|
+
"No funds remain in Zyfai for this asset.",
|
|
2452
|
+
{ available: "0", chainId, token },
|
|
2453
|
+
this.id
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
let timer;
|
|
2458
|
+
try {
|
|
2459
|
+
await Promise.race([read2(), new Promise((_, reject) => {
|
|
2460
|
+
timer = setTimeout(() => {
|
|
2461
|
+
expired = true;
|
|
2462
|
+
reject(unavailable());
|
|
2463
|
+
}, WITHDRAW_BALANCE_TIMEOUT_MS);
|
|
2464
|
+
})]);
|
|
2465
|
+
} finally {
|
|
2466
|
+
expired = true;
|
|
2467
|
+
if (timer) clearTimeout(timer);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
/** Called only after the execution endpoint explicitly rejected a request.
|
|
2471
|
+
* A timeout or an accepted request never authorizes repair and resubmission. */
|
|
2472
|
+
async recoverWithdrawalSession(state, chainId, token, owner) {
|
|
2473
|
+
await this.assertNotEmptyRejectedWithdrawal(state, chainId, token, owner);
|
|
2474
|
+
const internals = this.sdk;
|
|
2475
|
+
const assertScope = () => {
|
|
2476
|
+
const account = internals.walletClient?.account?.address;
|
|
2477
|
+
if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
|
|
2478
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2479
|
+
};
|
|
2480
|
+
assertScope();
|
|
2481
|
+
const walletState = await this.readWalletState(owner);
|
|
2482
|
+
assertScope();
|
|
2483
|
+
const unavailable = () => new OwneyError(
|
|
2484
|
+
"AGENT_AUTH_FAILED",
|
|
2485
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
2486
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2487
|
+
this.id
|
|
2488
|
+
);
|
|
2489
|
+
if (walletState.predeployed !== false || internals.isPredeployed === true) throw unavailable();
|
|
2490
|
+
internals.hasActiveSessionKey = false;
|
|
2491
|
+
const key2 = `${owner.toLowerCase()}:${chainId}`;
|
|
2492
|
+
this.sessionKeyAttempts.delete(key2);
|
|
2493
|
+
try {
|
|
2494
|
+
const result = await this.sdk.createSessionKey(owner, chainId);
|
|
2495
|
+
assertScope();
|
|
2496
|
+
if (result.success === false || result.alreadyActive === true) throw unavailable();
|
|
2497
|
+
this.sessionKeyAttempts.add(key2);
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
assertScope();
|
|
2500
|
+
const code = error?.code;
|
|
2501
|
+
if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) throw error;
|
|
2502
|
+
throw unavailable();
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2347
2505
|
async withdraw(state, chainId, token, amount, onProgress) {
|
|
2348
2506
|
const validChainId = isValidChainId(chainId);
|
|
2349
2507
|
try {
|
|
@@ -2361,11 +2519,42 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2361
2519
|
});
|
|
2362
2520
|
throw error;
|
|
2363
2521
|
}
|
|
2364
|
-
|
|
2522
|
+
let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2365
2523
|
const authToken = this.sdk.httpClient?.authToken;
|
|
2524
|
+
const owner = this.getAddress();
|
|
2366
2525
|
let raw2;
|
|
2367
2526
|
try {
|
|
2368
|
-
|
|
2527
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2528
|
+
try {
|
|
2529
|
+
raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
|
|
2530
|
+
break;
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
|
|
2533
|
+
try {
|
|
2534
|
+
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
|
|
2535
|
+
} catch (proofError) {
|
|
2536
|
+
if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
|
|
2540
|
+
if (attempt === 1)
|
|
2541
|
+
throw new OwneyError(
|
|
2542
|
+
"AGENT_AUTH_FAILED",
|
|
2543
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
2544
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2545
|
+
this.id
|
|
2546
|
+
);
|
|
2547
|
+
await this.recoverWithdrawalSession(state, validChainId, token, owner);
|
|
2548
|
+
observation.stop();
|
|
2549
|
+
observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
if (!raw2) throw new OwneyError(
|
|
2553
|
+
"AGENT_INVALID_RESPONSE",
|
|
2554
|
+
"Zyfai returned no withdrawal result.",
|
|
2555
|
+
{ chainId, token, phase: "observe" },
|
|
2556
|
+
this.id
|
|
2557
|
+
);
|
|
2369
2558
|
} catch (error) {
|
|
2370
2559
|
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
2371
2560
|
if (observation.attempted() !== void 0) {
|
|
@@ -2440,6 +2629,67 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2440
2629
|
async getFundingWallet(state, chainId) {
|
|
2441
2630
|
return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
|
|
2442
2631
|
}
|
|
2632
|
+
/** A positive Safe balance is a withdrawal planning floor when the portfolio
|
|
2633
|
+
* index reports zero. Idle zero cannot prove that Zyfai's protocol positions
|
|
2634
|
+
* are empty: its SDK does not expose a complete contract catalog for them. */
|
|
2635
|
+
async getWithdrawableBalance(state, chainId, token) {
|
|
2636
|
+
const validChainId = isValidChainId(chainId);
|
|
2637
|
+
const asset = SupportedAssets.find((value) => value.chainId === validChainId && value.symbol === token);
|
|
2638
|
+
if (!asset) throw new OwneyError("WITHDRAW_NO_PERMITTED_TOKENS", "Asset selected is not supported", void 0, this.id);
|
|
2639
|
+
const owner = state.walletAddress;
|
|
2640
|
+
const generation = this.earningsGeneration;
|
|
2641
|
+
const internals = this.sdk;
|
|
2642
|
+
let expired = false;
|
|
2643
|
+
const unavailable = () => new OwneyError(
|
|
2644
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
2645
|
+
"Could not verify Zyfai's withdrawal balance. Its vault positions may still contain funds.",
|
|
2646
|
+
{ reason: "ZYFAI_VAULT_BALANCE_UNVERIFIED", chainId, token },
|
|
2647
|
+
this.id
|
|
2648
|
+
);
|
|
2649
|
+
const assertScope = () => {
|
|
2650
|
+
if (expired) throw unavailable();
|
|
2651
|
+
const account = internals.walletClient?.account?.address;
|
|
2652
|
+
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())
|
|
2653
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while checking Zyfai's withdrawal balance.", void 0, this.id);
|
|
2654
|
+
};
|
|
2655
|
+
let timer;
|
|
2656
|
+
const read2 = async () => {
|
|
2657
|
+
assertScope();
|
|
2658
|
+
const [wallet, registered] = await Promise.all([
|
|
2659
|
+
this.sdk.getSmartWalletAddress(owner, validChainId),
|
|
2660
|
+
this.sdk.getSmartWalletByEOA(owner)
|
|
2661
|
+
]);
|
|
2662
|
+
assertScope();
|
|
2663
|
+
if (!isAddress(wallet.address) || wallet.isDeployed !== true || wallet.isOwner !== true || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
|
|
2664
|
+
const client = this.getPublicClient(validChainId);
|
|
2665
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
2666
|
+
assertScope();
|
|
2667
|
+
const idle = await client.readContract({
|
|
2668
|
+
address: asset.address,
|
|
2669
|
+
abi: erc20Abi2,
|
|
2670
|
+
functionName: "balanceOf",
|
|
2671
|
+
args: [wallet.address],
|
|
2672
|
+
blockNumber
|
|
2673
|
+
});
|
|
2674
|
+
assertScope();
|
|
2675
|
+
if (typeof idle !== "bigint" || idle <= 0n) throw unavailable();
|
|
2676
|
+
return idle;
|
|
2677
|
+
};
|
|
2678
|
+
try {
|
|
2679
|
+
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
2680
|
+
timer = setTimeout(() => {
|
|
2681
|
+
expired = true;
|
|
2682
|
+
reject(unavailable());
|
|
2683
|
+
}, WITHDRAW_BALANCE_TIMEOUT_MS);
|
|
2684
|
+
})]);
|
|
2685
|
+
} catch (error) {
|
|
2686
|
+
if (error instanceof OwneyError) throw error;
|
|
2687
|
+
throw unavailable();
|
|
2688
|
+
} finally {
|
|
2689
|
+
expired = true;
|
|
2690
|
+
if (timer) clearTimeout(timer);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2443
2693
|
async getBalances(state, chainId) {
|
|
2444
2694
|
const requestedAt = Date.now();
|
|
2445
2695
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -2648,10 +2898,13 @@ import {
|
|
|
2648
2898
|
createPublicClient as createPublicClient3,
|
|
2649
2899
|
createWalletClient as createWalletClient3,
|
|
2650
2900
|
custom as custom3,
|
|
2901
|
+
decodeEventLog as decodeEventLog2,
|
|
2902
|
+
encodeEventTopics,
|
|
2651
2903
|
encodeFunctionData,
|
|
2652
|
-
erc20Abi as
|
|
2904
|
+
erc20Abi as erc20Abi3,
|
|
2905
|
+
erc4626Abi,
|
|
2653
2906
|
getAddress as getAddress4,
|
|
2654
|
-
isAddress as
|
|
2907
|
+
isAddress as isAddress3
|
|
2655
2908
|
} from "viem";
|
|
2656
2909
|
import { base as base4 } from "viem/chains";
|
|
2657
2910
|
|
|
@@ -3270,7 +3523,7 @@ var YieldseekerApiClient = class {
|
|
|
3270
3523
|
};
|
|
3271
3524
|
|
|
3272
3525
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
3273
|
-
import { formatUnits as
|
|
3526
|
+
import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
|
|
3274
3527
|
|
|
3275
3528
|
// src/lib/helpers/snapshot-apy.ts
|
|
3276
3529
|
var DAY_MS = 864e5;
|
|
@@ -3337,10 +3590,10 @@ function raw(value, endpoint) {
|
|
|
3337
3590
|
return BigInt(value);
|
|
3338
3591
|
}
|
|
3339
3592
|
function decimal(value, decimals, endpoint) {
|
|
3340
|
-
return
|
|
3593
|
+
return formatUnits3(raw(value, endpoint), decimals);
|
|
3341
3594
|
}
|
|
3342
3595
|
function usd(rawAmount, decimals, price) {
|
|
3343
|
-
return Number(
|
|
3596
|
+
return Number(formatUnits3(rawAmount, decimals)) * price;
|
|
3344
3597
|
}
|
|
3345
3598
|
function percent(value) {
|
|
3346
3599
|
const result = Number(value);
|
|
@@ -3366,7 +3619,7 @@ function assetAddressValue(record, address) {
|
|
|
3366
3619
|
}
|
|
3367
3620
|
function position(value, asset, baseAssetDecimals) {
|
|
3368
3621
|
const option = value?.yieldOption;
|
|
3369
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !
|
|
3622
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress2(option.address)) {
|
|
3370
3623
|
return invalid("yield positions", "missing vault metadata");
|
|
3371
3624
|
}
|
|
3372
3625
|
return {
|
|
@@ -3458,7 +3711,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
3458
3711
|
chain: "BASE",
|
|
3459
3712
|
chainId: 8453,
|
|
3460
3713
|
asset: context.asset,
|
|
3461
|
-
amount:
|
|
3714
|
+
amount: formatUnits3(amount, context.snapshot.baseAssetDecimals)
|
|
3462
3715
|
});
|
|
3463
3716
|
lifetimeEarnings += usd(
|
|
3464
3717
|
amount,
|
|
@@ -3587,7 +3840,7 @@ function allocationEntry(action, context) {
|
|
|
3587
3840
|
const leg = deposit;
|
|
3588
3841
|
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
3589
3842
|
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
3590
|
-
if (typeof leg.vaultAddress !== "string" || !
|
|
3843
|
+
if (typeof leg.vaultAddress !== "string" || !isAddress2(leg.vaultAddress)) return void 0;
|
|
3591
3844
|
const rawAmount = typeof leg.rawAmount === "string" && /^\d+$/.test(leg.rawAmount) ? BigInt(leg.rawAmount) : typeof leg.rawAmount === "number" && Number.isSafeInteger(leg.rawAmount) ? BigInt(leg.rawAmount) : void 0;
|
|
3592
3845
|
if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
|
|
3593
3846
|
const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
|
|
@@ -3595,7 +3848,7 @@ function allocationEntry(action, context) {
|
|
|
3595
3848
|
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
3596
3849
|
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
3597
3850
|
tokenSymbol: context.asset,
|
|
3598
|
-
amount:
|
|
3851
|
+
amount: formatUnits3(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3599
3852
|
});
|
|
3600
3853
|
amountRaw += rawAmount;
|
|
3601
3854
|
}
|
|
@@ -3614,7 +3867,7 @@ function allocationEntry(action, context) {
|
|
|
3614
3867
|
tokenSymbol: context.asset,
|
|
3615
3868
|
agentWallet: context.wallet.walletAddress,
|
|
3616
3869
|
amountRaw: String(amountRaw),
|
|
3617
|
-
amount:
|
|
3870
|
+
amount: formatUnits3(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3618
3871
|
}]
|
|
3619
3872
|
};
|
|
3620
3873
|
}
|
|
@@ -3738,7 +3991,7 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3738
3991
|
const known = new Set(target.logIndexes ?? []);
|
|
3739
3992
|
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
3740
3993
|
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
3741
|
-
target.amount =
|
|
3994
|
+
target.amount = formatUnits3(
|
|
3742
3995
|
BigInt(target.amountRaw),
|
|
3743
3996
|
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
3744
3997
|
);
|
|
@@ -3833,7 +4086,8 @@ var walletAbi = parseAbi3([
|
|
|
3833
4086
|
"function ownerAgentIndex() view returns (uint256)",
|
|
3834
4087
|
"function baseAsset() view returns (address)",
|
|
3835
4088
|
"function FACTORY() view returns (address)",
|
|
3836
|
-
"function upgradeToLatest()"
|
|
4089
|
+
"function upgradeToLatest()",
|
|
4090
|
+
"event Upgraded(address indexed implementation)"
|
|
3837
4091
|
]);
|
|
3838
4092
|
var factoryAbi = parseAbi3([
|
|
3839
4093
|
"function userWallets(address owner, uint256 index) view returns (address)",
|
|
@@ -3842,19 +4096,19 @@ var factoryAbi = parseAbi3([
|
|
|
3842
4096
|
function yieldseekerOwnerPath(code) {
|
|
3843
4097
|
return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
|
|
3844
4098
|
}
|
|
3845
|
-
async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
4099
|
+
async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
|
|
3846
4100
|
const agentWallet = getAddress2(wallet);
|
|
3847
4101
|
const expectedOwner = getAddress2(owner);
|
|
3848
4102
|
const expectedAsset = getAddress2(asset);
|
|
3849
4103
|
const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
|
|
3850
|
-
client.getBytecode({ address: agentWallet }),
|
|
3851
|
-
client.getBytecode({ address: expectedOwner }),
|
|
3852
|
-
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
|
|
3853
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
|
|
3854
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
|
|
3855
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
|
|
3856
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
|
|
3857
|
-
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
|
|
4104
|
+
client.getBytecode({ address: agentWallet, blockNumber }),
|
|
4105
|
+
client.getBytecode({ address: expectedOwner, blockNumber }),
|
|
4106
|
+
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT, blockNumber }),
|
|
4107
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner", blockNumber }),
|
|
4108
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex", blockNumber }),
|
|
4109
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset", blockNumber }),
|
|
4110
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY", blockNumber }),
|
|
4111
|
+
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation", blockNumber })
|
|
3858
4112
|
]);
|
|
3859
4113
|
if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
|
|
3860
4114
|
throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
|
|
@@ -3864,7 +4118,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
|
3864
4118
|
address: YIELDSEEKER_FACTORY,
|
|
3865
4119
|
abi: factoryAbi,
|
|
3866
4120
|
functionName: "userWallets",
|
|
3867
|
-
args: [expectedOwner, index]
|
|
4121
|
+
args: [expectedOwner, index],
|
|
4122
|
+
blockNumber
|
|
3868
4123
|
});
|
|
3869
4124
|
const verified = isAddressEqual(actualOwner, expectedOwner) && isAddressEqual(actualAsset, expectedAsset) && isAddressEqual(actualFactory, YIELDSEEKER_FACTORY) && isAddressEqual(mapped, agentWallet);
|
|
3870
4125
|
const version = !verified ? "unknown" : isAddressEqual(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : isAddressEqual(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
|
|
@@ -3893,6 +4148,8 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
|
3893
4148
|
import { base as base3 } from "viem/chains";
|
|
3894
4149
|
var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
3895
4150
|
var USER_OP_FEE_HEADROOM = 2n;
|
|
4151
|
+
var OBSERVATION_WINDOW_MS = 6e4;
|
|
4152
|
+
var OBSERVATION_POLL_MS = 1500;
|
|
3896
4153
|
var ENTRY_POINT_NONCE_ABI = [
|
|
3897
4154
|
{ type: "function", name: "getNonce", stateMutability: "view", inputs: [
|
|
3898
4155
|
{ name: "sender", type: "address" },
|
|
@@ -3925,10 +4182,119 @@ function sponsorError(message, safeToFallback, details = {}) {
|
|
|
3925
4182
|
return new OwneyError(
|
|
3926
4183
|
"SPONSOR_REQUEST_FAILED",
|
|
3927
4184
|
message,
|
|
3928
|
-
{
|
|
4185
|
+
{
|
|
4186
|
+
safeToFallback,
|
|
4187
|
+
phase: safeToFallback ? "prepare" : "observe",
|
|
4188
|
+
failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
|
|
4189
|
+
...safeToFallback ? { rejectedBeforeSubmission: true } : {},
|
|
4190
|
+
...details
|
|
4191
|
+
},
|
|
3929
4192
|
"yieldseeker"
|
|
3930
4193
|
);
|
|
3931
4194
|
}
|
|
4195
|
+
function providerReason(value) {
|
|
4196
|
+
if (typeof value !== "string") return void 0;
|
|
4197
|
+
return value.replace(/https?:\/\/\S+/gi, "[url]").replace(/0x[0-9a-fA-F]{40,}/g, "[hex]").replace(/[A-Za-z0-9_+=\/-]{48,}/g, "[token]").slice(0, 300);
|
|
4198
|
+
}
|
|
4199
|
+
function rpcDiagnostics(data) {
|
|
4200
|
+
const details = {};
|
|
4201
|
+
if (typeof data?.failureCode === "string" && [
|
|
4202
|
+
"SPONSOR_RATE_LIMIT",
|
|
4203
|
+
"SPONSOR_UPSTREAM_UNAVAILABLE",
|
|
4204
|
+
"SPONSOR_RPC_UNSUPPORTED",
|
|
4205
|
+
"SPONSOR_POLICY_REJECTED",
|
|
4206
|
+
"SPONSOR_BUDGET_REJECTED",
|
|
4207
|
+
"SPONSOR_EXECUTION_REVERTED"
|
|
4208
|
+
].includes(data.failureCode))
|
|
4209
|
+
details.failureCode = data.failureCode;
|
|
4210
|
+
if (typeof data?.revertSelector === "string" && /^0x[0-9a-fA-F]{8}$/.test(data.revertSelector))
|
|
4211
|
+
details.revertSelector = data.revertSelector;
|
|
4212
|
+
if (typeof data?.revertName === "string" && [
|
|
4213
|
+
"InsufficientBalance",
|
|
4214
|
+
"InvalidAsset",
|
|
4215
|
+
"Unauthorized",
|
|
4216
|
+
"Error",
|
|
4217
|
+
"Panic",
|
|
4218
|
+
"FailedOp",
|
|
4219
|
+
"FailedOpWithRevert"
|
|
4220
|
+
].includes(data.revertName))
|
|
4221
|
+
details.revertName = data.revertName;
|
|
4222
|
+
if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
|
|
4223
|
+
if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
|
|
4224
|
+
const panicCode = toHex(BigInt(data.panicCode));
|
|
4225
|
+
if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
|
|
4226
|
+
details.panicCode = panicCode;
|
|
4227
|
+
}
|
|
4228
|
+
return details;
|
|
4229
|
+
}
|
|
4230
|
+
function isHash(value) {
|
|
4231
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
4232
|
+
}
|
|
4233
|
+
async function assertOwnerSession(state, owner) {
|
|
4234
|
+
const [chain, accounts] = await Promise.all([
|
|
4235
|
+
state.provider.request({ method: "eth_chainId" }),
|
|
4236
|
+
state.provider.request({ method: "eth_accounts" })
|
|
4237
|
+
]);
|
|
4238
|
+
if (Number(chain) !== 8453)
|
|
4239
|
+
throw new OwneyError(
|
|
4240
|
+
"CHAIN_MISMATCH",
|
|
4241
|
+
"Switch back to Base before withdrawing.",
|
|
4242
|
+
{ rejectedBeforeSubmission: true, phase: "prepare" },
|
|
4243
|
+
"yieldseeker"
|
|
4244
|
+
);
|
|
4245
|
+
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase())
|
|
4246
|
+
throw new OwneyError(
|
|
4247
|
+
"NOT_CONNECTED",
|
|
4248
|
+
"The connected withdrawal account changed.",
|
|
4249
|
+
{ rejectedBeforeSubmission: true, phase: "prepare" },
|
|
4250
|
+
"yieldseeker"
|
|
4251
|
+
);
|
|
4252
|
+
}
|
|
4253
|
+
function savePending(onPending, evidence, beforeSubmission = false) {
|
|
4254
|
+
try {
|
|
4255
|
+
onPending?.(evidence);
|
|
4256
|
+
} catch {
|
|
4257
|
+
if (beforeSubmission)
|
|
4258
|
+
throw sponsorError(
|
|
4259
|
+
"Withdrawal status cannot be saved before submission.",
|
|
4260
|
+
true,
|
|
4261
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
|
|
4262
|
+
);
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
async function checkpoint(callback, pending) {
|
|
4266
|
+
try {
|
|
4267
|
+
await callback?.(pending);
|
|
4268
|
+
} catch {
|
|
4269
|
+
throw sponsorError(
|
|
4270
|
+
"Withdrawal status cannot be saved before submission.",
|
|
4271
|
+
true,
|
|
4272
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
|
|
4273
|
+
);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
function finalPaymasterFields(value) {
|
|
4277
|
+
const result = {};
|
|
4278
|
+
for (const key2 of [
|
|
4279
|
+
"paymasterAndData",
|
|
4280
|
+
"callGasLimit",
|
|
4281
|
+
"verificationGasLimit",
|
|
4282
|
+
"preVerificationGas",
|
|
4283
|
+
"maxFeePerGas",
|
|
4284
|
+
"maxPriorityFeePerGas"
|
|
4285
|
+
]) {
|
|
4286
|
+
const field = value[key2];
|
|
4287
|
+
if (field === void 0) continue;
|
|
4288
|
+
if (typeof field !== "string" || !/^0x[0-9a-fA-F]+$/.test(field))
|
|
4289
|
+
throw sponsorError(
|
|
4290
|
+
"Paymaster returned an invalid final operation.",
|
|
4291
|
+
true,
|
|
4292
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INVALID_PAYMASTER_QUOTE" }
|
|
4293
|
+
);
|
|
4294
|
+
result[key2] = field;
|
|
4295
|
+
}
|
|
4296
|
+
return result;
|
|
4297
|
+
}
|
|
3932
4298
|
async function requestIntent(input) {
|
|
3933
4299
|
const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
|
|
3934
4300
|
let response;
|
|
@@ -3950,18 +4316,35 @@ async function requestIntent(input) {
|
|
|
3950
4316
|
action: input.action,
|
|
3951
4317
|
yieldseekerUserId: input.userId,
|
|
3952
4318
|
yieldseekerAgentId: input.agentId
|
|
3953
|
-
})
|
|
4319
|
+
}),
|
|
4320
|
+
signal: AbortSignal.timeout(15e3)
|
|
3954
4321
|
});
|
|
3955
4322
|
} catch {
|
|
3956
|
-
throw sponsorError(
|
|
4323
|
+
throw sponsorError(
|
|
4324
|
+
"Withdrawal sponsorship could not be requested.",
|
|
4325
|
+
true,
|
|
4326
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
|
|
4327
|
+
);
|
|
3957
4328
|
}
|
|
3958
4329
|
const envelope = await response.json().catch(() => null);
|
|
3959
4330
|
const intent = envelope?.data;
|
|
3960
4331
|
if (!response.ok || !envelope?.success || !intent?.intent || !["eoa", "smart"].includes(intent.ownerPath)) {
|
|
4332
|
+
const rawCode = typeof envelope?.failureCode === "string" ? envelope.failureCode : envelope?.code;
|
|
4333
|
+
const failureCode = typeof rawCode === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(rawCode) ? rawCode : void 0;
|
|
4334
|
+
if (input.refreshYieldseekerSignature && (failureCode === "YIELDSEEKER_AUTH_REJECTED" || failureCode === "AUTH_REJECTED" || envelope?.sponsorshipStage === "provider-auth" && (envelope.providerStatus === 401 || envelope.providerStatus === 403))) {
|
|
4335
|
+
const signature = await input.refreshYieldseekerSignature(input.yieldseekerSignature);
|
|
4336
|
+
return requestIntent({ ...input, yieldseekerSignature: signature, refreshYieldseekerSignature: void 0 });
|
|
4337
|
+
}
|
|
3961
4338
|
throw sponsorError(
|
|
3962
4339
|
"Withdrawal sponsorship is currently unavailable.",
|
|
3963
4340
|
true,
|
|
3964
|
-
{
|
|
4341
|
+
{
|
|
4342
|
+
statusCode: response.status,
|
|
4343
|
+
phase: "prepare",
|
|
4344
|
+
rejectedBeforeSubmission: true,
|
|
4345
|
+
failureCode: failureCode ?? "INTENT_REJECTED",
|
|
4346
|
+
providerReason: providerReason(envelope?.message)
|
|
4347
|
+
}
|
|
3965
4348
|
);
|
|
3966
4349
|
}
|
|
3967
4350
|
const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
|
|
@@ -3974,19 +4357,55 @@ async function rpc(url, method, params, phase) {
|
|
|
3974
4357
|
response = await fetch(url, {
|
|
3975
4358
|
method: "POST",
|
|
3976
4359
|
headers: { "content-type": "application/json" },
|
|
3977
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
4360
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
4361
|
+
signal: AbortSignal.timeout(15e3)
|
|
3978
4362
|
});
|
|
3979
4363
|
} catch {
|
|
3980
|
-
throw sponsorError(
|
|
4364
|
+
throw sponsorError(
|
|
4365
|
+
"Withdrawal sponsorship request status is unknown.",
|
|
4366
|
+
phase === "prepare",
|
|
4367
|
+
{
|
|
4368
|
+
phase,
|
|
4369
|
+
rpcMethod: method,
|
|
4370
|
+
...phase === "prepare" ? { rejectedBeforeSubmission: true } : {},
|
|
4371
|
+
failureCode: "RPC_NETWORK_ERROR"
|
|
4372
|
+
}
|
|
4373
|
+
);
|
|
3981
4374
|
}
|
|
3982
4375
|
const payload = await response.json().catch(() => null);
|
|
3983
4376
|
if (!response.ok || payload?.error || payload?.result === void 0) {
|
|
3984
4377
|
const rejection = payload?.error;
|
|
3985
|
-
const
|
|
4378
|
+
const diagnostics = rpcDiagnostics(rejection?.data);
|
|
4379
|
+
const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
|
|
4380
|
+
const details = {
|
|
4381
|
+
statusCode: response.status,
|
|
4382
|
+
rpcCode: rejection?.code,
|
|
4383
|
+
rpcMethod: method,
|
|
4384
|
+
phase,
|
|
4385
|
+
providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
|
|
4386
|
+
failureCode: "RPC_REJECTED",
|
|
4387
|
+
...diagnostics
|
|
4388
|
+
};
|
|
4389
|
+
if (phase === "prepare" && executionReverted)
|
|
4390
|
+
throw new OwneyError(
|
|
4391
|
+
"WITHDRAW_FAILED",
|
|
4392
|
+
"Yieldseeker withdrawal simulation reverted before submission.",
|
|
4393
|
+
{
|
|
4394
|
+
...details,
|
|
4395
|
+
failureCode: "SPONSOR_EXECUTION_REVERTED",
|
|
4396
|
+
safeToFallback: false,
|
|
4397
|
+
rejectedBeforeSubmission: true
|
|
4398
|
+
},
|
|
4399
|
+
"yieldseeker"
|
|
4400
|
+
);
|
|
4401
|
+
const definitiveRejection = phase === "send" && !executionReverted && response.status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
|
|
3986
4402
|
throw sponsorError(
|
|
3987
4403
|
phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
|
|
3988
4404
|
phase === "prepare" || definitiveRejection,
|
|
3989
|
-
{
|
|
4405
|
+
{
|
|
4406
|
+
...details,
|
|
4407
|
+
...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
|
|
4408
|
+
}
|
|
3990
4409
|
);
|
|
3991
4410
|
}
|
|
3992
4411
|
return payload.result;
|
|
@@ -4006,13 +4425,15 @@ async function submitEoa(input) {
|
|
|
4006
4425
|
let nonce;
|
|
4007
4426
|
let fees;
|
|
4008
4427
|
try {
|
|
4009
|
-
nonce = await
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4428
|
+
[nonce, fees] = await Promise.all([
|
|
4429
|
+
input.client.readContract({
|
|
4430
|
+
address: ENTRY_POINT_V06,
|
|
4431
|
+
abi: ENTRY_POINT_NONCE_ABI,
|
|
4432
|
+
functionName: "getNonce",
|
|
4433
|
+
args: [input.agentWallet, 0n]
|
|
4434
|
+
}),
|
|
4435
|
+
input.client.estimateFeesPerGas()
|
|
4436
|
+
]);
|
|
4016
4437
|
} catch {
|
|
4017
4438
|
throw sponsorError("Could not prepare the sponsored withdrawal.", true);
|
|
4018
4439
|
}
|
|
@@ -4055,9 +4476,10 @@ async function submitEoa(input) {
|
|
|
4055
4476
|
);
|
|
4056
4477
|
if (!final.paymasterAndData?.startsWith("0x"))
|
|
4057
4478
|
throw sponsorError("CDP did not return final paymaster data.", true);
|
|
4058
|
-
operation = { ...operation,
|
|
4479
|
+
operation = { ...operation, ...finalPaymasterFields(final) };
|
|
4059
4480
|
}
|
|
4060
4481
|
const userOpHash = operationHash(operation);
|
|
4482
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4061
4483
|
const wallet = createWalletClient2({
|
|
4062
4484
|
account: input.owner,
|
|
4063
4485
|
chain: base3,
|
|
@@ -4065,24 +4487,47 @@ async function submitEoa(input) {
|
|
|
4065
4487
|
});
|
|
4066
4488
|
const signature = await wallet.signMessage({ message: { raw: userOpHash } });
|
|
4067
4489
|
operation = { ...operation, signature };
|
|
4068
|
-
input.
|
|
4490
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4491
|
+
const userOperation = {
|
|
4492
|
+
sender: operation.sender,
|
|
4493
|
+
nonce: operation.nonce,
|
|
4494
|
+
initCode: operation.initCode,
|
|
4495
|
+
callData: operation.callData,
|
|
4496
|
+
callGasLimit: operation.callGasLimit,
|
|
4497
|
+
verificationGasLimit: operation.verificationGasLimit,
|
|
4498
|
+
preVerificationGas: operation.preVerificationGas,
|
|
4499
|
+
maxFeePerGas: operation.maxFeePerGas,
|
|
4500
|
+
maxPriorityFeePerGas: operation.maxPriorityFeePerGas,
|
|
4501
|
+
paymasterAndData: operation.paymasterAndData
|
|
4502
|
+
};
|
|
4503
|
+
savePending(input.onPending, { userOpHash, userOperation }, true);
|
|
4504
|
+
await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
|
|
4069
4505
|
const submittedHash = await rpc(
|
|
4070
4506
|
input.rpcUrl,
|
|
4071
4507
|
"eth_sendUserOperation",
|
|
4072
4508
|
[operation, ENTRY_POINT_V06],
|
|
4073
4509
|
"send"
|
|
4074
4510
|
);
|
|
4075
|
-
if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4511
|
+
if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4076
4512
|
throw sponsorError(
|
|
4077
4513
|
"Bundler returned a different UserOperation hash.",
|
|
4078
4514
|
false,
|
|
4079
|
-
{ userOpHash, submittedHash }
|
|
4515
|
+
{ userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
|
|
4080
4516
|
);
|
|
4081
|
-
|
|
4082
|
-
|
|
4517
|
+
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
4518
|
+
let lastReadError;
|
|
4519
|
+
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
4520
|
+
let response;
|
|
4521
|
+
try {
|
|
4522
|
+
response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
|
|
4523
|
+
} catch (error) {
|
|
4524
|
+
lastReadError = error;
|
|
4525
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4526
|
+
continue;
|
|
4527
|
+
}
|
|
4083
4528
|
const txHash = response?.receipt?.transactionHash;
|
|
4084
|
-
if (txHash) {
|
|
4085
|
-
input.onPending
|
|
4529
|
+
if (isHash(txHash)) {
|
|
4530
|
+
savePending(input.onPending, { userOpHash, txHash });
|
|
4086
4531
|
if (response?.success === false)
|
|
4087
4532
|
throw new OwneyError(
|
|
4088
4533
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -4092,12 +4537,17 @@ async function submitEoa(input) {
|
|
|
4092
4537
|
);
|
|
4093
4538
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4094
4539
|
}
|
|
4095
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4540
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4096
4541
|
}
|
|
4097
4542
|
throw sponsorError(
|
|
4098
4543
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4099
4544
|
false,
|
|
4100
|
-
{
|
|
4545
|
+
{
|
|
4546
|
+
userOpHash,
|
|
4547
|
+
phase: "observe",
|
|
4548
|
+
failureCode: "RECEIPT_UNRESOLVED",
|
|
4549
|
+
...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
|
|
4550
|
+
}
|
|
4101
4551
|
);
|
|
4102
4552
|
}
|
|
4103
4553
|
async function submitSmart(input) {
|
|
@@ -4110,14 +4560,14 @@ async function submitSmart(input) {
|
|
|
4110
4560
|
} catch {
|
|
4111
4561
|
throw sponsorError("This account cannot check paymaster support on Base.", true);
|
|
4112
4562
|
}
|
|
4113
|
-
|
|
4563
|
+
const supportsPaymaster = capabilities?.[toHex(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
|
|
4564
|
+
if (supportsPaymaster !== true)
|
|
4114
4565
|
throw sponsorError("This account cannot use a paymaster on Base.", true);
|
|
4566
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4115
4567
|
let sent;
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
|
|
4120
|
-
}
|
|
4568
|
+
const requestedId = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
4569
|
+
savePending(input.onPending, { callsId: requestedId }, true);
|
|
4570
|
+
await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
|
|
4121
4571
|
try {
|
|
4122
4572
|
sent = await input.state.provider.request({
|
|
4123
4573
|
method: "wallet_sendCalls",
|
|
@@ -4125,6 +4575,7 @@ async function submitSmart(input) {
|
|
|
4125
4575
|
version: "2.0.0",
|
|
4126
4576
|
from: input.owner,
|
|
4127
4577
|
chainId: toHex(8453),
|
|
4578
|
+
id: requestedId,
|
|
4128
4579
|
atomicRequired: false,
|
|
4129
4580
|
calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
|
|
4130
4581
|
capabilities: { paymasterService: { url: input.rpcUrl } }
|
|
@@ -4133,17 +4584,27 @@ async function submitSmart(input) {
|
|
|
4133
4584
|
} catch (error) {
|
|
4134
4585
|
if (error?.code === 4001) throw error;
|
|
4135
4586
|
if ([4200, -32601].includes(Number(error?.code)))
|
|
4136
|
-
throw sponsorError(
|
|
4137
|
-
|
|
4587
|
+
throw sponsorError(
|
|
4588
|
+
"This account does not support sponsored calls.",
|
|
4589
|
+
true,
|
|
4590
|
+
{ phase: "send", failureCode: "WALLET_CALLS_UNSUPPORTED" }
|
|
4591
|
+
);
|
|
4592
|
+
throw sponsorError(
|
|
4593
|
+
"Wallet submission status is unknown. Check activity before retrying.",
|
|
4594
|
+
false,
|
|
4595
|
+
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
4596
|
+
);
|
|
4138
4597
|
}
|
|
4139
4598
|
const callsId = typeof sent === "string" ? sent : sent?.id;
|
|
4140
|
-
if (
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4599
|
+
if (typeof callsId !== "string" || callsId.length === 0 || callsId.length > 8194)
|
|
4600
|
+
throw sponsorError(
|
|
4601
|
+
"Wallet returned no calls ID; submission status is unknown.",
|
|
4602
|
+
false,
|
|
4603
|
+
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
4604
|
+
);
|
|
4605
|
+
savePending(input.onPending, { callsId });
|
|
4606
|
+
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
4607
|
+
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
4147
4608
|
let status;
|
|
4148
4609
|
try {
|
|
4149
4610
|
status = await input.state.provider.request({
|
|
@@ -4151,33 +4612,40 @@ async function submitSmart(input) {
|
|
|
4151
4612
|
params: [callsId]
|
|
4152
4613
|
});
|
|
4153
4614
|
} catch {
|
|
4154
|
-
|
|
4615
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4616
|
+
continue;
|
|
4155
4617
|
}
|
|
4156
|
-
if (status?.status
|
|
4618
|
+
if (status?.id !== void 0 && status.id !== callsId || status?.chainId !== void 0 && Number(status.chainId) !== 8453)
|
|
4619
|
+
throw sponsorError(
|
|
4620
|
+
"Wallet returned a different sponsored operation status.",
|
|
4621
|
+
false,
|
|
4622
|
+
{ callsId, phase: "observe", failureCode: "WALLET_STATUS_MISMATCH" }
|
|
4623
|
+
);
|
|
4624
|
+
if (Number(status?.status) === 400 || Number(status?.status) === 500)
|
|
4157
4625
|
throw sponsorError(
|
|
4158
4626
|
"Sponsored withdrawal failed in the wallet.",
|
|
4159
4627
|
false,
|
|
4160
|
-
{ callsId, executionFailed: true }
|
|
4628
|
+
{ callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
|
|
4161
4629
|
);
|
|
4162
|
-
const
|
|
4630
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
|
|
4631
|
+
const txHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4163
4632
|
if (txHash) {
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
"
|
|
4169
|
-
|
|
4170
|
-
|
|
4633
|
+
savePending(input.onPending, { callsId, txHash });
|
|
4634
|
+
if (status?.receipts?.some((receipt) => receipt.transactionHash === txHash && /^(?:0x0+|0|failed|reverted)$/i.test(String(receipt.status))))
|
|
4635
|
+
throw new OwneyError(
|
|
4636
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
4637
|
+
"Sponsored withdrawal execution reverted.",
|
|
4638
|
+
{ callsId, transactionHash: txHash },
|
|
4639
|
+
"yieldseeker"
|
|
4171
4640
|
);
|
|
4172
|
-
}
|
|
4173
4641
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4174
4642
|
}
|
|
4175
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4643
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4176
4644
|
}
|
|
4177
4645
|
throw sponsorError(
|
|
4178
4646
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4179
4647
|
false,
|
|
4180
|
-
{ callsId }
|
|
4648
|
+
{ callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
|
|
4181
4649
|
);
|
|
4182
4650
|
}
|
|
4183
4651
|
async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
@@ -4192,7 +4660,8 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
|
4192
4660
|
agentWallet: input.agentWallet,
|
|
4193
4661
|
asset: input.asset,
|
|
4194
4662
|
callData: input.callData,
|
|
4195
|
-
action: "withdraw"
|
|
4663
|
+
action: "withdraw",
|
|
4664
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4196
4665
|
});
|
|
4197
4666
|
const args = { ...input, owner, rpcUrl };
|
|
4198
4667
|
return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
|
|
@@ -4209,7 +4678,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
|
|
|
4209
4678
|
agentWallet: input.agentWallet,
|
|
4210
4679
|
asset: input.asset,
|
|
4211
4680
|
callData: input.callData,
|
|
4212
|
-
action: "upgrade"
|
|
4681
|
+
action: "upgrade",
|
|
4682
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4213
4683
|
});
|
|
4214
4684
|
if (intent.ownerPath !== "smart")
|
|
4215
4685
|
throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
|
|
@@ -4231,6 +4701,9 @@ var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
|
4231
4701
|
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
4232
4702
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4233
4703
|
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4704
|
+
var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
|
|
4705
|
+
var YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT = 64;
|
|
4706
|
+
var YIELDSEEKER_VAULTS_PER_RPC_BATCH = 10;
|
|
4234
4707
|
function generateYieldseekerUsername() {
|
|
4235
4708
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4236
4709
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4241,6 +4714,13 @@ function isUsernameConflict(error) {
|
|
|
4241
4714
|
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
4242
4715
|
}
|
|
4243
4716
|
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
4717
|
+
{
|
|
4718
|
+
type: "function",
|
|
4719
|
+
name: "feeTracker",
|
|
4720
|
+
stateMutability: "view",
|
|
4721
|
+
inputs: [],
|
|
4722
|
+
outputs: [{ name: "", type: "address" }]
|
|
4723
|
+
},
|
|
4244
4724
|
{
|
|
4245
4725
|
type: "function",
|
|
4246
4726
|
name: "withdrawAssetToUser",
|
|
@@ -4263,6 +4743,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
|
4263
4743
|
outputs: []
|
|
4264
4744
|
}
|
|
4265
4745
|
];
|
|
4746
|
+
var YIELDSEEKER_FEE_TRACKER_ABI = [{
|
|
4747
|
+
type: "function",
|
|
4748
|
+
name: "getFeesOwed",
|
|
4749
|
+
stateMutability: "view",
|
|
4750
|
+
inputs: [{ name: "wallet", type: "address" }],
|
|
4751
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
4752
|
+
}];
|
|
4266
4753
|
function query(params) {
|
|
4267
4754
|
const search = new URLSearchParams();
|
|
4268
4755
|
for (const [key2, value] of Object.entries(params)) {
|
|
@@ -4346,17 +4833,22 @@ var YieldseekerAgent = class {
|
|
|
4346
4833
|
getAddress4(state.walletAddress),
|
|
4347
4834
|
YIELDSEEKER_ASSET_METADATA[asset].address
|
|
4348
4835
|
);
|
|
4836
|
+
this.rememberWalletStatus(key2, status);
|
|
4837
|
+
return status;
|
|
4838
|
+
}
|
|
4839
|
+
rememberWalletStatus(key2, status) {
|
|
4349
4840
|
if (status.verified && status.version === "v2") {
|
|
4350
4841
|
this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
|
|
4351
4842
|
if (this.withdrawalVersionCache.size > 128)
|
|
4352
4843
|
this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
|
|
4353
4844
|
} else this.withdrawalVersionCache.delete(key2);
|
|
4354
|
-
return status;
|
|
4355
4845
|
}
|
|
4356
4846
|
/** Explicit owner action. This never runs as a side effect of withdrawal. */
|
|
4357
4847
|
async upgradeWallet(state, chainId, asset, gasMode = "paid") {
|
|
4848
|
+
const generation = this.readGeneration;
|
|
4358
4849
|
const before = await this.getWalletStatus(state, chainId, asset);
|
|
4359
|
-
|
|
4850
|
+
this.assertSession(generation, chainId);
|
|
4851
|
+
if (!before?.verified || before.version === "unknown") {
|
|
4360
4852
|
throw new OwneyError(
|
|
4361
4853
|
"WITHDRAW_FAILED",
|
|
4362
4854
|
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
@@ -4364,48 +4856,77 @@ var YieldseekerAgent = class {
|
|
|
4364
4856
|
this.id
|
|
4365
4857
|
);
|
|
4366
4858
|
}
|
|
4367
|
-
const
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
if (!storage5)
|
|
4859
|
+
const versionKey = `${chainId}:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}:${asset}`;
|
|
4860
|
+
const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
|
|
4861
|
+
const confirmUpgrade = async (hash, receipt2) => {
|
|
4862
|
+
this.withdrawalVersionCache.delete(versionKey);
|
|
4863
|
+
const upgradeTopic = encodeEventTopics({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
|
|
4864
|
+
const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
|
|
4865
|
+
const expectedUpgrade = (() => {
|
|
4866
|
+
if (!lastUpgrade || lastUpgrade.removed) return false;
|
|
4867
|
+
try {
|
|
4868
|
+
const event = decodeEventLog2({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
|
|
4869
|
+
return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
|
|
4870
|
+
} catch {
|
|
4871
|
+
return false;
|
|
4872
|
+
}
|
|
4873
|
+
})();
|
|
4874
|
+
if (!receipt2 || receipt2.status !== "success" || receipt2.transactionHash.toLowerCase() !== hash.toLowerCase() || typeof receipt2.blockNumber !== "bigint" || !expectedUpgrade)
|
|
4384
4875
|
throw new OwneyError(
|
|
4385
|
-
"
|
|
4386
|
-
"
|
|
4387
|
-
{
|
|
4876
|
+
"WITHDRAW_FAILED",
|
|
4877
|
+
"Yieldseeker wallet upgrade receipt could not be verified.",
|
|
4878
|
+
{ txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
|
|
4388
4879
|
this.id
|
|
4389
4880
|
);
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4881
|
+
this.assertSession(generation, chainId);
|
|
4882
|
+
const status = {
|
|
4883
|
+
...before,
|
|
4884
|
+
implementation: YIELDSEEKER_V2_IMPLEMENTATION,
|
|
4885
|
+
version: "v2",
|
|
4886
|
+
upgradeAvailable: false
|
|
4887
|
+
};
|
|
4888
|
+
this.rememberWalletStatus(versionKey, status);
|
|
4889
|
+
return { txHash: hash, status };
|
|
4890
|
+
};
|
|
4891
|
+
let storage5;
|
|
4892
|
+
try {
|
|
4893
|
+
storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
4894
|
+
} catch {
|
|
4895
|
+
storage5 = void 0;
|
|
4896
|
+
}
|
|
4897
|
+
const clearConfirmedUpgrade = () => {
|
|
4393
4898
|
try {
|
|
4394
|
-
|
|
4899
|
+
storage5?.removeItem(pendingKey2);
|
|
4395
4900
|
} catch {
|
|
4396
|
-
throw new OwneyError(
|
|
4397
|
-
"SPONSOR_REQUEST_FAILED",
|
|
4398
|
-
"Upgrade status cannot be read in this browser.",
|
|
4399
|
-
{ safeToFallback: true },
|
|
4400
|
-
this.id
|
|
4401
|
-
);
|
|
4402
4901
|
}
|
|
4902
|
+
};
|
|
4903
|
+
if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
|
|
4904
|
+
throw new OwneyError(
|
|
4905
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4906
|
+
"Upgrade status cannot be saved in this browser.",
|
|
4907
|
+
{ safeToFallback: false },
|
|
4908
|
+
this.id
|
|
4909
|
+
);
|
|
4910
|
+
let prior = null;
|
|
4911
|
+
try {
|
|
4912
|
+
const saved = storage5?.getItem(pendingKey2);
|
|
4403
4913
|
try {
|
|
4404
4914
|
prior = JSON.parse(saved ?? "null");
|
|
4405
4915
|
} catch {
|
|
4406
4916
|
prior = { callsId: void 0 };
|
|
4407
4917
|
}
|
|
4408
|
-
|
|
4918
|
+
} catch {
|
|
4919
|
+
if (gasMode === "paid" && before.ownerPath === "eoa") storage5 = void 0;
|
|
4920
|
+
else throw new OwneyError(
|
|
4921
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4922
|
+
"Upgrade status cannot be read in this browser.",
|
|
4923
|
+
{ safeToFallback: false },
|
|
4924
|
+
this.id
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
if (prior) {
|
|
4928
|
+
let earlierHash = this.isTransactionHash(prior.txHash) ? prior.txHash : void 0;
|
|
4929
|
+
if (!earlierHash) {
|
|
4409
4930
|
if (!prior.callsId)
|
|
4410
4931
|
throw new OwneyError(
|
|
4411
4932
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4415,10 +4936,7 @@ var YieldseekerAgent = class {
|
|
|
4415
4936
|
);
|
|
4416
4937
|
let callsStatus;
|
|
4417
4938
|
try {
|
|
4418
|
-
callsStatus = await state.provider.request({
|
|
4419
|
-
method: "wallet_getCallsStatus",
|
|
4420
|
-
params: [prior.callsId]
|
|
4421
|
-
});
|
|
4939
|
+
callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
|
|
4422
4940
|
} catch {
|
|
4423
4941
|
throw new OwneyError(
|
|
4424
4942
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4427,8 +4945,15 @@ var YieldseekerAgent = class {
|
|
|
4427
4945
|
this.id
|
|
4428
4946
|
);
|
|
4429
4947
|
}
|
|
4430
|
-
if (callsStatus?.
|
|
4431
|
-
|
|
4948
|
+
if (callsStatus?.id !== void 0 && callsStatus.id !== prior.callsId || callsStatus?.chainId !== void 0 && Number(callsStatus.chainId) !== chainId)
|
|
4949
|
+
throw new OwneyError(
|
|
4950
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4951
|
+
"The earlier upgrade returned a different operation status.",
|
|
4952
|
+
{ safeToFallback: false, callsId: prior.callsId },
|
|
4953
|
+
this.id
|
|
4954
|
+
);
|
|
4955
|
+
if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
|
|
4956
|
+
storage5.removeItem(pendingKey2);
|
|
4432
4957
|
throw new OwneyError(
|
|
4433
4958
|
"SPONSOR_REQUEST_FAILED",
|
|
4434
4959
|
"The earlier sponsored upgrade failed.",
|
|
@@ -4436,22 +4961,39 @@ var YieldseekerAgent = class {
|
|
|
4436
4961
|
this.id
|
|
4437
4962
|
);
|
|
4438
4963
|
}
|
|
4439
|
-
const
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4964
|
+
const hashes = [...new Set((callsStatus?.receipts ?? []).map((value) => value.transactionHash).filter(this.isTransactionHash))];
|
|
4965
|
+
earlierHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4966
|
+
}
|
|
4967
|
+
if (earlierHash) {
|
|
4968
|
+
const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
|
|
4969
|
+
clearConfirmedUpgrade();
|
|
4970
|
+
return recovered;
|
|
4971
|
+
}
|
|
4972
|
+
throw new OwneyError(
|
|
4973
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4974
|
+
"An earlier upgrade is still pending.",
|
|
4975
|
+
{ safeToFallback: false, callsId: prior.callsId },
|
|
4976
|
+
this.id
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4979
|
+
if (!before.upgradeAvailable)
|
|
4980
|
+
throw new OwneyError(
|
|
4981
|
+
"WITHDRAW_FAILED",
|
|
4982
|
+
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
4983
|
+
{ version: before.version },
|
|
4984
|
+
this.id
|
|
4985
|
+
);
|
|
4986
|
+
const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
|
|
4987
|
+
let txHash;
|
|
4988
|
+
let receipt;
|
|
4989
|
+
if (gasMode === "sponsored") {
|
|
4990
|
+
if (before.ownerPath !== "smart")
|
|
4448
4991
|
throw new OwneyError(
|
|
4449
4992
|
"SPONSOR_REQUEST_FAILED",
|
|
4450
|
-
"
|
|
4451
|
-
{ safeToFallback:
|
|
4993
|
+
"EOA owners pay Base gas for the V1 upgrade.",
|
|
4994
|
+
{ safeToFallback: true },
|
|
4452
4995
|
this.id
|
|
4453
4996
|
);
|
|
4454
|
-
}
|
|
4455
4997
|
const context = await this.findAgent(state, chainId, asset);
|
|
4456
4998
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
|
|
4457
4999
|
try {
|
|
@@ -4461,14 +5003,15 @@ var YieldseekerAgent = class {
|
|
|
4461
5003
|
apiKey: this.owneyApiKey,
|
|
4462
5004
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4463
5005
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5006
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4464
5007
|
userId: context.user.userId,
|
|
4465
5008
|
agentId: context.agent.agentId,
|
|
4466
5009
|
agentWallet: before.agentWallet,
|
|
4467
5010
|
asset: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
4468
5011
|
callData: data,
|
|
4469
5012
|
onPending: (pending) => {
|
|
4470
|
-
const current = storage5.getItem(
|
|
4471
|
-
storage5.setItem(
|
|
5013
|
+
const current = storage5.getItem(pendingKey2);
|
|
5014
|
+
storage5.setItem(pendingKey2, JSON.stringify({
|
|
4472
5015
|
startedAt: Date.now(),
|
|
4473
5016
|
...current ? JSON.parse(current) : {},
|
|
4474
5017
|
...pending
|
|
@@ -4476,8 +5019,9 @@ var YieldseekerAgent = class {
|
|
|
4476
5019
|
}
|
|
4477
5020
|
});
|
|
4478
5021
|
txHash = upgraded.txHash;
|
|
5022
|
+
receipt = upgraded.receipt;
|
|
4479
5023
|
} catch (error) {
|
|
4480
|
-
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(
|
|
5024
|
+
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
|
|
4481
5025
|
throw error;
|
|
4482
5026
|
}
|
|
4483
5027
|
} else {
|
|
@@ -4487,20 +5031,13 @@ var YieldseekerAgent = class {
|
|
|
4487
5031
|
data,
|
|
4488
5032
|
value: "0",
|
|
4489
5033
|
chainId
|
|
5034
|
+
}, void 0, (value) => {
|
|
5035
|
+
receipt = value;
|
|
4490
5036
|
});
|
|
4491
5037
|
}
|
|
4492
|
-
const
|
|
4493
|
-
if (
|
|
4494
|
-
|
|
4495
|
-
"WITHDRAW_FAILED",
|
|
4496
|
-
"Yieldseeker wallet upgrade was not confirmed as V2.",
|
|
4497
|
-
{ txHash, version: status?.version ?? "missing" },
|
|
4498
|
-
this.id
|
|
4499
|
-
);
|
|
4500
|
-
}
|
|
4501
|
-
if (gasMode === "sponsored" && typeof window !== "undefined")
|
|
4502
|
-
window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
|
|
4503
|
-
return { txHash, status };
|
|
5038
|
+
const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
|
|
5039
|
+
if (gasMode === "sponsored") clearConfirmedUpgrade();
|
|
5040
|
+
return confirmed;
|
|
4504
5041
|
}
|
|
4505
5042
|
async disconnect() {
|
|
4506
5043
|
this.withdrawalVersionCache.clear();
|
|
@@ -4573,30 +5110,48 @@ var YieldseekerAgent = class {
|
|
|
4573
5110
|
submitted(txHash);
|
|
4574
5111
|
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
4575
5112
|
} else {
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
5113
|
+
try {
|
|
5114
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
5115
|
+
from: getAddress4(state.walletAddress),
|
|
5116
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5117
|
+
data: encodeFunctionData({
|
|
5118
|
+
abi: erc20Abi3,
|
|
5119
|
+
functionName: "transfer",
|
|
5120
|
+
args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
|
|
5121
|
+
}),
|
|
5122
|
+
value: "0",
|
|
5123
|
+
chainId
|
|
5124
|
+
}, submitted, (value) => {
|
|
5125
|
+
receipt = value;
|
|
5126
|
+
});
|
|
5127
|
+
} catch (error) {
|
|
5128
|
+
if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
|
|
5129
|
+
notifyFunding(onProgress, {
|
|
5130
|
+
agentId: this.id,
|
|
5131
|
+
chainId,
|
|
5132
|
+
token: asset,
|
|
5133
|
+
walletAddress: state.walletAddress,
|
|
5134
|
+
agentWallet: context.wallet.walletAddress,
|
|
5135
|
+
intendedAmount: amount,
|
|
5136
|
+
txHash: String(error.details.originalTransactionHash),
|
|
5137
|
+
transfer: "failed",
|
|
5138
|
+
registration: "not-applicable",
|
|
5139
|
+
delivery: "not-applicable"
|
|
5140
|
+
});
|
|
5141
|
+
}
|
|
5142
|
+
throw error;
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
if (this.readGeneration === generation) {
|
|
5146
|
+
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
5147
|
+
}
|
|
5148
|
+
const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, confirmedAmount: amount, txHash, transfer: "confirmed", registration: "not-applicable", delivery: "not-applicable" };
|
|
5149
|
+
const evidence = receipt ? inspectFundingReceipt(receipt, base6, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base6;
|
|
5150
|
+
notifyFunding(onProgress, evidence);
|
|
5151
|
+
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
5152
|
+
}
|
|
5153
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
|
|
5154
|
+
let ownerTransferAttempted = false;
|
|
4600
5155
|
let evidence = {
|
|
4601
5156
|
agentId: this.id,
|
|
4602
5157
|
chainId,
|
|
@@ -4613,10 +5168,10 @@ var YieldseekerAgent = class {
|
|
|
4613
5168
|
notifyFunding(onProgress, evidence);
|
|
4614
5169
|
}, () => {
|
|
4615
5170
|
ownerTransferAttempted = true;
|
|
4616
|
-
}, gasMode);
|
|
5171
|
+
}, gasMode, onBeforeSubmission);
|
|
4617
5172
|
} catch (error) {
|
|
4618
5173
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
|
|
4619
|
-
if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
|
|
5174
|
+
if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.details?.ownerTransferNotExecuted === true || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
|
|
4620
5175
|
notifyFunding(onProgress, {
|
|
4621
5176
|
...evidence,
|
|
4622
5177
|
...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
|
|
@@ -4626,7 +5181,7 @@ var YieldseekerAgent = class {
|
|
|
4626
5181
|
throw error;
|
|
4627
5182
|
}
|
|
4628
5183
|
}
|
|
4629
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
|
|
5184
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
|
|
4630
5185
|
this.assertChain(chainId);
|
|
4631
5186
|
this.assertAsset(asset);
|
|
4632
5187
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -4671,37 +5226,140 @@ var YieldseekerAgent = class {
|
|
|
4671
5226
|
let confirmedMovement = false;
|
|
4672
5227
|
let settlement = null;
|
|
4673
5228
|
try {
|
|
4674
|
-
|
|
4675
|
-
const
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
5229
|
+
const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
|
|
5230
|
+
const contextKey = this.contextKey(state, chainId, asset);
|
|
5231
|
+
const priorPositions = this.withdrawalVaultHints(state, chainId, context);
|
|
5232
|
+
this.advancePortfolioVersion(contextKey);
|
|
5233
|
+
const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5234
|
+
let timer;
|
|
5235
|
+
try {
|
|
5236
|
+
return await Promise.race([read2, new Promise((resolve) => {
|
|
5237
|
+
timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
|
|
5238
|
+
})]);
|
|
5239
|
+
} finally {
|
|
5240
|
+
if (timer) clearTimeout(timer);
|
|
5241
|
+
}
|
|
5242
|
+
};
|
|
5243
|
+
const feeRateRead = amount === void 0 ? Promise.resolve(0) : boundedVerification(
|
|
5244
|
+
(() => {
|
|
5245
|
+
try {
|
|
5246
|
+
return this.portfolioSnapshot(state, chainId, context, contextKey).read.then((value) => value.agentSnapshot.feeRateBps);
|
|
5247
|
+
} catch (error) {
|
|
5248
|
+
return Promise.reject(error);
|
|
5249
|
+
}
|
|
5250
|
+
})().catch(() => void 0)
|
|
4679
5251
|
);
|
|
5252
|
+
const positionsRead = this.readWithdrawalPositions(state, chainId, context);
|
|
4680
5253
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
4681
|
-
const
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
5254
|
+
const agentWallet = getAddress4(context.wallet.walletAddress);
|
|
5255
|
+
const readOwnerFunds = async (blockNumber) => {
|
|
5256
|
+
const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5257
|
+
address: agentWallet,
|
|
5258
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5259
|
+
functionName: "feeTracker",
|
|
5260
|
+
blockNumber
|
|
4687
5261
|
}), chainId, "eth_call", this.id);
|
|
4688
5262
|
this.assertSession(generation, chainId, asset);
|
|
4689
|
-
|
|
5263
|
+
if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
|
|
5264
|
+
throw this.invalidResponse("wallet fee tracker");
|
|
5265
|
+
const [idle2, fees] = await Promise.all([
|
|
5266
|
+
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5267
|
+
address: metadata.address,
|
|
5268
|
+
abi: erc20Abi3,
|
|
5269
|
+
functionName: "balanceOf",
|
|
5270
|
+
args: [agentWallet],
|
|
5271
|
+
blockNumber
|
|
5272
|
+
}), chainId, "eth_call", this.id),
|
|
5273
|
+
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5274
|
+
address: getAddress4(feeTracker),
|
|
5275
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5276
|
+
functionName: "getFeesOwed",
|
|
5277
|
+
args: [agentWallet],
|
|
5278
|
+
blockNumber
|
|
5279
|
+
}), chainId, "eth_call", this.id)
|
|
5280
|
+
]);
|
|
5281
|
+
this.assertSession(generation, chainId, asset);
|
|
5282
|
+
if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
|
|
5283
|
+
throw this.invalidResponse("wallet withdrawal balance");
|
|
5284
|
+
return { blockNumber, idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
|
|
4690
5285
|
};
|
|
4691
|
-
const
|
|
4692
|
-
|
|
5286
|
+
const [funds, feeRateBps] = await Promise.all([
|
|
5287
|
+
positionsRead.then((positions2) => this.resolveWithdrawalLiquidity(
|
|
5288
|
+
state,
|
|
5289
|
+
chainId,
|
|
5290
|
+
context,
|
|
5291
|
+
positions2,
|
|
5292
|
+
priorPositions,
|
|
5293
|
+
amount === void 0 ? void 0 : BigInt(amount)
|
|
5294
|
+
)),
|
|
5295
|
+
feeRateRead
|
|
5296
|
+
]);
|
|
5297
|
+
const initialFunds = funds;
|
|
5298
|
+
const idle = initialFunds.idle;
|
|
5299
|
+
const maxLiquidity = amount === void 0 ? funds : void 0;
|
|
5300
|
+
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()))];
|
|
4693
5301
|
let positionsStatus = "empty";
|
|
4694
|
-
const validatePositions = (values) =>
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
5302
|
+
const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
|
|
5303
|
+
validatePositions(positions);
|
|
5304
|
+
const zeroValuePositions = /* @__PURE__ */ new WeakSet();
|
|
5305
|
+
const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
|
|
5306
|
+
const knownPositions = /* @__PURE__ */ new Map();
|
|
5307
|
+
const rememberPositions = (values) => {
|
|
5308
|
+
for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
|
|
5309
|
+
};
|
|
5310
|
+
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5311
|
+
const verifiedPositions = [];
|
|
5312
|
+
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
5313
|
+
let active = true;
|
|
5314
|
+
const checks = this.readVaultLiquidity(values.map((position2) => position2.yieldOption), agentWallet, blockNumber).catch(() => []);
|
|
5315
|
+
const reads = values.map(async (position2, index) => {
|
|
5316
|
+
const address = getAddress4(position2.yieldOption.address);
|
|
5317
|
+
try {
|
|
5318
|
+
const group = (await checks).slice(index * 3, index * 3 + 3);
|
|
5319
|
+
if (!active || group.length !== 3 || group.some((value) => value.status !== "success")) return null;
|
|
5320
|
+
this.assertSession(generation, chainId, asset);
|
|
5321
|
+
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
5322
|
+
if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
|
|
5323
|
+
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
|
|
5324
|
+
if (shares > 0n && withdrawable === 0n) {
|
|
5325
|
+
let positiveValue = false;
|
|
5326
|
+
const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
|
|
5327
|
+
try {
|
|
5328
|
+
const value = await this.getReceiptClient().readContract({
|
|
5329
|
+
address,
|
|
5330
|
+
abi: erc4626Abi,
|
|
5331
|
+
functionName,
|
|
5332
|
+
args: [shares],
|
|
5333
|
+
blockNumber
|
|
5334
|
+
});
|
|
5335
|
+
if (typeof value !== "bigint" || value < 0n) return null;
|
|
5336
|
+
if (value > 0n && !positiveValue) {
|
|
5337
|
+
positiveValue = true;
|
|
5338
|
+
verifiedPositions.push(verified);
|
|
5339
|
+
}
|
|
5340
|
+
return value;
|
|
5341
|
+
} catch {
|
|
5342
|
+
return null;
|
|
5343
|
+
}
|
|
5344
|
+
}));
|
|
5345
|
+
if (positiveValue) return verified;
|
|
5346
|
+
if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
|
|
5347
|
+
zeroValuePositions.add(verified);
|
|
5348
|
+
}
|
|
5349
|
+
verifiedPositions.push(verified);
|
|
5350
|
+
return verified;
|
|
5351
|
+
} catch {
|
|
5352
|
+
return null;
|
|
5353
|
+
}
|
|
5354
|
+
});
|
|
5355
|
+
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5356
|
+
active = false;
|
|
5357
|
+
this.assertSession(generation, chainId, asset);
|
|
5358
|
+
return {
|
|
5359
|
+
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
5360
|
+
positions: [...verifiedPositions]
|
|
5361
|
+
};
|
|
4703
5362
|
};
|
|
4704
|
-
const hasPositions = (values) => values.some((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
4705
5363
|
const refreshPositions = async () => {
|
|
4706
5364
|
this.assertSession(generation, chainId, asset);
|
|
4707
5365
|
const response = await this.walletRequest(
|
|
@@ -4714,22 +5372,41 @@ var YieldseekerAgent = class {
|
|
|
4714
5372
|
return validatePositions(response.yieldPositions);
|
|
4715
5373
|
};
|
|
4716
5374
|
if (amount === void 0) {
|
|
4717
|
-
|
|
5375
|
+
rememberPositions(funds.indexedPositions);
|
|
5376
|
+
rememberPositions(positions);
|
|
4718
5377
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4719
5378
|
}
|
|
4720
5379
|
const deployed = positions.reduce(
|
|
4721
5380
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
4722
5381
|
0n
|
|
4723
5382
|
);
|
|
4724
|
-
const
|
|
5383
|
+
const grossAvailable = idle + deployed;
|
|
5384
|
+
const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
|
|
4725
5385
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4726
5386
|
if (requested === 0n) {
|
|
4727
5387
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4728
|
-
|
|
5388
|
+
const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5389
|
+
if (amount === void 0 && zeroPayoutCandidates.length) {
|
|
5390
|
+
const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5391
|
+
const blockNumber = initialFunds.blockNumber ?? await boundedVerification(
|
|
5392
|
+
Promise.resolve().then(() => this.getReceiptClient().getBlockNumber({ cacheTime: 0 })).catch(() => void 0)
|
|
5393
|
+
);
|
|
5394
|
+
const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
|
|
5395
|
+
positions = proof.positions;
|
|
5396
|
+
positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
|
|
5397
|
+
if (positionsStatus !== "empty")
|
|
5398
|
+
throw new OwneyError(
|
|
5399
|
+
"WITHDRAW_FAILED",
|
|
5400
|
+
"Yieldseeker vault funds are not currently withdrawable.",
|
|
5401
|
+
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
|
|
5402
|
+
this.id
|
|
5403
|
+
);
|
|
5404
|
+
}
|
|
5405
|
+
if (grossAvailable > 0n && totalAvailable === 0n)
|
|
4729
5406
|
throw new OwneyError(
|
|
4730
|
-
"
|
|
4731
|
-
"Yieldseeker
|
|
4732
|
-
{ asset,
|
|
5407
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
5408
|
+
"No owner-withdrawable Yieldseeker funds remain after reserved fees.",
|
|
5409
|
+
{ asset, available: "0", reason: "YIELDSEEKER_FEES_ONLY", feesReserved: initialFunds.fees.toString() },
|
|
4733
5410
|
this.id
|
|
4734
5411
|
);
|
|
4735
5412
|
throw new OwneyError(
|
|
@@ -4751,28 +5428,50 @@ var YieldseekerAgent = class {
|
|
|
4751
5428
|
this.id
|
|
4752
5429
|
);
|
|
4753
5430
|
}
|
|
4754
|
-
|
|
5431
|
+
const requiredIdle = requested + initialFunds.fees;
|
|
5432
|
+
let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
|
|
4755
5433
|
let grossUnwound = 0n;
|
|
5434
|
+
let latestUnwindBlock;
|
|
4756
5435
|
const unwind = async (position2, assetsRaw) => {
|
|
4757
5436
|
this.assertSession(generation, chainId, asset);
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
5437
|
+
let unwindTransactionHash;
|
|
5438
|
+
try {
|
|
5439
|
+
const response = await this.walletRequest(
|
|
5440
|
+
state,
|
|
5441
|
+
chainId,
|
|
5442
|
+
this.agentPath(context, "withdraw-from-position"),
|
|
5443
|
+
{
|
|
5444
|
+
method: "POST",
|
|
5445
|
+
body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
|
|
5446
|
+
}
|
|
5447
|
+
);
|
|
5448
|
+
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
5449
|
+
unwindTransactionHash = response.transactionHash;
|
|
5450
|
+
const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
|
|
5451
|
+
if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
|
|
5452
|
+
} catch (error) {
|
|
5453
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
5454
|
+
throw new OwneyError(
|
|
5455
|
+
error instanceof OwneyError ? error.code : "WITHDRAW_FAILED",
|
|
5456
|
+
"Could not confirm the Yieldseeker vault release. No owner transfer was submitted.",
|
|
5457
|
+
{
|
|
5458
|
+
...error instanceof OwneyError ? error.details : {},
|
|
5459
|
+
withdrawalPhase: "unwind",
|
|
5460
|
+
reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
|
|
5461
|
+
vaultAddress: position2.yieldOption.address,
|
|
5462
|
+
unwindTransactionHash,
|
|
5463
|
+
ownerTransferSubmitted: false
|
|
5464
|
+
},
|
|
5465
|
+
this.id
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
4769
5468
|
confirmedMovement = true;
|
|
4770
5469
|
this.assertSession(generation, chainId, asset);
|
|
4771
5470
|
grossUnwound += assetsRaw;
|
|
4772
5471
|
};
|
|
4773
5472
|
if (amount === void 0) {
|
|
4774
5473
|
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
4775
|
-
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.
|
|
5474
|
+
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
4776
5475
|
let unwound = false;
|
|
4777
5476
|
for (const position2 of positions) {
|
|
4778
5477
|
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
@@ -4781,15 +5480,22 @@ var YieldseekerAgent = class {
|
|
|
4781
5480
|
unwound = true;
|
|
4782
5481
|
}
|
|
4783
5482
|
if (!unwound) break;
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
5483
|
+
const knownBeforeRefresh = new Set(knownPositions.keys());
|
|
5484
|
+
const [catalog, proof] = await Promise.all([
|
|
5485
|
+
boundedVerification(refreshPositions().catch(() => void 0)),
|
|
5486
|
+
verifyKnownPositions(latestUnwindBlock)
|
|
5487
|
+
]);
|
|
5488
|
+
if (catalog) rememberPositions(catalog);
|
|
5489
|
+
this.assertSession(generation, chainId, asset);
|
|
5490
|
+
if (proof.verified) {
|
|
5491
|
+
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
|
|
5492
|
+
} else if (catalog) positions = catalog;
|
|
5493
|
+
else {
|
|
4789
5494
|
positionsStatus = "unavailable";
|
|
4790
5495
|
break;
|
|
4791
5496
|
}
|
|
4792
|
-
|
|
5497
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5498
|
+
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
|
|
4793
5499
|
}
|
|
4794
5500
|
} else {
|
|
4795
5501
|
for (const position2 of positions) {
|
|
@@ -4807,8 +5513,9 @@ var YieldseekerAgent = class {
|
|
|
4807
5513
|
remaining: remaining.toString()
|
|
4808
5514
|
});
|
|
4809
5515
|
}
|
|
4810
|
-
const
|
|
4811
|
-
const
|
|
5516
|
+
const ownerFunds = confirmedMovement ? await readOwnerFunds(latestUnwindBlock) : initialFunds;
|
|
5517
|
+
const availableForOwner = ownerFunds.available;
|
|
5518
|
+
const feeRate = feeRateBps ?? 0;
|
|
4812
5519
|
const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
|
|
4813
5520
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
4814
5521
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
@@ -4828,7 +5535,11 @@ var YieldseekerAgent = class {
|
|
|
4828
5535
|
);
|
|
4829
5536
|
}
|
|
4830
5537
|
const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
|
|
4831
|
-
const plannedMovement =
|
|
5538
|
+
const plannedMovement = amount === void 0 && maxSettlementBasis ? {
|
|
5539
|
+
...maxSettlementBasis,
|
|
5540
|
+
delta: maxSettlementBasis.delta - payout,
|
|
5541
|
+
expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
|
|
5542
|
+
} : this.snapshotMovement(state, chainId, context, -payout);
|
|
4832
5543
|
const account = getAddress4(state.walletAddress);
|
|
4833
5544
|
let receipt;
|
|
4834
5545
|
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" };
|
|
@@ -4841,7 +5552,6 @@ var YieldseekerAgent = class {
|
|
|
4841
5552
|
functionName: "withdrawAssetToUser",
|
|
4842
5553
|
args: [account, metadata.address, payout]
|
|
4843
5554
|
});
|
|
4844
|
-
onOwnerTransferAttempted?.();
|
|
4845
5555
|
let txHash;
|
|
4846
5556
|
if (sponsor) {
|
|
4847
5557
|
const submitted = await submitYieldseekerSponsoredWithdrawal({
|
|
@@ -4850,16 +5560,22 @@ var YieldseekerAgent = class {
|
|
|
4850
5560
|
apiKey: this.owneyApiKey,
|
|
4851
5561
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4852
5562
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5563
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4853
5564
|
userId: context.user.userId,
|
|
4854
5565
|
agentId: context.agent.agentId,
|
|
4855
5566
|
agentWallet: getAddress4(context.wallet.walletAddress),
|
|
4856
5567
|
asset: metadata.address,
|
|
4857
5568
|
callData: data,
|
|
4858
|
-
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending })
|
|
5569
|
+
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
|
|
5570
|
+
onBeforeSubmission: async (pending) => {
|
|
5571
|
+
await onBeforeSubmission?.({ ...base6, ...pending });
|
|
5572
|
+
onOwnerTransferAttempted?.();
|
|
5573
|
+
}
|
|
4859
5574
|
});
|
|
4860
5575
|
txHash = submitted.txHash;
|
|
4861
5576
|
receipt = submitted.receipt;
|
|
4862
5577
|
} else {
|
|
5578
|
+
onOwnerTransferAttempted?.();
|
|
4863
5579
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4864
5580
|
from: account,
|
|
4865
5581
|
to: getAddress4(context.wallet.walletAddress),
|
|
@@ -4874,14 +5590,26 @@ var YieldseekerAgent = class {
|
|
|
4874
5590
|
settlement = plannedMovement;
|
|
4875
5591
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4876
5592
|
notifyFunding(onProgress, evidence);
|
|
5593
|
+
let completion = maxLiquidity && !maxLiquidity.complete ? "unverified" : void 0;
|
|
4877
5594
|
if (amount === void 0) {
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
5595
|
+
const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5596
|
+
const knownAtPayout = new Set(knownPositions.keys());
|
|
5597
|
+
const [catalogVerified, knownProof] = await Promise.all([
|
|
5598
|
+
boundedVerification(refreshPositions().then((values) => {
|
|
5599
|
+
rememberPositions(values);
|
|
5600
|
+
return true;
|
|
5601
|
+
}).catch(() => false)),
|
|
5602
|
+
verifyKnownPositions(receipt?.blockNumber)
|
|
5603
|
+
]);
|
|
5604
|
+
const newPositions = [...knownPositions].filter(([address]) => !knownAtPayout.has(address)).map(([, position2]) => position2);
|
|
5605
|
+
const newProof = newPositions.length ? await verifyKnownPositions(receipt?.blockNumber, newPositions, verificationDeadline - Date.now()) : { verified: true, positions: [] };
|
|
5606
|
+
const proof = {
|
|
5607
|
+
verified: catalogVerified === true && knownProof.verified && newProof.verified,
|
|
5608
|
+
positions: [...knownProof.positions, ...newProof.positions]
|
|
5609
|
+
};
|
|
5610
|
+
positions = proof.positions;
|
|
5611
|
+
positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
|
|
5612
|
+
if (positionsStatus === "remaining") {
|
|
4885
5613
|
const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
4886
5614
|
throw new OwneyError(
|
|
4887
5615
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
@@ -4890,7 +5618,7 @@ var YieldseekerAgent = class {
|
|
|
4890
5618
|
reason: "YIELDSEEKER_MAX_INCOMPLETE",
|
|
4891
5619
|
asset,
|
|
4892
5620
|
positionsStatus,
|
|
4893
|
-
remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
|
|
5621
|
+
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
4894
5622
|
vaultAddress: position2.yieldOption.address,
|
|
4895
5623
|
sharesRaw: position2.assetsRaw,
|
|
4896
5624
|
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
@@ -4900,6 +5628,7 @@ var YieldseekerAgent = class {
|
|
|
4900
5628
|
this.id
|
|
4901
5629
|
);
|
|
4902
5630
|
}
|
|
5631
|
+
if (!proof.verified) completion = "unverified";
|
|
4903
5632
|
}
|
|
4904
5633
|
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4905
5634
|
throw new OwneyError(
|
|
@@ -4908,7 +5637,17 @@ var YieldseekerAgent = class {
|
|
|
4908
5637
|
{ asset, available: "0", txHash },
|
|
4909
5638
|
this.id
|
|
4910
5639
|
);
|
|
4911
|
-
|
|
5640
|
+
const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
|
|
5641
|
+
const feeIncrease = ownerFunds.fees - initialFunds.fees;
|
|
5642
|
+
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;
|
|
5643
|
+
return {
|
|
5644
|
+
txHash,
|
|
5645
|
+
type: amount === void 0 && !completion ? "full" : "partial",
|
|
5646
|
+
...completion ? { completion } : {},
|
|
5647
|
+
...feeAdjustment ? { feeAdjustment } : {},
|
|
5648
|
+
amount: evidence.confirmedAmount ?? payout.toString(),
|
|
5649
|
+
evidence
|
|
5650
|
+
};
|
|
4912
5651
|
} finally {
|
|
4913
5652
|
if (confirmedMovement && this.readGeneration === generation) {
|
|
4914
5653
|
void this.refreshSnapshotAfterMovement(
|
|
@@ -4921,6 +5660,236 @@ var YieldseekerAgent = class {
|
|
|
4921
5660
|
}
|
|
4922
5661
|
}
|
|
4923
5662
|
}
|
|
5663
|
+
/** A withdrawal-only liquidity floor; display snapshots never authorize it. */
|
|
5664
|
+
async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
|
|
5665
|
+
this.assertChain(chainId);
|
|
5666
|
+
this.assertAsset(asset);
|
|
5667
|
+
const generation = this.readGeneration;
|
|
5668
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
5669
|
+
this.assertSession(generation, chainId, asset);
|
|
5670
|
+
if (!context) throw new OwneyError(
|
|
5671
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5672
|
+
"The Yieldseeker withdrawal wallet could not be verified.",
|
|
5673
|
+
{ asset },
|
|
5674
|
+
this.id
|
|
5675
|
+
);
|
|
5676
|
+
const hints = this.withdrawalVaultHints(state, chainId, context);
|
|
5677
|
+
const positions = await this.readWithdrawalPositions(state, chainId, context);
|
|
5678
|
+
const proof = await this.resolveWithdrawalLiquidity(state, chainId, context, positions, hints, minimumAmount);
|
|
5679
|
+
this.assertSession(generation, chainId, asset);
|
|
5680
|
+
if (proof.available === 0n && !proof.complete) throw new OwneyError(
|
|
5681
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5682
|
+
"Yieldseeker withdrawal liquidity could not be verified yet.",
|
|
5683
|
+
{ asset },
|
|
5684
|
+
this.id
|
|
5685
|
+
);
|
|
5686
|
+
return proof.available;
|
|
5687
|
+
}
|
|
5688
|
+
async readVaultLiquidity(options, wallet, blockNumber) {
|
|
5689
|
+
const owner = getAddress4(wallet);
|
|
5690
|
+
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));
|
|
5691
|
+
const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
|
|
5692
|
+
blockNumber,
|
|
5693
|
+
allowFailure: true,
|
|
5694
|
+
contracts: batch.flatMap((option) => {
|
|
5695
|
+
const address = getAddress4(option.address);
|
|
5696
|
+
return [
|
|
5697
|
+
{ address, abi: erc4626Abi, functionName: "asset" },
|
|
5698
|
+
{ address, abi: erc20Abi3, functionName: "balanceOf", args: [owner] },
|
|
5699
|
+
{ address, abi: erc4626Abi, functionName: "maxWithdraw", args: [owner] }
|
|
5700
|
+
];
|
|
5701
|
+
})
|
|
5702
|
+
})));
|
|
5703
|
+
return results.flat();
|
|
5704
|
+
}
|
|
5705
|
+
validateWithdrawalPositions(values, chainId, asset) {
|
|
5706
|
+
const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
|
|
5707
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5708
|
+
for (const value of values) {
|
|
5709
|
+
const option = value?.yieldOption;
|
|
5710
|
+
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))
|
|
5711
|
+
throw this.invalidResponse("yield positions");
|
|
5712
|
+
seen.add(option.address.toLowerCase());
|
|
5713
|
+
}
|
|
5714
|
+
return values;
|
|
5715
|
+
}
|
|
5716
|
+
withdrawalVaultHints(state, chainId, context) {
|
|
5717
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
5718
|
+
const positions = this.readCache.get(`positions:${key2}`)?.value?.yieldPositions ?? [];
|
|
5719
|
+
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
|
|
5720
|
+
const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
|
|
5721
|
+
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()));
|
|
5722
|
+
for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
|
|
5723
|
+
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;
|
|
5724
|
+
hints.set(option.address.toLowerCase(), {
|
|
5725
|
+
yieldOption: option,
|
|
5726
|
+
assetsRaw: "0",
|
|
5727
|
+
assetsBase: "0",
|
|
5728
|
+
assetsUsd: 0,
|
|
5729
|
+
withdrawableAssetsRaw: "0",
|
|
5730
|
+
withdrawableAssetsBase: "0"
|
|
5731
|
+
});
|
|
5732
|
+
}
|
|
5733
|
+
return [...hints.values()];
|
|
5734
|
+
}
|
|
5735
|
+
async readWithdrawalPositions(state, chainId, context) {
|
|
5736
|
+
const generation = this.readGeneration;
|
|
5737
|
+
let timer;
|
|
5738
|
+
try {
|
|
5739
|
+
const positions = await Promise.race([
|
|
5740
|
+
this.loadActivityContext(state, chainId, context, {}).then((value) => value.positions).catch((error) => {
|
|
5741
|
+
if (!(error instanceof OwneyError) || !["AGENT_API_ERROR", "AGENT_TIMEOUT"].includes(error.code)) throw error;
|
|
5742
|
+
return void 0;
|
|
5743
|
+
}),
|
|
5744
|
+
new Promise((resolve) => {
|
|
5745
|
+
timer = setTimeout(() => resolve(void 0), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
|
|
5746
|
+
})
|
|
5747
|
+
]);
|
|
5748
|
+
this.assertSession(generation, chainId, context.asset);
|
|
5749
|
+
return positions === void 0 ? void 0 : this.validateWithdrawalPositions(positions, chainId, context.asset);
|
|
5750
|
+
} finally {
|
|
5751
|
+
if (timer) clearTimeout(timer);
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
|
|
5755
|
+
const generation = this.readGeneration;
|
|
5756
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
5757
|
+
const version = this.portfolioVersion(contextKey);
|
|
5758
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
5759
|
+
const unavailable = () => new OwneyError(
|
|
5760
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5761
|
+
"Yieldseeker withdrawal liquidity could not be verified yet.",
|
|
5762
|
+
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
5763
|
+
this.id
|
|
5764
|
+
);
|
|
5765
|
+
let active = true;
|
|
5766
|
+
let knownProof;
|
|
5767
|
+
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5768
|
+
const assertReadCurrent = () => {
|
|
5769
|
+
this.assertSession(generation, chainId, context.asset);
|
|
5770
|
+
if (!active || this.portfolioVersion(contextKey) !== version) throw unavailable();
|
|
5771
|
+
};
|
|
5772
|
+
const read2 = async () => {
|
|
5773
|
+
const client = this.getReceiptClient();
|
|
5774
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
5775
|
+
const wallet = getAddress4(context.wallet.walletAddress);
|
|
5776
|
+
assertReadCurrent();
|
|
5777
|
+
if (typeof blockNumber !== "bigint") throw unavailable();
|
|
5778
|
+
const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
|
|
5779
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
5780
|
+
for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
|
|
5781
|
+
candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
|
|
5782
|
+
for (const position2 of indexedPositions) candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
|
|
5783
|
+
const options = [...candidates.values()];
|
|
5784
|
+
if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
5785
|
+
const [checks, idle, feeTracker] = await Promise.all([
|
|
5786
|
+
this.readVaultLiquidity(options, wallet, blockNumber),
|
|
5787
|
+
client.readContract({
|
|
5788
|
+
address: metadata.address,
|
|
5789
|
+
abi: erc20Abi3,
|
|
5790
|
+
functionName: "balanceOf",
|
|
5791
|
+
args: [wallet],
|
|
5792
|
+
blockNumber
|
|
5793
|
+
}),
|
|
5794
|
+
client.readContract({
|
|
5795
|
+
address: wallet,
|
|
5796
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5797
|
+
functionName: "feeTracker",
|
|
5798
|
+
blockNumber
|
|
5799
|
+
})
|
|
5800
|
+
]);
|
|
5801
|
+
if (typeof idle !== "bigint" || idle < 0n || !isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
|
|
5802
|
+
assertReadCurrent();
|
|
5803
|
+
const fees = await client.readContract({
|
|
5804
|
+
address: getAddress4(feeTracker),
|
|
5805
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5806
|
+
functionName: "getFeesOwed",
|
|
5807
|
+
args: [wallet],
|
|
5808
|
+
blockNumber
|
|
5809
|
+
});
|
|
5810
|
+
if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
|
|
5811
|
+
const verified = [];
|
|
5812
|
+
const verifiedVaults = [];
|
|
5813
|
+
let deployed = 0n;
|
|
5814
|
+
const collect = (options2, results) => {
|
|
5815
|
+
let valid2 = true;
|
|
5816
|
+
options2.forEach((option, index) => {
|
|
5817
|
+
const group = results.slice(index * 3, index * 3 + 3);
|
|
5818
|
+
if (group.length !== 3 || group.some((value) => value.status !== "success")) {
|
|
5819
|
+
valid2 = false;
|
|
5820
|
+
return;
|
|
5821
|
+
}
|
|
5822
|
+
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
5823
|
+
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) {
|
|
5824
|
+
valid2 = false;
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
verifiedVaults.push(option.address.toLowerCase());
|
|
5828
|
+
deployed += withdrawable;
|
|
5829
|
+
verified.push({
|
|
5830
|
+
yieldOption: option,
|
|
5831
|
+
assetsRaw: shares.toString(),
|
|
5832
|
+
assetsBase: "0",
|
|
5833
|
+
assetsUsd: 0,
|
|
5834
|
+
withdrawableAssetsRaw: withdrawable.toString(),
|
|
5835
|
+
withdrawableAssetsBase: withdrawable.toString()
|
|
5836
|
+
});
|
|
5837
|
+
});
|
|
5838
|
+
return valid2;
|
|
5839
|
+
};
|
|
5840
|
+
const allKnownVerified = collect(options, checks);
|
|
5841
|
+
const proof = (complete) => ({
|
|
5842
|
+
blockNumber,
|
|
5843
|
+
idle,
|
|
5844
|
+
fees,
|
|
5845
|
+
available: idle + deployed > fees ? idle + deployed - fees : 0n,
|
|
5846
|
+
positions: [...verified],
|
|
5847
|
+
verifiedVaults: [...verifiedVaults],
|
|
5848
|
+
indexedPositions,
|
|
5849
|
+
complete
|
|
5850
|
+
});
|
|
5851
|
+
knownProof = proof(false);
|
|
5852
|
+
const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
|
|
5853
|
+
const discrepancy = verified.some((position2) => {
|
|
5854
|
+
const row = indexed.get(position2.yieldOption.address.toLowerCase());
|
|
5855
|
+
return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
|
|
5856
|
+
});
|
|
5857
|
+
const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
|
|
5858
|
+
const expand = positions === void 0 || indexedPositions.length === 0 && !typedCoveredByIdle || !allKnownVerified || discrepancy || knownProof.available === 0n || requested !== void 0 && requested > knownProof.available;
|
|
5859
|
+
if (!expand) {
|
|
5860
|
+
assertReadCurrent();
|
|
5861
|
+
return proof(true);
|
|
5862
|
+
}
|
|
5863
|
+
const catalog = await this.loadYieldOptions(context.asset).catch(() => void 0);
|
|
5864
|
+
assertReadCurrent();
|
|
5865
|
+
const eligible = /* @__PURE__ */ new Map();
|
|
5866
|
+
for (const option of catalog ?? []) {
|
|
5867
|
+
if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && isAddress3(option.address))
|
|
5868
|
+
eligible.set(option.address.toLowerCase(), option);
|
|
5869
|
+
}
|
|
5870
|
+
const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
|
|
5871
|
+
if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
5872
|
+
const allExtraVerified = collect(extra, await this.readVaultLiquidity(extra, wallet, blockNumber));
|
|
5873
|
+
assertReadCurrent();
|
|
5874
|
+
const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
|
|
5875
|
+
if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
|
|
5876
|
+
return result;
|
|
5877
|
+
};
|
|
5878
|
+
let timer;
|
|
5879
|
+
try {
|
|
5880
|
+
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
5881
|
+
timer = setTimeout(() => reject(unavailable()), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
|
|
5882
|
+
})]);
|
|
5883
|
+
} catch (error) {
|
|
5884
|
+
assertReadCurrent();
|
|
5885
|
+
if (knownProof && (knownProof.available > 0n || hasUnverifiedIndexedFunds(knownProof)) && (!(error instanceof OwneyError) || error.code === "WITHDRAW_BALANCE_UNAVAILABLE")) return knownProof;
|
|
5886
|
+
if (error instanceof OwneyError) throw error;
|
|
5887
|
+
throw unavailable();
|
|
5888
|
+
} finally {
|
|
5889
|
+
active = false;
|
|
5890
|
+
if (timer) clearTimeout(timer);
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
4924
5893
|
async getBalances(state, chainId, asset) {
|
|
4925
5894
|
this.assertChain(chainId);
|
|
4926
5895
|
if (asset !== void 0) this.assertAsset(asset);
|
|
@@ -4955,6 +5924,23 @@ var YieldseekerAgent = class {
|
|
|
4955
5924
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
|
|
4956
5925
|
return context.wallet.walletAddress;
|
|
4957
5926
|
}
|
|
5927
|
+
async hasExistingDepositFunds(state, chainId, asset) {
|
|
5928
|
+
this.assertChain(chainId);
|
|
5929
|
+
this.assertAsset(asset);
|
|
5930
|
+
const generation = this.readGeneration;
|
|
5931
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
5932
|
+
this.assertSession(generation, chainId, asset);
|
|
5933
|
+
if (!context) return void 0;
|
|
5934
|
+
const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5935
|
+
address: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5936
|
+
abi: erc20Abi3,
|
|
5937
|
+
functionName: "balanceOf",
|
|
5938
|
+
args: [getAddress4(context.wallet.walletAddress)]
|
|
5939
|
+
}), chainId, "eth_call", this.id);
|
|
5940
|
+
this.assertSession(generation, chainId, asset);
|
|
5941
|
+
if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
|
|
5942
|
+
return idle > 0n ? true : void 0;
|
|
5943
|
+
}
|
|
4958
5944
|
async getEarnings(state, chainId) {
|
|
4959
5945
|
this.assertChain(chainId);
|
|
4960
5946
|
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
@@ -4985,7 +5971,7 @@ var YieldseekerAgent = class {
|
|
|
4985
5971
|
);
|
|
4986
5972
|
const vaultAddresses = new Set(
|
|
4987
5973
|
catalog.flat().filter(
|
|
4988
|
-
(yieldOption) => yieldOption.chainId === chainId &&
|
|
5974
|
+
(yieldOption) => yieldOption.chainId === chainId && isAddress3(yieldOption.address)
|
|
4989
5975
|
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
4990
5976
|
);
|
|
4991
5977
|
return mapYieldseekerHistory(contexts, {
|
|
@@ -5112,7 +6098,7 @@ var YieldseekerAgent = class {
|
|
|
5112
6098
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
5113
6099
|
).then((walletResponse) => {
|
|
5114
6100
|
this.assertSession(generation, chainId, asset);
|
|
5115
|
-
if (!walletResponse?.agentWallet || !
|
|
6101
|
+
if (!walletResponse?.agentWallet || !isAddress3(walletResponse.agentWallet.walletAddress)) {
|
|
5116
6102
|
throw this.invalidResponse("agent wallet");
|
|
5117
6103
|
}
|
|
5118
6104
|
const context = {
|
|
@@ -5525,7 +6511,7 @@ var YieldseekerAgent = class {
|
|
|
5525
6511
|
this.agentPath(context, "deploy"),
|
|
5526
6512
|
{ method: "POST", body: {} }
|
|
5527
6513
|
);
|
|
5528
|
-
if (!deployed?.agentWallet || !
|
|
6514
|
+
if (!deployed?.agentWallet || !isAddress3(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
5529
6515
|
throw this.invalidResponse("agent deployment", {
|
|
5530
6516
|
reason: "Deploy did not return the expected Agent Wallet."
|
|
5531
6517
|
});
|
|
@@ -5656,25 +6642,79 @@ var YieldseekerAgent = class {
|
|
|
5656
6642
|
value: BigInt(transaction.value)
|
|
5657
6643
|
});
|
|
5658
6644
|
onSubmitted?.(hash);
|
|
6645
|
+
let replacement;
|
|
5659
6646
|
const receipt = await withPaidRpcDiagnostics(
|
|
5660
6647
|
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
5661
6648
|
hash,
|
|
5662
|
-
confirmations: 1
|
|
6649
|
+
confirmations: 1,
|
|
6650
|
+
onReplaced: (value) => {
|
|
6651
|
+
replacement = value;
|
|
6652
|
+
}
|
|
5663
6653
|
}),
|
|
5664
6654
|
8453,
|
|
5665
6655
|
"eth_getTransactionReceipt",
|
|
5666
6656
|
this.id
|
|
5667
6657
|
);
|
|
6658
|
+
const actualHash = receipt.transactionHash;
|
|
6659
|
+
if (!this.isTransactionHash(actualHash))
|
|
6660
|
+
throw new OwneyError(
|
|
6661
|
+
"WITHDRAW_FAILED",
|
|
6662
|
+
"The confirmed transaction hash could not be verified.",
|
|
6663
|
+
{ originalTransactionHash: hash, ownerTransferNotExecuted: false },
|
|
6664
|
+
this.id
|
|
6665
|
+
);
|
|
6666
|
+
if (actualHash.toLowerCase() !== hash.toLowerCase()) {
|
|
6667
|
+
if (!replacement || replacement.transaction.hash.toLowerCase() !== actualHash.toLowerCase()) {
|
|
6668
|
+
try {
|
|
6669
|
+
const [original2, actual2] = await Promise.all([
|
|
6670
|
+
this.getReceiptClient().getTransaction({ hash }),
|
|
6671
|
+
this.getReceiptClient().getTransaction({ hash: actualHash })
|
|
6672
|
+
]);
|
|
6673
|
+
replacement = { replacedTransaction: original2, transaction: actual2 };
|
|
6674
|
+
} catch {
|
|
6675
|
+
replacement = void 0;
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
const original = replacement?.replacedTransaction;
|
|
6679
|
+
const actual = replacement?.transaction;
|
|
6680
|
+
const sameNonce = !!original && !!actual && original.hash.toLowerCase() === hash.toLowerCase() && actual.hash.toLowerCase() === actualHash.toLowerCase() && original.from.toLowerCase() === account.toLowerCase() && actual.from.toLowerCase() === account.toLowerCase() && original.nonce === actual.nonce && (actual.chainId === void 0 || actual.chainId === chainId) && receipt.blockNumber !== null && receipt.blockNumber !== void 0;
|
|
6681
|
+
if (!sameNonce)
|
|
6682
|
+
throw new OwneyError(
|
|
6683
|
+
"WITHDRAW_FAILED",
|
|
6684
|
+
"The replacement transaction could not be verified.",
|
|
6685
|
+
{
|
|
6686
|
+
originalTransactionHash: hash,
|
|
6687
|
+
replacementTransactionHash: actualHash,
|
|
6688
|
+
reason: "TRANSACTION_REPLACEMENT_UNRESOLVED",
|
|
6689
|
+
ownerTransferNotExecuted: false
|
|
6690
|
+
},
|
|
6691
|
+
this.id
|
|
6692
|
+
);
|
|
6693
|
+
const sameCall = actual.to?.toLowerCase() === transaction.to.toLowerCase() && actual.input?.toLowerCase() === transaction.data.toLowerCase() && actual.value === BigInt(transaction.value);
|
|
6694
|
+
if (!sameCall)
|
|
6695
|
+
throw new OwneyError(
|
|
6696
|
+
"WITHDRAW_FAILED",
|
|
6697
|
+
"The transaction was replaced without executing this withdrawal.",
|
|
6698
|
+
{
|
|
6699
|
+
originalTransactionHash: hash,
|
|
6700
|
+
replacementTransactionHash: actualHash,
|
|
6701
|
+
reason: "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL",
|
|
6702
|
+
ownerTransferNotExecuted: true
|
|
6703
|
+
},
|
|
6704
|
+
this.id
|
|
6705
|
+
);
|
|
6706
|
+
onSubmitted?.(actualHash);
|
|
6707
|
+
}
|
|
5668
6708
|
if (receipt.status !== "success") {
|
|
5669
6709
|
throw new OwneyError(
|
|
5670
6710
|
"AGENT_TRANSACTION_REVERTED",
|
|
5671
|
-
`Yieldseeker transaction reverted (${
|
|
5672
|
-
{ transactionHash:
|
|
6711
|
+
`Yieldseeker transaction reverted (${actualHash}).`,
|
|
6712
|
+
{ transactionHash: actualHash },
|
|
5673
6713
|
this.id
|
|
5674
6714
|
);
|
|
5675
6715
|
}
|
|
5676
6716
|
onReceipt?.(receipt);
|
|
5677
|
-
return
|
|
6717
|
+
return actualHash;
|
|
5678
6718
|
}
|
|
5679
6719
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
5680
6720
|
if (this.unwindReceiptWaiter) {
|
|
@@ -5701,12 +6741,12 @@ var YieldseekerAgent = class {
|
|
|
5701
6741
|
return receipt;
|
|
5702
6742
|
}
|
|
5703
6743
|
assertTransaction(transaction, state, chainId) {
|
|
5704
|
-
if (!transaction || typeof transaction.from !== "string" || !
|
|
6744
|
+
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 || getAddress4(transaction.from) !== getAddress4(state.walletAddress)) {
|
|
5705
6745
|
throw this.invalidResponse("transaction");
|
|
5706
6746
|
}
|
|
5707
6747
|
}
|
|
5708
6748
|
assertAgent(agent) {
|
|
5709
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !
|
|
6749
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress3(agent.assetAddress) || agent.chainId !== 8453) {
|
|
5710
6750
|
throw this.invalidResponse("agent");
|
|
5711
6751
|
}
|
|
5712
6752
|
}
|
|
@@ -6047,6 +7087,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
6047
7087
|
|
|
6048
7088
|
// src/client.ts
|
|
6049
7089
|
import {
|
|
7090
|
+
parseUnits as parseUnits2,
|
|
6050
7091
|
createPublicClient as createPublicClient4,
|
|
6051
7092
|
createWalletClient as createWalletClient4,
|
|
6052
7093
|
custom as custom4
|
|
@@ -6154,6 +7195,18 @@ var planOf = (transfers) => JSON.stringify(
|
|
|
6154
7195
|
amount: BigInt(t.amount).toString()
|
|
6155
7196
|
}))
|
|
6156
7197
|
);
|
|
7198
|
+
var pendingKey = (i) => `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
|
|
7199
|
+
function pendingTokenBatch(i) {
|
|
7200
|
+
try {
|
|
7201
|
+
const saved = read(pendingKey(i));
|
|
7202
|
+
if (!saved) return;
|
|
7203
|
+
const body = JSON.parse(saved);
|
|
7204
|
+
if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
|
|
7205
|
+
return { txHash: keccak256(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
|
|
7206
|
+
} catch {
|
|
7207
|
+
return;
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
6157
7210
|
function read(key2) {
|
|
6158
7211
|
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
6159
7212
|
}
|
|
@@ -6170,11 +7223,11 @@ function clear(key2) {
|
|
|
6170
7223
|
else window.localStorage.removeItem(key2);
|
|
6171
7224
|
}
|
|
6172
7225
|
function sponsorTokenBatch(i) {
|
|
6173
|
-
const key2 =
|
|
7226
|
+
const key2 = pendingKey(i);
|
|
6174
7227
|
const plan = planOf(i.transfers);
|
|
6175
7228
|
const active = inflight.get(key2);
|
|
6176
7229
|
if (active) {
|
|
6177
|
-
if (active.plan !== plan)
|
|
7230
|
+
if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
6178
7231
|
return Promise.reject(
|
|
6179
7232
|
new Error(
|
|
6180
7233
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
@@ -6249,6 +7302,8 @@ async function execute(i, key2, plan) {
|
|
|
6249
7302
|
}
|
|
6250
7303
|
};
|
|
6251
7304
|
const saved = read(key2);
|
|
7305
|
+
if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
7306
|
+
throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
|
|
6252
7307
|
if (saved) {
|
|
6253
7308
|
const previous = JSON.parse(saved);
|
|
6254
7309
|
if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
@@ -6360,7 +7415,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
6360
7415
|
pub,
|
|
6361
7416
|
wallet,
|
|
6362
7417
|
onApproved: deps.onApproved,
|
|
6363
|
-
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
7418
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
|
|
7419
|
+
resumeTxHash: deps.resumeTxHash
|
|
6364
7420
|
});
|
|
6365
7421
|
};
|
|
6366
7422
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -6446,7 +7502,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
6446
7502
|
}
|
|
6447
7503
|
|
|
6448
7504
|
// src/lib/sponsored-calls-deposit.ts
|
|
6449
|
-
import { encodeFunctionData as encodeFunctionData2, erc20Abi as
|
|
7505
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi4, toHex as toHex2 } from "viem";
|
|
6450
7506
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
6451
7507
|
var DEFAULT_MAX_POLLS = 30;
|
|
6452
7508
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -6492,7 +7548,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6492
7548
|
to: token,
|
|
6493
7549
|
value: "0x0",
|
|
6494
7550
|
data: encodeFunctionData2({
|
|
6495
|
-
abi:
|
|
7551
|
+
abi: erc20Abi4,
|
|
6496
7552
|
functionName: "transfer",
|
|
6497
7553
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
6498
7554
|
})
|
|
@@ -6524,50 +7580,73 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6524
7580
|
url.searchParams.append("owneyIntent", intent);
|
|
6525
7581
|
paymasterUrl = url.toString();
|
|
6526
7582
|
}
|
|
6527
|
-
const
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
7583
|
+
const requestedId = toHex2(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
7584
|
+
const progress = (id, transfer) => {
|
|
7585
|
+
try {
|
|
7586
|
+
deps.onCallsProgress?.(id, transfers, transfer);
|
|
7587
|
+
} catch {
|
|
7588
|
+
}
|
|
7589
|
+
};
|
|
7590
|
+
progress(requestedId, "pending");
|
|
7591
|
+
let sendResult;
|
|
7592
|
+
try {
|
|
7593
|
+
sendResult = await deps.provider.request({
|
|
7594
|
+
method: "wallet_sendCalls",
|
|
7595
|
+
params: [
|
|
7596
|
+
{
|
|
7597
|
+
version: "2.0.0",
|
|
7598
|
+
id: requestedId,
|
|
7599
|
+
from: deps.ownerAddress,
|
|
7600
|
+
chainId: toHex2(chainId),
|
|
7601
|
+
atomicRequired: transfers.length > 1,
|
|
7602
|
+
calls,
|
|
7603
|
+
capabilities: {
|
|
7604
|
+
paymasterService: { url: paymasterUrl }
|
|
7605
|
+
}
|
|
6538
7606
|
}
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
})
|
|
6542
|
-
|
|
7607
|
+
]
|
|
7608
|
+
});
|
|
7609
|
+
} catch (error) {
|
|
7610
|
+
if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
|
|
7611
|
+
progress(requestedId, "failed");
|
|
7612
|
+
throw error;
|
|
7613
|
+
}
|
|
7614
|
+
try {
|
|
7615
|
+
deps.onApproved?.();
|
|
7616
|
+
} catch {
|
|
7617
|
+
}
|
|
6543
7618
|
const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
|
|
6544
7619
|
if (!callsId) {
|
|
6545
7620
|
throw new OwneyError(
|
|
6546
7621
|
"SPONSORED_CALLS_NO_ID",
|
|
6547
7622
|
"Wallet accepted wallet_sendCalls but returned no calls id.",
|
|
6548
|
-
{ chainId }
|
|
7623
|
+
{ chainId, callsId: requestedId }
|
|
6549
7624
|
);
|
|
6550
7625
|
}
|
|
7626
|
+
progress(callsId, "pending");
|
|
6551
7627
|
for (let attempt = 0; attempt < maxPolls; attempt += 1) {
|
|
6552
7628
|
const status = await deps.provider.request({
|
|
6553
7629
|
method: "wallet_getCallsStatus",
|
|
6554
7630
|
params: [callsId]
|
|
6555
7631
|
});
|
|
7632
|
+
if (status?.id && status.id !== callsId || status?.chainId && Number(status.chainId) !== chainId)
|
|
7633
|
+
throw new OwneyError("SPONSORED_CALLS_NO_RECEIPT", "Wallet returned status for a different deposit batch.", { chainId, callsId });
|
|
6556
7634
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
6557
|
-
if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
7635
|
+
if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
6558
7636
|
try {
|
|
6559
7637
|
deps.onSubmitted?.(txHash, transfers);
|
|
6560
7638
|
} catch {
|
|
6561
7639
|
}
|
|
6562
7640
|
}
|
|
6563
|
-
if (status?.
|
|
7641
|
+
if (status?.status === 400 || status?.status === 500) {
|
|
7642
|
+
progress(callsId, "failed");
|
|
6564
7643
|
throw new OwneyError(
|
|
6565
7644
|
"SPONSOR_REQUEST_FAILED",
|
|
6566
7645
|
"The sponsored deposit did not complete successfully.",
|
|
6567
7646
|
{ chainId, callsId, safeToFallback: false }
|
|
6568
7647
|
);
|
|
6569
7648
|
}
|
|
6570
|
-
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
7649
|
+
if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
6571
7650
|
if (status?.receipts?.some(
|
|
6572
7651
|
(receipt) => receipt.transactionHash !== txHash
|
|
6573
7652
|
))
|
|
@@ -6783,8 +7862,8 @@ var OwneySDK = class {
|
|
|
6783
7862
|
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
6784
7863
|
}
|
|
6785
7864
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
6786
|
-
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
6787
|
-
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
7865
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7866
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
|
|
6788
7867
|
return this.cachedSponsoredCallback;
|
|
6789
7868
|
const provider = this.requireConnectedProvider();
|
|
6790
7869
|
const owner = this.state.walletAddress;
|
|
@@ -6794,6 +7873,7 @@ var OwneySDK = class {
|
|
|
6794
7873
|
ownerAddress: owner,
|
|
6795
7874
|
onApproved,
|
|
6796
7875
|
onSubmitted,
|
|
7876
|
+
resumeTxHash,
|
|
6797
7877
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
6798
7878
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6799
7879
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -6809,13 +7889,13 @@ var OwneySDK = class {
|
|
|
6809
7889
|
transport: custom4(provider)
|
|
6810
7890
|
})
|
|
6811
7891
|
});
|
|
6812
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
7892
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
|
|
6813
7893
|
return callback;
|
|
6814
7894
|
}
|
|
6815
7895
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
6816
|
-
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
7896
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
|
|
6817
7897
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
6818
|
-
if (!onApproved && !onSubmitted && cached) return cached;
|
|
7898
|
+
if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
|
|
6819
7899
|
const provider = this.requireConnectedProvider();
|
|
6820
7900
|
const callback = makeSponsoredCallsCallback({
|
|
6821
7901
|
apiKey: this.apiKey,
|
|
@@ -6825,9 +7905,10 @@ var OwneySDK = class {
|
|
|
6825
7905
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
6826
7906
|
onApproved,
|
|
6827
7907
|
onSubmitted,
|
|
7908
|
+
onCallsProgress,
|
|
6828
7909
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
6829
7910
|
});
|
|
6830
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
7911
|
+
if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
6831
7912
|
return callback;
|
|
6832
7913
|
}
|
|
6833
7914
|
/**
|
|
@@ -6836,8 +7917,8 @@ var OwneySDK = class {
|
|
|
6836
7917
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
6837
7918
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
6838
7919
|
*/
|
|
6839
|
-
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6840
|
-
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
7920
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7921
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
|
|
6841
7922
|
return this.cachedWethSponsoredCallback;
|
|
6842
7923
|
const provider = this.requireConnectedProvider();
|
|
6843
7924
|
const owner = this.state.walletAddress;
|
|
@@ -6847,6 +7928,7 @@ var OwneySDK = class {
|
|
|
6847
7928
|
ownerAddress: owner,
|
|
6848
7929
|
onApproved,
|
|
6849
7930
|
onSubmitted,
|
|
7931
|
+
resumeTxHash,
|
|
6850
7932
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
6851
7933
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6852
7934
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -6862,7 +7944,7 @@ var OwneySDK = class {
|
|
|
6862
7944
|
transport: custom4(provider)
|
|
6863
7945
|
})
|
|
6864
7946
|
});
|
|
6865
|
-
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
7947
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
|
|
6866
7948
|
return callback;
|
|
6867
7949
|
}
|
|
6868
7950
|
getAgent(agentId) {
|
|
@@ -7171,6 +8253,7 @@ var OwneySDK = class {
|
|
|
7171
8253
|
async deposit(options) {
|
|
7172
8254
|
const { amount, asset, depositCallback, agentId, onApproved } = options;
|
|
7173
8255
|
const prepared = /* @__PURE__ */ new Map();
|
|
8256
|
+
const paidFallbackAgents = /* @__PURE__ */ new Set();
|
|
7174
8257
|
const onProgress = options.onProgress ? (evidence) => {
|
|
7175
8258
|
if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
|
|
7176
8259
|
notifyFunding(options.onProgress, evidence);
|
|
@@ -7181,152 +8264,184 @@ var OwneySDK = class {
|
|
|
7181
8264
|
if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
|
|
7182
8265
|
}
|
|
7183
8266
|
} : void 0;
|
|
8267
|
+
const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
|
|
8268
|
+
for (const destination of destinations) {
|
|
8269
|
+
const evidence = prepared.get(destination.to.toLowerCase());
|
|
8270
|
+
if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
|
|
8271
|
+
}
|
|
8272
|
+
} : void 0;
|
|
7184
8273
|
if (BigInt(amount) === 0n) {
|
|
7185
8274
|
return { agentResults: {} };
|
|
7186
8275
|
}
|
|
7187
8276
|
const state = this.requireState();
|
|
7188
8277
|
const chainId = this.requireChainId();
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
7201
|
-
return this.depositWithFallback(
|
|
7202
|
-
agent,
|
|
7203
|
-
state,
|
|
7204
|
-
chainId,
|
|
7205
|
-
amount,
|
|
7206
|
-
asset,
|
|
7207
|
-
effectiveCallback,
|
|
7208
|
-
depositCallback,
|
|
7209
|
-
onProgress
|
|
7210
|
-
);
|
|
7211
|
-
}
|
|
7212
|
-
const totalAmount = BigInt(amount);
|
|
7213
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
7214
|
-
excludeDisabled: true
|
|
7215
|
-
});
|
|
7216
|
-
const exemptFlags = await Promise.all(
|
|
7217
|
-
eligibleAgents.map(
|
|
7218
|
-
(a) => this.hasExistingBalance(a, state, chainId, asset, true)
|
|
7219
|
-
)
|
|
7220
|
-
);
|
|
7221
|
-
const exempt = new Set(
|
|
7222
|
-
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
7223
|
-
);
|
|
7224
|
-
const agentAmounts = this.splitDepositAmount(
|
|
7225
|
-
totalAmount,
|
|
7226
|
-
eligibleAgents,
|
|
7227
|
-
chainId,
|
|
7228
|
-
asset,
|
|
7229
|
-
exempt
|
|
7230
|
-
);
|
|
7231
|
-
if (agentAmounts.length === 0) {
|
|
7232
|
-
const agentsRequiringActivation = eligibleAgents.filter(
|
|
7233
|
-
(agent) => !exempt.has(agent.id)
|
|
7234
|
-
);
|
|
7235
|
-
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
7236
|
-
(highest, agent) => {
|
|
7237
|
-
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
7238
|
-
return minimum > highest ? minimum : highest;
|
|
7239
|
-
},
|
|
7240
|
-
0n
|
|
7241
|
-
);
|
|
7242
|
-
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7243
|
-
throw new OwneyError(
|
|
7244
|
-
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
7245
|
-
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7246
|
-
{
|
|
7247
|
-
amount,
|
|
7248
|
-
asset,
|
|
7249
|
-
chainId,
|
|
7250
|
-
minDepositAmount: minimumRequired.toString(),
|
|
7251
|
-
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
7252
|
-
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
7253
|
-
agentId: agent.id,
|
|
7254
|
-
minDepositAmount: this.getMinDepositAmount(
|
|
7255
|
-
agent,
|
|
7256
|
-
chainId,
|
|
7257
|
-
asset
|
|
7258
|
-
).toString()
|
|
7259
|
-
}))
|
|
7260
|
-
}
|
|
7261
|
-
);
|
|
7262
|
-
}
|
|
7263
|
-
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
7264
|
-
if (!depositCallback && batchTransfer) {
|
|
7265
|
-
return runAgentDepositBatch(
|
|
7266
|
-
chainId,
|
|
7267
|
-
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
7268
|
-
id: agent.id,
|
|
7269
|
-
amount: amount2,
|
|
7270
|
-
run: (callback) => withFailureReporting(
|
|
7271
|
-
this.apiKey,
|
|
7272
|
-
agent.id,
|
|
7273
|
-
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
7274
|
-
this.routingApiBaseUrl
|
|
7275
|
-
)
|
|
7276
|
-
})),
|
|
7277
|
-
async (cid, transfers) => {
|
|
7278
|
-
try {
|
|
7279
|
-
return await batchTransfer(cid, transfers);
|
|
7280
|
-
} catch (error) {
|
|
7281
|
-
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
7282
|
-
throw error;
|
|
7283
|
-
const requiredAmount = transfers.reduce(
|
|
7284
|
-
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
7285
|
-
0n
|
|
7286
|
-
);
|
|
7287
|
-
await this.approvePermit2(
|
|
7288
|
-
asset,
|
|
7289
|
-
requiredAmount,
|
|
7290
|
-
cid
|
|
7291
|
-
);
|
|
7292
|
-
return batchTransfer(cid, transfers);
|
|
7293
|
-
}
|
|
8278
|
+
if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
|
|
8279
|
+
throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
|
|
8280
|
+
const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash));
|
|
8281
|
+
try {
|
|
8282
|
+
if (agentId) {
|
|
8283
|
+
if (this.isAgentDisabled(agentId)) {
|
|
8284
|
+
throw new OwneyError(
|
|
8285
|
+
"AGENT_DISABLED",
|
|
8286
|
+
`Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
|
|
8287
|
+
{ agentId }
|
|
8288
|
+
);
|
|
7294
8289
|
}
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
index,
|
|
7300
|
-
{ agent, amount: agentAmount }
|
|
7301
|
-
] of agentAmounts.entries()) {
|
|
7302
|
-
try {
|
|
7303
|
-
agentResults[agent.id] = await this.depositWithFallback(
|
|
8290
|
+
const agent = this.getAgent(agentId);
|
|
8291
|
+
this.validateAssetSupport(agent, chainId, asset);
|
|
8292
|
+
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
8293
|
+
return await this.depositWithFallback(
|
|
7304
8294
|
agent,
|
|
7305
8295
|
state,
|
|
7306
8296
|
chainId,
|
|
7307
|
-
|
|
8297
|
+
amount,
|
|
7308
8298
|
asset,
|
|
7309
8299
|
effectiveCallback,
|
|
7310
8300
|
depositCallback,
|
|
7311
|
-
onProgress
|
|
8301
|
+
onProgress,
|
|
8302
|
+
() => paidFallbackAgents.add(agent.id),
|
|
8303
|
+
!!options.resumeTxHash
|
|
7312
8304
|
);
|
|
7313
|
-
}
|
|
7314
|
-
|
|
8305
|
+
}
|
|
8306
|
+
const totalAmount = BigInt(amount);
|
|
8307
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
8308
|
+
excludeDisabled: true
|
|
8309
|
+
});
|
|
8310
|
+
const prospectiveShare = totalAmount / BigInt(eligibleAgents.length);
|
|
8311
|
+
const meetsEveryMinimum = eligibleAgents.every((agent) => prospectiveShare >= this.getMinDepositAmount(agent, chainId, asset));
|
|
8312
|
+
const exemptFlags = meetsEveryMinimum ? eligibleAgents.map(() => false) : await Promise.all(eligibleAgents.map((agent) => this.hasExistingBalance(agent, state, chainId, asset, true)));
|
|
8313
|
+
const exempt = new Set(
|
|
8314
|
+
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
8315
|
+
);
|
|
8316
|
+
const agentAmounts = this.splitDepositAmount(
|
|
8317
|
+
totalAmount,
|
|
8318
|
+
eligibleAgents,
|
|
8319
|
+
chainId,
|
|
8320
|
+
asset,
|
|
8321
|
+
exempt
|
|
8322
|
+
);
|
|
8323
|
+
if (agentAmounts.length === 0) {
|
|
8324
|
+
const agentsRequiringActivation = eligibleAgents.filter(
|
|
8325
|
+
(agent) => !exempt.has(agent.id)
|
|
8326
|
+
);
|
|
8327
|
+
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
8328
|
+
(highest, agent) => {
|
|
8329
|
+
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
8330
|
+
return minimum > highest ? minimum : highest;
|
|
8331
|
+
},
|
|
8332
|
+
0n
|
|
8333
|
+
);
|
|
8334
|
+
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7315
8335
|
throw new OwneyError(
|
|
7316
|
-
"
|
|
7317
|
-
"
|
|
8336
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
8337
|
+
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7318
8338
|
{
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
8339
|
+
amount,
|
|
8340
|
+
asset,
|
|
8341
|
+
chainId,
|
|
8342
|
+
minDepositAmount: minimumRequired.toString(),
|
|
8343
|
+
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
8344
|
+
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
8345
|
+
agentId: agent.id,
|
|
8346
|
+
minDepositAmount: this.getMinDepositAmount(
|
|
8347
|
+
agent,
|
|
8348
|
+
chainId,
|
|
8349
|
+
asset
|
|
8350
|
+
).toString()
|
|
8351
|
+
}))
|
|
8352
|
+
}
|
|
7326
8353
|
);
|
|
7327
8354
|
}
|
|
8355
|
+
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
8356
|
+
if (!depositCallback && batchTransfer) {
|
|
8357
|
+
return await runAgentDepositBatch(
|
|
8358
|
+
chainId,
|
|
8359
|
+
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
8360
|
+
id: agent.id,
|
|
8361
|
+
amount: amount2,
|
|
8362
|
+
run: (callback) => withFailureReporting(
|
|
8363
|
+
this.apiKey,
|
|
8364
|
+
agent.id,
|
|
8365
|
+
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
8366
|
+
this.routingApiBaseUrl
|
|
8367
|
+
)
|
|
8368
|
+
})),
|
|
8369
|
+
async (cid, transfers) => {
|
|
8370
|
+
try {
|
|
8371
|
+
return await batchTransfer(cid, transfers);
|
|
8372
|
+
} catch (error) {
|
|
8373
|
+
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
8374
|
+
throw error;
|
|
8375
|
+
const requiredAmount = transfers.reduce(
|
|
8376
|
+
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
8377
|
+
0n
|
|
8378
|
+
);
|
|
8379
|
+
await this.approvePermit2(
|
|
8380
|
+
asset,
|
|
8381
|
+
requiredAmount,
|
|
8382
|
+
cid
|
|
8383
|
+
);
|
|
8384
|
+
return batchTransfer(cid, transfers);
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
);
|
|
8388
|
+
}
|
|
8389
|
+
const agentResults = {};
|
|
8390
|
+
for (const [
|
|
8391
|
+
index,
|
|
8392
|
+
{ agent, amount: agentAmount }
|
|
8393
|
+
] of agentAmounts.entries()) {
|
|
8394
|
+
try {
|
|
8395
|
+
agentResults[agent.id] = await this.depositWithFallback(
|
|
8396
|
+
agent,
|
|
8397
|
+
state,
|
|
8398
|
+
chainId,
|
|
8399
|
+
agentAmount,
|
|
8400
|
+
asset,
|
|
8401
|
+
effectiveCallback,
|
|
8402
|
+
depositCallback,
|
|
8403
|
+
onProgress,
|
|
8404
|
+
() => paidFallbackAgents.add(agent.id),
|
|
8405
|
+
!!options.resumeTxHash
|
|
8406
|
+
);
|
|
8407
|
+
} catch (error) {
|
|
8408
|
+
if (Object.keys(agentResults).length === 0) throw error;
|
|
8409
|
+
throw new OwneyError(
|
|
8410
|
+
"DEPOSIT_PARTIAL_FAILURE",
|
|
8411
|
+
"Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
|
|
8412
|
+
{
|
|
8413
|
+
agentResults,
|
|
8414
|
+
failedAgentId: agent.id,
|
|
8415
|
+
failedAmount: agentAmount,
|
|
8416
|
+
unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
|
|
8417
|
+
cause: error
|
|
8418
|
+
},
|
|
8419
|
+
agent.id
|
|
8420
|
+
);
|
|
8421
|
+
}
|
|
8422
|
+
}
|
|
8423
|
+
return { agentResults };
|
|
8424
|
+
} catch (error) {
|
|
8425
|
+
if (!depositCallback && onProgress && !options.resumeTxHash) {
|
|
8426
|
+
const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
|
|
8427
|
+
for (const id of ids) {
|
|
8428
|
+
const evidence = [...prepared.values()].find((value) => value.agentId === id);
|
|
8429
|
+
if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
|
|
8430
|
+
const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
|
|
8431
|
+
if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
|
|
8432
|
+
notifyFunding(onProgress, { ...evidence ?? {
|
|
8433
|
+
agentId: id,
|
|
8434
|
+
chainId,
|
|
8435
|
+
token: asset,
|
|
8436
|
+
walletAddress: state.walletAddress,
|
|
8437
|
+
intendedAmount: amount,
|
|
8438
|
+
registration: "unknown",
|
|
8439
|
+
delivery: "not-applicable"
|
|
8440
|
+
}, transfer: "failed" });
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
throw error;
|
|
7328
8444
|
}
|
|
7329
|
-
return { agentResults };
|
|
7330
8445
|
}
|
|
7331
8446
|
/**
|
|
7332
8447
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
@@ -7355,7 +8470,7 @@ var OwneySDK = class {
|
|
|
7355
8470
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
7356
8471
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
7357
8472
|
*/
|
|
7358
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
8473
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
|
|
7359
8474
|
const attempt = (callback) => withFailureReporting(
|
|
7360
8475
|
this.apiKey,
|
|
7361
8476
|
agent.id,
|
|
@@ -7367,6 +8482,7 @@ var OwneySDK = class {
|
|
|
7367
8482
|
try {
|
|
7368
8483
|
return await attempt(effectiveCallback);
|
|
7369
8484
|
} catch (error) {
|
|
8485
|
+
if (resumeOnly) throw error;
|
|
7370
8486
|
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
7371
8487
|
approvalAttempted = true;
|
|
7372
8488
|
console.warn(
|
|
@@ -7384,6 +8500,7 @@ var OwneySDK = class {
|
|
|
7384
8500
|
`[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
|
|
7385
8501
|
error
|
|
7386
8502
|
);
|
|
8503
|
+
onPaidFallback?.();
|
|
7387
8504
|
return attempt(void 0);
|
|
7388
8505
|
}
|
|
7389
8506
|
}
|
|
@@ -7466,7 +8583,12 @@ var OwneySDK = class {
|
|
|
7466
8583
|
*/
|
|
7467
8584
|
async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
|
|
7468
8585
|
try {
|
|
7469
|
-
|
|
8586
|
+
if (agent.hasExistingDepositFunds && await agent.hasExistingDepositFunds(
|
|
8587
|
+
state,
|
|
8588
|
+
chainId,
|
|
8589
|
+
asset
|
|
8590
|
+
).catch(() => void 0) === true) return true;
|
|
8591
|
+
const balance = await agent.getBalances(state, chainId, asset);
|
|
7470
8592
|
const target = asset.toLowerCase();
|
|
7471
8593
|
const token = balance.tokens.find(
|
|
7472
8594
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
@@ -7592,7 +8714,8 @@ var OwneySDK = class {
|
|
|
7592
8714
|
return verified;
|
|
7593
8715
|
}
|
|
7594
8716
|
/** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
|
|
7595
|
-
async resolveSponsoredWithdrawalEvidence(evidence) {
|
|
8717
|
+
async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
|
|
8718
|
+
if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
|
|
7596
8719
|
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
7597
8720
|
if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
7598
8721
|
const agent = this.getAgent("yieldseeker");
|
|
@@ -7608,18 +8731,26 @@ var OwneySDK = class {
|
|
|
7608
8731
|
owner: state.walletAddress,
|
|
7609
8732
|
agentWallet: wallet,
|
|
7610
8733
|
asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
|
|
7611
|
-
userOpHash: evidence.userOpHash
|
|
8734
|
+
userOpHash: evidence.userOpHash,
|
|
8735
|
+
...evidence.userOperation ? { userOperation: Object.fromEntries([
|
|
8736
|
+
"sender",
|
|
8737
|
+
"nonce",
|
|
8738
|
+
"initCode",
|
|
8739
|
+
"callData",
|
|
8740
|
+
"callGasLimit",
|
|
8741
|
+
"verificationGasLimit",
|
|
8742
|
+
"preVerificationGas",
|
|
8743
|
+
"maxFeePerGas",
|
|
8744
|
+
"maxPriorityFeePerGas",
|
|
8745
|
+
"paymasterAndData"
|
|
8746
|
+
].map((key2) => [key2, evidence.userOperation[key2]])) } : {},
|
|
8747
|
+
...Number.isFinite(options.submittedAt) && options.submittedAt > 0 ? { submittedAt: options.submittedAt } : {}
|
|
7612
8748
|
})
|
|
7613
8749
|
});
|
|
7614
8750
|
if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
|
|
7615
8751
|
const payload = await response.json();
|
|
7616
8752
|
txHash = payload.data?.txHash ?? void 0;
|
|
7617
8753
|
executionFailed = payload.data?.executionFailed === true;
|
|
7618
|
-
} else if (evidence.callsId) {
|
|
7619
|
-
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
7620
|
-
if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
|
|
7621
|
-
executionFailed = status?.status === 400 || status?.status === 500;
|
|
7622
|
-
txHash = status?.receipts?.[0]?.transactionHash;
|
|
7623
8754
|
}
|
|
7624
8755
|
assertCurrent();
|
|
7625
8756
|
if (executionFailed) return {
|
|
@@ -7630,6 +8761,60 @@ var OwneySDK = class {
|
|
|
7630
8761
|
};
|
|
7631
8762
|
return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
|
|
7632
8763
|
}
|
|
8764
|
+
/** Recover an EIP-5792 deposit or payout using this account's exact batch ID. */
|
|
8765
|
+
async resolveWalletCallsFundingEvidence(evidence, direction) {
|
|
8766
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
8767
|
+
if (!evidence.callsId || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
8768
|
+
const agent = this.getAgent(evidence.agentId);
|
|
8769
|
+
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
8770
|
+
if (!wallet || wallet.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return evidence;
|
|
8771
|
+
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
8772
|
+
assertCurrent();
|
|
8773
|
+
if (status?.id !== void 0 && status.id !== evidence.callsId || status?.chainId !== void 0 && Number(status.chainId) !== chainId) return evidence;
|
|
8774
|
+
const statusCode2 = Number(status?.status);
|
|
8775
|
+
if (statusCode2 === 400 || statusCode2 === 500) return {
|
|
8776
|
+
...evidence,
|
|
8777
|
+
transfer: "failed",
|
|
8778
|
+
delivery: direction === "withdraw" ? "failed" : "not-applicable"
|
|
8779
|
+
};
|
|
8780
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash?.toLowerCase()).filter((hash) => !!hash && /^0x[\da-f]{64}$/i.test(hash)))];
|
|
8781
|
+
if (hashes.length === 1) return { ...evidence, txHash: hashes[0] };
|
|
8782
|
+
if (hashes.length > 16) return evidence;
|
|
8783
|
+
const matches = [];
|
|
8784
|
+
for (const txHash of hashes) {
|
|
8785
|
+
const inspected = await this.inspectFunding({ ...evidence, txHash }, direction);
|
|
8786
|
+
assertCurrent();
|
|
8787
|
+
if (inspected.transfer === "confirmed") matches.push(inspected);
|
|
8788
|
+
}
|
|
8789
|
+
return matches.length === 1 ? matches[0] : evidence;
|
|
8790
|
+
}
|
|
8791
|
+
/** Whether a foreground deposit can resume the same signed batch, never a
|
|
8792
|
+
* fresh authorization. The batch rail independently rechecks the exact plan. */
|
|
8793
|
+
async canResumePendingDeposit(evidences, amount) {
|
|
8794
|
+
if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
|
|
8795
|
+
const first = evidences[0];
|
|
8796
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
|
|
8797
|
+
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
|
|
8798
|
+
if (!token) return false;
|
|
8799
|
+
const pending = pendingTokenBatch({
|
|
8800
|
+
apiKey: this.apiKey,
|
|
8801
|
+
baseUrl: this.routingApiBaseUrl,
|
|
8802
|
+
owner: state.walletAddress,
|
|
8803
|
+
chainId,
|
|
8804
|
+
token: token.address
|
|
8805
|
+
});
|
|
8806
|
+
if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
|
|
8807
|
+
const destinations = /* @__PURE__ */ new Set();
|
|
8808
|
+
for (const evidence of evidences) {
|
|
8809
|
+
if (evidence.chainId !== chainId || evidence.token !== first.token || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.callsId || evidence.userOpHash || evidence.txHash?.toLowerCase() !== pending.txHash.toLowerCase()) return false;
|
|
8810
|
+
const agent = this.getAgent(evidence.agentId);
|
|
8811
|
+
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
8812
|
+
if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
|
|
8813
|
+
destinations.add(wallet.toLowerCase());
|
|
8814
|
+
}
|
|
8815
|
+
assertCurrent();
|
|
8816
|
+
return true;
|
|
8817
|
+
}
|
|
7633
8818
|
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
7634
8819
|
endFundingBalanceRefresh(evidence, read2) {
|
|
7635
8820
|
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
@@ -7730,6 +8915,30 @@ var OwneySDK = class {
|
|
|
7730
8915
|
}
|
|
7731
8916
|
};
|
|
7732
8917
|
const token = asset;
|
|
8918
|
+
const agentFailures = {};
|
|
8919
|
+
const recordAgentFailure = (id, error) => {
|
|
8920
|
+
if (!(error instanceof OwneyError)) return;
|
|
8921
|
+
const details = error.details ?? {};
|
|
8922
|
+
const codeValue = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : void 0;
|
|
8923
|
+
const revertNames = ["InsufficientBalance", "InvalidAsset", "Unauthorized", "Error", "Panic", "FailedOp", "FailedOpWithRevert"];
|
|
8924
|
+
const panicCodes = [0, 1, 17, 18, 33, 34, 49, 50, 65, 81];
|
|
8925
|
+
agentFailures[id] = {
|
|
8926
|
+
code: error.code,
|
|
8927
|
+
phase: ["prepare", "send", "observe"].includes(String(details.phase)) ? details.phase : void 0,
|
|
8928
|
+
failureCode: codeValue(details.failureCode),
|
|
8929
|
+
reason: codeValue(details.reason),
|
|
8930
|
+
withdrawalPhase: details.withdrawalPhase === "unwind" ? "unwind" : void 0,
|
|
8931
|
+
statusCode: typeof details.statusCode === "number" ? details.statusCode : void 0,
|
|
8932
|
+
rpcCode: typeof details.rpcCode === "number" ? details.rpcCode : void 0,
|
|
8933
|
+
rpcMethod: typeof details.rpcMethod === "string" && /^[A-Za-z_]{1,80}$/.test(details.rpcMethod) ? details.rpcMethod : void 0,
|
|
8934
|
+
revertSelector: typeof details.revertSelector === "string" && /^0x[0-9a-f]{8}$/i.test(details.revertSelector) ? details.revertSelector : void 0,
|
|
8935
|
+
revertName: typeof details.revertName === "string" && revertNames.includes(details.revertName) ? details.revertName : void 0,
|
|
8936
|
+
aaCode: typeof details.aaCode === "string" && /^AA[0-9]{2}$/.test(details.aaCode) ? details.aaCode : void 0,
|
|
8937
|
+
panicCode: typeof details.panicCode === "string" && /^0x[0-9a-f]{1,2}$/i.test(details.panicCode) && panicCodes.includes(Number.parseInt(details.panicCode.slice(2), 16)) ? details.panicCode : void 0,
|
|
8938
|
+
safeToFallback: typeof details.safeToFallback === "boolean" ? details.safeToFallback : void 0,
|
|
8939
|
+
executionFailed: typeof details.executionFailed === "boolean" ? details.executionFailed : void 0
|
|
8940
|
+
};
|
|
8941
|
+
};
|
|
7733
8942
|
const submissionEvidence = /* @__PURE__ */ new Map();
|
|
7734
8943
|
const submit = async (agent, requested2) => {
|
|
7735
8944
|
attempted.add(agent.id);
|
|
@@ -7750,9 +8959,19 @@ var OwneySDK = class {
|
|
|
7750
8959
|
};
|
|
7751
8960
|
progress(evidence);
|
|
7752
8961
|
try {
|
|
8962
|
+
if (options.onBeforeSubmission)
|
|
8963
|
+
return await agent.withdraw(
|
|
8964
|
+
state,
|
|
8965
|
+
chainId,
|
|
8966
|
+
token,
|
|
8967
|
+
requested2,
|
|
8968
|
+
progress,
|
|
8969
|
+
options.gasMode ?? "auto",
|
|
8970
|
+
(next) => options.onBeforeSubmission({ ...evidence, ...next })
|
|
8971
|
+
);
|
|
7753
8972
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
7754
8973
|
} catch (error) {
|
|
7755
|
-
if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
8974
|
+
if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
7756
8975
|
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
7757
8976
|
throw error;
|
|
7758
8977
|
}
|
|
@@ -7802,6 +9021,7 @@ var OwneySDK = class {
|
|
|
7802
9021
|
}
|
|
7803
9022
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
7804
9023
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
9024
|
+
recordAgentFailure(agent.id, err);
|
|
7805
9025
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7806
9026
|
void reportAgentFailure(
|
|
7807
9027
|
this.apiKey,
|
|
@@ -7812,17 +9032,23 @@ var OwneySDK = class {
|
|
|
7812
9032
|
}
|
|
7813
9033
|
}
|
|
7814
9034
|
if (Object.keys(results2).length === 0) {
|
|
9035
|
+
if (Object.keys(agentErrors2).length === 0)
|
|
9036
|
+
throw new OwneyError(
|
|
9037
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
9038
|
+
"No funds are currently available to withdraw for this asset.",
|
|
9039
|
+
{ asset, available: "0" }
|
|
9040
|
+
);
|
|
7815
9041
|
throw new OwneyError(
|
|
7816
9042
|
"WITHDRAW_ALL_FAILED",
|
|
7817
9043
|
`Failed to withdraw "${asset}" from all eligible agents.`,
|
|
7818
|
-
{ asset, agentErrors: agentErrors2 }
|
|
9044
|
+
{ asset, agentErrors: agentErrors2, agentFailures }
|
|
7819
9045
|
);
|
|
7820
9046
|
}
|
|
7821
9047
|
if (Object.keys(agentErrors2).length > 0) {
|
|
7822
9048
|
throw new OwneyError(
|
|
7823
9049
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7824
9050
|
"Some agents could not complete the withdrawal.",
|
|
7825
|
-
{ asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
9051
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
7826
9052
|
);
|
|
7827
9053
|
}
|
|
7828
9054
|
const amountsKnown = Object.values(results2).every(
|
|
@@ -7834,11 +9060,10 @@ var OwneySDK = class {
|
|
|
7834
9060
|
};
|
|
7835
9061
|
}
|
|
7836
9062
|
const requested = BigInt(amount);
|
|
7837
|
-
const aggregated = await this.
|
|
9063
|
+
const { balances: aggregated, failures: balanceFailures } = await this.readAggregatedBalances({ chainId, asset: token });
|
|
7838
9064
|
const unavailableAgents = eligibleAgents.filter(
|
|
7839
9065
|
(agent) => !(agent.id in aggregated.agentBalances)
|
|
7840
9066
|
);
|
|
7841
|
-
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
7842
9067
|
const balances = projectAgentBalancesForAsset(
|
|
7843
9068
|
eligibleAgents,
|
|
7844
9069
|
aggregated.agentBalances,
|
|
@@ -7846,6 +9071,44 @@ var OwneySDK = class {
|
|
|
7846
9071
|
asset,
|
|
7847
9072
|
assetInfo.decimals
|
|
7848
9073
|
);
|
|
9074
|
+
const indexedAvailable = balances.reduce((sum, entry) => sum + entry.balance, 0n);
|
|
9075
|
+
await Promise.all(balances.map(async (entry) => {
|
|
9076
|
+
if (entry.balance !== 0n && indexedAvailable >= requested || !entry.agent.getWithdrawableBalance) return;
|
|
9077
|
+
const originalFailure = balanceFailures.get(entry.agent.id);
|
|
9078
|
+
if (originalFailure instanceof OwneyError && ![
|
|
9079
|
+
"AGENT_API_ERROR",
|
|
9080
|
+
"AGENT_TIMEOUT",
|
|
9081
|
+
"AGENT_RATE_LIMITED"
|
|
9082
|
+
].includes(originalFailure.code)) return;
|
|
9083
|
+
if (this.isUserRejectedWithdrawal(originalFailure)) throw originalFailure;
|
|
9084
|
+
if (entry.balance !== 0n && entry.agent.id !== "yieldseeker") {
|
|
9085
|
+
const total = aggregated.agentBalances[entry.agent.id]?.assetBalances?.find((value) => value.chainId === chainId && value.asset.toUpperCase() === token);
|
|
9086
|
+
if (!total || parseUnits2(total.amount, assetInfo.decimals) <= entry.balance) return;
|
|
9087
|
+
}
|
|
9088
|
+
try {
|
|
9089
|
+
const otherAvailable = indexedAvailable - entry.balance;
|
|
9090
|
+
const minimumAmount = requested > otherAvailable ? requested - otherAvailable : void 0;
|
|
9091
|
+
const verified = await entry.agent.getWithdrawableBalance(state, chainId, token, minimumAmount);
|
|
9092
|
+
if (typeof verified !== "bigint" || verified < 0n)
|
|
9093
|
+
throw new OwneyError("WITHDRAW_BALANCE_UNAVAILABLE", "Invalid withdrawal balance proof.", {}, entry.agent.id);
|
|
9094
|
+
if (this.state !== state || this.state.walletAddress !== state.walletAddress || this.state.chainId !== chainId)
|
|
9095
|
+
throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
|
|
9096
|
+
entry.balance = verified;
|
|
9097
|
+
const unavailableIndex = unavailableAgents.indexOf(entry.agent);
|
|
9098
|
+
if (unavailableIndex !== -1) unavailableAgents.splice(unavailableIndex, 1);
|
|
9099
|
+
if (aggregated.agentErrors) delete aggregated.agentErrors[entry.agent.id];
|
|
9100
|
+
} catch (error) {
|
|
9101
|
+
entry.balance = 0n;
|
|
9102
|
+
if (!unavailableAgents.includes(entry.agent)) unavailableAgents.push(entry.agent);
|
|
9103
|
+
aggregated.agentErrors = {
|
|
9104
|
+
...aggregated.agentErrors,
|
|
9105
|
+
[entry.agent.id]: error instanceof Error ? error.message : String(error)
|
|
9106
|
+
};
|
|
9107
|
+
}
|
|
9108
|
+
}));
|
|
9109
|
+
if (this.state !== state || this.state.chainId !== chainId)
|
|
9110
|
+
throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
|
|
9111
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
7849
9112
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
7850
9113
|
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
7851
9114
|
throw new OwneyError(
|
|
@@ -7903,6 +9166,7 @@ var OwneySDK = class {
|
|
|
7903
9166
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7904
9167
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
7905
9168
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
9169
|
+
recordAgentFailure(p.agent.id, err);
|
|
7906
9170
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7907
9171
|
void reportAgentFailure(
|
|
7908
9172
|
this.apiKey,
|
|
@@ -7923,12 +9187,24 @@ var OwneySDK = class {
|
|
|
7923
9187
|
{
|
|
7924
9188
|
asset,
|
|
7925
9189
|
amount,
|
|
7926
|
-
agentErrors
|
|
9190
|
+
agentErrors,
|
|
9191
|
+
agentFailures
|
|
7927
9192
|
}
|
|
7928
9193
|
);
|
|
7929
9194
|
}
|
|
7930
9195
|
const withdrawn = sumWithdrawnAmount(results);
|
|
7931
|
-
|
|
9196
|
+
const verifiedFeeDeductions = orderedPlans.reduce((total, plan) => {
|
|
9197
|
+
const result = results[plan.agent.id];
|
|
9198
|
+
const adjustment = result?.feeAdjustment;
|
|
9199
|
+
const evidence = result?.evidence;
|
|
9200
|
+
const submitted = submissionEvidence.get(plan.agent.id);
|
|
9201
|
+
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;
|
|
9202
|
+
const fee = BigInt(adjustment.feeAmount);
|
|
9203
|
+
const net = BigInt(result.amount);
|
|
9204
|
+
const gross = BigInt(adjustment.grossAmount);
|
|
9205
|
+
return fee > 0n && net > 0n && gross === plan.planned && net + fee === gross ? total + fee : total;
|
|
9206
|
+
}, 0n);
|
|
9207
|
+
if (withdrawn + verifiedFeeDeductions < requested) {
|
|
7932
9208
|
throw new OwneyError(
|
|
7933
9209
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7934
9210
|
`Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
|
|
@@ -7938,6 +9214,7 @@ var OwneySDK = class {
|
|
|
7938
9214
|
withdrawn: withdrawn.toString(),
|
|
7939
9215
|
partialResults: results,
|
|
7940
9216
|
agentErrors,
|
|
9217
|
+
agentFailures,
|
|
7941
9218
|
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
7942
9219
|
}
|
|
7943
9220
|
);
|
|
@@ -7951,23 +9228,42 @@ var OwneySDK = class {
|
|
|
7951
9228
|
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
7952
9229
|
*/
|
|
7953
9230
|
async getBalances(agentId, options) {
|
|
7954
|
-
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
7955
|
-
const asset = options?.asset;
|
|
7956
|
-
const readBalance = (agent) => this.readAgent(
|
|
7957
|
-
agent,
|
|
7958
|
-
"balances",
|
|
7959
|
-
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
7960
|
-
asset ? { asset } : null
|
|
7961
|
-
);
|
|
7962
9231
|
if (agentId) {
|
|
9232
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
9233
|
+
const asset = options?.asset;
|
|
7963
9234
|
const agent = this.getAgent(agentId);
|
|
7964
|
-
const result = await
|
|
9235
|
+
const result = await this.readAgent(
|
|
9236
|
+
agent,
|
|
9237
|
+
"balances",
|
|
9238
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
9239
|
+
asset ? { asset } : null
|
|
9240
|
+
);
|
|
7965
9241
|
assertCurrent();
|
|
7966
9242
|
return {
|
|
7967
9243
|
...result,
|
|
7968
9244
|
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
7969
9245
|
};
|
|
7970
9246
|
}
|
|
9247
|
+
const { balances, failures } = await this.readAggregatedBalances(options);
|
|
9248
|
+
if (Object.keys(balances.agentBalances).length === 0) {
|
|
9249
|
+
throw new OwneyError(
|
|
9250
|
+
"BALANCE_ALL_FAILED",
|
|
9251
|
+
"Failed to fetch balances for all active agents.",
|
|
9252
|
+
{ agentErrors: balances.agentErrors ?? {}, failures: [...failures.values()] }
|
|
9253
|
+
);
|
|
9254
|
+
}
|
|
9255
|
+
return balances;
|
|
9256
|
+
}
|
|
9257
|
+
/** Preserve failure objects for withdrawal recovery without changing public balance results. */
|
|
9258
|
+
async readAggregatedBalances(options) {
|
|
9259
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
9260
|
+
const asset = options?.asset;
|
|
9261
|
+
const readBalance = (agent) => this.readAgent(
|
|
9262
|
+
agent,
|
|
9263
|
+
"balances",
|
|
9264
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
9265
|
+
asset ? { asset } : null
|
|
9266
|
+
);
|
|
7971
9267
|
let totalBalance = 0;
|
|
7972
9268
|
const results = {};
|
|
7973
9269
|
const entries = [...this.getActiveAgents().entries()];
|
|
@@ -7984,39 +9280,33 @@ var OwneySDK = class {
|
|
|
7984
9280
|
})
|
|
7985
9281
|
);
|
|
7986
9282
|
assertCurrent();
|
|
7987
|
-
let successCount = 0;
|
|
7988
9283
|
const agentErrors = {};
|
|
7989
9284
|
const agentRetryAt = {};
|
|
7990
|
-
const agentFailures =
|
|
9285
|
+
const agentFailures = /* @__PURE__ */ new Map();
|
|
7991
9286
|
for (let i = 0; i < balanceResults.length; i++) {
|
|
7992
9287
|
const settledResult = balanceResults[i];
|
|
7993
|
-
const [
|
|
9288
|
+
const [agentId] = entries[i];
|
|
7994
9289
|
if (settledResult.status === "fulfilled") {
|
|
7995
9290
|
const [id, b] = settledResult.value;
|
|
7996
9291
|
results[id] = b;
|
|
7997
9292
|
totalBalance += Number(b.totalBalance);
|
|
7998
|
-
successCount += 1;
|
|
7999
9293
|
continue;
|
|
8000
9294
|
}
|
|
8001
9295
|
const reason = settledResult.reason;
|
|
8002
|
-
agentFailures.
|
|
9296
|
+
agentFailures.set(agentId, reason);
|
|
8003
9297
|
const retryDelay = rateLimitDelay(reason);
|
|
8004
|
-
if (retryDelay !== void 0) agentRetryAt[
|
|
8005
|
-
agentErrors[
|
|
8006
|
-
console.error(`[owney-sdk] Balance fetch failed for agent "${
|
|
8007
|
-
}
|
|
8008
|
-
if (successCount === 0) {
|
|
8009
|
-
throw new OwneyError(
|
|
8010
|
-
"BALANCE_ALL_FAILED",
|
|
8011
|
-
"Failed to fetch balances for all active agents.",
|
|
8012
|
-
{ agentErrors, failures: agentFailures }
|
|
8013
|
-
);
|
|
9298
|
+
if (retryDelay !== void 0) agentRetryAt[agentId] = Date.now() + retryDelay;
|
|
9299
|
+
agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
|
|
9300
|
+
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId}":`, reason);
|
|
8014
9301
|
}
|
|
8015
9302
|
return {
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
9303
|
+
balances: {
|
|
9304
|
+
totalBalance: String(totalBalance),
|
|
9305
|
+
totalBalanceAsset: "usdc",
|
|
9306
|
+
agentBalances: results,
|
|
9307
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
|
|
9308
|
+
},
|
|
9309
|
+
failures: agentFailures
|
|
8020
9310
|
};
|
|
8021
9311
|
}
|
|
8022
9312
|
/**
|