@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.cjs
CHANGED
|
@@ -102,6 +102,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
102
102
|
const attributed = /* @__PURE__ */ new Map();
|
|
103
103
|
const agent = evidence.agentWallet?.toLowerCase();
|
|
104
104
|
const owner = evidence.walletAddress.toLowerCase();
|
|
105
|
+
const source = direction === "deposit" ? owner : agent;
|
|
106
|
+
const destination = direction === "deposit" ? agent : owner;
|
|
105
107
|
const logIndexes = [];
|
|
106
108
|
const transferLogs = [];
|
|
107
109
|
const zeroTransferLogs = [];
|
|
@@ -116,17 +118,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
116
118
|
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
117
119
|
zeroTransferLogs.push(log.logIndex);
|
|
118
120
|
let matched = 0n;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (sender !== agent) attributed.set(sender, available - carried);
|
|
127
|
-
if (recipient === owner) matched = carried;
|
|
128
|
-
else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
129
|
-
}
|
|
121
|
+
const sender = from.toLowerCase();
|
|
122
|
+
const recipient = to.toLowerCase();
|
|
123
|
+
const available = sender === source ? value : attributed.get(sender) ?? 0n;
|
|
124
|
+
const carried = available < value ? available : value;
|
|
125
|
+
if (sender !== source) attributed.set(sender, available - carried);
|
|
126
|
+
if (recipient === destination) matched = carried;
|
|
127
|
+
else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
130
128
|
if (matched === 0n) continue;
|
|
131
129
|
amount += matched;
|
|
132
130
|
transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
|
|
@@ -406,20 +404,44 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
406
404
|
};
|
|
407
405
|
const notify = (outcome) => {
|
|
408
406
|
if (outcome === "rejected" && matching.length !== 1) return;
|
|
407
|
+
const evidence = matching.length === 1 ? { ...request2 } : {
|
|
408
|
+
attempted: request2.attempted,
|
|
409
|
+
method: request2.method,
|
|
410
|
+
path: request2.path,
|
|
411
|
+
...request2.elapsedMs !== void 0 ? { elapsedMs: request2.elapsedMs } : {}
|
|
412
|
+
};
|
|
409
413
|
for (const observer of matching) {
|
|
410
414
|
try {
|
|
411
|
-
observer.notify(outcome,
|
|
415
|
+
observer.notify(outcome, evidence);
|
|
412
416
|
} catch {
|
|
413
417
|
}
|
|
414
418
|
}
|
|
415
419
|
};
|
|
416
420
|
notify("attempted");
|
|
421
|
+
let callArgs = args;
|
|
422
|
+
if (withdrawal && client.client?.defaults) {
|
|
423
|
+
const configIndex = method === "get" ? 1 : 2;
|
|
424
|
+
const config = args[configIndex];
|
|
425
|
+
const transforms = config?.transformResponse !== void 0 ? config.transformResponse : client.client.defaults.transformResponse;
|
|
426
|
+
const captureStatus = (data, _headers, status) => {
|
|
427
|
+
if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599) request2.httpStatus = status;
|
|
428
|
+
return data;
|
|
429
|
+
};
|
|
430
|
+
callArgs = [...args];
|
|
431
|
+
callArgs[configIndex] = { ...config, transformResponse: [
|
|
432
|
+
...Array.isArray(transforms) ? transforms : typeof transforms === "function" ? [transforms] : [],
|
|
433
|
+
captureStatus
|
|
434
|
+
] };
|
|
435
|
+
}
|
|
417
436
|
try {
|
|
418
|
-
return await original.apply(this,
|
|
437
|
+
return await original.apply(this, callArgs);
|
|
419
438
|
} catch (error) {
|
|
420
439
|
const message = error instanceof Error ? error.message : "";
|
|
421
|
-
|
|
422
|
-
if (
|
|
440
|
+
const status = error?.response?.status;
|
|
441
|
+
if (request2.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
|
|
442
|
+
request2.httpStatus = status;
|
|
443
|
+
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";
|
|
444
|
+
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");
|
|
423
445
|
throw error;
|
|
424
446
|
} finally {
|
|
425
447
|
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
@@ -442,9 +464,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
442
464
|
} };
|
|
443
465
|
}
|
|
444
466
|
|
|
467
|
+
// src/agents/zyfai/zyfai.withdraw-empty.ts
|
|
468
|
+
function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
469
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
470
|
+
const response = record(raw2);
|
|
471
|
+
const portfolio = record(response?.portfolio);
|
|
472
|
+
const chains = record(portfolio?.portfolioByChain);
|
|
473
|
+
if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
|
|
474
|
+
if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
|
|
475
|
+
const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
|
|
476
|
+
const chain = (value) => {
|
|
477
|
+
if (typeof value === "number") return value;
|
|
478
|
+
if (typeof value !== "string") return void 0;
|
|
479
|
+
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
480
|
+
};
|
|
481
|
+
const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
482
|
+
for (const [id, assets] of Object.entries(chains)) {
|
|
483
|
+
const idNumber = chain(id);
|
|
484
|
+
if (idNumber === void 0) return false;
|
|
485
|
+
if (idNumber !== chainId) continue;
|
|
486
|
+
const entries = record(assets);
|
|
487
|
+
if (!entries) return false;
|
|
488
|
+
for (const [asset, value] of Object.entries(entries)) {
|
|
489
|
+
if (symbol(asset) !== token) continue;
|
|
490
|
+
const balance = record(value);
|
|
491
|
+
if (!balance || !zero(balance.balance)) return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
|
|
495
|
+
const entry = record(value);
|
|
496
|
+
if (!entry) return false;
|
|
497
|
+
const id = chain(entry.chainId ?? entry.chain);
|
|
498
|
+
const asset = symbol(entry.tokenSymbol ?? entry.token_symbol);
|
|
499
|
+
if (id === void 0 || asset === void 0) return false;
|
|
500
|
+
if (id !== chainId || asset !== token) continue;
|
|
501
|
+
const amounts = [entry.balance, entry.amount, entry.underlyingAmount].filter((value2) => value2 !== void 0);
|
|
502
|
+
if (!amounts.length || !amounts.every(zero)) return false;
|
|
503
|
+
}
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
|
|
445
507
|
// src/agents/zyfai/zyfai.agent.ts
|
|
446
508
|
var import_sdk = require("@zyfai/sdk");
|
|
447
|
-
var
|
|
509
|
+
var import_viem4 = require("viem");
|
|
448
510
|
var import_chains = require("viem/chains");
|
|
449
511
|
|
|
450
512
|
// src/types/config.ts
|
|
@@ -531,14 +593,6 @@ function hexToDecimal(hex, decimals = 6) {
|
|
|
531
593
|
const remainder = parsed % divisor;
|
|
532
594
|
return Number(whole) + Number(remainder) / Number(divisor);
|
|
533
595
|
}
|
|
534
|
-
function normalizeDecimalPlace(value, decimals = 6) {
|
|
535
|
-
const parsed = BigInt(value);
|
|
536
|
-
if (decimals <= 0) return Number(parsed);
|
|
537
|
-
const divisor = BigInt(10 ** decimals);
|
|
538
|
-
const whole = parsed / divisor;
|
|
539
|
-
const remainder = parsed % divisor;
|
|
540
|
-
return Number(whole) + Number(remainder) / Number(divisor);
|
|
541
|
-
}
|
|
542
596
|
var DayFilterMapping = {
|
|
543
597
|
"30D": 30,
|
|
544
598
|
"14D": 14,
|
|
@@ -546,6 +600,8 @@ var DayFilterMapping = {
|
|
|
546
600
|
};
|
|
547
601
|
|
|
548
602
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
603
|
+
var import_viem2 = require("viem");
|
|
604
|
+
var exactHexAmount = (raw2, decimals) => (0, import_viem2.formatUnits)(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
|
|
549
605
|
var DECIMALS_BY_SYMBOL = {
|
|
550
606
|
usdc: 6,
|
|
551
607
|
weth: 18,
|
|
@@ -600,15 +656,15 @@ function mapPendingAllocations(raw2) {
|
|
|
600
656
|
if (typeof e.balance !== "string") continue;
|
|
601
657
|
let amount;
|
|
602
658
|
try {
|
|
603
|
-
|
|
659
|
+
if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
|
|
660
|
+
amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
|
|
604
661
|
} catch {
|
|
605
662
|
continue;
|
|
606
663
|
}
|
|
607
|
-
if (!Number.isFinite(amount) || amount <= 0) continue;
|
|
608
664
|
pending.push({
|
|
609
665
|
chainId: e.chainId,
|
|
610
666
|
asset: e.tokenSymbol,
|
|
611
|
-
amount
|
|
667
|
+
amount,
|
|
612
668
|
isPending: e.isPending === true,
|
|
613
669
|
...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
|
|
614
670
|
});
|
|
@@ -631,13 +687,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
631
687
|
{ balance, balanceWithFee, decimals }
|
|
632
688
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
633
689
|
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
634
|
-
const amount =
|
|
635
|
-
totalBalance += amount;
|
|
690
|
+
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
|
|
691
|
+
totalBalance += Number(amount);
|
|
636
692
|
tokens.push({
|
|
637
693
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
638
694
|
chainId: positionChainId,
|
|
639
695
|
asset: symbol,
|
|
640
|
-
amount
|
|
696
|
+
amount
|
|
641
697
|
});
|
|
642
698
|
}
|
|
643
699
|
}
|
|
@@ -651,19 +707,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
651
707
|
// legacy `amount` fallback is hex. Runtime
|
|
652
708
|
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
653
709
|
// when present, otherwise fall back to the per-symbol lookup.
|
|
654
|
-
amount:
|
|
655
|
-
p.underlyingAmountWithFee ?? p.underlyingAmount
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
p.decimals
|
|
666
|
-
)
|
|
710
|
+
amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? (0, import_viem2.formatUnits)(
|
|
711
|
+
BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
|
|
712
|
+
tokenDecimals(
|
|
713
|
+
p.token_symbol,
|
|
714
|
+
p.decimals
|
|
715
|
+
)
|
|
716
|
+
) : exactHexAmount(
|
|
717
|
+
p.amount || "0",
|
|
718
|
+
tokenDecimals(
|
|
719
|
+
p.token_symbol,
|
|
720
|
+
p.decimals
|
|
667
721
|
)
|
|
668
722
|
),
|
|
669
723
|
apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
|
|
@@ -1318,7 +1372,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
1318
1372
|
}
|
|
1319
1373
|
|
|
1320
1374
|
// src/lib/paid-rpc.ts
|
|
1321
|
-
var
|
|
1375
|
+
var import_viem3 = require("viem");
|
|
1322
1376
|
var PAID_RPC_RETRY_COUNT = 3;
|
|
1323
1377
|
var PAID_RPC_RETRY_DELAY_MS = 1e3;
|
|
1324
1378
|
function configuredRpcProxyBaseUrl(baseUrl) {
|
|
@@ -1354,9 +1408,9 @@ function createPaidRpcClient(chain, rpcUrls) {
|
|
|
1354
1408
|
rpcUrls,
|
|
1355
1409
|
chain.id
|
|
1356
1410
|
);
|
|
1357
|
-
return (0,
|
|
1411
|
+
return (0, import_viem3.createPublicClient)({
|
|
1358
1412
|
chain,
|
|
1359
|
-
transport: (0,
|
|
1413
|
+
transport: (0, import_viem3.http)(url, {
|
|
1360
1414
|
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1361
1415
|
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
1362
1416
|
})
|
|
@@ -1453,7 +1507,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
|
1453
1507
|
}
|
|
1454
1508
|
|
|
1455
1509
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1456
|
-
var ERC7579_IS_MODULE_INSTALLED_ABI = (0,
|
|
1510
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem4.parseAbi)([
|
|
1457
1511
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1458
1512
|
]);
|
|
1459
1513
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
@@ -1465,6 +1519,7 @@ var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum, 1: i
|
|
|
1465
1519
|
var SPLITTING_ASSETS = ["USDC", "WETH"];
|
|
1466
1520
|
var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
|
|
1467
1521
|
var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1522
|
+
var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
|
|
1468
1523
|
var ZyfaiAgent = class _ZyfaiAgent {
|
|
1469
1524
|
id = "zyfai";
|
|
1470
1525
|
supportedChainIds = [8453, 42161, 1];
|
|
@@ -1604,7 +1659,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1604
1659
|
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
1605
1660
|
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
1606
1661
|
Object.assign(tx, {
|
|
1607
|
-
amount: (0,
|
|
1662
|
+
amount: (0, import_viem4.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
1608
1663
|
amountRaw: evidence.confirmedAmount,
|
|
1609
1664
|
blockNumber: evidence.blockNumber,
|
|
1610
1665
|
logIndexes: evidence.logIndexes,
|
|
@@ -2331,6 +2386,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2331
2386
|
evidence2 = { ...evidence2, transfer: "unknown" };
|
|
2332
2387
|
}
|
|
2333
2388
|
notifyFunding(onProgress, evidence2);
|
|
2389
|
+
if (evidence2.transfer === "failed")
|
|
2390
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
|
|
2334
2391
|
try {
|
|
2335
2392
|
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
2336
2393
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
@@ -2369,12 +2426,113 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2369
2426
|
} catch {
|
|
2370
2427
|
}
|
|
2371
2428
|
notifyFunding(onProgress, evidence);
|
|
2429
|
+
if (evidence.transfer === "failed")
|
|
2430
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
|
|
2372
2431
|
return { ...mapDeposit(raw2), evidence };
|
|
2373
2432
|
} catch (error) {
|
|
2374
2433
|
throw error;
|
|
2375
2434
|
}
|
|
2376
2435
|
}
|
|
2377
2436
|
// --- IAgent: Fund operations ---
|
|
2437
|
+
/** Only after a definitive endpoint rejection, combine a new provider
|
|
2438
|
+
* snapshot with the Safe's exact idle balance. Never parse its rounded error
|
|
2439
|
+
* amount as proof of zero, or use this to skip sending a new MAX request. */
|
|
2440
|
+
async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner) {
|
|
2441
|
+
const internals = this.sdk;
|
|
2442
|
+
const generation = this.earningsGeneration;
|
|
2443
|
+
let expired = false;
|
|
2444
|
+
const unavailable = () => new OwneyError(
|
|
2445
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
2446
|
+
"Could not verify Zyfai's balance after its withdrawal rejection.",
|
|
2447
|
+
{ chainId, token },
|
|
2448
|
+
this.id
|
|
2449
|
+
);
|
|
2450
|
+
const assertScope = () => {
|
|
2451
|
+
if (expired) throw unavailable();
|
|
2452
|
+
const account = internals.walletClient?.account?.address;
|
|
2453
|
+
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())
|
|
2454
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2455
|
+
};
|
|
2456
|
+
const read2 = async () => {
|
|
2457
|
+
assertScope();
|
|
2458
|
+
const [wallet, registered, portfolio] = await Promise.all([
|
|
2459
|
+
this.sdk.getSmartWalletAddress(owner, chainId),
|
|
2460
|
+
this.sdk.getSmartWalletByEOA(owner),
|
|
2461
|
+
this.sdk.getPortfolio(owner).catch(() => void 0)
|
|
2462
|
+
]);
|
|
2463
|
+
assertScope();
|
|
2464
|
+
if (!(0, import_viem4.isAddress)(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
|
|
2465
|
+
throw new OwneyError(
|
|
2466
|
+
"AGENT_AUTH_FAILED",
|
|
2467
|
+
"Could not verify Zyfai's withdrawal wallet.",
|
|
2468
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2469
|
+
this.id
|
|
2470
|
+
);
|
|
2471
|
+
if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
|
|
2472
|
+
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
2473
|
+
const idle = asset ? await this.getPublicClient(chainId).readContract({
|
|
2474
|
+
address: asset.address,
|
|
2475
|
+
abi: import_viem4.erc20Abi,
|
|
2476
|
+
functionName: "balanceOf",
|
|
2477
|
+
args: [wallet.address]
|
|
2478
|
+
}).catch(() => void 0) : void 0;
|
|
2479
|
+
assertScope();
|
|
2480
|
+
if (idle === 0n) throw new OwneyError(
|
|
2481
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2482
|
+
"No funds remain in Zyfai for this asset.",
|
|
2483
|
+
{ available: "0", chainId, token },
|
|
2484
|
+
this.id
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
let timer;
|
|
2489
|
+
try {
|
|
2490
|
+
await Promise.race([read2(), new Promise((_, reject) => {
|
|
2491
|
+
timer = setTimeout(() => {
|
|
2492
|
+
expired = true;
|
|
2493
|
+
reject(unavailable());
|
|
2494
|
+
}, WITHDRAW_BALANCE_TIMEOUT_MS);
|
|
2495
|
+
})]);
|
|
2496
|
+
} finally {
|
|
2497
|
+
expired = true;
|
|
2498
|
+
if (timer) clearTimeout(timer);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
/** Called only after the execution endpoint explicitly rejected a request.
|
|
2502
|
+
* A timeout or an accepted request never authorizes repair and resubmission. */
|
|
2503
|
+
async recoverWithdrawalSession(state, chainId, token, owner) {
|
|
2504
|
+
await this.assertNotEmptyRejectedWithdrawal(state, chainId, token, owner);
|
|
2505
|
+
const internals = this.sdk;
|
|
2506
|
+
const assertScope = () => {
|
|
2507
|
+
const account = internals.walletClient?.account?.address;
|
|
2508
|
+
if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
|
|
2509
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2510
|
+
};
|
|
2511
|
+
assertScope();
|
|
2512
|
+
const walletState = await this.readWalletState(owner);
|
|
2513
|
+
assertScope();
|
|
2514
|
+
const unavailable = () => new OwneyError(
|
|
2515
|
+
"AGENT_AUTH_FAILED",
|
|
2516
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
2517
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2518
|
+
this.id
|
|
2519
|
+
);
|
|
2520
|
+
if (walletState.predeployed !== false || internals.isPredeployed === true) throw unavailable();
|
|
2521
|
+
internals.hasActiveSessionKey = false;
|
|
2522
|
+
const key2 = `${owner.toLowerCase()}:${chainId}`;
|
|
2523
|
+
this.sessionKeyAttempts.delete(key2);
|
|
2524
|
+
try {
|
|
2525
|
+
const result = await this.sdk.createSessionKey(owner, chainId);
|
|
2526
|
+
assertScope();
|
|
2527
|
+
if (result.success === false || result.alreadyActive === true) throw unavailable();
|
|
2528
|
+
this.sessionKeyAttempts.add(key2);
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
assertScope();
|
|
2531
|
+
const code = error?.code;
|
|
2532
|
+
if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) throw error;
|
|
2533
|
+
throw unavailable();
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2378
2536
|
async withdraw(state, chainId, token, amount, onProgress) {
|
|
2379
2537
|
const validChainId = isValidChainId(chainId);
|
|
2380
2538
|
try {
|
|
@@ -2392,11 +2550,42 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2392
2550
|
});
|
|
2393
2551
|
throw error;
|
|
2394
2552
|
}
|
|
2395
|
-
|
|
2553
|
+
let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2396
2554
|
const authToken = this.sdk.httpClient?.authToken;
|
|
2555
|
+
const owner = this.getAddress();
|
|
2397
2556
|
let raw2;
|
|
2398
2557
|
try {
|
|
2399
|
-
|
|
2558
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2559
|
+
try {
|
|
2560
|
+
raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
|
|
2561
|
+
break;
|
|
2562
|
+
} catch (error) {
|
|
2563
|
+
if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
|
|
2564
|
+
try {
|
|
2565
|
+
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
|
|
2566
|
+
} catch (proofError) {
|
|
2567
|
+
if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
|
|
2571
|
+
if (attempt === 1)
|
|
2572
|
+
throw new OwneyError(
|
|
2573
|
+
"AGENT_AUTH_FAILED",
|
|
2574
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
2575
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2576
|
+
this.id
|
|
2577
|
+
);
|
|
2578
|
+
await this.recoverWithdrawalSession(state, validChainId, token, owner);
|
|
2579
|
+
observation.stop();
|
|
2580
|
+
observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (!raw2) throw new OwneyError(
|
|
2584
|
+
"AGENT_INVALID_RESPONSE",
|
|
2585
|
+
"Zyfai returned no withdrawal result.",
|
|
2586
|
+
{ chainId, token, phase: "observe" },
|
|
2587
|
+
this.id
|
|
2588
|
+
);
|
|
2400
2589
|
} catch (error) {
|
|
2401
2590
|
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
2402
2591
|
if (observation.attempted() !== void 0) {
|
|
@@ -2471,6 +2660,67 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2471
2660
|
async getFundingWallet(state, chainId) {
|
|
2472
2661
|
return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
|
|
2473
2662
|
}
|
|
2663
|
+
/** A positive Safe balance is a withdrawal planning floor when the portfolio
|
|
2664
|
+
* index reports zero. Idle zero cannot prove that Zyfai's protocol positions
|
|
2665
|
+
* are empty: its SDK does not expose a complete contract catalog for them. */
|
|
2666
|
+
async getWithdrawableBalance(state, chainId, token) {
|
|
2667
|
+
const validChainId = isValidChainId(chainId);
|
|
2668
|
+
const asset = SupportedAssets.find((value) => value.chainId === validChainId && value.symbol === token);
|
|
2669
|
+
if (!asset) throw new OwneyError("WITHDRAW_NO_PERMITTED_TOKENS", "Asset selected is not supported", void 0, this.id);
|
|
2670
|
+
const owner = state.walletAddress;
|
|
2671
|
+
const generation = this.earningsGeneration;
|
|
2672
|
+
const internals = this.sdk;
|
|
2673
|
+
let expired = false;
|
|
2674
|
+
const unavailable = () => new OwneyError(
|
|
2675
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
2676
|
+
"Could not verify Zyfai's withdrawal balance. Its vault positions may still contain funds.",
|
|
2677
|
+
{ reason: "ZYFAI_VAULT_BALANCE_UNVERIFIED", chainId, token },
|
|
2678
|
+
this.id
|
|
2679
|
+
);
|
|
2680
|
+
const assertScope = () => {
|
|
2681
|
+
if (expired) throw unavailable();
|
|
2682
|
+
const account = internals.walletClient?.account?.address;
|
|
2683
|
+
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())
|
|
2684
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while checking Zyfai's withdrawal balance.", void 0, this.id);
|
|
2685
|
+
};
|
|
2686
|
+
let timer;
|
|
2687
|
+
const read2 = async () => {
|
|
2688
|
+
assertScope();
|
|
2689
|
+
const [wallet, registered] = await Promise.all([
|
|
2690
|
+
this.sdk.getSmartWalletAddress(owner, validChainId),
|
|
2691
|
+
this.sdk.getSmartWalletByEOA(owner)
|
|
2692
|
+
]);
|
|
2693
|
+
assertScope();
|
|
2694
|
+
if (!(0, import_viem4.isAddress)(wallet.address) || wallet.isDeployed !== true || wallet.isOwner !== true || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
|
|
2695
|
+
const client = this.getPublicClient(validChainId);
|
|
2696
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
2697
|
+
assertScope();
|
|
2698
|
+
const idle = await client.readContract({
|
|
2699
|
+
address: asset.address,
|
|
2700
|
+
abi: import_viem4.erc20Abi,
|
|
2701
|
+
functionName: "balanceOf",
|
|
2702
|
+
args: [wallet.address],
|
|
2703
|
+
blockNumber
|
|
2704
|
+
});
|
|
2705
|
+
assertScope();
|
|
2706
|
+
if (typeof idle !== "bigint" || idle <= 0n) throw unavailable();
|
|
2707
|
+
return idle;
|
|
2708
|
+
};
|
|
2709
|
+
try {
|
|
2710
|
+
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
2711
|
+
timer = setTimeout(() => {
|
|
2712
|
+
expired = true;
|
|
2713
|
+
reject(unavailable());
|
|
2714
|
+
}, WITHDRAW_BALANCE_TIMEOUT_MS);
|
|
2715
|
+
})]);
|
|
2716
|
+
} catch (error) {
|
|
2717
|
+
if (error instanceof OwneyError) throw error;
|
|
2718
|
+
throw unavailable();
|
|
2719
|
+
} finally {
|
|
2720
|
+
expired = true;
|
|
2721
|
+
if (timer) clearTimeout(timer);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2474
2724
|
async getBalances(state, chainId) {
|
|
2475
2725
|
const requestedAt = Date.now();
|
|
2476
2726
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -2675,7 +2925,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2675
2925
|
};
|
|
2676
2926
|
|
|
2677
2927
|
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2678
|
-
var
|
|
2928
|
+
var import_viem11 = require("viem");
|
|
2679
2929
|
var import_chains4 = require("viem/chains");
|
|
2680
2930
|
|
|
2681
2931
|
// src/lib/chain-guard.ts
|
|
@@ -2714,7 +2964,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
|
|
|
2714
2964
|
}
|
|
2715
2965
|
|
|
2716
2966
|
// src/lib/transfer-auth.ts
|
|
2717
|
-
var
|
|
2967
|
+
var import_viem5 = require("viem");
|
|
2718
2968
|
|
|
2719
2969
|
// src/lib/sponsor-client.ts
|
|
2720
2970
|
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
@@ -2830,7 +3080,7 @@ async function postSponsorBatchTransfer(input) {
|
|
|
2830
3080
|
}
|
|
2831
3081
|
|
|
2832
3082
|
// src/lib/permit2.ts
|
|
2833
|
-
var
|
|
3083
|
+
var import_viem6 = require("viem");
|
|
2834
3084
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2835
3085
|
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2836
3086
|
function permit2ApprovalAmount(requiredAmount) {
|
|
@@ -2871,7 +3121,7 @@ var ERC20_ALLOWANCE_ABI = [
|
|
|
2871
3121
|
function randomPermit2Nonce() {
|
|
2872
3122
|
const bytes = new Uint8Array(32);
|
|
2873
3123
|
globalThis.crypto.getRandomValues(bytes);
|
|
2874
|
-
return BigInt((0,
|
|
3124
|
+
return BigInt((0, import_viem6.bytesToHex)(bytes));
|
|
2875
3125
|
}
|
|
2876
3126
|
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
2877
3127
|
return publicClient.readContract({
|
|
@@ -2914,7 +3164,7 @@ function makeVerificationAwareDepositCallback(implementation) {
|
|
|
2914
3164
|
|
|
2915
3165
|
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2916
3166
|
var import_siwe = require("siwe");
|
|
2917
|
-
var
|
|
3167
|
+
var import_viem7 = require("viem");
|
|
2918
3168
|
var import_chains2 = require("viem/chains");
|
|
2919
3169
|
|
|
2920
3170
|
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
@@ -3027,7 +3277,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
|
3027
3277
|
return new import_siwe.SiweMessage({
|
|
3028
3278
|
scheme: url.protocol.slice(0, -1),
|
|
3029
3279
|
domain: url.host,
|
|
3030
|
-
address: (0,
|
|
3280
|
+
address: (0, import_viem7.getAddress)(address),
|
|
3031
3281
|
uri: url.origin,
|
|
3032
3282
|
version: "1",
|
|
3033
3283
|
chainId,
|
|
@@ -3113,15 +3363,15 @@ var YieldseekerAuth = class {
|
|
|
3113
3363
|
clearYieldseekerSession(state.walletAddress, chainId);
|
|
3114
3364
|
}
|
|
3115
3365
|
async sign(state, chainId) {
|
|
3116
|
-
const account = (0,
|
|
3117
|
-
const publicClient = (0,
|
|
3366
|
+
const account = (0, import_viem7.getAddress)(state.walletAddress);
|
|
3367
|
+
const publicClient = (0, import_viem7.createPublicClient)({
|
|
3118
3368
|
chain: import_chains2.base,
|
|
3119
|
-
transport: (0,
|
|
3369
|
+
transport: (0, import_viem7.custom)(state.provider)
|
|
3120
3370
|
});
|
|
3121
|
-
const walletClient = (0,
|
|
3371
|
+
const walletClient = (0, import_viem7.createWalletClient)({
|
|
3122
3372
|
account,
|
|
3123
3373
|
chain: import_chains2.base,
|
|
3124
|
-
transport: (0,
|
|
3374
|
+
transport: (0, import_viem7.custom)(state.provider)
|
|
3125
3375
|
});
|
|
3126
3376
|
await ensureWalletOnChain(
|
|
3127
3377
|
publicClient,
|
|
@@ -3288,7 +3538,7 @@ var YieldseekerApiClient = class {
|
|
|
3288
3538
|
};
|
|
3289
3539
|
|
|
3290
3540
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
3291
|
-
var
|
|
3541
|
+
var import_viem8 = require("viem");
|
|
3292
3542
|
|
|
3293
3543
|
// src/lib/helpers/snapshot-apy.ts
|
|
3294
3544
|
var DAY_MS = 864e5;
|
|
@@ -3355,10 +3605,10 @@ function raw(value, endpoint) {
|
|
|
3355
3605
|
return BigInt(value);
|
|
3356
3606
|
}
|
|
3357
3607
|
function decimal(value, decimals, endpoint) {
|
|
3358
|
-
return (0,
|
|
3608
|
+
return (0, import_viem8.formatUnits)(raw(value, endpoint), decimals);
|
|
3359
3609
|
}
|
|
3360
3610
|
function usd(rawAmount, decimals, price) {
|
|
3361
|
-
return Number((0,
|
|
3611
|
+
return Number((0, import_viem8.formatUnits)(rawAmount, decimals)) * price;
|
|
3362
3612
|
}
|
|
3363
3613
|
function percent(value) {
|
|
3364
3614
|
const result = Number(value);
|
|
@@ -3384,7 +3634,7 @@ function assetAddressValue(record, address) {
|
|
|
3384
3634
|
}
|
|
3385
3635
|
function position(value, asset, baseAssetDecimals) {
|
|
3386
3636
|
const option = value?.yieldOption;
|
|
3387
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0,
|
|
3637
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem8.isAddress)(option.address)) {
|
|
3388
3638
|
return invalid("yield positions", "missing vault metadata");
|
|
3389
3639
|
}
|
|
3390
3640
|
return {
|
|
@@ -3476,7 +3726,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
3476
3726
|
chain: "BASE",
|
|
3477
3727
|
chainId: 8453,
|
|
3478
3728
|
asset: context.asset,
|
|
3479
|
-
amount: (0,
|
|
3729
|
+
amount: (0, import_viem8.formatUnits)(amount, context.snapshot.baseAssetDecimals)
|
|
3480
3730
|
});
|
|
3481
3731
|
lifetimeEarnings += usd(
|
|
3482
3732
|
amount,
|
|
@@ -3605,7 +3855,7 @@ function allocationEntry(action, context) {
|
|
|
3605
3855
|
const leg = deposit;
|
|
3606
3856
|
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
3607
3857
|
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
3608
|
-
if (typeof leg.vaultAddress !== "string" || !(0,
|
|
3858
|
+
if (typeof leg.vaultAddress !== "string" || !(0, import_viem8.isAddress)(leg.vaultAddress)) return void 0;
|
|
3609
3859
|
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;
|
|
3610
3860
|
if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
|
|
3611
3861
|
const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
|
|
@@ -3613,7 +3863,7 @@ function allocationEntry(action, context) {
|
|
|
3613
3863
|
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
3614
3864
|
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
3615
3865
|
tokenSymbol: context.asset,
|
|
3616
|
-
amount: (0,
|
|
3866
|
+
amount: (0, import_viem8.formatUnits)(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3617
3867
|
});
|
|
3618
3868
|
amountRaw += rawAmount;
|
|
3619
3869
|
}
|
|
@@ -3632,7 +3882,7 @@ function allocationEntry(action, context) {
|
|
|
3632
3882
|
tokenSymbol: context.asset,
|
|
3633
3883
|
agentWallet: context.wallet.walletAddress,
|
|
3634
3884
|
amountRaw: String(amountRaw),
|
|
3635
|
-
amount: (0,
|
|
3885
|
+
amount: (0, import_viem8.formatUnits)(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3636
3886
|
}]
|
|
3637
3887
|
};
|
|
3638
3888
|
}
|
|
@@ -3756,7 +4006,7 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3756
4006
|
const known = new Set(target.logIndexes ?? []);
|
|
3757
4007
|
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
3758
4008
|
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
3759
|
-
target.amount = (0,
|
|
4009
|
+
target.amount = (0, import_viem8.formatUnits)(
|
|
3760
4010
|
BigInt(target.amountRaw),
|
|
3761
4011
|
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
3762
4012
|
);
|
|
@@ -3837,51 +4087,53 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
3837
4087
|
}
|
|
3838
4088
|
|
|
3839
4089
|
// src/agents/yieldseeker/yieldseeker.wallet-version.ts
|
|
3840
|
-
var
|
|
4090
|
+
var import_viem9 = require("viem");
|
|
3841
4091
|
var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
|
|
3842
4092
|
var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
|
|
3843
4093
|
var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
|
|
3844
4094
|
var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
3845
|
-
var walletAbi = (0,
|
|
4095
|
+
var walletAbi = (0, import_viem9.parseAbi)([
|
|
3846
4096
|
"function owner() view returns (address)",
|
|
3847
4097
|
"function ownerAgentIndex() view returns (uint256)",
|
|
3848
4098
|
"function baseAsset() view returns (address)",
|
|
3849
4099
|
"function FACTORY() view returns (address)",
|
|
3850
|
-
"function upgradeToLatest()"
|
|
4100
|
+
"function upgradeToLatest()",
|
|
4101
|
+
"event Upgraded(address indexed implementation)"
|
|
3851
4102
|
]);
|
|
3852
|
-
var factoryAbi = (0,
|
|
4103
|
+
var factoryAbi = (0, import_viem9.parseAbi)([
|
|
3853
4104
|
"function userWallets(address owner, uint256 index) view returns (address)",
|
|
3854
4105
|
"function agentWalletImplementation() view returns (address)"
|
|
3855
4106
|
]);
|
|
3856
4107
|
function yieldseekerOwnerPath(code) {
|
|
3857
4108
|
return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
|
|
3858
4109
|
}
|
|
3859
|
-
async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
3860
|
-
const agentWallet = (0,
|
|
3861
|
-
const expectedOwner = (0,
|
|
3862
|
-
const expectedAsset = (0,
|
|
4110
|
+
async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
|
|
4111
|
+
const agentWallet = (0, import_viem9.getAddress)(wallet);
|
|
4112
|
+
const expectedOwner = (0, import_viem9.getAddress)(owner);
|
|
4113
|
+
const expectedAsset = (0, import_viem9.getAddress)(asset);
|
|
3863
4114
|
const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
|
|
3864
|
-
client.getBytecode({ address: agentWallet }),
|
|
3865
|
-
client.getBytecode({ address: expectedOwner }),
|
|
3866
|
-
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
|
|
3867
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
|
|
3868
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
|
|
3869
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
|
|
3870
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
|
|
3871
|
-
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
|
|
4115
|
+
client.getBytecode({ address: agentWallet, blockNumber }),
|
|
4116
|
+
client.getBytecode({ address: expectedOwner, blockNumber }),
|
|
4117
|
+
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT, blockNumber }),
|
|
4118
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner", blockNumber }),
|
|
4119
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex", blockNumber }),
|
|
4120
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset", blockNumber }),
|
|
4121
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY", blockNumber }),
|
|
4122
|
+
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation", blockNumber })
|
|
3872
4123
|
]);
|
|
3873
4124
|
if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
|
|
3874
4125
|
throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
|
|
3875
4126
|
}
|
|
3876
|
-
const implementation = (0,
|
|
4127
|
+
const implementation = (0, import_viem9.getAddress)(`0x${slot.slice(-40)}`);
|
|
3877
4128
|
const mapped = await client.readContract({
|
|
3878
4129
|
address: YIELDSEEKER_FACTORY,
|
|
3879
4130
|
abi: factoryAbi,
|
|
3880
4131
|
functionName: "userWallets",
|
|
3881
|
-
args: [expectedOwner, index]
|
|
4132
|
+
args: [expectedOwner, index],
|
|
4133
|
+
blockNumber
|
|
3882
4134
|
});
|
|
3883
|
-
const verified = (0,
|
|
3884
|
-
const version = !verified ? "unknown" : (0,
|
|
4135
|
+
const verified = (0, import_viem9.isAddressEqual)(actualOwner, expectedOwner) && (0, import_viem9.isAddressEqual)(actualAsset, expectedAsset) && (0, import_viem9.isAddressEqual)(actualFactory, YIELDSEEKER_FACTORY) && (0, import_viem9.isAddressEqual)(mapped, agentWallet);
|
|
4136
|
+
const version = !verified ? "unknown" : (0, import_viem9.isAddressEqual)(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : (0, import_viem9.isAddressEqual)(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
|
|
3885
4137
|
return {
|
|
3886
4138
|
agentWallet,
|
|
3887
4139
|
owner: actualOwner,
|
|
@@ -3890,18 +4142,20 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
|
3890
4142
|
implementation,
|
|
3891
4143
|
version,
|
|
3892
4144
|
verified,
|
|
3893
|
-
upgradeAvailable: version === "v1" && (0,
|
|
4145
|
+
upgradeAvailable: version === "v1" && (0, import_viem9.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
|
|
3894
4146
|
};
|
|
3895
4147
|
}
|
|
3896
4148
|
var YIELDSEEKER_UPGRADE_ABI = walletAbi;
|
|
3897
4149
|
|
|
3898
4150
|
// src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
|
|
3899
|
-
var
|
|
4151
|
+
var import_viem10 = require("viem");
|
|
3900
4152
|
var import_account_abstraction2 = require("viem/account-abstraction");
|
|
3901
4153
|
var import_accounts = require("viem/accounts");
|
|
3902
4154
|
var import_chains3 = require("viem/chains");
|
|
3903
4155
|
var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
3904
4156
|
var USER_OP_FEE_HEADROOM = 2n;
|
|
4157
|
+
var OBSERVATION_WINDOW_MS = 6e4;
|
|
4158
|
+
var OBSERVATION_POLL_MS = 1500;
|
|
3905
4159
|
var ENTRY_POINT_NONCE_ABI = [
|
|
3906
4160
|
{ type: "function", name: "getNonce", stateMutability: "view", inputs: [
|
|
3907
4161
|
{ name: "sender", type: "address" },
|
|
@@ -3934,10 +4188,119 @@ function sponsorError(message, safeToFallback, details = {}) {
|
|
|
3934
4188
|
return new OwneyError(
|
|
3935
4189
|
"SPONSOR_REQUEST_FAILED",
|
|
3936
4190
|
message,
|
|
3937
|
-
{
|
|
4191
|
+
{
|
|
4192
|
+
safeToFallback,
|
|
4193
|
+
phase: safeToFallback ? "prepare" : "observe",
|
|
4194
|
+
failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
|
|
4195
|
+
...safeToFallback ? { rejectedBeforeSubmission: true } : {},
|
|
4196
|
+
...details
|
|
4197
|
+
},
|
|
3938
4198
|
"yieldseeker"
|
|
3939
4199
|
);
|
|
3940
4200
|
}
|
|
4201
|
+
function providerReason(value) {
|
|
4202
|
+
if (typeof value !== "string") return void 0;
|
|
4203
|
+
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);
|
|
4204
|
+
}
|
|
4205
|
+
function rpcDiagnostics(data) {
|
|
4206
|
+
const details = {};
|
|
4207
|
+
if (typeof data?.failureCode === "string" && [
|
|
4208
|
+
"SPONSOR_RATE_LIMIT",
|
|
4209
|
+
"SPONSOR_UPSTREAM_UNAVAILABLE",
|
|
4210
|
+
"SPONSOR_RPC_UNSUPPORTED",
|
|
4211
|
+
"SPONSOR_POLICY_REJECTED",
|
|
4212
|
+
"SPONSOR_BUDGET_REJECTED",
|
|
4213
|
+
"SPONSOR_EXECUTION_REVERTED"
|
|
4214
|
+
].includes(data.failureCode))
|
|
4215
|
+
details.failureCode = data.failureCode;
|
|
4216
|
+
if (typeof data?.revertSelector === "string" && /^0x[0-9a-fA-F]{8}$/.test(data.revertSelector))
|
|
4217
|
+
details.revertSelector = data.revertSelector;
|
|
4218
|
+
if (typeof data?.revertName === "string" && [
|
|
4219
|
+
"InsufficientBalance",
|
|
4220
|
+
"InvalidAsset",
|
|
4221
|
+
"Unauthorized",
|
|
4222
|
+
"Error",
|
|
4223
|
+
"Panic",
|
|
4224
|
+
"FailedOp",
|
|
4225
|
+
"FailedOpWithRevert"
|
|
4226
|
+
].includes(data.revertName))
|
|
4227
|
+
details.revertName = data.revertName;
|
|
4228
|
+
if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
|
|
4229
|
+
if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
|
|
4230
|
+
const panicCode = (0, import_viem10.toHex)(BigInt(data.panicCode));
|
|
4231
|
+
if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
|
|
4232
|
+
details.panicCode = panicCode;
|
|
4233
|
+
}
|
|
4234
|
+
return details;
|
|
4235
|
+
}
|
|
4236
|
+
function isHash(value) {
|
|
4237
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
4238
|
+
}
|
|
4239
|
+
async function assertOwnerSession(state, owner) {
|
|
4240
|
+
const [chain, accounts] = await Promise.all([
|
|
4241
|
+
state.provider.request({ method: "eth_chainId" }),
|
|
4242
|
+
state.provider.request({ method: "eth_accounts" })
|
|
4243
|
+
]);
|
|
4244
|
+
if (Number(chain) !== 8453)
|
|
4245
|
+
throw new OwneyError(
|
|
4246
|
+
"CHAIN_MISMATCH",
|
|
4247
|
+
"Switch back to Base before withdrawing.",
|
|
4248
|
+
{ rejectedBeforeSubmission: true, phase: "prepare" },
|
|
4249
|
+
"yieldseeker"
|
|
4250
|
+
);
|
|
4251
|
+
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase())
|
|
4252
|
+
throw new OwneyError(
|
|
4253
|
+
"NOT_CONNECTED",
|
|
4254
|
+
"The connected withdrawal account changed.",
|
|
4255
|
+
{ rejectedBeforeSubmission: true, phase: "prepare" },
|
|
4256
|
+
"yieldseeker"
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
function savePending(onPending, evidence, beforeSubmission = false) {
|
|
4260
|
+
try {
|
|
4261
|
+
onPending?.(evidence);
|
|
4262
|
+
} catch {
|
|
4263
|
+
if (beforeSubmission)
|
|
4264
|
+
throw sponsorError(
|
|
4265
|
+
"Withdrawal status cannot be saved before submission.",
|
|
4266
|
+
true,
|
|
4267
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
|
|
4268
|
+
);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
async function checkpoint(callback, pending) {
|
|
4272
|
+
try {
|
|
4273
|
+
await callback?.(pending);
|
|
4274
|
+
} catch {
|
|
4275
|
+
throw sponsorError(
|
|
4276
|
+
"Withdrawal status cannot be saved before submission.",
|
|
4277
|
+
true,
|
|
4278
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
|
|
4279
|
+
);
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
function finalPaymasterFields(value) {
|
|
4283
|
+
const result = {};
|
|
4284
|
+
for (const key2 of [
|
|
4285
|
+
"paymasterAndData",
|
|
4286
|
+
"callGasLimit",
|
|
4287
|
+
"verificationGasLimit",
|
|
4288
|
+
"preVerificationGas",
|
|
4289
|
+
"maxFeePerGas",
|
|
4290
|
+
"maxPriorityFeePerGas"
|
|
4291
|
+
]) {
|
|
4292
|
+
const field = value[key2];
|
|
4293
|
+
if (field === void 0) continue;
|
|
4294
|
+
if (typeof field !== "string" || !/^0x[0-9a-fA-F]+$/.test(field))
|
|
4295
|
+
throw sponsorError(
|
|
4296
|
+
"Paymaster returned an invalid final operation.",
|
|
4297
|
+
true,
|
|
4298
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INVALID_PAYMASTER_QUOTE" }
|
|
4299
|
+
);
|
|
4300
|
+
result[key2] = field;
|
|
4301
|
+
}
|
|
4302
|
+
return result;
|
|
4303
|
+
}
|
|
3941
4304
|
async function requestIntent(input) {
|
|
3942
4305
|
const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
|
|
3943
4306
|
let response;
|
|
@@ -3959,18 +4322,35 @@ async function requestIntent(input) {
|
|
|
3959
4322
|
action: input.action,
|
|
3960
4323
|
yieldseekerUserId: input.userId,
|
|
3961
4324
|
yieldseekerAgentId: input.agentId
|
|
3962
|
-
})
|
|
4325
|
+
}),
|
|
4326
|
+
signal: AbortSignal.timeout(15e3)
|
|
3963
4327
|
});
|
|
3964
4328
|
} catch {
|
|
3965
|
-
throw sponsorError(
|
|
4329
|
+
throw sponsorError(
|
|
4330
|
+
"Withdrawal sponsorship could not be requested.",
|
|
4331
|
+
true,
|
|
4332
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
|
|
4333
|
+
);
|
|
3966
4334
|
}
|
|
3967
4335
|
const envelope = await response.json().catch(() => null);
|
|
3968
4336
|
const intent = envelope?.data;
|
|
3969
4337
|
if (!response.ok || !envelope?.success || !intent?.intent || !["eoa", "smart"].includes(intent.ownerPath)) {
|
|
4338
|
+
const rawCode = typeof envelope?.failureCode === "string" ? envelope.failureCode : envelope?.code;
|
|
4339
|
+
const failureCode = typeof rawCode === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(rawCode) ? rawCode : void 0;
|
|
4340
|
+
if (input.refreshYieldseekerSignature && (failureCode === "YIELDSEEKER_AUTH_REJECTED" || failureCode === "AUTH_REJECTED" || envelope?.sponsorshipStage === "provider-auth" && (envelope.providerStatus === 401 || envelope.providerStatus === 403))) {
|
|
4341
|
+
const signature = await input.refreshYieldseekerSignature(input.yieldseekerSignature);
|
|
4342
|
+
return requestIntent({ ...input, yieldseekerSignature: signature, refreshYieldseekerSignature: void 0 });
|
|
4343
|
+
}
|
|
3970
4344
|
throw sponsorError(
|
|
3971
4345
|
"Withdrawal sponsorship is currently unavailable.",
|
|
3972
4346
|
true,
|
|
3973
|
-
{
|
|
4347
|
+
{
|
|
4348
|
+
statusCode: response.status,
|
|
4349
|
+
phase: "prepare",
|
|
4350
|
+
rejectedBeforeSubmission: true,
|
|
4351
|
+
failureCode: failureCode ?? "INTENT_REJECTED",
|
|
4352
|
+
providerReason: providerReason(envelope?.message)
|
|
4353
|
+
}
|
|
3974
4354
|
);
|
|
3975
4355
|
}
|
|
3976
4356
|
const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
|
|
@@ -3983,19 +4363,55 @@ async function rpc(url, method, params, phase) {
|
|
|
3983
4363
|
response = await fetch(url, {
|
|
3984
4364
|
method: "POST",
|
|
3985
4365
|
headers: { "content-type": "application/json" },
|
|
3986
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
4366
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
4367
|
+
signal: AbortSignal.timeout(15e3)
|
|
3987
4368
|
});
|
|
3988
4369
|
} catch {
|
|
3989
|
-
throw sponsorError(
|
|
4370
|
+
throw sponsorError(
|
|
4371
|
+
"Withdrawal sponsorship request status is unknown.",
|
|
4372
|
+
phase === "prepare",
|
|
4373
|
+
{
|
|
4374
|
+
phase,
|
|
4375
|
+
rpcMethod: method,
|
|
4376
|
+
...phase === "prepare" ? { rejectedBeforeSubmission: true } : {},
|
|
4377
|
+
failureCode: "RPC_NETWORK_ERROR"
|
|
4378
|
+
}
|
|
4379
|
+
);
|
|
3990
4380
|
}
|
|
3991
4381
|
const payload = await response.json().catch(() => null);
|
|
3992
4382
|
if (!response.ok || payload?.error || payload?.result === void 0) {
|
|
3993
4383
|
const rejection = payload?.error;
|
|
3994
|
-
const
|
|
4384
|
+
const diagnostics = rpcDiagnostics(rejection?.data);
|
|
4385
|
+
const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
|
|
4386
|
+
const details = {
|
|
4387
|
+
statusCode: response.status,
|
|
4388
|
+
rpcCode: rejection?.code,
|
|
4389
|
+
rpcMethod: method,
|
|
4390
|
+
phase,
|
|
4391
|
+
providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
|
|
4392
|
+
failureCode: "RPC_REJECTED",
|
|
4393
|
+
...diagnostics
|
|
4394
|
+
};
|
|
4395
|
+
if (phase === "prepare" && executionReverted)
|
|
4396
|
+
throw new OwneyError(
|
|
4397
|
+
"WITHDRAW_FAILED",
|
|
4398
|
+
"Yieldseeker withdrawal simulation reverted before submission.",
|
|
4399
|
+
{
|
|
4400
|
+
...details,
|
|
4401
|
+
failureCode: "SPONSOR_EXECUTION_REVERTED",
|
|
4402
|
+
safeToFallback: false,
|
|
4403
|
+
rejectedBeforeSubmission: true
|
|
4404
|
+
},
|
|
4405
|
+
"yieldseeker"
|
|
4406
|
+
);
|
|
4407
|
+
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 ?? ""));
|
|
3995
4408
|
throw sponsorError(
|
|
3996
4409
|
phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
|
|
3997
4410
|
phase === "prepare" || definitiveRejection,
|
|
3998
|
-
{
|
|
4411
|
+
{
|
|
4412
|
+
...details,
|
|
4413
|
+
...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
|
|
4414
|
+
}
|
|
3999
4415
|
);
|
|
4000
4416
|
}
|
|
4001
4417
|
return payload.result;
|
|
@@ -4015,13 +4431,15 @@ async function submitEoa(input) {
|
|
|
4015
4431
|
let nonce;
|
|
4016
4432
|
let fees;
|
|
4017
4433
|
try {
|
|
4018
|
-
nonce = await
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4434
|
+
[nonce, fees] = await Promise.all([
|
|
4435
|
+
input.client.readContract({
|
|
4436
|
+
address: ENTRY_POINT_V06,
|
|
4437
|
+
abi: ENTRY_POINT_NONCE_ABI,
|
|
4438
|
+
functionName: "getNonce",
|
|
4439
|
+
args: [input.agentWallet, 0n]
|
|
4440
|
+
}),
|
|
4441
|
+
input.client.estimateFeesPerGas()
|
|
4442
|
+
]);
|
|
4025
4443
|
} catch {
|
|
4026
4444
|
throw sponsorError("Could not prepare the sponsored withdrawal.", true);
|
|
4027
4445
|
}
|
|
@@ -4029,14 +4447,14 @@ async function submitEoa(input) {
|
|
|
4029
4447
|
throw sponsorError("Base did not return EIP-1559 gas fees.", true);
|
|
4030
4448
|
let operation = {
|
|
4031
4449
|
sender: input.agentWallet,
|
|
4032
|
-
nonce: (0,
|
|
4450
|
+
nonce: (0, import_viem10.toHex)(nonce),
|
|
4033
4451
|
initCode: "0x",
|
|
4034
4452
|
callData: input.callData,
|
|
4035
|
-
callGasLimit: (0,
|
|
4036
|
-
verificationGasLimit: (0,
|
|
4037
|
-
preVerificationGas: (0,
|
|
4038
|
-
maxFeePerGas: (0,
|
|
4039
|
-
maxPriorityFeePerGas: (0,
|
|
4453
|
+
callGasLimit: (0, import_viem10.toHex)(3e5),
|
|
4454
|
+
verificationGasLimit: (0, import_viem10.toHex)(3e5),
|
|
4455
|
+
preVerificationGas: (0, import_viem10.toHex)(6e4),
|
|
4456
|
+
maxFeePerGas: (0, import_viem10.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
|
|
4457
|
+
maxPriorityFeePerGas: (0, import_viem10.toHex)(fees.maxPriorityFeePerGas),
|
|
4040
4458
|
paymasterAndData: "0x",
|
|
4041
4459
|
signature: "0x"
|
|
4042
4460
|
};
|
|
@@ -4044,7 +4462,7 @@ async function submitEoa(input) {
|
|
|
4044
4462
|
const stub = await rpc(
|
|
4045
4463
|
input.rpcUrl,
|
|
4046
4464
|
"pm_getPaymasterStubData",
|
|
4047
|
-
[operation, ENTRY_POINT_V06, (0,
|
|
4465
|
+
[operation, ENTRY_POINT_V06, (0, import_viem10.toHex)(8453), {}],
|
|
4048
4466
|
"prepare"
|
|
4049
4467
|
);
|
|
4050
4468
|
if (!stub.paymasterAndData?.startsWith("0x"))
|
|
@@ -4059,39 +4477,63 @@ async function submitEoa(input) {
|
|
|
4059
4477
|
const final = await rpc(
|
|
4060
4478
|
input.rpcUrl,
|
|
4061
4479
|
"pm_getPaymasterData",
|
|
4062
|
-
[operation, ENTRY_POINT_V06, (0,
|
|
4480
|
+
[operation, ENTRY_POINT_V06, (0, import_viem10.toHex)(8453), {}],
|
|
4063
4481
|
"prepare"
|
|
4064
4482
|
);
|
|
4065
4483
|
if (!final.paymasterAndData?.startsWith("0x"))
|
|
4066
4484
|
throw sponsorError("CDP did not return final paymaster data.", true);
|
|
4067
|
-
operation = { ...operation,
|
|
4485
|
+
operation = { ...operation, ...finalPaymasterFields(final) };
|
|
4068
4486
|
}
|
|
4069
4487
|
const userOpHash = operationHash(operation);
|
|
4070
|
-
|
|
4488
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4489
|
+
const wallet = (0, import_viem10.createWalletClient)({
|
|
4071
4490
|
account: input.owner,
|
|
4072
4491
|
chain: import_chains3.base,
|
|
4073
|
-
transport: (0,
|
|
4492
|
+
transport: (0, import_viem10.custom)(input.state.provider)
|
|
4074
4493
|
});
|
|
4075
4494
|
const signature = await wallet.signMessage({ message: { raw: userOpHash } });
|
|
4076
4495
|
operation = { ...operation, signature };
|
|
4077
|
-
input.
|
|
4496
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4497
|
+
const userOperation = {
|
|
4498
|
+
sender: operation.sender,
|
|
4499
|
+
nonce: operation.nonce,
|
|
4500
|
+
initCode: operation.initCode,
|
|
4501
|
+
callData: operation.callData,
|
|
4502
|
+
callGasLimit: operation.callGasLimit,
|
|
4503
|
+
verificationGasLimit: operation.verificationGasLimit,
|
|
4504
|
+
preVerificationGas: operation.preVerificationGas,
|
|
4505
|
+
maxFeePerGas: operation.maxFeePerGas,
|
|
4506
|
+
maxPriorityFeePerGas: operation.maxPriorityFeePerGas,
|
|
4507
|
+
paymasterAndData: operation.paymasterAndData
|
|
4508
|
+
};
|
|
4509
|
+
savePending(input.onPending, { userOpHash, userOperation }, true);
|
|
4510
|
+
await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
|
|
4078
4511
|
const submittedHash = await rpc(
|
|
4079
4512
|
input.rpcUrl,
|
|
4080
4513
|
"eth_sendUserOperation",
|
|
4081
4514
|
[operation, ENTRY_POINT_V06],
|
|
4082
4515
|
"send"
|
|
4083
4516
|
);
|
|
4084
|
-
if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4517
|
+
if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4085
4518
|
throw sponsorError(
|
|
4086
4519
|
"Bundler returned a different UserOperation hash.",
|
|
4087
4520
|
false,
|
|
4088
|
-
{ userOpHash, submittedHash }
|
|
4521
|
+
{ userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
|
|
4089
4522
|
);
|
|
4090
|
-
|
|
4091
|
-
|
|
4523
|
+
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
4524
|
+
let lastReadError;
|
|
4525
|
+
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
4526
|
+
let response;
|
|
4527
|
+
try {
|
|
4528
|
+
response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
|
|
4529
|
+
} catch (error) {
|
|
4530
|
+
lastReadError = error;
|
|
4531
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4532
|
+
continue;
|
|
4533
|
+
}
|
|
4092
4534
|
const txHash = response?.receipt?.transactionHash;
|
|
4093
|
-
if (txHash) {
|
|
4094
|
-
input.onPending
|
|
4535
|
+
if (isHash(txHash)) {
|
|
4536
|
+
savePending(input.onPending, { userOpHash, txHash });
|
|
4095
4537
|
if (response?.success === false)
|
|
4096
4538
|
throw new OwneyError(
|
|
4097
4539
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -4101,12 +4543,17 @@ async function submitEoa(input) {
|
|
|
4101
4543
|
);
|
|
4102
4544
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4103
4545
|
}
|
|
4104
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4546
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4105
4547
|
}
|
|
4106
4548
|
throw sponsorError(
|
|
4107
4549
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4108
4550
|
false,
|
|
4109
|
-
{
|
|
4551
|
+
{
|
|
4552
|
+
userOpHash,
|
|
4553
|
+
phase: "observe",
|
|
4554
|
+
failureCode: "RECEIPT_UNRESOLVED",
|
|
4555
|
+
...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
|
|
4556
|
+
}
|
|
4110
4557
|
);
|
|
4111
4558
|
}
|
|
4112
4559
|
async function submitSmart(input) {
|
|
@@ -4119,21 +4566,22 @@ async function submitSmart(input) {
|
|
|
4119
4566
|
} catch {
|
|
4120
4567
|
throw sponsorError("This account cannot check paymaster support on Base.", true);
|
|
4121
4568
|
}
|
|
4122
|
-
|
|
4569
|
+
const supportsPaymaster = capabilities?.[(0, import_viem10.toHex)(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
|
|
4570
|
+
if (supportsPaymaster !== true)
|
|
4123
4571
|
throw sponsorError("This account cannot use a paymaster on Base.", true);
|
|
4572
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4124
4573
|
let sent;
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
|
|
4129
|
-
}
|
|
4574
|
+
const requestedId = (0, import_viem10.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
4575
|
+
savePending(input.onPending, { callsId: requestedId }, true);
|
|
4576
|
+
await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
|
|
4130
4577
|
try {
|
|
4131
4578
|
sent = await input.state.provider.request({
|
|
4132
4579
|
method: "wallet_sendCalls",
|
|
4133
4580
|
params: [{
|
|
4134
4581
|
version: "2.0.0",
|
|
4135
4582
|
from: input.owner,
|
|
4136
|
-
chainId: (0,
|
|
4583
|
+
chainId: (0, import_viem10.toHex)(8453),
|
|
4584
|
+
id: requestedId,
|
|
4137
4585
|
atomicRequired: false,
|
|
4138
4586
|
calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
|
|
4139
4587
|
capabilities: { paymasterService: { url: input.rpcUrl } }
|
|
@@ -4142,17 +4590,27 @@ async function submitSmart(input) {
|
|
|
4142
4590
|
} catch (error) {
|
|
4143
4591
|
if (error?.code === 4001) throw error;
|
|
4144
4592
|
if ([4200, -32601].includes(Number(error?.code)))
|
|
4145
|
-
throw sponsorError(
|
|
4146
|
-
|
|
4593
|
+
throw sponsorError(
|
|
4594
|
+
"This account does not support sponsored calls.",
|
|
4595
|
+
true,
|
|
4596
|
+
{ phase: "send", failureCode: "WALLET_CALLS_UNSUPPORTED" }
|
|
4597
|
+
);
|
|
4598
|
+
throw sponsorError(
|
|
4599
|
+
"Wallet submission status is unknown. Check activity before retrying.",
|
|
4600
|
+
false,
|
|
4601
|
+
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
4602
|
+
);
|
|
4147
4603
|
}
|
|
4148
4604
|
const callsId = typeof sent === "string" ? sent : sent?.id;
|
|
4149
|
-
if (
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4605
|
+
if (typeof callsId !== "string" || callsId.length === 0 || callsId.length > 8194)
|
|
4606
|
+
throw sponsorError(
|
|
4607
|
+
"Wallet returned no calls ID; submission status is unknown.",
|
|
4608
|
+
false,
|
|
4609
|
+
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
4610
|
+
);
|
|
4611
|
+
savePending(input.onPending, { callsId });
|
|
4612
|
+
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
4613
|
+
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
4156
4614
|
let status;
|
|
4157
4615
|
try {
|
|
4158
4616
|
status = await input.state.provider.request({
|
|
@@ -4160,37 +4618,44 @@ async function submitSmart(input) {
|
|
|
4160
4618
|
params: [callsId]
|
|
4161
4619
|
});
|
|
4162
4620
|
} catch {
|
|
4163
|
-
|
|
4621
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4622
|
+
continue;
|
|
4164
4623
|
}
|
|
4165
|
-
if (status?.status
|
|
4624
|
+
if (status?.id !== void 0 && status.id !== callsId || status?.chainId !== void 0 && Number(status.chainId) !== 8453)
|
|
4625
|
+
throw sponsorError(
|
|
4626
|
+
"Wallet returned a different sponsored operation status.",
|
|
4627
|
+
false,
|
|
4628
|
+
{ callsId, phase: "observe", failureCode: "WALLET_STATUS_MISMATCH" }
|
|
4629
|
+
);
|
|
4630
|
+
if (Number(status?.status) === 400 || Number(status?.status) === 500)
|
|
4166
4631
|
throw sponsorError(
|
|
4167
4632
|
"Sponsored withdrawal failed in the wallet.",
|
|
4168
4633
|
false,
|
|
4169
|
-
{ callsId, executionFailed: true }
|
|
4634
|
+
{ callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
|
|
4170
4635
|
);
|
|
4171
|
-
const
|
|
4636
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
|
|
4637
|
+
const txHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4172
4638
|
if (txHash) {
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
"
|
|
4178
|
-
|
|
4179
|
-
|
|
4639
|
+
savePending(input.onPending, { callsId, txHash });
|
|
4640
|
+
if (status?.receipts?.some((receipt) => receipt.transactionHash === txHash && /^(?:0x0+|0|failed|reverted)$/i.test(String(receipt.status))))
|
|
4641
|
+
throw new OwneyError(
|
|
4642
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
4643
|
+
"Sponsored withdrawal execution reverted.",
|
|
4644
|
+
{ callsId, transactionHash: txHash },
|
|
4645
|
+
"yieldseeker"
|
|
4180
4646
|
);
|
|
4181
|
-
}
|
|
4182
4647
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4183
4648
|
}
|
|
4184
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4649
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4185
4650
|
}
|
|
4186
4651
|
throw sponsorError(
|
|
4187
4652
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4188
4653
|
false,
|
|
4189
|
-
{ callsId }
|
|
4654
|
+
{ callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
|
|
4190
4655
|
);
|
|
4191
4656
|
}
|
|
4192
4657
|
async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
4193
|
-
const owner = (0,
|
|
4658
|
+
const owner = (0, import_viem10.getAddress)(input.state.walletAddress);
|
|
4194
4659
|
const { intent, rpcUrl } = await requestIntent({
|
|
4195
4660
|
apiKey: input.apiKey,
|
|
4196
4661
|
routingApiBaseUrl: input.routingApiBaseUrl,
|
|
@@ -4201,13 +4666,14 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
|
4201
4666
|
agentWallet: input.agentWallet,
|
|
4202
4667
|
asset: input.asset,
|
|
4203
4668
|
callData: input.callData,
|
|
4204
|
-
action: "withdraw"
|
|
4669
|
+
action: "withdraw",
|
|
4670
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4205
4671
|
});
|
|
4206
4672
|
const args = { ...input, owner, rpcUrl };
|
|
4207
4673
|
return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
|
|
4208
4674
|
}
|
|
4209
4675
|
async function submitYieldseekerSponsoredUpgrade(input) {
|
|
4210
|
-
const owner = (0,
|
|
4676
|
+
const owner = (0, import_viem10.getAddress)(input.state.walletAddress);
|
|
4211
4677
|
const { intent, rpcUrl } = await requestIntent({
|
|
4212
4678
|
apiKey: input.apiKey,
|
|
4213
4679
|
routingApiBaseUrl: input.routingApiBaseUrl,
|
|
@@ -4218,7 +4684,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
|
|
|
4218
4684
|
agentWallet: input.agentWallet,
|
|
4219
4685
|
asset: input.asset,
|
|
4220
4686
|
callData: input.callData,
|
|
4221
|
-
action: "upgrade"
|
|
4687
|
+
action: "upgrade",
|
|
4688
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4222
4689
|
});
|
|
4223
4690
|
if (intent.ownerPath !== "smart")
|
|
4224
4691
|
throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
|
|
@@ -4240,6 +4707,9 @@ var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
|
4240
4707
|
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
4241
4708
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4242
4709
|
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4710
|
+
var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
|
|
4711
|
+
var YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT = 64;
|
|
4712
|
+
var YIELDSEEKER_VAULTS_PER_RPC_BATCH = 10;
|
|
4243
4713
|
function generateYieldseekerUsername() {
|
|
4244
4714
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4245
4715
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4250,6 +4720,13 @@ function isUsernameConflict(error) {
|
|
|
4250
4720
|
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
4251
4721
|
}
|
|
4252
4722
|
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
4723
|
+
{
|
|
4724
|
+
type: "function",
|
|
4725
|
+
name: "feeTracker",
|
|
4726
|
+
stateMutability: "view",
|
|
4727
|
+
inputs: [],
|
|
4728
|
+
outputs: [{ name: "", type: "address" }]
|
|
4729
|
+
},
|
|
4253
4730
|
{
|
|
4254
4731
|
type: "function",
|
|
4255
4732
|
name: "withdrawAssetToUser",
|
|
@@ -4272,6 +4749,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
|
4272
4749
|
outputs: []
|
|
4273
4750
|
}
|
|
4274
4751
|
];
|
|
4752
|
+
var YIELDSEEKER_FEE_TRACKER_ABI = [{
|
|
4753
|
+
type: "function",
|
|
4754
|
+
name: "getFeesOwed",
|
|
4755
|
+
stateMutability: "view",
|
|
4756
|
+
inputs: [{ name: "wallet", type: "address" }],
|
|
4757
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
4758
|
+
}];
|
|
4275
4759
|
function query(params) {
|
|
4276
4760
|
const search = new URLSearchParams();
|
|
4277
4761
|
for (const [key2, value] of Object.entries(params)) {
|
|
@@ -4351,21 +4835,26 @@ var YieldseekerAgent = class {
|
|
|
4351
4835
|
if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
|
|
4352
4836
|
const status = await readYieldseekerWalletStatus(
|
|
4353
4837
|
this.getReceiptClient(),
|
|
4354
|
-
(0,
|
|
4355
|
-
(0,
|
|
4838
|
+
(0, import_viem11.getAddress)(context.wallet.walletAddress),
|
|
4839
|
+
(0, import_viem11.getAddress)(state.walletAddress),
|
|
4356
4840
|
YIELDSEEKER_ASSET_METADATA[asset].address
|
|
4357
4841
|
);
|
|
4842
|
+
this.rememberWalletStatus(key2, status);
|
|
4843
|
+
return status;
|
|
4844
|
+
}
|
|
4845
|
+
rememberWalletStatus(key2, status) {
|
|
4358
4846
|
if (status.verified && status.version === "v2") {
|
|
4359
4847
|
this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
|
|
4360
4848
|
if (this.withdrawalVersionCache.size > 128)
|
|
4361
4849
|
this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
|
|
4362
4850
|
} else this.withdrawalVersionCache.delete(key2);
|
|
4363
|
-
return status;
|
|
4364
4851
|
}
|
|
4365
4852
|
/** Explicit owner action. This never runs as a side effect of withdrawal. */
|
|
4366
4853
|
async upgradeWallet(state, chainId, asset, gasMode = "paid") {
|
|
4854
|
+
const generation = this.readGeneration;
|
|
4367
4855
|
const before = await this.getWalletStatus(state, chainId, asset);
|
|
4368
|
-
|
|
4856
|
+
this.assertSession(generation, chainId);
|
|
4857
|
+
if (!before?.verified || before.version === "unknown") {
|
|
4369
4858
|
throw new OwneyError(
|
|
4370
4859
|
"WITHDRAW_FAILED",
|
|
4371
4860
|
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
@@ -4373,48 +4862,77 @@ var YieldseekerAgent = class {
|
|
|
4373
4862
|
this.id
|
|
4374
4863
|
);
|
|
4375
4864
|
}
|
|
4376
|
-
const
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
if (!storage5)
|
|
4865
|
+
const versionKey = `${chainId}:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}:${asset}`;
|
|
4866
|
+
const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
|
|
4867
|
+
const confirmUpgrade = async (hash, receipt2) => {
|
|
4868
|
+
this.withdrawalVersionCache.delete(versionKey);
|
|
4869
|
+
const upgradeTopic = (0, import_viem11.encodeEventTopics)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
|
|
4870
|
+
const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
|
|
4871
|
+
const expectedUpgrade = (() => {
|
|
4872
|
+
if (!lastUpgrade || lastUpgrade.removed) return false;
|
|
4873
|
+
try {
|
|
4874
|
+
const event = (0, import_viem11.decodeEventLog)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
|
|
4875
|
+
return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
|
|
4876
|
+
} catch {
|
|
4877
|
+
return false;
|
|
4878
|
+
}
|
|
4879
|
+
})();
|
|
4880
|
+
if (!receipt2 || receipt2.status !== "success" || receipt2.transactionHash.toLowerCase() !== hash.toLowerCase() || typeof receipt2.blockNumber !== "bigint" || !expectedUpgrade)
|
|
4393
4881
|
throw new OwneyError(
|
|
4394
|
-
"
|
|
4395
|
-
"
|
|
4396
|
-
{
|
|
4882
|
+
"WITHDRAW_FAILED",
|
|
4883
|
+
"Yieldseeker wallet upgrade receipt could not be verified.",
|
|
4884
|
+
{ txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
|
|
4397
4885
|
this.id
|
|
4398
4886
|
);
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4887
|
+
this.assertSession(generation, chainId);
|
|
4888
|
+
const status = {
|
|
4889
|
+
...before,
|
|
4890
|
+
implementation: YIELDSEEKER_V2_IMPLEMENTATION,
|
|
4891
|
+
version: "v2",
|
|
4892
|
+
upgradeAvailable: false
|
|
4893
|
+
};
|
|
4894
|
+
this.rememberWalletStatus(versionKey, status);
|
|
4895
|
+
return { txHash: hash, status };
|
|
4896
|
+
};
|
|
4897
|
+
let storage5;
|
|
4898
|
+
try {
|
|
4899
|
+
storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
4900
|
+
} catch {
|
|
4901
|
+
storage5 = void 0;
|
|
4902
|
+
}
|
|
4903
|
+
const clearConfirmedUpgrade = () => {
|
|
4402
4904
|
try {
|
|
4403
|
-
|
|
4905
|
+
storage5?.removeItem(pendingKey2);
|
|
4404
4906
|
} catch {
|
|
4405
|
-
throw new OwneyError(
|
|
4406
|
-
"SPONSOR_REQUEST_FAILED",
|
|
4407
|
-
"Upgrade status cannot be read in this browser.",
|
|
4408
|
-
{ safeToFallback: true },
|
|
4409
|
-
this.id
|
|
4410
|
-
);
|
|
4411
4907
|
}
|
|
4908
|
+
};
|
|
4909
|
+
if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
|
|
4910
|
+
throw new OwneyError(
|
|
4911
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4912
|
+
"Upgrade status cannot be saved in this browser.",
|
|
4913
|
+
{ safeToFallback: false },
|
|
4914
|
+
this.id
|
|
4915
|
+
);
|
|
4916
|
+
let prior = null;
|
|
4917
|
+
try {
|
|
4918
|
+
const saved = storage5?.getItem(pendingKey2);
|
|
4412
4919
|
try {
|
|
4413
4920
|
prior = JSON.parse(saved ?? "null");
|
|
4414
4921
|
} catch {
|
|
4415
4922
|
prior = { callsId: void 0 };
|
|
4416
4923
|
}
|
|
4417
|
-
|
|
4924
|
+
} catch {
|
|
4925
|
+
if (gasMode === "paid" && before.ownerPath === "eoa") storage5 = void 0;
|
|
4926
|
+
else throw new OwneyError(
|
|
4927
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4928
|
+
"Upgrade status cannot be read in this browser.",
|
|
4929
|
+
{ safeToFallback: false },
|
|
4930
|
+
this.id
|
|
4931
|
+
);
|
|
4932
|
+
}
|
|
4933
|
+
if (prior) {
|
|
4934
|
+
let earlierHash = this.isTransactionHash(prior.txHash) ? prior.txHash : void 0;
|
|
4935
|
+
if (!earlierHash) {
|
|
4418
4936
|
if (!prior.callsId)
|
|
4419
4937
|
throw new OwneyError(
|
|
4420
4938
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4424,10 +4942,7 @@ var YieldseekerAgent = class {
|
|
|
4424
4942
|
);
|
|
4425
4943
|
let callsStatus;
|
|
4426
4944
|
try {
|
|
4427
|
-
callsStatus = await state.provider.request({
|
|
4428
|
-
method: "wallet_getCallsStatus",
|
|
4429
|
-
params: [prior.callsId]
|
|
4430
|
-
});
|
|
4945
|
+
callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
|
|
4431
4946
|
} catch {
|
|
4432
4947
|
throw new OwneyError(
|
|
4433
4948
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4436,8 +4951,15 @@ var YieldseekerAgent = class {
|
|
|
4436
4951
|
this.id
|
|
4437
4952
|
);
|
|
4438
4953
|
}
|
|
4439
|
-
if (callsStatus?.
|
|
4440
|
-
|
|
4954
|
+
if (callsStatus?.id !== void 0 && callsStatus.id !== prior.callsId || callsStatus?.chainId !== void 0 && Number(callsStatus.chainId) !== chainId)
|
|
4955
|
+
throw new OwneyError(
|
|
4956
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4957
|
+
"The earlier upgrade returned a different operation status.",
|
|
4958
|
+
{ safeToFallback: false, callsId: prior.callsId },
|
|
4959
|
+
this.id
|
|
4960
|
+
);
|
|
4961
|
+
if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
|
|
4962
|
+
storage5.removeItem(pendingKey2);
|
|
4441
4963
|
throw new OwneyError(
|
|
4442
4964
|
"SPONSOR_REQUEST_FAILED",
|
|
4443
4965
|
"The earlier sponsored upgrade failed.",
|
|
@@ -4445,22 +4967,39 @@ var YieldseekerAgent = class {
|
|
|
4445
4967
|
this.id
|
|
4446
4968
|
);
|
|
4447
4969
|
}
|
|
4448
|
-
const
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4970
|
+
const hashes = [...new Set((callsStatus?.receipts ?? []).map((value) => value.transactionHash).filter(this.isTransactionHash))];
|
|
4971
|
+
earlierHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4972
|
+
}
|
|
4973
|
+
if (earlierHash) {
|
|
4974
|
+
const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
|
|
4975
|
+
clearConfirmedUpgrade();
|
|
4976
|
+
return recovered;
|
|
4977
|
+
}
|
|
4978
|
+
throw new OwneyError(
|
|
4979
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4980
|
+
"An earlier upgrade is still pending.",
|
|
4981
|
+
{ safeToFallback: false, callsId: prior.callsId },
|
|
4982
|
+
this.id
|
|
4983
|
+
);
|
|
4984
|
+
}
|
|
4985
|
+
if (!before.upgradeAvailable)
|
|
4986
|
+
throw new OwneyError(
|
|
4987
|
+
"WITHDRAW_FAILED",
|
|
4988
|
+
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
4989
|
+
{ version: before.version },
|
|
4990
|
+
this.id
|
|
4991
|
+
);
|
|
4992
|
+
const data = (0, import_viem11.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
|
|
4993
|
+
let txHash;
|
|
4994
|
+
let receipt;
|
|
4995
|
+
if (gasMode === "sponsored") {
|
|
4996
|
+
if (before.ownerPath !== "smart")
|
|
4457
4997
|
throw new OwneyError(
|
|
4458
4998
|
"SPONSOR_REQUEST_FAILED",
|
|
4459
|
-
"
|
|
4460
|
-
{ safeToFallback:
|
|
4999
|
+
"EOA owners pay Base gas for the V1 upgrade.",
|
|
5000
|
+
{ safeToFallback: true },
|
|
4461
5001
|
this.id
|
|
4462
5002
|
);
|
|
4463
|
-
}
|
|
4464
5003
|
const context = await this.findAgent(state, chainId, asset);
|
|
4465
5004
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
|
|
4466
5005
|
try {
|
|
@@ -4470,14 +5009,15 @@ var YieldseekerAgent = class {
|
|
|
4470
5009
|
apiKey: this.owneyApiKey,
|
|
4471
5010
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4472
5011
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5012
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4473
5013
|
userId: context.user.userId,
|
|
4474
5014
|
agentId: context.agent.agentId,
|
|
4475
5015
|
agentWallet: before.agentWallet,
|
|
4476
5016
|
asset: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
4477
5017
|
callData: data,
|
|
4478
5018
|
onPending: (pending) => {
|
|
4479
|
-
const current = storage5.getItem(
|
|
4480
|
-
storage5.setItem(
|
|
5019
|
+
const current = storage5.getItem(pendingKey2);
|
|
5020
|
+
storage5.setItem(pendingKey2, JSON.stringify({
|
|
4481
5021
|
startedAt: Date.now(),
|
|
4482
5022
|
...current ? JSON.parse(current) : {},
|
|
4483
5023
|
...pending
|
|
@@ -4485,31 +5025,25 @@ var YieldseekerAgent = class {
|
|
|
4485
5025
|
}
|
|
4486
5026
|
});
|
|
4487
5027
|
txHash = upgraded.txHash;
|
|
5028
|
+
receipt = upgraded.receipt;
|
|
4488
5029
|
} catch (error) {
|
|
4489
|
-
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(
|
|
5030
|
+
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
|
|
4490
5031
|
throw error;
|
|
4491
5032
|
}
|
|
4492
5033
|
} else {
|
|
4493
5034
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4494
|
-
from: (0,
|
|
5035
|
+
from: (0, import_viem11.getAddress)(state.walletAddress),
|
|
4495
5036
|
to: before.agentWallet,
|
|
4496
5037
|
data,
|
|
4497
5038
|
value: "0",
|
|
4498
5039
|
chainId
|
|
5040
|
+
}, void 0, (value) => {
|
|
5041
|
+
receipt = value;
|
|
4499
5042
|
});
|
|
4500
5043
|
}
|
|
4501
|
-
const
|
|
4502
|
-
if (
|
|
4503
|
-
|
|
4504
|
-
"WITHDRAW_FAILED",
|
|
4505
|
-
"Yieldseeker wallet upgrade was not confirmed as V2.",
|
|
4506
|
-
{ txHash, version: status?.version ?? "missing" },
|
|
4507
|
-
this.id
|
|
4508
|
-
);
|
|
4509
|
-
}
|
|
4510
|
-
if (gasMode === "sponsored" && typeof window !== "undefined")
|
|
4511
|
-
window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
|
|
4512
|
-
return { txHash, status };
|
|
5044
|
+
const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
|
|
5045
|
+
if (gasMode === "sponsored") clearConfirmedUpgrade();
|
|
5046
|
+
return confirmed;
|
|
4513
5047
|
}
|
|
4514
5048
|
async disconnect() {
|
|
4515
5049
|
this.withdrawalVersionCache.clear();
|
|
@@ -4582,19 +5116,37 @@ var YieldseekerAgent = class {
|
|
|
4582
5116
|
submitted(txHash);
|
|
4583
5117
|
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
4584
5118
|
} else {
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
5119
|
+
try {
|
|
5120
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
5121
|
+
from: (0, import_viem11.getAddress)(state.walletAddress),
|
|
5122
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5123
|
+
data: (0, import_viem11.encodeFunctionData)({
|
|
5124
|
+
abi: import_viem11.erc20Abi,
|
|
5125
|
+
functionName: "transfer",
|
|
5126
|
+
args: [(0, import_viem11.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
5127
|
+
}),
|
|
5128
|
+
value: "0",
|
|
5129
|
+
chainId
|
|
5130
|
+
}, submitted, (value) => {
|
|
5131
|
+
receipt = value;
|
|
5132
|
+
});
|
|
5133
|
+
} catch (error) {
|
|
5134
|
+
if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
|
|
5135
|
+
notifyFunding(onProgress, {
|
|
5136
|
+
agentId: this.id,
|
|
5137
|
+
chainId,
|
|
5138
|
+
token: asset,
|
|
5139
|
+
walletAddress: state.walletAddress,
|
|
5140
|
+
agentWallet: context.wallet.walletAddress,
|
|
5141
|
+
intendedAmount: amount,
|
|
5142
|
+
txHash: String(error.details.originalTransactionHash),
|
|
5143
|
+
transfer: "failed",
|
|
5144
|
+
registration: "not-applicable",
|
|
5145
|
+
delivery: "not-applicable"
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
throw error;
|
|
5149
|
+
}
|
|
4598
5150
|
}
|
|
4599
5151
|
if (this.readGeneration === generation) {
|
|
4600
5152
|
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
@@ -4604,7 +5156,7 @@ var YieldseekerAgent = class {
|
|
|
4604
5156
|
notifyFunding(onProgress, evidence);
|
|
4605
5157
|
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
4606
5158
|
}
|
|
4607
|
-
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto") {
|
|
5159
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
|
|
4608
5160
|
let ownerTransferAttempted = false;
|
|
4609
5161
|
let evidence = {
|
|
4610
5162
|
agentId: this.id,
|
|
@@ -4622,10 +5174,10 @@ var YieldseekerAgent = class {
|
|
|
4622
5174
|
notifyFunding(onProgress, evidence);
|
|
4623
5175
|
}, () => {
|
|
4624
5176
|
ownerTransferAttempted = true;
|
|
4625
|
-
}, gasMode);
|
|
5177
|
+
}, gasMode, onBeforeSubmission);
|
|
4626
5178
|
} catch (error) {
|
|
4627
5179
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
|
|
4628
|
-
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)))
|
|
5180
|
+
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)))
|
|
4629
5181
|
notifyFunding(onProgress, {
|
|
4630
5182
|
...evidence,
|
|
4631
5183
|
...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
|
|
@@ -4635,7 +5187,7 @@ var YieldseekerAgent = class {
|
|
|
4635
5187
|
throw error;
|
|
4636
5188
|
}
|
|
4637
5189
|
}
|
|
4638
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
|
|
5190
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
|
|
4639
5191
|
this.assertChain(chainId);
|
|
4640
5192
|
this.assertAsset(asset);
|
|
4641
5193
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -4680,37 +5232,140 @@ var YieldseekerAgent = class {
|
|
|
4680
5232
|
let confirmedMovement = false;
|
|
4681
5233
|
let settlement = null;
|
|
4682
5234
|
try {
|
|
4683
|
-
|
|
4684
|
-
const
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
5235
|
+
const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
|
|
5236
|
+
const contextKey = this.contextKey(state, chainId, asset);
|
|
5237
|
+
const priorPositions = this.withdrawalVaultHints(state, chainId, context);
|
|
5238
|
+
this.advancePortfolioVersion(contextKey);
|
|
5239
|
+
const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5240
|
+
let timer;
|
|
5241
|
+
try {
|
|
5242
|
+
return await Promise.race([read2, new Promise((resolve) => {
|
|
5243
|
+
timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
|
|
5244
|
+
})]);
|
|
5245
|
+
} finally {
|
|
5246
|
+
if (timer) clearTimeout(timer);
|
|
5247
|
+
}
|
|
5248
|
+
};
|
|
5249
|
+
const feeRateRead = amount === void 0 ? Promise.resolve(0) : boundedVerification(
|
|
5250
|
+
(() => {
|
|
5251
|
+
try {
|
|
5252
|
+
return this.portfolioSnapshot(state, chainId, context, contextKey).read.then((value) => value.agentSnapshot.feeRateBps);
|
|
5253
|
+
} catch (error) {
|
|
5254
|
+
return Promise.reject(error);
|
|
5255
|
+
}
|
|
5256
|
+
})().catch(() => void 0)
|
|
4688
5257
|
);
|
|
5258
|
+
const positionsRead = this.readWithdrawalPositions(state, chainId, context);
|
|
4689
5259
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
4690
|
-
const
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
5260
|
+
const agentWallet = (0, import_viem11.getAddress)(context.wallet.walletAddress);
|
|
5261
|
+
const readOwnerFunds = async (blockNumber) => {
|
|
5262
|
+
const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5263
|
+
address: agentWallet,
|
|
5264
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5265
|
+
functionName: "feeTracker",
|
|
5266
|
+
blockNumber
|
|
4696
5267
|
}), chainId, "eth_call", this.id);
|
|
4697
5268
|
this.assertSession(generation, chainId, asset);
|
|
4698
|
-
|
|
5269
|
+
if (!(0, import_viem11.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker))
|
|
5270
|
+
throw this.invalidResponse("wallet fee tracker");
|
|
5271
|
+
const [idle2, fees] = await Promise.all([
|
|
5272
|
+
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5273
|
+
address: metadata.address,
|
|
5274
|
+
abi: import_viem11.erc20Abi,
|
|
5275
|
+
functionName: "balanceOf",
|
|
5276
|
+
args: [agentWallet],
|
|
5277
|
+
blockNumber
|
|
5278
|
+
}), chainId, "eth_call", this.id),
|
|
5279
|
+
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5280
|
+
address: (0, import_viem11.getAddress)(feeTracker),
|
|
5281
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5282
|
+
functionName: "getFeesOwed",
|
|
5283
|
+
args: [agentWallet],
|
|
5284
|
+
blockNumber
|
|
5285
|
+
}), chainId, "eth_call", this.id)
|
|
5286
|
+
]);
|
|
5287
|
+
this.assertSession(generation, chainId, asset);
|
|
5288
|
+
if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
|
|
5289
|
+
throw this.invalidResponse("wallet withdrawal balance");
|
|
5290
|
+
return { blockNumber, idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
|
|
4699
5291
|
};
|
|
4700
|
-
const
|
|
4701
|
-
|
|
5292
|
+
const [funds, feeRateBps] = await Promise.all([
|
|
5293
|
+
positionsRead.then((positions2) => this.resolveWithdrawalLiquidity(
|
|
5294
|
+
state,
|
|
5295
|
+
chainId,
|
|
5296
|
+
context,
|
|
5297
|
+
positions2,
|
|
5298
|
+
priorPositions,
|
|
5299
|
+
amount === void 0 ? void 0 : BigInt(amount)
|
|
5300
|
+
)),
|
|
5301
|
+
feeRateRead
|
|
5302
|
+
]);
|
|
5303
|
+
const initialFunds = funds;
|
|
5304
|
+
const idle = initialFunds.idle;
|
|
5305
|
+
const maxLiquidity = amount === void 0 ? funds : void 0;
|
|
5306
|
+
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()))];
|
|
4702
5307
|
let positionsStatus = "empty";
|
|
4703
|
-
const validatePositions = (values) =>
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
5308
|
+
const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
|
|
5309
|
+
validatePositions(positions);
|
|
5310
|
+
const zeroValuePositions = /* @__PURE__ */ new WeakSet();
|
|
5311
|
+
const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
|
|
5312
|
+
const knownPositions = /* @__PURE__ */ new Map();
|
|
5313
|
+
const rememberPositions = (values) => {
|
|
5314
|
+
for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
|
|
5315
|
+
};
|
|
5316
|
+
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5317
|
+
const verifiedPositions = [];
|
|
5318
|
+
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
5319
|
+
let active = true;
|
|
5320
|
+
const checks = this.readVaultLiquidity(values.map((position2) => position2.yieldOption), agentWallet, blockNumber).catch(() => []);
|
|
5321
|
+
const reads = values.map(async (position2, index) => {
|
|
5322
|
+
const address = (0, import_viem11.getAddress)(position2.yieldOption.address);
|
|
5323
|
+
try {
|
|
5324
|
+
const group = (await checks).slice(index * 3, index * 3 + 3);
|
|
5325
|
+
if (!active || group.length !== 3 || group.some((value) => value.status !== "success")) return null;
|
|
5326
|
+
this.assertSession(generation, chainId, asset);
|
|
5327
|
+
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
5328
|
+
if (typeof underlying !== "string" || !(0, import_viem11.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
|
|
5329
|
+
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
|
|
5330
|
+
if (shares > 0n && withdrawable === 0n) {
|
|
5331
|
+
let positiveValue = false;
|
|
5332
|
+
const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
|
|
5333
|
+
try {
|
|
5334
|
+
const value = await this.getReceiptClient().readContract({
|
|
5335
|
+
address,
|
|
5336
|
+
abi: import_viem11.erc4626Abi,
|
|
5337
|
+
functionName,
|
|
5338
|
+
args: [shares],
|
|
5339
|
+
blockNumber
|
|
5340
|
+
});
|
|
5341
|
+
if (typeof value !== "bigint" || value < 0n) return null;
|
|
5342
|
+
if (value > 0n && !positiveValue) {
|
|
5343
|
+
positiveValue = true;
|
|
5344
|
+
verifiedPositions.push(verified);
|
|
5345
|
+
}
|
|
5346
|
+
return value;
|
|
5347
|
+
} catch {
|
|
5348
|
+
return null;
|
|
5349
|
+
}
|
|
5350
|
+
}));
|
|
5351
|
+
if (positiveValue) return verified;
|
|
5352
|
+
if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
|
|
5353
|
+
zeroValuePositions.add(verified);
|
|
5354
|
+
}
|
|
5355
|
+
verifiedPositions.push(verified);
|
|
5356
|
+
return verified;
|
|
5357
|
+
} catch {
|
|
5358
|
+
return null;
|
|
5359
|
+
}
|
|
5360
|
+
});
|
|
5361
|
+
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5362
|
+
active = false;
|
|
5363
|
+
this.assertSession(generation, chainId, asset);
|
|
5364
|
+
return {
|
|
5365
|
+
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
5366
|
+
positions: [...verifiedPositions]
|
|
5367
|
+
};
|
|
4712
5368
|
};
|
|
4713
|
-
const hasPositions = (values) => values.some((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
4714
5369
|
const refreshPositions = async () => {
|
|
4715
5370
|
this.assertSession(generation, chainId, asset);
|
|
4716
5371
|
const response = await this.walletRequest(
|
|
@@ -4723,22 +5378,41 @@ var YieldseekerAgent = class {
|
|
|
4723
5378
|
return validatePositions(response.yieldPositions);
|
|
4724
5379
|
};
|
|
4725
5380
|
if (amount === void 0) {
|
|
4726
|
-
|
|
5381
|
+
rememberPositions(funds.indexedPositions);
|
|
5382
|
+
rememberPositions(positions);
|
|
4727
5383
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4728
5384
|
}
|
|
4729
5385
|
const deployed = positions.reduce(
|
|
4730
5386
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
4731
5387
|
0n
|
|
4732
5388
|
);
|
|
4733
|
-
const
|
|
5389
|
+
const grossAvailable = idle + deployed;
|
|
5390
|
+
const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
|
|
4734
5391
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4735
5392
|
if (requested === 0n) {
|
|
4736
5393
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4737
|
-
|
|
5394
|
+
const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5395
|
+
if (amount === void 0 && zeroPayoutCandidates.length) {
|
|
5396
|
+
const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5397
|
+
const blockNumber = initialFunds.blockNumber ?? await boundedVerification(
|
|
5398
|
+
Promise.resolve().then(() => this.getReceiptClient().getBlockNumber({ cacheTime: 0 })).catch(() => void 0)
|
|
5399
|
+
);
|
|
5400
|
+
const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
|
|
5401
|
+
positions = proof.positions;
|
|
5402
|
+
positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
|
|
5403
|
+
if (positionsStatus !== "empty")
|
|
5404
|
+
throw new OwneyError(
|
|
5405
|
+
"WITHDRAW_FAILED",
|
|
5406
|
+
"Yieldseeker vault funds are not currently withdrawable.",
|
|
5407
|
+
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
|
|
5408
|
+
this.id
|
|
5409
|
+
);
|
|
5410
|
+
}
|
|
5411
|
+
if (grossAvailable > 0n && totalAvailable === 0n)
|
|
4738
5412
|
throw new OwneyError(
|
|
4739
|
-
"
|
|
4740
|
-
"Yieldseeker
|
|
4741
|
-
{ asset,
|
|
5413
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
5414
|
+
"No owner-withdrawable Yieldseeker funds remain after reserved fees.",
|
|
5415
|
+
{ asset, available: "0", reason: "YIELDSEEKER_FEES_ONLY", feesReserved: initialFunds.fees.toString() },
|
|
4742
5416
|
this.id
|
|
4743
5417
|
);
|
|
4744
5418
|
throw new OwneyError(
|
|
@@ -4760,28 +5434,50 @@ var YieldseekerAgent = class {
|
|
|
4760
5434
|
this.id
|
|
4761
5435
|
);
|
|
4762
5436
|
}
|
|
4763
|
-
|
|
5437
|
+
const requiredIdle = requested + initialFunds.fees;
|
|
5438
|
+
let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
|
|
4764
5439
|
let grossUnwound = 0n;
|
|
5440
|
+
let latestUnwindBlock;
|
|
4765
5441
|
const unwind = async (position2, assetsRaw) => {
|
|
4766
5442
|
this.assertSession(generation, chainId, asset);
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
5443
|
+
let unwindTransactionHash;
|
|
5444
|
+
try {
|
|
5445
|
+
const response = await this.walletRequest(
|
|
5446
|
+
state,
|
|
5447
|
+
chainId,
|
|
5448
|
+
this.agentPath(context, "withdraw-from-position"),
|
|
5449
|
+
{
|
|
5450
|
+
method: "POST",
|
|
5451
|
+
body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
|
|
5452
|
+
}
|
|
5453
|
+
);
|
|
5454
|
+
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
5455
|
+
unwindTransactionHash = response.transactionHash;
|
|
5456
|
+
const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
|
|
5457
|
+
if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
|
|
5458
|
+
} catch (error) {
|
|
5459
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
5460
|
+
throw new OwneyError(
|
|
5461
|
+
error instanceof OwneyError ? error.code : "WITHDRAW_FAILED",
|
|
5462
|
+
"Could not confirm the Yieldseeker vault release. No owner transfer was submitted.",
|
|
5463
|
+
{
|
|
5464
|
+
...error instanceof OwneyError ? error.details : {},
|
|
5465
|
+
withdrawalPhase: "unwind",
|
|
5466
|
+
reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
|
|
5467
|
+
vaultAddress: position2.yieldOption.address,
|
|
5468
|
+
unwindTransactionHash,
|
|
5469
|
+
ownerTransferSubmitted: false
|
|
5470
|
+
},
|
|
5471
|
+
this.id
|
|
5472
|
+
);
|
|
5473
|
+
}
|
|
4778
5474
|
confirmedMovement = true;
|
|
4779
5475
|
this.assertSession(generation, chainId, asset);
|
|
4780
5476
|
grossUnwound += assetsRaw;
|
|
4781
5477
|
};
|
|
4782
5478
|
if (amount === void 0) {
|
|
4783
5479
|
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
4784
|
-
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.
|
|
5480
|
+
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
4785
5481
|
let unwound = false;
|
|
4786
5482
|
for (const position2 of positions) {
|
|
4787
5483
|
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
@@ -4790,15 +5486,22 @@ var YieldseekerAgent = class {
|
|
|
4790
5486
|
unwound = true;
|
|
4791
5487
|
}
|
|
4792
5488
|
if (!unwound) break;
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
5489
|
+
const knownBeforeRefresh = new Set(knownPositions.keys());
|
|
5490
|
+
const [catalog, proof] = await Promise.all([
|
|
5491
|
+
boundedVerification(refreshPositions().catch(() => void 0)),
|
|
5492
|
+
verifyKnownPositions(latestUnwindBlock)
|
|
5493
|
+
]);
|
|
5494
|
+
if (catalog) rememberPositions(catalog);
|
|
5495
|
+
this.assertSession(generation, chainId, asset);
|
|
5496
|
+
if (proof.verified) {
|
|
5497
|
+
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
|
|
5498
|
+
} else if (catalog) positions = catalog;
|
|
5499
|
+
else {
|
|
4798
5500
|
positionsStatus = "unavailable";
|
|
4799
5501
|
break;
|
|
4800
5502
|
}
|
|
4801
|
-
|
|
5503
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5504
|
+
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
|
|
4802
5505
|
}
|
|
4803
5506
|
} else {
|
|
4804
5507
|
for (const position2 of positions) {
|
|
@@ -4816,8 +5519,9 @@ var YieldseekerAgent = class {
|
|
|
4816
5519
|
remaining: remaining.toString()
|
|
4817
5520
|
});
|
|
4818
5521
|
}
|
|
4819
|
-
const
|
|
4820
|
-
const
|
|
5522
|
+
const ownerFunds = confirmedMovement ? await readOwnerFunds(latestUnwindBlock) : initialFunds;
|
|
5523
|
+
const availableForOwner = ownerFunds.available;
|
|
5524
|
+
const feeRate = feeRateBps ?? 0;
|
|
4821
5525
|
const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
|
|
4822
5526
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
4823
5527
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
@@ -4837,20 +5541,23 @@ var YieldseekerAgent = class {
|
|
|
4837
5541
|
);
|
|
4838
5542
|
}
|
|
4839
5543
|
const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
|
|
4840
|
-
const plannedMovement =
|
|
4841
|
-
|
|
5544
|
+
const plannedMovement = amount === void 0 && maxSettlementBasis ? {
|
|
5545
|
+
...maxSettlementBasis,
|
|
5546
|
+
delta: maxSettlementBasis.delta - payout,
|
|
5547
|
+
expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
|
|
5548
|
+
} : this.snapshotMovement(state, chainId, context, -payout);
|
|
5549
|
+
const account = (0, import_viem11.getAddress)(state.walletAddress);
|
|
4842
5550
|
let receipt;
|
|
4843
5551
|
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" };
|
|
4844
|
-
const data = amount === void 0 ? (0,
|
|
5552
|
+
const data = amount === void 0 ? (0, import_viem11.encodeFunctionData)({
|
|
4845
5553
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
4846
5554
|
functionName: "withdrawAllAssetToUser",
|
|
4847
5555
|
args: [account, metadata.address]
|
|
4848
|
-
}) : (0,
|
|
5556
|
+
}) : (0, import_viem11.encodeFunctionData)({
|
|
4849
5557
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
4850
5558
|
functionName: "withdrawAssetToUser",
|
|
4851
5559
|
args: [account, metadata.address, payout]
|
|
4852
5560
|
});
|
|
4853
|
-
onOwnerTransferAttempted?.();
|
|
4854
5561
|
let txHash;
|
|
4855
5562
|
if (sponsor) {
|
|
4856
5563
|
const submitted = await submitYieldseekerSponsoredWithdrawal({
|
|
@@ -4859,19 +5566,25 @@ var YieldseekerAgent = class {
|
|
|
4859
5566
|
apiKey: this.owneyApiKey,
|
|
4860
5567
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4861
5568
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5569
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4862
5570
|
userId: context.user.userId,
|
|
4863
5571
|
agentId: context.agent.agentId,
|
|
4864
|
-
agentWallet: (0,
|
|
5572
|
+
agentWallet: (0, import_viem11.getAddress)(context.wallet.walletAddress),
|
|
4865
5573
|
asset: metadata.address,
|
|
4866
5574
|
callData: data,
|
|
4867
|
-
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending })
|
|
5575
|
+
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
|
|
5576
|
+
onBeforeSubmission: async (pending) => {
|
|
5577
|
+
await onBeforeSubmission?.({ ...base6, ...pending });
|
|
5578
|
+
onOwnerTransferAttempted?.();
|
|
5579
|
+
}
|
|
4868
5580
|
});
|
|
4869
5581
|
txHash = submitted.txHash;
|
|
4870
5582
|
receipt = submitted.receipt;
|
|
4871
5583
|
} else {
|
|
5584
|
+
onOwnerTransferAttempted?.();
|
|
4872
5585
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4873
5586
|
from: account,
|
|
4874
|
-
to: (0,
|
|
5587
|
+
to: (0, import_viem11.getAddress)(context.wallet.walletAddress),
|
|
4875
5588
|
data,
|
|
4876
5589
|
value: "0",
|
|
4877
5590
|
chainId
|
|
@@ -4883,14 +5596,26 @@ var YieldseekerAgent = class {
|
|
|
4883
5596
|
settlement = plannedMovement;
|
|
4884
5597
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4885
5598
|
notifyFunding(onProgress, evidence);
|
|
5599
|
+
let completion = maxLiquidity && !maxLiquidity.complete ? "unverified" : void 0;
|
|
4886
5600
|
if (amount === void 0) {
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
5601
|
+
const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5602
|
+
const knownAtPayout = new Set(knownPositions.keys());
|
|
5603
|
+
const [catalogVerified, knownProof] = await Promise.all([
|
|
5604
|
+
boundedVerification(refreshPositions().then((values) => {
|
|
5605
|
+
rememberPositions(values);
|
|
5606
|
+
return true;
|
|
5607
|
+
}).catch(() => false)),
|
|
5608
|
+
verifyKnownPositions(receipt?.blockNumber)
|
|
5609
|
+
]);
|
|
5610
|
+
const newPositions = [...knownPositions].filter(([address]) => !knownAtPayout.has(address)).map(([, position2]) => position2);
|
|
5611
|
+
const newProof = newPositions.length ? await verifyKnownPositions(receipt?.blockNumber, newPositions, verificationDeadline - Date.now()) : { verified: true, positions: [] };
|
|
5612
|
+
const proof = {
|
|
5613
|
+
verified: catalogVerified === true && knownProof.verified && newProof.verified,
|
|
5614
|
+
positions: [...knownProof.positions, ...newProof.positions]
|
|
5615
|
+
};
|
|
5616
|
+
positions = proof.positions;
|
|
5617
|
+
positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
|
|
5618
|
+
if (positionsStatus === "remaining") {
|
|
4894
5619
|
const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
4895
5620
|
throw new OwneyError(
|
|
4896
5621
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
@@ -4899,7 +5624,7 @@ var YieldseekerAgent = class {
|
|
|
4899
5624
|
reason: "YIELDSEEKER_MAX_INCOMPLETE",
|
|
4900
5625
|
asset,
|
|
4901
5626
|
positionsStatus,
|
|
4902
|
-
remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
|
|
5627
|
+
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
4903
5628
|
vaultAddress: position2.yieldOption.address,
|
|
4904
5629
|
sharesRaw: position2.assetsRaw,
|
|
4905
5630
|
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
@@ -4909,6 +5634,7 @@ var YieldseekerAgent = class {
|
|
|
4909
5634
|
this.id
|
|
4910
5635
|
);
|
|
4911
5636
|
}
|
|
5637
|
+
if (!proof.verified) completion = "unverified";
|
|
4912
5638
|
}
|
|
4913
5639
|
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4914
5640
|
throw new OwneyError(
|
|
@@ -4917,7 +5643,17 @@ var YieldseekerAgent = class {
|
|
|
4917
5643
|
{ asset, available: "0", txHash },
|
|
4918
5644
|
this.id
|
|
4919
5645
|
);
|
|
4920
|
-
|
|
5646
|
+
const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
|
|
5647
|
+
const feeIncrease = ownerFunds.fees - initialFunds.fees;
|
|
5648
|
+
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;
|
|
5649
|
+
return {
|
|
5650
|
+
txHash,
|
|
5651
|
+
type: amount === void 0 && !completion ? "full" : "partial",
|
|
5652
|
+
...completion ? { completion } : {},
|
|
5653
|
+
...feeAdjustment ? { feeAdjustment } : {},
|
|
5654
|
+
amount: evidence.confirmedAmount ?? payout.toString(),
|
|
5655
|
+
evidence
|
|
5656
|
+
};
|
|
4921
5657
|
} finally {
|
|
4922
5658
|
if (confirmedMovement && this.readGeneration === generation) {
|
|
4923
5659
|
void this.refreshSnapshotAfterMovement(
|
|
@@ -4930,6 +5666,236 @@ var YieldseekerAgent = class {
|
|
|
4930
5666
|
}
|
|
4931
5667
|
}
|
|
4932
5668
|
}
|
|
5669
|
+
/** A withdrawal-only liquidity floor; display snapshots never authorize it. */
|
|
5670
|
+
async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
|
|
5671
|
+
this.assertChain(chainId);
|
|
5672
|
+
this.assertAsset(asset);
|
|
5673
|
+
const generation = this.readGeneration;
|
|
5674
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
5675
|
+
this.assertSession(generation, chainId, asset);
|
|
5676
|
+
if (!context) throw new OwneyError(
|
|
5677
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5678
|
+
"The Yieldseeker withdrawal wallet could not be verified.",
|
|
5679
|
+
{ asset },
|
|
5680
|
+
this.id
|
|
5681
|
+
);
|
|
5682
|
+
const hints = this.withdrawalVaultHints(state, chainId, context);
|
|
5683
|
+
const positions = await this.readWithdrawalPositions(state, chainId, context);
|
|
5684
|
+
const proof = await this.resolveWithdrawalLiquidity(state, chainId, context, positions, hints, minimumAmount);
|
|
5685
|
+
this.assertSession(generation, chainId, asset);
|
|
5686
|
+
if (proof.available === 0n && !proof.complete) throw new OwneyError(
|
|
5687
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5688
|
+
"Yieldseeker withdrawal liquidity could not be verified yet.",
|
|
5689
|
+
{ asset },
|
|
5690
|
+
this.id
|
|
5691
|
+
);
|
|
5692
|
+
return proof.available;
|
|
5693
|
+
}
|
|
5694
|
+
async readVaultLiquidity(options, wallet, blockNumber) {
|
|
5695
|
+
const owner = (0, import_viem11.getAddress)(wallet);
|
|
5696
|
+
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));
|
|
5697
|
+
const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
|
|
5698
|
+
blockNumber,
|
|
5699
|
+
allowFailure: true,
|
|
5700
|
+
contracts: batch.flatMap((option) => {
|
|
5701
|
+
const address = (0, import_viem11.getAddress)(option.address);
|
|
5702
|
+
return [
|
|
5703
|
+
{ address, abi: import_viem11.erc4626Abi, functionName: "asset" },
|
|
5704
|
+
{ address, abi: import_viem11.erc20Abi, functionName: "balanceOf", args: [owner] },
|
|
5705
|
+
{ address, abi: import_viem11.erc4626Abi, functionName: "maxWithdraw", args: [owner] }
|
|
5706
|
+
];
|
|
5707
|
+
})
|
|
5708
|
+
})));
|
|
5709
|
+
return results.flat();
|
|
5710
|
+
}
|
|
5711
|
+
validateWithdrawalPositions(values, chainId, asset) {
|
|
5712
|
+
const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
|
|
5713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5714
|
+
for (const value of values) {
|
|
5715
|
+
const option = value?.yieldOption;
|
|
5716
|
+
if (!option || typeof option.address !== "string" || !(0, import_viem11.isAddress)(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))
|
|
5717
|
+
throw this.invalidResponse("yield positions");
|
|
5718
|
+
seen.add(option.address.toLowerCase());
|
|
5719
|
+
}
|
|
5720
|
+
return values;
|
|
5721
|
+
}
|
|
5722
|
+
withdrawalVaultHints(state, chainId, context) {
|
|
5723
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
5724
|
+
const positions = this.readCache.get(`positions:${key2}`)?.value?.yieldPositions ?? [];
|
|
5725
|
+
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
|
|
5726
|
+
const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
|
|
5727
|
+
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()));
|
|
5728
|
+
for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
|
|
5729
|
+
if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !(0, import_viem11.isAddress)(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
|
|
5730
|
+
hints.set(option.address.toLowerCase(), {
|
|
5731
|
+
yieldOption: option,
|
|
5732
|
+
assetsRaw: "0",
|
|
5733
|
+
assetsBase: "0",
|
|
5734
|
+
assetsUsd: 0,
|
|
5735
|
+
withdrawableAssetsRaw: "0",
|
|
5736
|
+
withdrawableAssetsBase: "0"
|
|
5737
|
+
});
|
|
5738
|
+
}
|
|
5739
|
+
return [...hints.values()];
|
|
5740
|
+
}
|
|
5741
|
+
async readWithdrawalPositions(state, chainId, context) {
|
|
5742
|
+
const generation = this.readGeneration;
|
|
5743
|
+
let timer;
|
|
5744
|
+
try {
|
|
5745
|
+
const positions = await Promise.race([
|
|
5746
|
+
this.loadActivityContext(state, chainId, context, {}).then((value) => value.positions).catch((error) => {
|
|
5747
|
+
if (!(error instanceof OwneyError) || !["AGENT_API_ERROR", "AGENT_TIMEOUT"].includes(error.code)) throw error;
|
|
5748
|
+
return void 0;
|
|
5749
|
+
}),
|
|
5750
|
+
new Promise((resolve) => {
|
|
5751
|
+
timer = setTimeout(() => resolve(void 0), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
|
|
5752
|
+
})
|
|
5753
|
+
]);
|
|
5754
|
+
this.assertSession(generation, chainId, context.asset);
|
|
5755
|
+
return positions === void 0 ? void 0 : this.validateWithdrawalPositions(positions, chainId, context.asset);
|
|
5756
|
+
} finally {
|
|
5757
|
+
if (timer) clearTimeout(timer);
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
|
|
5761
|
+
const generation = this.readGeneration;
|
|
5762
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
5763
|
+
const version = this.portfolioVersion(contextKey);
|
|
5764
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
5765
|
+
const unavailable = () => new OwneyError(
|
|
5766
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5767
|
+
"Yieldseeker withdrawal liquidity could not be verified yet.",
|
|
5768
|
+
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
5769
|
+
this.id
|
|
5770
|
+
);
|
|
5771
|
+
let active = true;
|
|
5772
|
+
let knownProof;
|
|
5773
|
+
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5774
|
+
const assertReadCurrent = () => {
|
|
5775
|
+
this.assertSession(generation, chainId, context.asset);
|
|
5776
|
+
if (!active || this.portfolioVersion(contextKey) !== version) throw unavailable();
|
|
5777
|
+
};
|
|
5778
|
+
const read2 = async () => {
|
|
5779
|
+
const client = this.getReceiptClient();
|
|
5780
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
5781
|
+
const wallet = (0, import_viem11.getAddress)(context.wallet.walletAddress);
|
|
5782
|
+
assertReadCurrent();
|
|
5783
|
+
if (typeof blockNumber !== "bigint") throw unavailable();
|
|
5784
|
+
const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
|
|
5785
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
5786
|
+
for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
|
|
5787
|
+
candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
|
|
5788
|
+
for (const position2 of indexedPositions) candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
|
|
5789
|
+
const options = [...candidates.values()];
|
|
5790
|
+
if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
5791
|
+
const [checks, idle, feeTracker] = await Promise.all([
|
|
5792
|
+
this.readVaultLiquidity(options, wallet, blockNumber),
|
|
5793
|
+
client.readContract({
|
|
5794
|
+
address: metadata.address,
|
|
5795
|
+
abi: import_viem11.erc20Abi,
|
|
5796
|
+
functionName: "balanceOf",
|
|
5797
|
+
args: [wallet],
|
|
5798
|
+
blockNumber
|
|
5799
|
+
}),
|
|
5800
|
+
client.readContract({
|
|
5801
|
+
address: wallet,
|
|
5802
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5803
|
+
functionName: "feeTracker",
|
|
5804
|
+
blockNumber
|
|
5805
|
+
})
|
|
5806
|
+
]);
|
|
5807
|
+
if (typeof idle !== "bigint" || idle < 0n || !(0, import_viem11.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
|
|
5808
|
+
assertReadCurrent();
|
|
5809
|
+
const fees = await client.readContract({
|
|
5810
|
+
address: (0, import_viem11.getAddress)(feeTracker),
|
|
5811
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5812
|
+
functionName: "getFeesOwed",
|
|
5813
|
+
args: [wallet],
|
|
5814
|
+
blockNumber
|
|
5815
|
+
});
|
|
5816
|
+
if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
|
|
5817
|
+
const verified = [];
|
|
5818
|
+
const verifiedVaults = [];
|
|
5819
|
+
let deployed = 0n;
|
|
5820
|
+
const collect = (options2, results) => {
|
|
5821
|
+
let valid2 = true;
|
|
5822
|
+
options2.forEach((option, index) => {
|
|
5823
|
+
const group = results.slice(index * 3, index * 3 + 3);
|
|
5824
|
+
if (group.length !== 3 || group.some((value) => value.status !== "success")) {
|
|
5825
|
+
valid2 = false;
|
|
5826
|
+
return;
|
|
5827
|
+
}
|
|
5828
|
+
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
5829
|
+
if (typeof underlying !== "string" || !(0, import_viem11.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
|
|
5830
|
+
valid2 = false;
|
|
5831
|
+
return;
|
|
5832
|
+
}
|
|
5833
|
+
verifiedVaults.push(option.address.toLowerCase());
|
|
5834
|
+
deployed += withdrawable;
|
|
5835
|
+
verified.push({
|
|
5836
|
+
yieldOption: option,
|
|
5837
|
+
assetsRaw: shares.toString(),
|
|
5838
|
+
assetsBase: "0",
|
|
5839
|
+
assetsUsd: 0,
|
|
5840
|
+
withdrawableAssetsRaw: withdrawable.toString(),
|
|
5841
|
+
withdrawableAssetsBase: withdrawable.toString()
|
|
5842
|
+
});
|
|
5843
|
+
});
|
|
5844
|
+
return valid2;
|
|
5845
|
+
};
|
|
5846
|
+
const allKnownVerified = collect(options, checks);
|
|
5847
|
+
const proof = (complete) => ({
|
|
5848
|
+
blockNumber,
|
|
5849
|
+
idle,
|
|
5850
|
+
fees,
|
|
5851
|
+
available: idle + deployed > fees ? idle + deployed - fees : 0n,
|
|
5852
|
+
positions: [...verified],
|
|
5853
|
+
verifiedVaults: [...verifiedVaults],
|
|
5854
|
+
indexedPositions,
|
|
5855
|
+
complete
|
|
5856
|
+
});
|
|
5857
|
+
knownProof = proof(false);
|
|
5858
|
+
const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
|
|
5859
|
+
const discrepancy = verified.some((position2) => {
|
|
5860
|
+
const row = indexed.get(position2.yieldOption.address.toLowerCase());
|
|
5861
|
+
return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
|
|
5862
|
+
});
|
|
5863
|
+
const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
|
|
5864
|
+
const expand = positions === void 0 || indexedPositions.length === 0 && !typedCoveredByIdle || !allKnownVerified || discrepancy || knownProof.available === 0n || requested !== void 0 && requested > knownProof.available;
|
|
5865
|
+
if (!expand) {
|
|
5866
|
+
assertReadCurrent();
|
|
5867
|
+
return proof(true);
|
|
5868
|
+
}
|
|
5869
|
+
const catalog = await this.loadYieldOptions(context.asset).catch(() => void 0);
|
|
5870
|
+
assertReadCurrent();
|
|
5871
|
+
const eligible = /* @__PURE__ */ new Map();
|
|
5872
|
+
for (const option of catalog ?? []) {
|
|
5873
|
+
if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem11.isAddress)(option.address))
|
|
5874
|
+
eligible.set(option.address.toLowerCase(), option);
|
|
5875
|
+
}
|
|
5876
|
+
const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
|
|
5877
|
+
if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
5878
|
+
const allExtraVerified = collect(extra, await this.readVaultLiquidity(extra, wallet, blockNumber));
|
|
5879
|
+
assertReadCurrent();
|
|
5880
|
+
const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
|
|
5881
|
+
if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
|
|
5882
|
+
return result;
|
|
5883
|
+
};
|
|
5884
|
+
let timer;
|
|
5885
|
+
try {
|
|
5886
|
+
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
5887
|
+
timer = setTimeout(() => reject(unavailable()), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
|
|
5888
|
+
})]);
|
|
5889
|
+
} catch (error) {
|
|
5890
|
+
assertReadCurrent();
|
|
5891
|
+
if (knownProof && (knownProof.available > 0n || hasUnverifiedIndexedFunds(knownProof)) && (!(error instanceof OwneyError) || error.code === "WITHDRAW_BALANCE_UNAVAILABLE")) return knownProof;
|
|
5892
|
+
if (error instanceof OwneyError) throw error;
|
|
5893
|
+
throw unavailable();
|
|
5894
|
+
} finally {
|
|
5895
|
+
active = false;
|
|
5896
|
+
if (timer) clearTimeout(timer);
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
4933
5899
|
async getBalances(state, chainId, asset) {
|
|
4934
5900
|
this.assertChain(chainId);
|
|
4935
5901
|
if (asset !== void 0) this.assertAsset(asset);
|
|
@@ -4964,6 +5930,23 @@ var YieldseekerAgent = class {
|
|
|
4964
5930
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
|
|
4965
5931
|
return context.wallet.walletAddress;
|
|
4966
5932
|
}
|
|
5933
|
+
async hasExistingDepositFunds(state, chainId, asset) {
|
|
5934
|
+
this.assertChain(chainId);
|
|
5935
|
+
this.assertAsset(asset);
|
|
5936
|
+
const generation = this.readGeneration;
|
|
5937
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
5938
|
+
this.assertSession(generation, chainId, asset);
|
|
5939
|
+
if (!context) return void 0;
|
|
5940
|
+
const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5941
|
+
address: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5942
|
+
abi: import_viem11.erc20Abi,
|
|
5943
|
+
functionName: "balanceOf",
|
|
5944
|
+
args: [(0, import_viem11.getAddress)(context.wallet.walletAddress)]
|
|
5945
|
+
}), chainId, "eth_call", this.id);
|
|
5946
|
+
this.assertSession(generation, chainId, asset);
|
|
5947
|
+
if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
|
|
5948
|
+
return idle > 0n ? true : void 0;
|
|
5949
|
+
}
|
|
4967
5950
|
async getEarnings(state, chainId) {
|
|
4968
5951
|
this.assertChain(chainId);
|
|
4969
5952
|
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
@@ -4994,7 +5977,7 @@ var YieldseekerAgent = class {
|
|
|
4994
5977
|
);
|
|
4995
5978
|
const vaultAddresses = new Set(
|
|
4996
5979
|
catalog.flat().filter(
|
|
4997
|
-
(yieldOption) => yieldOption.chainId === chainId && (0,
|
|
5980
|
+
(yieldOption) => yieldOption.chainId === chainId && (0, import_viem11.isAddress)(yieldOption.address)
|
|
4998
5981
|
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
4999
5982
|
);
|
|
5000
5983
|
return mapYieldseekerHistory(contexts, {
|
|
@@ -5121,7 +6104,7 @@ var YieldseekerAgent = class {
|
|
|
5121
6104
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
5122
6105
|
).then((walletResponse) => {
|
|
5123
6106
|
this.assertSession(generation, chainId, asset);
|
|
5124
|
-
if (!walletResponse?.agentWallet || !(0,
|
|
6107
|
+
if (!walletResponse?.agentWallet || !(0, import_viem11.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
5125
6108
|
throw this.invalidResponse("agent wallet");
|
|
5126
6109
|
}
|
|
5127
6110
|
const context = {
|
|
@@ -5151,7 +6134,7 @@ var YieldseekerAgent = class {
|
|
|
5151
6134
|
this.users.set(key2, persisted);
|
|
5152
6135
|
return persisted;
|
|
5153
6136
|
}
|
|
5154
|
-
const walletAddress = (0,
|
|
6137
|
+
const walletAddress = (0, import_viem11.getAddress)(state.walletAddress);
|
|
5155
6138
|
const generation = this.readGeneration;
|
|
5156
6139
|
let user = null;
|
|
5157
6140
|
try {
|
|
@@ -5534,7 +6517,7 @@ var YieldseekerAgent = class {
|
|
|
5534
6517
|
this.agentPath(context, "deploy"),
|
|
5535
6518
|
{ method: "POST", body: {} }
|
|
5536
6519
|
);
|
|
5537
|
-
if (!deployed?.agentWallet || !(0,
|
|
6520
|
+
if (!deployed?.agentWallet || !(0, import_viem11.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
5538
6521
|
throw this.invalidResponse("agent deployment", {
|
|
5539
6522
|
reason: "Deploy did not return the expected Agent Wallet."
|
|
5540
6523
|
});
|
|
@@ -5642,15 +6625,15 @@ var YieldseekerAgent = class {
|
|
|
5642
6625
|
return hash2;
|
|
5643
6626
|
}
|
|
5644
6627
|
this.assertTransaction(transaction, state, chainId);
|
|
5645
|
-
const account = (0,
|
|
5646
|
-
const walletClient = (0,
|
|
6628
|
+
const account = (0, import_viem11.getAddress)(state.walletAddress);
|
|
6629
|
+
const walletClient = (0, import_viem11.createWalletClient)({
|
|
5647
6630
|
account,
|
|
5648
6631
|
chain: import_chains4.base,
|
|
5649
|
-
transport: (0,
|
|
6632
|
+
transport: (0, import_viem11.custom)(state.provider)
|
|
5650
6633
|
});
|
|
5651
|
-
const walletChainClient = (0,
|
|
6634
|
+
const walletChainClient = (0, import_viem11.createPublicClient)({
|
|
5652
6635
|
chain: import_chains4.base,
|
|
5653
|
-
transport: (0,
|
|
6636
|
+
transport: (0, import_viem11.custom)(state.provider)
|
|
5654
6637
|
});
|
|
5655
6638
|
await ensureWalletOnChain(
|
|
5656
6639
|
walletChainClient,
|
|
@@ -5660,30 +6643,84 @@ var YieldseekerAgent = class {
|
|
|
5660
6643
|
const hash = await walletClient.sendTransaction({
|
|
5661
6644
|
account,
|
|
5662
6645
|
chain: import_chains4.base,
|
|
5663
|
-
to: (0,
|
|
6646
|
+
to: (0, import_viem11.getAddress)(transaction.to),
|
|
5664
6647
|
data: transaction.data,
|
|
5665
6648
|
value: BigInt(transaction.value)
|
|
5666
6649
|
});
|
|
5667
6650
|
onSubmitted?.(hash);
|
|
6651
|
+
let replacement;
|
|
5668
6652
|
const receipt = await withPaidRpcDiagnostics(
|
|
5669
6653
|
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
5670
6654
|
hash,
|
|
5671
|
-
confirmations: 1
|
|
6655
|
+
confirmations: 1,
|
|
6656
|
+
onReplaced: (value) => {
|
|
6657
|
+
replacement = value;
|
|
6658
|
+
}
|
|
5672
6659
|
}),
|
|
5673
6660
|
8453,
|
|
5674
6661
|
"eth_getTransactionReceipt",
|
|
5675
6662
|
this.id
|
|
5676
6663
|
);
|
|
6664
|
+
const actualHash = receipt.transactionHash;
|
|
6665
|
+
if (!this.isTransactionHash(actualHash))
|
|
6666
|
+
throw new OwneyError(
|
|
6667
|
+
"WITHDRAW_FAILED",
|
|
6668
|
+
"The confirmed transaction hash could not be verified.",
|
|
6669
|
+
{ originalTransactionHash: hash, ownerTransferNotExecuted: false },
|
|
6670
|
+
this.id
|
|
6671
|
+
);
|
|
6672
|
+
if (actualHash.toLowerCase() !== hash.toLowerCase()) {
|
|
6673
|
+
if (!replacement || replacement.transaction.hash.toLowerCase() !== actualHash.toLowerCase()) {
|
|
6674
|
+
try {
|
|
6675
|
+
const [original2, actual2] = await Promise.all([
|
|
6676
|
+
this.getReceiptClient().getTransaction({ hash }),
|
|
6677
|
+
this.getReceiptClient().getTransaction({ hash: actualHash })
|
|
6678
|
+
]);
|
|
6679
|
+
replacement = { replacedTransaction: original2, transaction: actual2 };
|
|
6680
|
+
} catch {
|
|
6681
|
+
replacement = void 0;
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
const original = replacement?.replacedTransaction;
|
|
6685
|
+
const actual = replacement?.transaction;
|
|
6686
|
+
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;
|
|
6687
|
+
if (!sameNonce)
|
|
6688
|
+
throw new OwneyError(
|
|
6689
|
+
"WITHDRAW_FAILED",
|
|
6690
|
+
"The replacement transaction could not be verified.",
|
|
6691
|
+
{
|
|
6692
|
+
originalTransactionHash: hash,
|
|
6693
|
+
replacementTransactionHash: actualHash,
|
|
6694
|
+
reason: "TRANSACTION_REPLACEMENT_UNRESOLVED",
|
|
6695
|
+
ownerTransferNotExecuted: false
|
|
6696
|
+
},
|
|
6697
|
+
this.id
|
|
6698
|
+
);
|
|
6699
|
+
const sameCall = actual.to?.toLowerCase() === transaction.to.toLowerCase() && actual.input?.toLowerCase() === transaction.data.toLowerCase() && actual.value === BigInt(transaction.value);
|
|
6700
|
+
if (!sameCall)
|
|
6701
|
+
throw new OwneyError(
|
|
6702
|
+
"WITHDRAW_FAILED",
|
|
6703
|
+
"The transaction was replaced without executing this withdrawal.",
|
|
6704
|
+
{
|
|
6705
|
+
originalTransactionHash: hash,
|
|
6706
|
+
replacementTransactionHash: actualHash,
|
|
6707
|
+
reason: "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL",
|
|
6708
|
+
ownerTransferNotExecuted: true
|
|
6709
|
+
},
|
|
6710
|
+
this.id
|
|
6711
|
+
);
|
|
6712
|
+
onSubmitted?.(actualHash);
|
|
6713
|
+
}
|
|
5677
6714
|
if (receipt.status !== "success") {
|
|
5678
6715
|
throw new OwneyError(
|
|
5679
6716
|
"AGENT_TRANSACTION_REVERTED",
|
|
5680
|
-
`Yieldseeker transaction reverted (${
|
|
5681
|
-
{ transactionHash:
|
|
6717
|
+
`Yieldseeker transaction reverted (${actualHash}).`,
|
|
6718
|
+
{ transactionHash: actualHash },
|
|
5682
6719
|
this.id
|
|
5683
6720
|
);
|
|
5684
6721
|
}
|
|
5685
6722
|
onReceipt?.(receipt);
|
|
5686
|
-
return
|
|
6723
|
+
return actualHash;
|
|
5687
6724
|
}
|
|
5688
6725
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
5689
6726
|
if (this.unwindReceiptWaiter) {
|
|
@@ -5710,12 +6747,12 @@ var YieldseekerAgent = class {
|
|
|
5710
6747
|
return receipt;
|
|
5711
6748
|
}
|
|
5712
6749
|
assertTransaction(transaction, state, chainId) {
|
|
5713
|
-
if (!transaction || typeof transaction.from !== "string" || !(0,
|
|
6750
|
+
if (!transaction || typeof transaction.from !== "string" || !(0, import_viem11.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem11.isAddress)(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 || (0, import_viem11.getAddress)(transaction.from) !== (0, import_viem11.getAddress)(state.walletAddress)) {
|
|
5714
6751
|
throw this.invalidResponse("transaction");
|
|
5715
6752
|
}
|
|
5716
6753
|
}
|
|
5717
6754
|
assertAgent(agent) {
|
|
5718
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0,
|
|
6755
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem11.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
|
|
5719
6756
|
throw this.invalidResponse("agent");
|
|
5720
6757
|
}
|
|
5721
6758
|
}
|
|
@@ -5869,7 +6906,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
5869
6906
|
}
|
|
5870
6907
|
|
|
5871
6908
|
// src/lib/helpers/withdraw-helper.ts
|
|
5872
|
-
var
|
|
6909
|
+
var import_viem12 = require("viem");
|
|
5873
6910
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
5874
6911
|
const target = asset.toUpperCase();
|
|
5875
6912
|
return agents.map((agent) => {
|
|
@@ -5877,7 +6914,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
5877
6914
|
const tokenBalance = agentBalance?.tokens.find(
|
|
5878
6915
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
5879
6916
|
);
|
|
5880
|
-
let balance = tokenBalance ? (0,
|
|
6917
|
+
let balance = tokenBalance ? (0, import_viem12.parseUnits)(tokenBalance.amount, decimals) : 0n;
|
|
5881
6918
|
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
5882
6919
|
const chainNameById = {
|
|
5883
6920
|
1: "ETHEREUM",
|
|
@@ -5889,14 +6926,14 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
5889
6926
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
5890
6927
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
5891
6928
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
5892
|
-
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0,
|
|
6929
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem12.parseUnits)(position2.amount, decimals);
|
|
5893
6930
|
}
|
|
5894
6931
|
}
|
|
5895
6932
|
const assetTotal = agentBalance?.assetBalances?.find(
|
|
5896
6933
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
5897
6934
|
);
|
|
5898
6935
|
if (assetTotal) {
|
|
5899
|
-
const total = (0,
|
|
6936
|
+
const total = (0, import_viem12.parseUnits)(assetTotal.amount, decimals);
|
|
5900
6937
|
if (total < balance) balance = total > 0n ? total : 0n;
|
|
5901
6938
|
}
|
|
5902
6939
|
return { agent, balance };
|
|
@@ -6055,14 +7092,14 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
6055
7092
|
}
|
|
6056
7093
|
|
|
6057
7094
|
// src/client.ts
|
|
6058
|
-
var
|
|
7095
|
+
var import_viem16 = require("viem");
|
|
6059
7096
|
var import_chains5 = require("viem/chains");
|
|
6060
7097
|
|
|
6061
7098
|
// src/lib/sponsored-token-batch.ts
|
|
6062
|
-
var
|
|
7099
|
+
var import_viem14 = require("viem");
|
|
6063
7100
|
|
|
6064
7101
|
// src/lib/permit2-batch.ts
|
|
6065
|
-
var
|
|
7102
|
+
var import_viem13 = require("viem");
|
|
6066
7103
|
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
6067
7104
|
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
6068
7105
|
var ERC2612_PERMIT_TYPES = {
|
|
@@ -6074,7 +7111,7 @@ var ERC2612_PERMIT_TYPES = {
|
|
|
6074
7111
|
{ name: "deadline", type: "uint256" }
|
|
6075
7112
|
]
|
|
6076
7113
|
};
|
|
6077
|
-
var ERC2612_READ_ABI = (0,
|
|
7114
|
+
var ERC2612_READ_ABI = (0, import_viem13.parseAbi)([
|
|
6078
7115
|
"function name() view returns (string)",
|
|
6079
7116
|
"function version() view returns (string)",
|
|
6080
7117
|
"function nonces(address owner) view returns (uint256)"
|
|
@@ -6112,7 +7149,7 @@ var PERMIT_BATCH_TYPES = {
|
|
|
6112
7149
|
{ name: "amount", type: "uint256" }
|
|
6113
7150
|
]
|
|
6114
7151
|
};
|
|
6115
|
-
var PERMIT2_BATCH_ABI = (0,
|
|
7152
|
+
var PERMIT2_BATCH_ABI = (0, import_viem13.parseAbi)([
|
|
6116
7153
|
"struct TokenPermissions { address token; uint256 amount; }",
|
|
6117
7154
|
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
6118
7155
|
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
@@ -6155,6 +7192,18 @@ var planOf = (transfers) => JSON.stringify(
|
|
|
6155
7192
|
amount: BigInt(t.amount).toString()
|
|
6156
7193
|
}))
|
|
6157
7194
|
);
|
|
7195
|
+
var pendingKey = (i) => `owney.token-batch.v1:${(0, import_viem14.keccak256)((0, import_viem14.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
|
|
7196
|
+
function pendingTokenBatch(i) {
|
|
7197
|
+
try {
|
|
7198
|
+
const saved = read(pendingKey(i));
|
|
7199
|
+
if (!saved) return;
|
|
7200
|
+
const body = JSON.parse(saved);
|
|
7201
|
+
if (!body.serializedTransaction || body.chainId !== i.chainId || !(0, import_viem14.isAddressEqual)(body.from, i.owner) || !(0, import_viem14.isAddressEqual)(body.token, i.token) || !body.transfers?.length) return;
|
|
7202
|
+
return { txHash: (0, import_viem14.keccak256)(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
|
|
7203
|
+
} catch {
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
6158
7207
|
function read(key2) {
|
|
6159
7208
|
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
6160
7209
|
}
|
|
@@ -6171,11 +7220,11 @@ function clear(key2) {
|
|
|
6171
7220
|
else window.localStorage.removeItem(key2);
|
|
6172
7221
|
}
|
|
6173
7222
|
function sponsorTokenBatch(i) {
|
|
6174
|
-
const key2 =
|
|
7223
|
+
const key2 = pendingKey(i);
|
|
6175
7224
|
const plan = planOf(i.transfers);
|
|
6176
7225
|
const active = inflight.get(key2);
|
|
6177
7226
|
if (active) {
|
|
6178
|
-
if (active.plan !== plan)
|
|
7227
|
+
if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
6179
7228
|
return Promise.reject(
|
|
6180
7229
|
new Error(
|
|
6181
7230
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
@@ -6202,7 +7251,7 @@ async function execute(i, key2, plan) {
|
|
|
6202
7251
|
baseUrl: i.baseUrl,
|
|
6203
7252
|
body
|
|
6204
7253
|
});
|
|
6205
|
-
if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || (0,
|
|
7254
|
+
if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || (0, import_viem14.keccak256)(preparedPermit.serializedPermitTransaction) !== preparedPermit.permitTxHash)
|
|
6206
7255
|
throw new Error(
|
|
6207
7256
|
"Sponsorship API did not return a valid prepared permit transaction."
|
|
6208
7257
|
);
|
|
@@ -6218,7 +7267,7 @@ async function execute(i, key2, plan) {
|
|
|
6218
7267
|
baseUrl: i.baseUrl,
|
|
6219
7268
|
body
|
|
6220
7269
|
});
|
|
6221
|
-
if (!prepared.serializedTransaction || (0,
|
|
7270
|
+
if (!prepared.serializedTransaction || (0, import_viem14.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
|
|
6222
7271
|
throw new Error(
|
|
6223
7272
|
"Sponsorship API did not return a valid prepared transaction."
|
|
6224
7273
|
);
|
|
@@ -6229,7 +7278,7 @@ async function execute(i, key2, plan) {
|
|
|
6229
7278
|
save(key2, body);
|
|
6230
7279
|
}
|
|
6231
7280
|
try {
|
|
6232
|
-
i.onSubmitted?.((0,
|
|
7281
|
+
i.onSubmitted?.((0, import_viem14.keccak256)(body.serializedTransaction));
|
|
6233
7282
|
} catch {
|
|
6234
7283
|
}
|
|
6235
7284
|
const result = await postSponsorBatchTransfer({
|
|
@@ -6237,7 +7286,7 @@ async function execute(i, key2, plan) {
|
|
|
6237
7286
|
baseUrl: i.baseUrl,
|
|
6238
7287
|
body
|
|
6239
7288
|
});
|
|
6240
|
-
if (result.txHash !== (0,
|
|
7289
|
+
if (result.txHash !== (0, import_viem14.keccak256)(body.serializedTransaction))
|
|
6241
7290
|
throw new Error(
|
|
6242
7291
|
"Sponsorship receipt does not match the pending transaction."
|
|
6243
7292
|
);
|
|
@@ -6250,9 +7299,11 @@ async function execute(i, key2, plan) {
|
|
|
6250
7299
|
}
|
|
6251
7300
|
};
|
|
6252
7301
|
const saved = read(key2);
|
|
7302
|
+
if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
7303
|
+
throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
|
|
6253
7304
|
if (saved) {
|
|
6254
7305
|
const previous = JSON.parse(saved);
|
|
6255
|
-
if (previous.chainId !== i.chainId || !(0,
|
|
7306
|
+
if (previous.chainId !== i.chainId || !(0, import_viem14.isAddressEqual)(previous.from, i.owner) || !(0, import_viem14.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
6256
7307
|
throw new Error(
|
|
6257
7308
|
"Retry the previous token deposit and agent split first to reconcile its status."
|
|
6258
7309
|
);
|
|
@@ -6269,7 +7320,7 @@ async function execute(i, key2, plan) {
|
|
|
6269
7320
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
6270
7321
|
"Insufficient token balance for this deposit."
|
|
6271
7322
|
);
|
|
6272
|
-
if (allowance < total && (i.chainId !== 8453 || !(0,
|
|
7323
|
+
if (allowance < total && (i.chainId !== 8453 || !(0, import_viem14.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
|
|
6273
7324
|
throw new OwneyError(
|
|
6274
7325
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
6275
7326
|
"token deposits need a one-time Permit2 approval."
|
|
@@ -6361,7 +7412,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
6361
7412
|
pub,
|
|
6362
7413
|
wallet,
|
|
6363
7414
|
onApproved: deps.onApproved,
|
|
6364
|
-
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
7415
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
|
|
7416
|
+
resumeTxHash: deps.resumeTxHash
|
|
6365
7417
|
});
|
|
6366
7418
|
};
|
|
6367
7419
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -6447,7 +7499,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
6447
7499
|
}
|
|
6448
7500
|
|
|
6449
7501
|
// src/lib/sponsored-calls-deposit.ts
|
|
6450
|
-
var
|
|
7502
|
+
var import_viem15 = require("viem");
|
|
6451
7503
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
6452
7504
|
var DEFAULT_MAX_POLLS = 30;
|
|
6453
7505
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -6455,7 +7507,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
6455
7507
|
method: "wallet_getCapabilities",
|
|
6456
7508
|
params: [owner]
|
|
6457
7509
|
});
|
|
6458
|
-
const forChain = caps?.[(0,
|
|
7510
|
+
const forChain = caps?.[(0, import_viem15.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
6459
7511
|
return Boolean(forChain?.paymasterService?.supported);
|
|
6460
7512
|
}
|
|
6461
7513
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -6492,8 +7544,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6492
7544
|
const calls = transfers.map((transfer) => ({
|
|
6493
7545
|
to: token,
|
|
6494
7546
|
value: "0x0",
|
|
6495
|
-
data: (0,
|
|
6496
|
-
abi:
|
|
7547
|
+
data: (0, import_viem15.encodeFunctionData)({
|
|
7548
|
+
abi: import_viem15.erc20Abi,
|
|
6497
7549
|
functionName: "transfer",
|
|
6498
7550
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
6499
7551
|
})
|
|
@@ -6525,50 +7577,73 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6525
7577
|
url.searchParams.append("owneyIntent", intent);
|
|
6526
7578
|
paymasterUrl = url.toString();
|
|
6527
7579
|
}
|
|
6528
|
-
const
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
7580
|
+
const requestedId = (0, import_viem15.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
7581
|
+
const progress = (id, transfer) => {
|
|
7582
|
+
try {
|
|
7583
|
+
deps.onCallsProgress?.(id, transfers, transfer);
|
|
7584
|
+
} catch {
|
|
7585
|
+
}
|
|
7586
|
+
};
|
|
7587
|
+
progress(requestedId, "pending");
|
|
7588
|
+
let sendResult;
|
|
7589
|
+
try {
|
|
7590
|
+
sendResult = await deps.provider.request({
|
|
7591
|
+
method: "wallet_sendCalls",
|
|
7592
|
+
params: [
|
|
7593
|
+
{
|
|
7594
|
+
version: "2.0.0",
|
|
7595
|
+
id: requestedId,
|
|
7596
|
+
from: deps.ownerAddress,
|
|
7597
|
+
chainId: (0, import_viem15.toHex)(chainId),
|
|
7598
|
+
atomicRequired: transfers.length > 1,
|
|
7599
|
+
calls,
|
|
7600
|
+
capabilities: {
|
|
7601
|
+
paymasterService: { url: paymasterUrl }
|
|
7602
|
+
}
|
|
6539
7603
|
}
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
})
|
|
6543
|
-
|
|
7604
|
+
]
|
|
7605
|
+
});
|
|
7606
|
+
} catch (error) {
|
|
7607
|
+
if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
|
|
7608
|
+
progress(requestedId, "failed");
|
|
7609
|
+
throw error;
|
|
7610
|
+
}
|
|
7611
|
+
try {
|
|
7612
|
+
deps.onApproved?.();
|
|
7613
|
+
} catch {
|
|
7614
|
+
}
|
|
6544
7615
|
const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
|
|
6545
7616
|
if (!callsId) {
|
|
6546
7617
|
throw new OwneyError(
|
|
6547
7618
|
"SPONSORED_CALLS_NO_ID",
|
|
6548
7619
|
"Wallet accepted wallet_sendCalls but returned no calls id.",
|
|
6549
|
-
{ chainId }
|
|
7620
|
+
{ chainId, callsId: requestedId }
|
|
6550
7621
|
);
|
|
6551
7622
|
}
|
|
7623
|
+
progress(callsId, "pending");
|
|
6552
7624
|
for (let attempt = 0; attempt < maxPolls; attempt += 1) {
|
|
6553
7625
|
const status = await deps.provider.request({
|
|
6554
7626
|
method: "wallet_getCallsStatus",
|
|
6555
7627
|
params: [callsId]
|
|
6556
7628
|
});
|
|
7629
|
+
if (status?.id && status.id !== callsId || status?.chainId && Number(status.chainId) !== chainId)
|
|
7630
|
+
throw new OwneyError("SPONSORED_CALLS_NO_RECEIPT", "Wallet returned status for a different deposit batch.", { chainId, callsId });
|
|
6557
7631
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
6558
|
-
if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
7632
|
+
if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
6559
7633
|
try {
|
|
6560
7634
|
deps.onSubmitted?.(txHash, transfers);
|
|
6561
7635
|
} catch {
|
|
6562
7636
|
}
|
|
6563
7637
|
}
|
|
6564
|
-
if (status?.
|
|
7638
|
+
if (status?.status === 400 || status?.status === 500) {
|
|
7639
|
+
progress(callsId, "failed");
|
|
6565
7640
|
throw new OwneyError(
|
|
6566
7641
|
"SPONSOR_REQUEST_FAILED",
|
|
6567
7642
|
"The sponsored deposit did not complete successfully.",
|
|
6568
7643
|
{ chainId, callsId, safeToFallback: false }
|
|
6569
7644
|
);
|
|
6570
7645
|
}
|
|
6571
|
-
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
7646
|
+
if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
6572
7647
|
if (status?.receipts?.some(
|
|
6573
7648
|
(receipt) => receipt.transactionHash !== txHash
|
|
6574
7649
|
))
|
|
@@ -6784,8 +7859,8 @@ var OwneySDK = class {
|
|
|
6784
7859
|
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
6785
7860
|
}
|
|
6786
7861
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
6787
|
-
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
6788
|
-
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
7862
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7863
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
|
|
6789
7864
|
return this.cachedSponsoredCallback;
|
|
6790
7865
|
const provider = this.requireConnectedProvider();
|
|
6791
7866
|
const owner = this.state.walletAddress;
|
|
@@ -6795,28 +7870,29 @@ var OwneySDK = class {
|
|
|
6795
7870
|
ownerAddress: owner,
|
|
6796
7871
|
onApproved,
|
|
6797
7872
|
onSubmitted,
|
|
7873
|
+
resumeTxHash,
|
|
6798
7874
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
6799
7875
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6800
7876
|
// PublicClient/WalletClient param types — structurally identical at
|
|
6801
7877
|
// runtime, but the two share a name TS treats as unrelated.
|
|
6802
7878
|
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
6803
|
-
getWalletChainClient: (cid) => (0,
|
|
7879
|
+
getWalletChainClient: (cid) => (0, import_viem16.createPublicClient)({
|
|
6804
7880
|
chain: VIEM_CHAIN2[cid],
|
|
6805
|
-
transport: (0,
|
|
7881
|
+
transport: (0, import_viem16.custom)(provider)
|
|
6806
7882
|
}),
|
|
6807
|
-
getWalletClient: (cid) => (0,
|
|
7883
|
+
getWalletClient: (cid) => (0, import_viem16.createWalletClient)({
|
|
6808
7884
|
account: owner,
|
|
6809
7885
|
chain: VIEM_CHAIN2[cid],
|
|
6810
|
-
transport: (0,
|
|
7886
|
+
transport: (0, import_viem16.custom)(provider)
|
|
6811
7887
|
})
|
|
6812
7888
|
});
|
|
6813
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
7889
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
|
|
6814
7890
|
return callback;
|
|
6815
7891
|
}
|
|
6816
7892
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
6817
|
-
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
7893
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
|
|
6818
7894
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
6819
|
-
if (!onApproved && !onSubmitted && cached) return cached;
|
|
7895
|
+
if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
|
|
6820
7896
|
const provider = this.requireConnectedProvider();
|
|
6821
7897
|
const callback = makeSponsoredCallsCallback({
|
|
6822
7898
|
apiKey: this.apiKey,
|
|
@@ -6826,9 +7902,10 @@ var OwneySDK = class {
|
|
|
6826
7902
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
6827
7903
|
onApproved,
|
|
6828
7904
|
onSubmitted,
|
|
7905
|
+
onCallsProgress,
|
|
6829
7906
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
6830
7907
|
});
|
|
6831
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
7908
|
+
if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
6832
7909
|
return callback;
|
|
6833
7910
|
}
|
|
6834
7911
|
/**
|
|
@@ -6837,8 +7914,8 @@ var OwneySDK = class {
|
|
|
6837
7914
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
6838
7915
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
6839
7916
|
*/
|
|
6840
|
-
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6841
|
-
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
7917
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7918
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
|
|
6842
7919
|
return this.cachedWethSponsoredCallback;
|
|
6843
7920
|
const provider = this.requireConnectedProvider();
|
|
6844
7921
|
const owner = this.state.walletAddress;
|
|
@@ -6848,22 +7925,23 @@ var OwneySDK = class {
|
|
|
6848
7925
|
ownerAddress: owner,
|
|
6849
7926
|
onApproved,
|
|
6850
7927
|
onSubmitted,
|
|
7928
|
+
resumeTxHash,
|
|
6851
7929
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
6852
7930
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6853
7931
|
// PublicClient/WalletClient param types — structurally identical at
|
|
6854
7932
|
// runtime, but the two share a name TS treats as unrelated.
|
|
6855
7933
|
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
6856
|
-
getWalletChainClient: (cid) => (0,
|
|
7934
|
+
getWalletChainClient: (cid) => (0, import_viem16.createPublicClient)({
|
|
6857
7935
|
chain: VIEM_CHAIN2[cid],
|
|
6858
|
-
transport: (0,
|
|
7936
|
+
transport: (0, import_viem16.custom)(provider)
|
|
6859
7937
|
}),
|
|
6860
|
-
getWalletClient: (cid) => (0,
|
|
7938
|
+
getWalletClient: (cid) => (0, import_viem16.createWalletClient)({
|
|
6861
7939
|
account: owner,
|
|
6862
7940
|
chain: VIEM_CHAIN2[cid],
|
|
6863
|
-
transport: (0,
|
|
7941
|
+
transport: (0, import_viem16.custom)(provider)
|
|
6864
7942
|
})
|
|
6865
7943
|
});
|
|
6866
|
-
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
7944
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
|
|
6867
7945
|
return callback;
|
|
6868
7946
|
}
|
|
6869
7947
|
getAgent(agentId) {
|
|
@@ -7172,6 +8250,7 @@ var OwneySDK = class {
|
|
|
7172
8250
|
async deposit(options) {
|
|
7173
8251
|
const { amount, asset, depositCallback, agentId, onApproved } = options;
|
|
7174
8252
|
const prepared = /* @__PURE__ */ new Map();
|
|
8253
|
+
const paidFallbackAgents = /* @__PURE__ */ new Set();
|
|
7175
8254
|
const onProgress = options.onProgress ? (evidence) => {
|
|
7176
8255
|
if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
|
|
7177
8256
|
notifyFunding(options.onProgress, evidence);
|
|
@@ -7182,152 +8261,184 @@ var OwneySDK = class {
|
|
|
7182
8261
|
if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
|
|
7183
8262
|
}
|
|
7184
8263
|
} : void 0;
|
|
8264
|
+
const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
|
|
8265
|
+
for (const destination of destinations) {
|
|
8266
|
+
const evidence = prepared.get(destination.to.toLowerCase());
|
|
8267
|
+
if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
|
|
8268
|
+
}
|
|
8269
|
+
} : void 0;
|
|
7185
8270
|
if (BigInt(amount) === 0n) {
|
|
7186
8271
|
return { agentResults: {} };
|
|
7187
8272
|
}
|
|
7188
8273
|
const state = this.requireState();
|
|
7189
8274
|
const chainId = this.requireChainId();
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
7202
|
-
return this.depositWithFallback(
|
|
7203
|
-
agent,
|
|
7204
|
-
state,
|
|
7205
|
-
chainId,
|
|
7206
|
-
amount,
|
|
7207
|
-
asset,
|
|
7208
|
-
effectiveCallback,
|
|
7209
|
-
depositCallback,
|
|
7210
|
-
onProgress
|
|
7211
|
-
);
|
|
7212
|
-
}
|
|
7213
|
-
const totalAmount = BigInt(amount);
|
|
7214
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
7215
|
-
excludeDisabled: true
|
|
7216
|
-
});
|
|
7217
|
-
const exemptFlags = await Promise.all(
|
|
7218
|
-
eligibleAgents.map(
|
|
7219
|
-
(a) => this.hasExistingBalance(a, state, chainId, asset, true)
|
|
7220
|
-
)
|
|
7221
|
-
);
|
|
7222
|
-
const exempt = new Set(
|
|
7223
|
-
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
7224
|
-
);
|
|
7225
|
-
const agentAmounts = this.splitDepositAmount(
|
|
7226
|
-
totalAmount,
|
|
7227
|
-
eligibleAgents,
|
|
7228
|
-
chainId,
|
|
7229
|
-
asset,
|
|
7230
|
-
exempt
|
|
7231
|
-
);
|
|
7232
|
-
if (agentAmounts.length === 0) {
|
|
7233
|
-
const agentsRequiringActivation = eligibleAgents.filter(
|
|
7234
|
-
(agent) => !exempt.has(agent.id)
|
|
7235
|
-
);
|
|
7236
|
-
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
7237
|
-
(highest, agent) => {
|
|
7238
|
-
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
7239
|
-
return minimum > highest ? minimum : highest;
|
|
7240
|
-
},
|
|
7241
|
-
0n
|
|
7242
|
-
);
|
|
7243
|
-
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7244
|
-
throw new OwneyError(
|
|
7245
|
-
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
7246
|
-
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7247
|
-
{
|
|
7248
|
-
amount,
|
|
7249
|
-
asset,
|
|
7250
|
-
chainId,
|
|
7251
|
-
minDepositAmount: minimumRequired.toString(),
|
|
7252
|
-
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
7253
|
-
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
7254
|
-
agentId: agent.id,
|
|
7255
|
-
minDepositAmount: this.getMinDepositAmount(
|
|
7256
|
-
agent,
|
|
7257
|
-
chainId,
|
|
7258
|
-
asset
|
|
7259
|
-
).toString()
|
|
7260
|
-
}))
|
|
7261
|
-
}
|
|
7262
|
-
);
|
|
7263
|
-
}
|
|
7264
|
-
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
7265
|
-
if (!depositCallback && batchTransfer) {
|
|
7266
|
-
return runAgentDepositBatch(
|
|
7267
|
-
chainId,
|
|
7268
|
-
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
7269
|
-
id: agent.id,
|
|
7270
|
-
amount: amount2,
|
|
7271
|
-
run: (callback) => withFailureReporting(
|
|
7272
|
-
this.apiKey,
|
|
7273
|
-
agent.id,
|
|
7274
|
-
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
7275
|
-
this.routingApiBaseUrl
|
|
7276
|
-
)
|
|
7277
|
-
})),
|
|
7278
|
-
async (cid, transfers) => {
|
|
7279
|
-
try {
|
|
7280
|
-
return await batchTransfer(cid, transfers);
|
|
7281
|
-
} catch (error) {
|
|
7282
|
-
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
7283
|
-
throw error;
|
|
7284
|
-
const requiredAmount = transfers.reduce(
|
|
7285
|
-
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
7286
|
-
0n
|
|
7287
|
-
);
|
|
7288
|
-
await this.approvePermit2(
|
|
7289
|
-
asset,
|
|
7290
|
-
requiredAmount,
|
|
7291
|
-
cid
|
|
7292
|
-
);
|
|
7293
|
-
return batchTransfer(cid, transfers);
|
|
7294
|
-
}
|
|
8275
|
+
if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
|
|
8276
|
+
throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
|
|
8277
|
+
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));
|
|
8278
|
+
try {
|
|
8279
|
+
if (agentId) {
|
|
8280
|
+
if (this.isAgentDisabled(agentId)) {
|
|
8281
|
+
throw new OwneyError(
|
|
8282
|
+
"AGENT_DISABLED",
|
|
8283
|
+
`Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
|
|
8284
|
+
{ agentId }
|
|
8285
|
+
);
|
|
7295
8286
|
}
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
index,
|
|
7301
|
-
{ agent, amount: agentAmount }
|
|
7302
|
-
] of agentAmounts.entries()) {
|
|
7303
|
-
try {
|
|
7304
|
-
agentResults[agent.id] = await this.depositWithFallback(
|
|
8287
|
+
const agent = this.getAgent(agentId);
|
|
8288
|
+
this.validateAssetSupport(agent, chainId, asset);
|
|
8289
|
+
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
8290
|
+
return await this.depositWithFallback(
|
|
7305
8291
|
agent,
|
|
7306
8292
|
state,
|
|
7307
8293
|
chainId,
|
|
7308
|
-
|
|
8294
|
+
amount,
|
|
7309
8295
|
asset,
|
|
7310
8296
|
effectiveCallback,
|
|
7311
8297
|
depositCallback,
|
|
7312
|
-
onProgress
|
|
8298
|
+
onProgress,
|
|
8299
|
+
() => paidFallbackAgents.add(agent.id),
|
|
8300
|
+
!!options.resumeTxHash
|
|
7313
8301
|
);
|
|
7314
|
-
}
|
|
7315
|
-
|
|
8302
|
+
}
|
|
8303
|
+
const totalAmount = BigInt(amount);
|
|
8304
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
8305
|
+
excludeDisabled: true
|
|
8306
|
+
});
|
|
8307
|
+
const prospectiveShare = totalAmount / BigInt(eligibleAgents.length);
|
|
8308
|
+
const meetsEveryMinimum = eligibleAgents.every((agent) => prospectiveShare >= this.getMinDepositAmount(agent, chainId, asset));
|
|
8309
|
+
const exemptFlags = meetsEveryMinimum ? eligibleAgents.map(() => false) : await Promise.all(eligibleAgents.map((agent) => this.hasExistingBalance(agent, state, chainId, asset, true)));
|
|
8310
|
+
const exempt = new Set(
|
|
8311
|
+
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
8312
|
+
);
|
|
8313
|
+
const agentAmounts = this.splitDepositAmount(
|
|
8314
|
+
totalAmount,
|
|
8315
|
+
eligibleAgents,
|
|
8316
|
+
chainId,
|
|
8317
|
+
asset,
|
|
8318
|
+
exempt
|
|
8319
|
+
);
|
|
8320
|
+
if (agentAmounts.length === 0) {
|
|
8321
|
+
const agentsRequiringActivation = eligibleAgents.filter(
|
|
8322
|
+
(agent) => !exempt.has(agent.id)
|
|
8323
|
+
);
|
|
8324
|
+
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
8325
|
+
(highest, agent) => {
|
|
8326
|
+
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
8327
|
+
return minimum > highest ? minimum : highest;
|
|
8328
|
+
},
|
|
8329
|
+
0n
|
|
8330
|
+
);
|
|
8331
|
+
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7316
8332
|
throw new OwneyError(
|
|
7317
|
-
"
|
|
7318
|
-
"
|
|
8333
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
8334
|
+
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7319
8335
|
{
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
8336
|
+
amount,
|
|
8337
|
+
asset,
|
|
8338
|
+
chainId,
|
|
8339
|
+
minDepositAmount: minimumRequired.toString(),
|
|
8340
|
+
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
8341
|
+
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
8342
|
+
agentId: agent.id,
|
|
8343
|
+
minDepositAmount: this.getMinDepositAmount(
|
|
8344
|
+
agent,
|
|
8345
|
+
chainId,
|
|
8346
|
+
asset
|
|
8347
|
+
).toString()
|
|
8348
|
+
}))
|
|
8349
|
+
}
|
|
7327
8350
|
);
|
|
7328
8351
|
}
|
|
8352
|
+
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
8353
|
+
if (!depositCallback && batchTransfer) {
|
|
8354
|
+
return await runAgentDepositBatch(
|
|
8355
|
+
chainId,
|
|
8356
|
+
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
8357
|
+
id: agent.id,
|
|
8358
|
+
amount: amount2,
|
|
8359
|
+
run: (callback) => withFailureReporting(
|
|
8360
|
+
this.apiKey,
|
|
8361
|
+
agent.id,
|
|
8362
|
+
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
8363
|
+
this.routingApiBaseUrl
|
|
8364
|
+
)
|
|
8365
|
+
})),
|
|
8366
|
+
async (cid, transfers) => {
|
|
8367
|
+
try {
|
|
8368
|
+
return await batchTransfer(cid, transfers);
|
|
8369
|
+
} catch (error) {
|
|
8370
|
+
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
8371
|
+
throw error;
|
|
8372
|
+
const requiredAmount = transfers.reduce(
|
|
8373
|
+
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
8374
|
+
0n
|
|
8375
|
+
);
|
|
8376
|
+
await this.approvePermit2(
|
|
8377
|
+
asset,
|
|
8378
|
+
requiredAmount,
|
|
8379
|
+
cid
|
|
8380
|
+
);
|
|
8381
|
+
return batchTransfer(cid, transfers);
|
|
8382
|
+
}
|
|
8383
|
+
}
|
|
8384
|
+
);
|
|
8385
|
+
}
|
|
8386
|
+
const agentResults = {};
|
|
8387
|
+
for (const [
|
|
8388
|
+
index,
|
|
8389
|
+
{ agent, amount: agentAmount }
|
|
8390
|
+
] of agentAmounts.entries()) {
|
|
8391
|
+
try {
|
|
8392
|
+
agentResults[agent.id] = await this.depositWithFallback(
|
|
8393
|
+
agent,
|
|
8394
|
+
state,
|
|
8395
|
+
chainId,
|
|
8396
|
+
agentAmount,
|
|
8397
|
+
asset,
|
|
8398
|
+
effectiveCallback,
|
|
8399
|
+
depositCallback,
|
|
8400
|
+
onProgress,
|
|
8401
|
+
() => paidFallbackAgents.add(agent.id),
|
|
8402
|
+
!!options.resumeTxHash
|
|
8403
|
+
);
|
|
8404
|
+
} catch (error) {
|
|
8405
|
+
if (Object.keys(agentResults).length === 0) throw error;
|
|
8406
|
+
throw new OwneyError(
|
|
8407
|
+
"DEPOSIT_PARTIAL_FAILURE",
|
|
8408
|
+
"Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
|
|
8409
|
+
{
|
|
8410
|
+
agentResults,
|
|
8411
|
+
failedAgentId: agent.id,
|
|
8412
|
+
failedAmount: agentAmount,
|
|
8413
|
+
unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
|
|
8414
|
+
cause: error
|
|
8415
|
+
},
|
|
8416
|
+
agent.id
|
|
8417
|
+
);
|
|
8418
|
+
}
|
|
8419
|
+
}
|
|
8420
|
+
return { agentResults };
|
|
8421
|
+
} catch (error) {
|
|
8422
|
+
if (!depositCallback && onProgress && !options.resumeTxHash) {
|
|
8423
|
+
const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
|
|
8424
|
+
for (const id of ids) {
|
|
8425
|
+
const evidence = [...prepared.values()].find((value) => value.agentId === id);
|
|
8426
|
+
if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
|
|
8427
|
+
const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
|
|
8428
|
+
if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
|
|
8429
|
+
notifyFunding(onProgress, { ...evidence ?? {
|
|
8430
|
+
agentId: id,
|
|
8431
|
+
chainId,
|
|
8432
|
+
token: asset,
|
|
8433
|
+
walletAddress: state.walletAddress,
|
|
8434
|
+
intendedAmount: amount,
|
|
8435
|
+
registration: "unknown",
|
|
8436
|
+
delivery: "not-applicable"
|
|
8437
|
+
}, transfer: "failed" });
|
|
8438
|
+
}
|
|
8439
|
+
}
|
|
8440
|
+
throw error;
|
|
7329
8441
|
}
|
|
7330
|
-
return { agentResults };
|
|
7331
8442
|
}
|
|
7332
8443
|
/**
|
|
7333
8444
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
@@ -7356,7 +8467,7 @@ var OwneySDK = class {
|
|
|
7356
8467
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
7357
8468
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
7358
8469
|
*/
|
|
7359
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
8470
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
|
|
7360
8471
|
const attempt = (callback) => withFailureReporting(
|
|
7361
8472
|
this.apiKey,
|
|
7362
8473
|
agent.id,
|
|
@@ -7368,6 +8479,7 @@ var OwneySDK = class {
|
|
|
7368
8479
|
try {
|
|
7369
8480
|
return await attempt(effectiveCallback);
|
|
7370
8481
|
} catch (error) {
|
|
8482
|
+
if (resumeOnly) throw error;
|
|
7371
8483
|
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
7372
8484
|
approvalAttempted = true;
|
|
7373
8485
|
console.warn(
|
|
@@ -7385,6 +8497,7 @@ var OwneySDK = class {
|
|
|
7385
8497
|
`[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
|
|
7386
8498
|
error
|
|
7387
8499
|
);
|
|
8500
|
+
onPaidFallback?.();
|
|
7388
8501
|
return attempt(void 0);
|
|
7389
8502
|
}
|
|
7390
8503
|
}
|
|
@@ -7467,7 +8580,12 @@ var OwneySDK = class {
|
|
|
7467
8580
|
*/
|
|
7468
8581
|
async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
|
|
7469
8582
|
try {
|
|
7470
|
-
|
|
8583
|
+
if (agent.hasExistingDepositFunds && await agent.hasExistingDepositFunds(
|
|
8584
|
+
state,
|
|
8585
|
+
chainId,
|
|
8586
|
+
asset
|
|
8587
|
+
).catch(() => void 0) === true) return true;
|
|
8588
|
+
const balance = await agent.getBalances(state, chainId, asset);
|
|
7471
8589
|
const target = asset.toLowerCase();
|
|
7472
8590
|
const token = balance.tokens.find(
|
|
7473
8591
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
@@ -7593,7 +8711,8 @@ var OwneySDK = class {
|
|
|
7593
8711
|
return verified;
|
|
7594
8712
|
}
|
|
7595
8713
|
/** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
|
|
7596
|
-
async resolveSponsoredWithdrawalEvidence(evidence) {
|
|
8714
|
+
async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
|
|
8715
|
+
if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
|
|
7597
8716
|
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
7598
8717
|
if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
7599
8718
|
const agent = this.getAgent("yieldseeker");
|
|
@@ -7609,18 +8728,26 @@ var OwneySDK = class {
|
|
|
7609
8728
|
owner: state.walletAddress,
|
|
7610
8729
|
agentWallet: wallet,
|
|
7611
8730
|
asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
|
|
7612
|
-
userOpHash: evidence.userOpHash
|
|
8731
|
+
userOpHash: evidence.userOpHash,
|
|
8732
|
+
...evidence.userOperation ? { userOperation: Object.fromEntries([
|
|
8733
|
+
"sender",
|
|
8734
|
+
"nonce",
|
|
8735
|
+
"initCode",
|
|
8736
|
+
"callData",
|
|
8737
|
+
"callGasLimit",
|
|
8738
|
+
"verificationGasLimit",
|
|
8739
|
+
"preVerificationGas",
|
|
8740
|
+
"maxFeePerGas",
|
|
8741
|
+
"maxPriorityFeePerGas",
|
|
8742
|
+
"paymasterAndData"
|
|
8743
|
+
].map((key2) => [key2, evidence.userOperation[key2]])) } : {},
|
|
8744
|
+
...Number.isFinite(options.submittedAt) && options.submittedAt > 0 ? { submittedAt: options.submittedAt } : {}
|
|
7613
8745
|
})
|
|
7614
8746
|
});
|
|
7615
8747
|
if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
|
|
7616
8748
|
const payload = await response.json();
|
|
7617
8749
|
txHash = payload.data?.txHash ?? void 0;
|
|
7618
8750
|
executionFailed = payload.data?.executionFailed === true;
|
|
7619
|
-
} else if (evidence.callsId) {
|
|
7620
|
-
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
7621
|
-
if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
|
|
7622
|
-
executionFailed = status?.status === 400 || status?.status === 500;
|
|
7623
|
-
txHash = status?.receipts?.[0]?.transactionHash;
|
|
7624
8751
|
}
|
|
7625
8752
|
assertCurrent();
|
|
7626
8753
|
if (executionFailed) return {
|
|
@@ -7631,6 +8758,60 @@ var OwneySDK = class {
|
|
|
7631
8758
|
};
|
|
7632
8759
|
return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
|
|
7633
8760
|
}
|
|
8761
|
+
/** Recover an EIP-5792 deposit or payout using this account's exact batch ID. */
|
|
8762
|
+
async resolveWalletCallsFundingEvidence(evidence, direction) {
|
|
8763
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
8764
|
+
if (!evidence.callsId || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
8765
|
+
const agent = this.getAgent(evidence.agentId);
|
|
8766
|
+
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
8767
|
+
if (!wallet || wallet.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return evidence;
|
|
8768
|
+
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
8769
|
+
assertCurrent();
|
|
8770
|
+
if (status?.id !== void 0 && status.id !== evidence.callsId || status?.chainId !== void 0 && Number(status.chainId) !== chainId) return evidence;
|
|
8771
|
+
const statusCode2 = Number(status?.status);
|
|
8772
|
+
if (statusCode2 === 400 || statusCode2 === 500) return {
|
|
8773
|
+
...evidence,
|
|
8774
|
+
transfer: "failed",
|
|
8775
|
+
delivery: direction === "withdraw" ? "failed" : "not-applicable"
|
|
8776
|
+
};
|
|
8777
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash?.toLowerCase()).filter((hash) => !!hash && /^0x[\da-f]{64}$/i.test(hash)))];
|
|
8778
|
+
if (hashes.length === 1) return { ...evidence, txHash: hashes[0] };
|
|
8779
|
+
if (hashes.length > 16) return evidence;
|
|
8780
|
+
const matches = [];
|
|
8781
|
+
for (const txHash of hashes) {
|
|
8782
|
+
const inspected = await this.inspectFunding({ ...evidence, txHash }, direction);
|
|
8783
|
+
assertCurrent();
|
|
8784
|
+
if (inspected.transfer === "confirmed") matches.push(inspected);
|
|
8785
|
+
}
|
|
8786
|
+
return matches.length === 1 ? matches[0] : evidence;
|
|
8787
|
+
}
|
|
8788
|
+
/** Whether a foreground deposit can resume the same signed batch, never a
|
|
8789
|
+
* fresh authorization. The batch rail independently rechecks the exact plan. */
|
|
8790
|
+
async canResumePendingDeposit(evidences, amount) {
|
|
8791
|
+
if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
|
|
8792
|
+
const first = evidences[0];
|
|
8793
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
|
|
8794
|
+
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
|
|
8795
|
+
if (!token) return false;
|
|
8796
|
+
const pending = pendingTokenBatch({
|
|
8797
|
+
apiKey: this.apiKey,
|
|
8798
|
+
baseUrl: this.routingApiBaseUrl,
|
|
8799
|
+
owner: state.walletAddress,
|
|
8800
|
+
chainId,
|
|
8801
|
+
token: token.address
|
|
8802
|
+
});
|
|
8803
|
+
if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
|
|
8804
|
+
const destinations = /* @__PURE__ */ new Set();
|
|
8805
|
+
for (const evidence of evidences) {
|
|
8806
|
+
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;
|
|
8807
|
+
const agent = this.getAgent(evidence.agentId);
|
|
8808
|
+
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
8809
|
+
if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
|
|
8810
|
+
destinations.add(wallet.toLowerCase());
|
|
8811
|
+
}
|
|
8812
|
+
assertCurrent();
|
|
8813
|
+
return true;
|
|
8814
|
+
}
|
|
7634
8815
|
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
7635
8816
|
endFundingBalanceRefresh(evidence, read2) {
|
|
7636
8817
|
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
@@ -7731,6 +8912,30 @@ var OwneySDK = class {
|
|
|
7731
8912
|
}
|
|
7732
8913
|
};
|
|
7733
8914
|
const token = asset;
|
|
8915
|
+
const agentFailures = {};
|
|
8916
|
+
const recordAgentFailure = (id, error) => {
|
|
8917
|
+
if (!(error instanceof OwneyError)) return;
|
|
8918
|
+
const details = error.details ?? {};
|
|
8919
|
+
const codeValue = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : void 0;
|
|
8920
|
+
const revertNames = ["InsufficientBalance", "InvalidAsset", "Unauthorized", "Error", "Panic", "FailedOp", "FailedOpWithRevert"];
|
|
8921
|
+
const panicCodes = [0, 1, 17, 18, 33, 34, 49, 50, 65, 81];
|
|
8922
|
+
agentFailures[id] = {
|
|
8923
|
+
code: error.code,
|
|
8924
|
+
phase: ["prepare", "send", "observe"].includes(String(details.phase)) ? details.phase : void 0,
|
|
8925
|
+
failureCode: codeValue(details.failureCode),
|
|
8926
|
+
reason: codeValue(details.reason),
|
|
8927
|
+
withdrawalPhase: details.withdrawalPhase === "unwind" ? "unwind" : void 0,
|
|
8928
|
+
statusCode: typeof details.statusCode === "number" ? details.statusCode : void 0,
|
|
8929
|
+
rpcCode: typeof details.rpcCode === "number" ? details.rpcCode : void 0,
|
|
8930
|
+
rpcMethod: typeof details.rpcMethod === "string" && /^[A-Za-z_]{1,80}$/.test(details.rpcMethod) ? details.rpcMethod : void 0,
|
|
8931
|
+
revertSelector: typeof details.revertSelector === "string" && /^0x[0-9a-f]{8}$/i.test(details.revertSelector) ? details.revertSelector : void 0,
|
|
8932
|
+
revertName: typeof details.revertName === "string" && revertNames.includes(details.revertName) ? details.revertName : void 0,
|
|
8933
|
+
aaCode: typeof details.aaCode === "string" && /^AA[0-9]{2}$/.test(details.aaCode) ? details.aaCode : void 0,
|
|
8934
|
+
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,
|
|
8935
|
+
safeToFallback: typeof details.safeToFallback === "boolean" ? details.safeToFallback : void 0,
|
|
8936
|
+
executionFailed: typeof details.executionFailed === "boolean" ? details.executionFailed : void 0
|
|
8937
|
+
};
|
|
8938
|
+
};
|
|
7734
8939
|
const submissionEvidence = /* @__PURE__ */ new Map();
|
|
7735
8940
|
const submit = async (agent, requested2) => {
|
|
7736
8941
|
attempted.add(agent.id);
|
|
@@ -7751,9 +8956,19 @@ var OwneySDK = class {
|
|
|
7751
8956
|
};
|
|
7752
8957
|
progress(evidence);
|
|
7753
8958
|
try {
|
|
8959
|
+
if (options.onBeforeSubmission)
|
|
8960
|
+
return await agent.withdraw(
|
|
8961
|
+
state,
|
|
8962
|
+
chainId,
|
|
8963
|
+
token,
|
|
8964
|
+
requested2,
|
|
8965
|
+
progress,
|
|
8966
|
+
options.gasMode ?? "auto",
|
|
8967
|
+
(next) => options.onBeforeSubmission({ ...evidence, ...next })
|
|
8968
|
+
);
|
|
7754
8969
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
7755
8970
|
} catch (error) {
|
|
7756
|
-
if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
8971
|
+
if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
7757
8972
|
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
7758
8973
|
throw error;
|
|
7759
8974
|
}
|
|
@@ -7803,6 +9018,7 @@ var OwneySDK = class {
|
|
|
7803
9018
|
}
|
|
7804
9019
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
7805
9020
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
9021
|
+
recordAgentFailure(agent.id, err);
|
|
7806
9022
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7807
9023
|
void reportAgentFailure(
|
|
7808
9024
|
this.apiKey,
|
|
@@ -7813,17 +9029,23 @@ var OwneySDK = class {
|
|
|
7813
9029
|
}
|
|
7814
9030
|
}
|
|
7815
9031
|
if (Object.keys(results2).length === 0) {
|
|
9032
|
+
if (Object.keys(agentErrors2).length === 0)
|
|
9033
|
+
throw new OwneyError(
|
|
9034
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
9035
|
+
"No funds are currently available to withdraw for this asset.",
|
|
9036
|
+
{ asset, available: "0" }
|
|
9037
|
+
);
|
|
7816
9038
|
throw new OwneyError(
|
|
7817
9039
|
"WITHDRAW_ALL_FAILED",
|
|
7818
9040
|
`Failed to withdraw "${asset}" from all eligible agents.`,
|
|
7819
|
-
{ asset, agentErrors: agentErrors2 }
|
|
9041
|
+
{ asset, agentErrors: agentErrors2, agentFailures }
|
|
7820
9042
|
);
|
|
7821
9043
|
}
|
|
7822
9044
|
if (Object.keys(agentErrors2).length > 0) {
|
|
7823
9045
|
throw new OwneyError(
|
|
7824
9046
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7825
9047
|
"Some agents could not complete the withdrawal.",
|
|
7826
|
-
{ asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
9048
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
7827
9049
|
);
|
|
7828
9050
|
}
|
|
7829
9051
|
const amountsKnown = Object.values(results2).every(
|
|
@@ -7835,11 +9057,10 @@ var OwneySDK = class {
|
|
|
7835
9057
|
};
|
|
7836
9058
|
}
|
|
7837
9059
|
const requested = BigInt(amount);
|
|
7838
|
-
const aggregated = await this.
|
|
9060
|
+
const { balances: aggregated, failures: balanceFailures } = await this.readAggregatedBalances({ chainId, asset: token });
|
|
7839
9061
|
const unavailableAgents = eligibleAgents.filter(
|
|
7840
9062
|
(agent) => !(agent.id in aggregated.agentBalances)
|
|
7841
9063
|
);
|
|
7842
|
-
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
7843
9064
|
const balances = projectAgentBalancesForAsset(
|
|
7844
9065
|
eligibleAgents,
|
|
7845
9066
|
aggregated.agentBalances,
|
|
@@ -7847,6 +9068,44 @@ var OwneySDK = class {
|
|
|
7847
9068
|
asset,
|
|
7848
9069
|
assetInfo.decimals
|
|
7849
9070
|
);
|
|
9071
|
+
const indexedAvailable = balances.reduce((sum, entry) => sum + entry.balance, 0n);
|
|
9072
|
+
await Promise.all(balances.map(async (entry) => {
|
|
9073
|
+
if (entry.balance !== 0n && indexedAvailable >= requested || !entry.agent.getWithdrawableBalance) return;
|
|
9074
|
+
const originalFailure = balanceFailures.get(entry.agent.id);
|
|
9075
|
+
if (originalFailure instanceof OwneyError && ![
|
|
9076
|
+
"AGENT_API_ERROR",
|
|
9077
|
+
"AGENT_TIMEOUT",
|
|
9078
|
+
"AGENT_RATE_LIMITED"
|
|
9079
|
+
].includes(originalFailure.code)) return;
|
|
9080
|
+
if (this.isUserRejectedWithdrawal(originalFailure)) throw originalFailure;
|
|
9081
|
+
if (entry.balance !== 0n && entry.agent.id !== "yieldseeker") {
|
|
9082
|
+
const total = aggregated.agentBalances[entry.agent.id]?.assetBalances?.find((value) => value.chainId === chainId && value.asset.toUpperCase() === token);
|
|
9083
|
+
if (!total || (0, import_viem16.parseUnits)(total.amount, assetInfo.decimals) <= entry.balance) return;
|
|
9084
|
+
}
|
|
9085
|
+
try {
|
|
9086
|
+
const otherAvailable = indexedAvailable - entry.balance;
|
|
9087
|
+
const minimumAmount = requested > otherAvailable ? requested - otherAvailable : void 0;
|
|
9088
|
+
const verified = await entry.agent.getWithdrawableBalance(state, chainId, token, minimumAmount);
|
|
9089
|
+
if (typeof verified !== "bigint" || verified < 0n)
|
|
9090
|
+
throw new OwneyError("WITHDRAW_BALANCE_UNAVAILABLE", "Invalid withdrawal balance proof.", {}, entry.agent.id);
|
|
9091
|
+
if (this.state !== state || this.state.walletAddress !== state.walletAddress || this.state.chainId !== chainId)
|
|
9092
|
+
throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
|
|
9093
|
+
entry.balance = verified;
|
|
9094
|
+
const unavailableIndex = unavailableAgents.indexOf(entry.agent);
|
|
9095
|
+
if (unavailableIndex !== -1) unavailableAgents.splice(unavailableIndex, 1);
|
|
9096
|
+
if (aggregated.agentErrors) delete aggregated.agentErrors[entry.agent.id];
|
|
9097
|
+
} catch (error) {
|
|
9098
|
+
entry.balance = 0n;
|
|
9099
|
+
if (!unavailableAgents.includes(entry.agent)) unavailableAgents.push(entry.agent);
|
|
9100
|
+
aggregated.agentErrors = {
|
|
9101
|
+
...aggregated.agentErrors,
|
|
9102
|
+
[entry.agent.id]: error instanceof Error ? error.message : String(error)
|
|
9103
|
+
};
|
|
9104
|
+
}
|
|
9105
|
+
}));
|
|
9106
|
+
if (this.state !== state || this.state.chainId !== chainId)
|
|
9107
|
+
throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
|
|
9108
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
7850
9109
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
7851
9110
|
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
7852
9111
|
throw new OwneyError(
|
|
@@ -7904,6 +9163,7 @@ var OwneySDK = class {
|
|
|
7904
9163
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7905
9164
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
7906
9165
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
9166
|
+
recordAgentFailure(p.agent.id, err);
|
|
7907
9167
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7908
9168
|
void reportAgentFailure(
|
|
7909
9169
|
this.apiKey,
|
|
@@ -7924,12 +9184,24 @@ var OwneySDK = class {
|
|
|
7924
9184
|
{
|
|
7925
9185
|
asset,
|
|
7926
9186
|
amount,
|
|
7927
|
-
agentErrors
|
|
9187
|
+
agentErrors,
|
|
9188
|
+
agentFailures
|
|
7928
9189
|
}
|
|
7929
9190
|
);
|
|
7930
9191
|
}
|
|
7931
9192
|
const withdrawn = sumWithdrawnAmount(results);
|
|
7932
|
-
|
|
9193
|
+
const verifiedFeeDeductions = orderedPlans.reduce((total, plan) => {
|
|
9194
|
+
const result = results[plan.agent.id];
|
|
9195
|
+
const adjustment = result?.feeAdjustment;
|
|
9196
|
+
const evidence = result?.evidence;
|
|
9197
|
+
const submitted = submissionEvidence.get(plan.agent.id);
|
|
9198
|
+
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;
|
|
9199
|
+
const fee = BigInt(adjustment.feeAmount);
|
|
9200
|
+
const net = BigInt(result.amount);
|
|
9201
|
+
const gross = BigInt(adjustment.grossAmount);
|
|
9202
|
+
return fee > 0n && net > 0n && gross === plan.planned && net + fee === gross ? total + fee : total;
|
|
9203
|
+
}, 0n);
|
|
9204
|
+
if (withdrawn + verifiedFeeDeductions < requested) {
|
|
7933
9205
|
throw new OwneyError(
|
|
7934
9206
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7935
9207
|
`Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
|
|
@@ -7939,6 +9211,7 @@ var OwneySDK = class {
|
|
|
7939
9211
|
withdrawn: withdrawn.toString(),
|
|
7940
9212
|
partialResults: results,
|
|
7941
9213
|
agentErrors,
|
|
9214
|
+
agentFailures,
|
|
7942
9215
|
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
7943
9216
|
}
|
|
7944
9217
|
);
|
|
@@ -7952,23 +9225,42 @@ var OwneySDK = class {
|
|
|
7952
9225
|
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
7953
9226
|
*/
|
|
7954
9227
|
async getBalances(agentId, options) {
|
|
7955
|
-
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
7956
|
-
const asset = options?.asset;
|
|
7957
|
-
const readBalance = (agent) => this.readAgent(
|
|
7958
|
-
agent,
|
|
7959
|
-
"balances",
|
|
7960
|
-
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
7961
|
-
asset ? { asset } : null
|
|
7962
|
-
);
|
|
7963
9228
|
if (agentId) {
|
|
9229
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
9230
|
+
const asset = options?.asset;
|
|
7964
9231
|
const agent = this.getAgent(agentId);
|
|
7965
|
-
const result = await
|
|
9232
|
+
const result = await this.readAgent(
|
|
9233
|
+
agent,
|
|
9234
|
+
"balances",
|
|
9235
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
9236
|
+
asset ? { asset } : null
|
|
9237
|
+
);
|
|
7966
9238
|
assertCurrent();
|
|
7967
9239
|
return {
|
|
7968
9240
|
...result,
|
|
7969
9241
|
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
7970
9242
|
};
|
|
7971
9243
|
}
|
|
9244
|
+
const { balances, failures } = await this.readAggregatedBalances(options);
|
|
9245
|
+
if (Object.keys(balances.agentBalances).length === 0) {
|
|
9246
|
+
throw new OwneyError(
|
|
9247
|
+
"BALANCE_ALL_FAILED",
|
|
9248
|
+
"Failed to fetch balances for all active agents.",
|
|
9249
|
+
{ agentErrors: balances.agentErrors ?? {}, failures: [...failures.values()] }
|
|
9250
|
+
);
|
|
9251
|
+
}
|
|
9252
|
+
return balances;
|
|
9253
|
+
}
|
|
9254
|
+
/** Preserve failure objects for withdrawal recovery without changing public balance results. */
|
|
9255
|
+
async readAggregatedBalances(options) {
|
|
9256
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
9257
|
+
const asset = options?.asset;
|
|
9258
|
+
const readBalance = (agent) => this.readAgent(
|
|
9259
|
+
agent,
|
|
9260
|
+
"balances",
|
|
9261
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
9262
|
+
asset ? { asset } : null
|
|
9263
|
+
);
|
|
7972
9264
|
let totalBalance = 0;
|
|
7973
9265
|
const results = {};
|
|
7974
9266
|
const entries = [...this.getActiveAgents().entries()];
|
|
@@ -7985,39 +9277,33 @@ var OwneySDK = class {
|
|
|
7985
9277
|
})
|
|
7986
9278
|
);
|
|
7987
9279
|
assertCurrent();
|
|
7988
|
-
let successCount = 0;
|
|
7989
9280
|
const agentErrors = {};
|
|
7990
9281
|
const agentRetryAt = {};
|
|
7991
|
-
const agentFailures =
|
|
9282
|
+
const agentFailures = /* @__PURE__ */ new Map();
|
|
7992
9283
|
for (let i = 0; i < balanceResults.length; i++) {
|
|
7993
9284
|
const settledResult = balanceResults[i];
|
|
7994
|
-
const [
|
|
9285
|
+
const [agentId] = entries[i];
|
|
7995
9286
|
if (settledResult.status === "fulfilled") {
|
|
7996
9287
|
const [id, b] = settledResult.value;
|
|
7997
9288
|
results[id] = b;
|
|
7998
9289
|
totalBalance += Number(b.totalBalance);
|
|
7999
|
-
successCount += 1;
|
|
8000
9290
|
continue;
|
|
8001
9291
|
}
|
|
8002
9292
|
const reason = settledResult.reason;
|
|
8003
|
-
agentFailures.
|
|
9293
|
+
agentFailures.set(agentId, reason);
|
|
8004
9294
|
const retryDelay = rateLimitDelay(reason);
|
|
8005
|
-
if (retryDelay !== void 0) agentRetryAt[
|
|
8006
|
-
agentErrors[
|
|
8007
|
-
console.error(`[owney-sdk] Balance fetch failed for agent "${
|
|
8008
|
-
}
|
|
8009
|
-
if (successCount === 0) {
|
|
8010
|
-
throw new OwneyError(
|
|
8011
|
-
"BALANCE_ALL_FAILED",
|
|
8012
|
-
"Failed to fetch balances for all active agents.",
|
|
8013
|
-
{ agentErrors, failures: agentFailures }
|
|
8014
|
-
);
|
|
9295
|
+
if (retryDelay !== void 0) agentRetryAt[agentId] = Date.now() + retryDelay;
|
|
9296
|
+
agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
|
|
9297
|
+
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId}":`, reason);
|
|
8015
9298
|
}
|
|
8016
9299
|
return {
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
9300
|
+
balances: {
|
|
9301
|
+
totalBalance: String(totalBalance),
|
|
9302
|
+
totalBalanceAsset: "usdc",
|
|
9303
|
+
agentBalances: results,
|
|
9304
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
|
|
9305
|
+
},
|
|
9306
|
+
failures: agentFailures
|
|
8021
9307
|
};
|
|
8022
9308
|
}
|
|
8023
9309
|
/**
|
|
@@ -8398,16 +9684,16 @@ var OwneySDK = class {
|
|
|
8398
9684
|
);
|
|
8399
9685
|
}
|
|
8400
9686
|
const provider = this.requireConnectedProvider();
|
|
8401
|
-
const walletChainClient = (0,
|
|
9687
|
+
const walletChainClient = (0, import_viem16.createPublicClient)({
|
|
8402
9688
|
chain: VIEM_CHAIN2[chainId],
|
|
8403
|
-
transport: (0,
|
|
9689
|
+
transport: (0, import_viem16.custom)(provider)
|
|
8404
9690
|
});
|
|
8405
9691
|
const publicClient = this.getPaidRpcClient(chainId);
|
|
8406
9692
|
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
8407
|
-
const wallet = (0,
|
|
9693
|
+
const wallet = (0, import_viem16.createWalletClient)({
|
|
8408
9694
|
account: state.walletAddress,
|
|
8409
9695
|
chain: VIEM_CHAIN2[chainId],
|
|
8410
|
-
transport: (0,
|
|
9696
|
+
transport: (0, import_viem16.custom)(provider)
|
|
8411
9697
|
});
|
|
8412
9698
|
await ensureWalletOnChain(walletChainClient, wallet, chainId);
|
|
8413
9699
|
const hash = await wallet.writeContract({
|
|
@@ -8569,7 +9855,7 @@ var OwneySDK = class {
|
|
|
8569
9855
|
};
|
|
8570
9856
|
|
|
8571
9857
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
8572
|
-
var
|
|
9858
|
+
var import_viem17 = require("viem");
|
|
8573
9859
|
var import_siwe2 = require("siwe");
|
|
8574
9860
|
var import_sdk2 = require("@zyfai/sdk");
|
|
8575
9861
|
|
|
@@ -8696,7 +9982,7 @@ function buildSIWXConfig(deps) {
|
|
|
8696
9982
|
issuedAt,
|
|
8697
9983
|
toString() {
|
|
8698
9984
|
return new import_siwe2.SiweMessage({
|
|
8699
|
-
address: (0,
|
|
9985
|
+
address: (0, import_viem17.getAddress)(accountAddress),
|
|
8700
9986
|
chainId: numericChainId(chainId),
|
|
8701
9987
|
domain,
|
|
8702
9988
|
uri,
|