@owney/sdk 0.7.25-beta.13 → 0.7.25-beta.15
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 +1572 -534
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +1504 -463
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/lib/funding-evidence.ts
|
|
2
2
|
import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
|
|
3
|
+
import { entryPoint06Abi, entryPoint07Abi, getUserOperationHash, toUserOperation } from "viem/account-abstraction";
|
|
3
4
|
function notifyFunding(callback, evidence) {
|
|
4
5
|
try {
|
|
5
6
|
void Promise.resolve(callback?.({ ...evidence })).catch(() => {
|
|
@@ -9,11 +10,53 @@ function notifyFunding(callback, evidence) {
|
|
|
9
10
|
}
|
|
10
11
|
var fullWithdrawalAbi = parseAbi(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
11
12
|
var withdrawalEventAbi = parseAbi(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
var accountExecutionAbi = parseAbi([
|
|
14
|
+
"function execute(address target,uint256 value,bytes data)",
|
|
15
|
+
"function executeBatch((address target,uint256 value,bytes data)[] calls)"
|
|
16
|
+
]);
|
|
17
|
+
var entryPoint06 = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
|
|
18
|
+
var entryPoint07 = "0x0000000071727de22e5e9d8baf0edac6f37da032";
|
|
19
|
+
function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receipt) {
|
|
20
|
+
if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
|
|
21
|
+
const matchesFullCall = (data) => {
|
|
22
|
+
try {
|
|
23
|
+
const decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data });
|
|
24
|
+
return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
if (transaction.to?.toLowerCase() === evidence.agentWallet.toLowerCase() && transaction.from.toLowerCase() === evidence.walletAddress.toLowerCase()) return matchesFullCall(transaction.input);
|
|
14
30
|
try {
|
|
15
|
-
const
|
|
16
|
-
|
|
31
|
+
const address = transaction.to?.toLowerCase();
|
|
32
|
+
if (address !== entryPoint06 && address !== entryPoint07) return false;
|
|
33
|
+
const version = address === entryPoint06 ? "0.6" : "0.7";
|
|
34
|
+
const decoded = decodeFunctionData({ abi: version === "0.6" ? entryPoint06Abi : entryPoint07Abi, data: transaction.input });
|
|
35
|
+
if (decoded.functionName !== "handleOps") return false;
|
|
36
|
+
const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
|
|
37
|
+
if (operations.length !== 1) return false;
|
|
38
|
+
const operation = operations[0];
|
|
39
|
+
const userOperation = toUserOperation(operation);
|
|
40
|
+
const hash = getUserOperationHash({
|
|
41
|
+
chainId: evidence.chainId,
|
|
42
|
+
entryPointAddress: transaction.to,
|
|
43
|
+
entryPointVersion: version,
|
|
44
|
+
userOperation
|
|
45
|
+
});
|
|
46
|
+
if (evidence.userOpHash && hash.toLowerCase() !== evidence.userOpHash.toLowerCase()) return false;
|
|
47
|
+
if (!receipt?.logs.some((log) => {
|
|
48
|
+
if (log.address.toLowerCase() !== address) return false;
|
|
49
|
+
try {
|
|
50
|
+
const event = decodeEventLog({ abi: entryPoint06Abi, data: log.data, topics: log.topics });
|
|
51
|
+
return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
})) return false;
|
|
56
|
+
if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
|
|
57
|
+
const execution = decodeFunctionData({ abi: accountExecutionAbi, data: operation.callData });
|
|
58
|
+
const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
|
|
59
|
+
return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
|
|
17
60
|
} catch {
|
|
18
61
|
return false;
|
|
19
62
|
}
|
|
@@ -25,6 +68,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
25
68
|
const attributed = /* @__PURE__ */ new Map();
|
|
26
69
|
const agent = evidence.agentWallet?.toLowerCase();
|
|
27
70
|
const owner = evidence.walletAddress.toLowerCase();
|
|
71
|
+
const source = direction === "deposit" ? owner : agent;
|
|
72
|
+
const destination = direction === "deposit" ? agent : owner;
|
|
28
73
|
const logIndexes = [];
|
|
29
74
|
const transferLogs = [];
|
|
30
75
|
const zeroTransferLogs = [];
|
|
@@ -39,17 +84,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
39
84
|
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
40
85
|
zeroTransferLogs.push(log.logIndex);
|
|
41
86
|
let matched = 0n;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (sender !== agent) attributed.set(sender, available - carried);
|
|
50
|
-
if (recipient === owner) matched = carried;
|
|
51
|
-
else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
52
|
-
}
|
|
87
|
+
const sender = from.toLowerCase();
|
|
88
|
+
const recipient = to.toLowerCase();
|
|
89
|
+
const available = sender === source ? value : attributed.get(sender) ?? 0n;
|
|
90
|
+
const carried = available < value ? available : value;
|
|
91
|
+
if (sender !== source) attributed.set(sender, available - carried);
|
|
92
|
+
if (recipient === destination) matched = carried;
|
|
93
|
+
else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
53
94
|
if (matched === 0n) continue;
|
|
54
95
|
amount += matched;
|
|
55
96
|
transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
|
|
@@ -341,8 +382,8 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
341
382
|
return await original.apply(this, args);
|
|
342
383
|
} catch (error) {
|
|
343
384
|
const message = error instanceof Error ? error.message : "";
|
|
344
|
-
request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
345
|
-
if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
|
|
385
|
+
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" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
386
|
+
if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing"].includes(request2.failure)) notify("rejected");
|
|
346
387
|
throw error;
|
|
347
388
|
} finally {
|
|
348
389
|
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
@@ -365,9 +406,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
365
406
|
} };
|
|
366
407
|
}
|
|
367
408
|
|
|
409
|
+
// src/agents/zyfai/zyfai.withdraw-empty.ts
|
|
410
|
+
function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
411
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
412
|
+
const response = record(raw2);
|
|
413
|
+
const portfolio = record(response?.portfolio);
|
|
414
|
+
const chains = record(portfolio?.portfolioByChain);
|
|
415
|
+
if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
|
|
416
|
+
if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
|
|
417
|
+
const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
|
|
418
|
+
const chain = (value) => {
|
|
419
|
+
if (typeof value === "number") return value;
|
|
420
|
+
if (typeof value !== "string") return void 0;
|
|
421
|
+
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
422
|
+
};
|
|
423
|
+
const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
424
|
+
for (const [id, assets] of Object.entries(chains)) {
|
|
425
|
+
const idNumber = chain(id);
|
|
426
|
+
if (idNumber === void 0) return false;
|
|
427
|
+
if (idNumber !== chainId) continue;
|
|
428
|
+
const entries = record(assets);
|
|
429
|
+
if (!entries) return false;
|
|
430
|
+
for (const [asset, value] of Object.entries(entries)) {
|
|
431
|
+
if (symbol(asset) !== token) continue;
|
|
432
|
+
const balance = record(value);
|
|
433
|
+
if (!balance || !zero(balance.balance)) return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
|
|
437
|
+
const entry = record(value);
|
|
438
|
+
if (!entry) return false;
|
|
439
|
+
const id = chain(entry.chainId ?? entry.chain);
|
|
440
|
+
const asset = symbol(entry.tokenSymbol ?? entry.token_symbol);
|
|
441
|
+
if (id === void 0 || asset === void 0) return false;
|
|
442
|
+
if (id !== chainId || asset !== token) continue;
|
|
443
|
+
const amounts = [entry.balance, entry.amount, entry.underlyingAmount].filter((value2) => value2 !== void 0);
|
|
444
|
+
if (!amounts.length || !amounts.every(zero)) return false;
|
|
445
|
+
}
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
368
449
|
// src/agents/zyfai/zyfai.agent.ts
|
|
369
450
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
370
|
-
import { formatUnits, parseAbi as parseAbi2 } from "viem";
|
|
451
|
+
import { erc20Abi as erc20Abi2, formatUnits as formatUnits2, isAddress, parseAbi as parseAbi2 } from "viem";
|
|
371
452
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
372
453
|
|
|
373
454
|
// src/types/config.ts
|
|
@@ -454,14 +535,6 @@ function hexToDecimal(hex, decimals = 6) {
|
|
|
454
535
|
const remainder = parsed % divisor;
|
|
455
536
|
return Number(whole) + Number(remainder) / Number(divisor);
|
|
456
537
|
}
|
|
457
|
-
function normalizeDecimalPlace(value, decimals = 6) {
|
|
458
|
-
const parsed = BigInt(value);
|
|
459
|
-
if (decimals <= 0) return Number(parsed);
|
|
460
|
-
const divisor = BigInt(10 ** decimals);
|
|
461
|
-
const whole = parsed / divisor;
|
|
462
|
-
const remainder = parsed % divisor;
|
|
463
|
-
return Number(whole) + Number(remainder) / Number(divisor);
|
|
464
|
-
}
|
|
465
538
|
var DayFilterMapping = {
|
|
466
539
|
"30D": 30,
|
|
467
540
|
"14D": 14,
|
|
@@ -469,6 +542,8 @@ var DayFilterMapping = {
|
|
|
469
542
|
};
|
|
470
543
|
|
|
471
544
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
545
|
+
import { formatUnits } from "viem";
|
|
546
|
+
var exactHexAmount = (raw2, decimals) => formatUnits(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
|
|
472
547
|
var DECIMALS_BY_SYMBOL = {
|
|
473
548
|
usdc: 6,
|
|
474
549
|
weth: 18,
|
|
@@ -523,15 +598,15 @@ function mapPendingAllocations(raw2) {
|
|
|
523
598
|
if (typeof e.balance !== "string") continue;
|
|
524
599
|
let amount;
|
|
525
600
|
try {
|
|
526
|
-
|
|
601
|
+
if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
|
|
602
|
+
amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
|
|
527
603
|
} catch {
|
|
528
604
|
continue;
|
|
529
605
|
}
|
|
530
|
-
if (!Number.isFinite(amount) || amount <= 0) continue;
|
|
531
606
|
pending.push({
|
|
532
607
|
chainId: e.chainId,
|
|
533
608
|
asset: e.tokenSymbol,
|
|
534
|
-
amount
|
|
609
|
+
amount,
|
|
535
610
|
isPending: e.isPending === true,
|
|
536
611
|
...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
|
|
537
612
|
});
|
|
@@ -554,13 +629,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
554
629
|
{ balance, balanceWithFee, decimals }
|
|
555
630
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
556
631
|
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
557
|
-
const amount =
|
|
558
|
-
totalBalance += amount;
|
|
632
|
+
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
|
|
633
|
+
totalBalance += Number(amount);
|
|
559
634
|
tokens.push({
|
|
560
635
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
561
636
|
chainId: positionChainId,
|
|
562
637
|
asset: symbol,
|
|
563
|
-
amount
|
|
638
|
+
amount
|
|
564
639
|
});
|
|
565
640
|
}
|
|
566
641
|
}
|
|
@@ -574,19 +649,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
574
649
|
// legacy `amount` fallback is hex. Runtime
|
|
575
650
|
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
576
651
|
// when present, otherwise fall back to the per-symbol lookup.
|
|
577
|
-
amount:
|
|
578
|
-
p.underlyingAmountWithFee ?? p.underlyingAmount
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
p.decimals
|
|
589
|
-
)
|
|
652
|
+
amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? formatUnits(
|
|
653
|
+
BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
|
|
654
|
+
tokenDecimals(
|
|
655
|
+
p.token_symbol,
|
|
656
|
+
p.decimals
|
|
657
|
+
)
|
|
658
|
+
) : exactHexAmount(
|
|
659
|
+
p.amount || "0",
|
|
660
|
+
tokenDecimals(
|
|
661
|
+
p.token_symbol,
|
|
662
|
+
p.decimals
|
|
590
663
|
)
|
|
591
664
|
),
|
|
592
665
|
apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
|
|
@@ -1530,7 +1603,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1530
1603
|
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
1531
1604
|
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
1532
1605
|
Object.assign(tx, {
|
|
1533
|
-
amount:
|
|
1606
|
+
amount: formatUnits2(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
1534
1607
|
amountRaw: evidence.confirmedAmount,
|
|
1535
1608
|
blockNumber: evidence.blockNumber,
|
|
1536
1609
|
logIndexes: evidence.logIndexes,
|
|
@@ -2257,6 +2330,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2257
2330
|
evidence2 = { ...evidence2, transfer: "unknown" };
|
|
2258
2331
|
}
|
|
2259
2332
|
notifyFunding(onProgress, evidence2);
|
|
2333
|
+
if (evidence2.transfer === "failed")
|
|
2334
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
|
|
2260
2335
|
try {
|
|
2261
2336
|
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
2262
2337
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
@@ -2295,12 +2370,77 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2295
2370
|
} catch {
|
|
2296
2371
|
}
|
|
2297
2372
|
notifyFunding(onProgress, evidence);
|
|
2373
|
+
if (evidence.transfer === "failed")
|
|
2374
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
|
|
2298
2375
|
return { ...mapDeposit(raw2), evidence };
|
|
2299
2376
|
} catch (error) {
|
|
2300
2377
|
throw error;
|
|
2301
2378
|
}
|
|
2302
2379
|
}
|
|
2303
2380
|
// --- IAgent: Fund operations ---
|
|
2381
|
+
/** Called only after the execution endpoint explicitly rejected a request.
|
|
2382
|
+
* A timeout or an accepted request never authorizes repair and resubmission. */
|
|
2383
|
+
async recoverWithdrawalSession(state, chainId, token, owner) {
|
|
2384
|
+
const internals = this.sdk;
|
|
2385
|
+
const assertScope = () => {
|
|
2386
|
+
const account = internals.walletClient?.account?.address;
|
|
2387
|
+
if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
|
|
2388
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
|
|
2389
|
+
};
|
|
2390
|
+
assertScope();
|
|
2391
|
+
const [wallet, registered, portfolio] = await Promise.all([
|
|
2392
|
+
this.sdk.getSmartWalletAddress(owner, chainId),
|
|
2393
|
+
this.sdk.getSmartWalletByEOA(owner),
|
|
2394
|
+
this.sdk.getPortfolio(owner).catch(() => void 0)
|
|
2395
|
+
]);
|
|
2396
|
+
assertScope();
|
|
2397
|
+
if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
|
|
2398
|
+
throw new OwneyError(
|
|
2399
|
+
"AGENT_AUTH_FAILED",
|
|
2400
|
+
"Could not verify Zyfai's withdrawal wallet.",
|
|
2401
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2402
|
+
this.id
|
|
2403
|
+
);
|
|
2404
|
+
if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
|
|
2405
|
+
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
2406
|
+
const idle = asset ? await this.getPublicClient(chainId).readContract({
|
|
2407
|
+
address: asset.address,
|
|
2408
|
+
abi: erc20Abi2,
|
|
2409
|
+
functionName: "balanceOf",
|
|
2410
|
+
args: [wallet.address]
|
|
2411
|
+
}).catch(() => void 0) : void 0;
|
|
2412
|
+
assertScope();
|
|
2413
|
+
if (idle === 0n) throw new OwneyError(
|
|
2414
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2415
|
+
"No funds remain in Zyfai for this asset.",
|
|
2416
|
+
{ available: "0", chainId, token },
|
|
2417
|
+
this.id
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
const walletState = await this.readWalletState(owner);
|
|
2421
|
+
assertScope();
|
|
2422
|
+
const unavailable = () => new OwneyError(
|
|
2423
|
+
"AGENT_AUTH_FAILED",
|
|
2424
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
2425
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2426
|
+
this.id
|
|
2427
|
+
);
|
|
2428
|
+
if (walletState.predeployed !== false || internals.isPredeployed === true) throw unavailable();
|
|
2429
|
+
internals.hasActiveSessionKey = false;
|
|
2430
|
+
const key2 = `${owner.toLowerCase()}:${chainId}`;
|
|
2431
|
+
this.sessionKeyAttempts.delete(key2);
|
|
2432
|
+
try {
|
|
2433
|
+
const result = await this.sdk.createSessionKey(owner, chainId);
|
|
2434
|
+
assertScope();
|
|
2435
|
+
if (result.success === false || result.alreadyActive === true) throw unavailable();
|
|
2436
|
+
this.sessionKeyAttempts.add(key2);
|
|
2437
|
+
} catch (error) {
|
|
2438
|
+
assertScope();
|
|
2439
|
+
const code = error?.code;
|
|
2440
|
+
if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) throw error;
|
|
2441
|
+
throw unavailable();
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2304
2444
|
async withdraw(state, chainId, token, amount, onProgress) {
|
|
2305
2445
|
const validChainId = isValidChainId(chainId);
|
|
2306
2446
|
try {
|
|
@@ -2318,11 +2458,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2318
2458
|
});
|
|
2319
2459
|
throw error;
|
|
2320
2460
|
}
|
|
2321
|
-
|
|
2461
|
+
let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2322
2462
|
const authToken = this.sdk.httpClient?.authToken;
|
|
2463
|
+
const owner = this.getAddress();
|
|
2323
2464
|
let raw2;
|
|
2324
2465
|
try {
|
|
2325
|
-
|
|
2466
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2467
|
+
try {
|
|
2468
|
+
raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
|
|
2469
|
+
break;
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
|
|
2472
|
+
if (attempt === 1)
|
|
2473
|
+
throw new OwneyError(
|
|
2474
|
+
"AGENT_AUTH_FAILED",
|
|
2475
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
2476
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2477
|
+
this.id
|
|
2478
|
+
);
|
|
2479
|
+
await this.recoverWithdrawalSession(state, validChainId, token, owner);
|
|
2480
|
+
observation.stop();
|
|
2481
|
+
observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
if (!raw2) throw new OwneyError(
|
|
2485
|
+
"AGENT_INVALID_RESPONSE",
|
|
2486
|
+
"Zyfai returned no withdrawal result.",
|
|
2487
|
+
{ chainId, token, phase: "observe" },
|
|
2488
|
+
this.id
|
|
2489
|
+
);
|
|
2326
2490
|
} catch (error) {
|
|
2327
2491
|
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
2328
2492
|
if (observation.attempted() !== void 0) {
|
|
@@ -2605,10 +2769,13 @@ import {
|
|
|
2605
2769
|
createPublicClient as createPublicClient3,
|
|
2606
2770
|
createWalletClient as createWalletClient3,
|
|
2607
2771
|
custom as custom3,
|
|
2772
|
+
decodeEventLog as decodeEventLog2,
|
|
2773
|
+
encodeEventTopics,
|
|
2608
2774
|
encodeFunctionData,
|
|
2609
|
-
erc20Abi as
|
|
2775
|
+
erc20Abi as erc20Abi3,
|
|
2776
|
+
erc4626Abi,
|
|
2610
2777
|
getAddress as getAddress4,
|
|
2611
|
-
isAddress as
|
|
2778
|
+
isAddress as isAddress3
|
|
2612
2779
|
} from "viem";
|
|
2613
2780
|
import { base as base4 } from "viem/chains";
|
|
2614
2781
|
|
|
@@ -3227,7 +3394,7 @@ var YieldseekerApiClient = class {
|
|
|
3227
3394
|
};
|
|
3228
3395
|
|
|
3229
3396
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
3230
|
-
import { formatUnits as
|
|
3397
|
+
import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
|
|
3231
3398
|
|
|
3232
3399
|
// src/lib/helpers/snapshot-apy.ts
|
|
3233
3400
|
var DAY_MS = 864e5;
|
|
@@ -3294,10 +3461,10 @@ function raw(value, endpoint) {
|
|
|
3294
3461
|
return BigInt(value);
|
|
3295
3462
|
}
|
|
3296
3463
|
function decimal(value, decimals, endpoint) {
|
|
3297
|
-
return
|
|
3464
|
+
return formatUnits3(raw(value, endpoint), decimals);
|
|
3298
3465
|
}
|
|
3299
3466
|
function usd(rawAmount, decimals, price) {
|
|
3300
|
-
return Number(
|
|
3467
|
+
return Number(formatUnits3(rawAmount, decimals)) * price;
|
|
3301
3468
|
}
|
|
3302
3469
|
function percent(value) {
|
|
3303
3470
|
const result = Number(value);
|
|
@@ -3323,7 +3490,7 @@ function assetAddressValue(record, address) {
|
|
|
3323
3490
|
}
|
|
3324
3491
|
function position(value, asset, baseAssetDecimals) {
|
|
3325
3492
|
const option = value?.yieldOption;
|
|
3326
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !
|
|
3493
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress2(option.address)) {
|
|
3327
3494
|
return invalid("yield positions", "missing vault metadata");
|
|
3328
3495
|
}
|
|
3329
3496
|
return {
|
|
@@ -3415,7 +3582,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
3415
3582
|
chain: "BASE",
|
|
3416
3583
|
chainId: 8453,
|
|
3417
3584
|
asset: context.asset,
|
|
3418
|
-
amount:
|
|
3585
|
+
amount: formatUnits3(amount, context.snapshot.baseAssetDecimals)
|
|
3419
3586
|
});
|
|
3420
3587
|
lifetimeEarnings += usd(
|
|
3421
3588
|
amount,
|
|
@@ -3532,6 +3699,49 @@ function actionEntry(action) {
|
|
|
3532
3699
|
rebalanceLog: []
|
|
3533
3700
|
};
|
|
3534
3701
|
}
|
|
3702
|
+
function allocationEntry(action, context) {
|
|
3703
|
+
if (action.actionType.toLowerCase() !== "autoseek") return void 0;
|
|
3704
|
+
const hash = action.details?.depositTransactionHash;
|
|
3705
|
+
const deposits = action.details?.deposits;
|
|
3706
|
+
if (typeof hash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash) || !Array.isArray(deposits) || deposits.length === 0) return void 0;
|
|
3707
|
+
const positions = [];
|
|
3708
|
+
let amountRaw = 0n;
|
|
3709
|
+
for (const deposit of deposits) {
|
|
3710
|
+
if (!deposit || typeof deposit !== "object") return void 0;
|
|
3711
|
+
const leg = deposit;
|
|
3712
|
+
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
3713
|
+
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
3714
|
+
if (typeof leg.vaultAddress !== "string" || !isAddress2(leg.vaultAddress)) return void 0;
|
|
3715
|
+
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;
|
|
3716
|
+
if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
|
|
3717
|
+
const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
|
|
3718
|
+
positions.push({
|
|
3719
|
+
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
3720
|
+
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
3721
|
+
tokenSymbol: context.asset,
|
|
3722
|
+
amount: formatUnits3(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3723
|
+
});
|
|
3724
|
+
amountRaw += rawAmount;
|
|
3725
|
+
}
|
|
3726
|
+
return {
|
|
3727
|
+
agent: "yieldseeker",
|
|
3728
|
+
action: "Deposit",
|
|
3729
|
+
date: action.createdDate,
|
|
3730
|
+
oldApy: null,
|
|
3731
|
+
newApy: null,
|
|
3732
|
+
positions,
|
|
3733
|
+
rebalanceLog: [],
|
|
3734
|
+
transactions: [{
|
|
3735
|
+
txHashes: [hash],
|
|
3736
|
+
chainId: context.agent.chainId,
|
|
3737
|
+
tokenAddress: context.agent.assetAddress,
|
|
3738
|
+
tokenSymbol: context.asset,
|
|
3739
|
+
agentWallet: context.wallet.walletAddress,
|
|
3740
|
+
amountRaw: String(amountRaw),
|
|
3741
|
+
amount: formatUnits3(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3742
|
+
}]
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3535
3745
|
function depositDestination(context, movement) {
|
|
3536
3746
|
const to = movement.toAddress.toLowerCase();
|
|
3537
3747
|
const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
|
|
@@ -3599,20 +3809,30 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3599
3809
|
seenMovements.add(key2);
|
|
3600
3810
|
return true;
|
|
3601
3811
|
});
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3812
|
+
const movementEntries = movements.map(
|
|
3813
|
+
(movement) => movementEntry(
|
|
3814
|
+
movement,
|
|
3815
|
+
context.wallet,
|
|
3816
|
+
context.agent,
|
|
3817
|
+
context.asset,
|
|
3818
|
+
options.ownerAddress,
|
|
3819
|
+
options.vaultAddresses,
|
|
3820
|
+
depositDestination(context, movement)
|
|
3821
|
+
)
|
|
3822
|
+
).filter((entry) => entry !== void 0);
|
|
3823
|
+
const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
|
|
3824
|
+
const actions = (context.actions ?? []).flatMap((action) => {
|
|
3825
|
+
const allocation = allocationEntry(action, context);
|
|
3826
|
+
if (allocation) {
|
|
3827
|
+
const hash = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
3828
|
+
if (deposits.has(hash)) return [];
|
|
3829
|
+
deposits.add(hash);
|
|
3830
|
+
return [allocation];
|
|
3831
|
+
}
|
|
3832
|
+
const entry = actionEntry(action);
|
|
3833
|
+
return entry ? [entry] : [];
|
|
3834
|
+
});
|
|
3835
|
+
return [...movementEntries, ...actions];
|
|
3616
3836
|
});
|
|
3617
3837
|
const grouped = /* @__PURE__ */ new Map();
|
|
3618
3838
|
const ungrouped = [];
|
|
@@ -3642,7 +3862,7 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3642
3862
|
const known = new Set(target.logIndexes ?? []);
|
|
3643
3863
|
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
3644
3864
|
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
3645
|
-
target.amount =
|
|
3865
|
+
target.amount = formatUnits3(
|
|
3646
3866
|
BigInt(target.amountRaw),
|
|
3647
3867
|
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
3648
3868
|
);
|
|
@@ -3737,7 +3957,8 @@ var walletAbi = parseAbi3([
|
|
|
3737
3957
|
"function ownerAgentIndex() view returns (uint256)",
|
|
3738
3958
|
"function baseAsset() view returns (address)",
|
|
3739
3959
|
"function FACTORY() view returns (address)",
|
|
3740
|
-
"function upgradeToLatest()"
|
|
3960
|
+
"function upgradeToLatest()",
|
|
3961
|
+
"event Upgraded(address indexed implementation)"
|
|
3741
3962
|
]);
|
|
3742
3963
|
var factoryAbi = parseAbi3([
|
|
3743
3964
|
"function userWallets(address owner, uint256 index) view returns (address)",
|
|
@@ -3746,19 +3967,19 @@ var factoryAbi = parseAbi3([
|
|
|
3746
3967
|
function yieldseekerOwnerPath(code) {
|
|
3747
3968
|
return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
|
|
3748
3969
|
}
|
|
3749
|
-
async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
3970
|
+
async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
|
|
3750
3971
|
const agentWallet = getAddress2(wallet);
|
|
3751
3972
|
const expectedOwner = getAddress2(owner);
|
|
3752
3973
|
const expectedAsset = getAddress2(asset);
|
|
3753
3974
|
const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
|
|
3754
|
-
client.getBytecode({ address: agentWallet }),
|
|
3755
|
-
client.getBytecode({ address: expectedOwner }),
|
|
3756
|
-
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
|
|
3757
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
|
|
3758
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
|
|
3759
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
|
|
3760
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
|
|
3761
|
-
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
|
|
3975
|
+
client.getBytecode({ address: agentWallet, blockNumber }),
|
|
3976
|
+
client.getBytecode({ address: expectedOwner, blockNumber }),
|
|
3977
|
+
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT, blockNumber }),
|
|
3978
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner", blockNumber }),
|
|
3979
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex", blockNumber }),
|
|
3980
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset", blockNumber }),
|
|
3981
|
+
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY", blockNumber }),
|
|
3982
|
+
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation", blockNumber })
|
|
3762
3983
|
]);
|
|
3763
3984
|
if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
|
|
3764
3985
|
throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
|
|
@@ -3768,7 +3989,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
|
3768
3989
|
address: YIELDSEEKER_FACTORY,
|
|
3769
3990
|
abi: factoryAbi,
|
|
3770
3991
|
functionName: "userWallets",
|
|
3771
|
-
args: [expectedOwner, index]
|
|
3992
|
+
args: [expectedOwner, index],
|
|
3993
|
+
blockNumber
|
|
3772
3994
|
});
|
|
3773
3995
|
const verified = isAddressEqual(actualOwner, expectedOwner) && isAddressEqual(actualAsset, expectedAsset) && isAddressEqual(actualFactory, YIELDSEEKER_FACTORY) && isAddressEqual(mapped, agentWallet);
|
|
3774
3996
|
const version = !verified ? "unknown" : isAddressEqual(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : isAddressEqual(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
|
|
@@ -3792,11 +4014,13 @@ import {
|
|
|
3792
4014
|
getAddress as getAddress3,
|
|
3793
4015
|
toHex
|
|
3794
4016
|
} from "viem";
|
|
3795
|
-
import { getUserOperationHash } from "viem/account-abstraction";
|
|
4017
|
+
import { getUserOperationHash as getUserOperationHash2 } from "viem/account-abstraction";
|
|
3796
4018
|
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
3797
4019
|
import { base as base3 } from "viem/chains";
|
|
3798
4020
|
var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
3799
4021
|
var USER_OP_FEE_HEADROOM = 2n;
|
|
4022
|
+
var OBSERVATION_WINDOW_MS = 6e4;
|
|
4023
|
+
var OBSERVATION_POLL_MS = 1500;
|
|
3800
4024
|
var ENTRY_POINT_NONCE_ABI = [
|
|
3801
4025
|
{ type: "function", name: "getNonce", stateMutability: "view", inputs: [
|
|
3802
4026
|
{ name: "sender", type: "address" },
|
|
@@ -3806,7 +4030,7 @@ var ENTRY_POINT_NONCE_ABI = [
|
|
|
3806
4030
|
var DEFAULT_ROUTING_API = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3807
4031
|
var SIMULATION_ACCOUNT = privateKeyToAccount(generatePrivateKey());
|
|
3808
4032
|
function operationHash(operation) {
|
|
3809
|
-
return
|
|
4033
|
+
return getUserOperationHash2({
|
|
3810
4034
|
chainId: 8453,
|
|
3811
4035
|
entryPointAddress: ENTRY_POINT_V06,
|
|
3812
4036
|
entryPointVersion: "0.6",
|
|
@@ -3829,10 +4053,119 @@ function sponsorError(message, safeToFallback, details = {}) {
|
|
|
3829
4053
|
return new OwneyError(
|
|
3830
4054
|
"SPONSOR_REQUEST_FAILED",
|
|
3831
4055
|
message,
|
|
3832
|
-
{
|
|
4056
|
+
{
|
|
4057
|
+
safeToFallback,
|
|
4058
|
+
phase: safeToFallback ? "prepare" : "observe",
|
|
4059
|
+
failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
|
|
4060
|
+
...safeToFallback ? { rejectedBeforeSubmission: true } : {},
|
|
4061
|
+
...details
|
|
4062
|
+
},
|
|
3833
4063
|
"yieldseeker"
|
|
3834
4064
|
);
|
|
3835
4065
|
}
|
|
4066
|
+
function providerReason(value) {
|
|
4067
|
+
if (typeof value !== "string") return void 0;
|
|
4068
|
+
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);
|
|
4069
|
+
}
|
|
4070
|
+
function rpcDiagnostics(data) {
|
|
4071
|
+
const details = {};
|
|
4072
|
+
if (typeof data?.failureCode === "string" && [
|
|
4073
|
+
"SPONSOR_RATE_LIMIT",
|
|
4074
|
+
"SPONSOR_UPSTREAM_UNAVAILABLE",
|
|
4075
|
+
"SPONSOR_RPC_UNSUPPORTED",
|
|
4076
|
+
"SPONSOR_POLICY_REJECTED",
|
|
4077
|
+
"SPONSOR_BUDGET_REJECTED",
|
|
4078
|
+
"SPONSOR_EXECUTION_REVERTED"
|
|
4079
|
+
].includes(data.failureCode))
|
|
4080
|
+
details.failureCode = data.failureCode;
|
|
4081
|
+
if (typeof data?.revertSelector === "string" && /^0x[0-9a-fA-F]{8}$/.test(data.revertSelector))
|
|
4082
|
+
details.revertSelector = data.revertSelector;
|
|
4083
|
+
if (typeof data?.revertName === "string" && [
|
|
4084
|
+
"InsufficientBalance",
|
|
4085
|
+
"InvalidAsset",
|
|
4086
|
+
"Unauthorized",
|
|
4087
|
+
"Error",
|
|
4088
|
+
"Panic",
|
|
4089
|
+
"FailedOp",
|
|
4090
|
+
"FailedOpWithRevert"
|
|
4091
|
+
].includes(data.revertName))
|
|
4092
|
+
details.revertName = data.revertName;
|
|
4093
|
+
if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
|
|
4094
|
+
if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
|
|
4095
|
+
const panicCode = toHex(BigInt(data.panicCode));
|
|
4096
|
+
if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
|
|
4097
|
+
details.panicCode = panicCode;
|
|
4098
|
+
}
|
|
4099
|
+
return details;
|
|
4100
|
+
}
|
|
4101
|
+
function isHash(value) {
|
|
4102
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
4103
|
+
}
|
|
4104
|
+
async function assertOwnerSession(state, owner) {
|
|
4105
|
+
const [chain, accounts] = await Promise.all([
|
|
4106
|
+
state.provider.request({ method: "eth_chainId" }),
|
|
4107
|
+
state.provider.request({ method: "eth_accounts" })
|
|
4108
|
+
]);
|
|
4109
|
+
if (Number(chain) !== 8453)
|
|
4110
|
+
throw new OwneyError(
|
|
4111
|
+
"CHAIN_MISMATCH",
|
|
4112
|
+
"Switch back to Base before withdrawing.",
|
|
4113
|
+
{ rejectedBeforeSubmission: true, phase: "prepare" },
|
|
4114
|
+
"yieldseeker"
|
|
4115
|
+
);
|
|
4116
|
+
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase())
|
|
4117
|
+
throw new OwneyError(
|
|
4118
|
+
"NOT_CONNECTED",
|
|
4119
|
+
"The connected withdrawal account changed.",
|
|
4120
|
+
{ rejectedBeforeSubmission: true, phase: "prepare" },
|
|
4121
|
+
"yieldseeker"
|
|
4122
|
+
);
|
|
4123
|
+
}
|
|
4124
|
+
function savePending(onPending, evidence, beforeSubmission = false) {
|
|
4125
|
+
try {
|
|
4126
|
+
onPending?.(evidence);
|
|
4127
|
+
} catch {
|
|
4128
|
+
if (beforeSubmission)
|
|
4129
|
+
throw sponsorError(
|
|
4130
|
+
"Withdrawal status cannot be saved before submission.",
|
|
4131
|
+
true,
|
|
4132
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
async function checkpoint(callback, pending) {
|
|
4137
|
+
try {
|
|
4138
|
+
await callback?.(pending);
|
|
4139
|
+
} catch {
|
|
4140
|
+
throw sponsorError(
|
|
4141
|
+
"Withdrawal status cannot be saved before submission.",
|
|
4142
|
+
true,
|
|
4143
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
|
|
4144
|
+
);
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
function finalPaymasterFields(value) {
|
|
4148
|
+
const result = {};
|
|
4149
|
+
for (const key2 of [
|
|
4150
|
+
"paymasterAndData",
|
|
4151
|
+
"callGasLimit",
|
|
4152
|
+
"verificationGasLimit",
|
|
4153
|
+
"preVerificationGas",
|
|
4154
|
+
"maxFeePerGas",
|
|
4155
|
+
"maxPriorityFeePerGas"
|
|
4156
|
+
]) {
|
|
4157
|
+
const field = value[key2];
|
|
4158
|
+
if (field === void 0) continue;
|
|
4159
|
+
if (typeof field !== "string" || !/^0x[0-9a-fA-F]+$/.test(field))
|
|
4160
|
+
throw sponsorError(
|
|
4161
|
+
"Paymaster returned an invalid final operation.",
|
|
4162
|
+
true,
|
|
4163
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INVALID_PAYMASTER_QUOTE" }
|
|
4164
|
+
);
|
|
4165
|
+
result[key2] = field;
|
|
4166
|
+
}
|
|
4167
|
+
return result;
|
|
4168
|
+
}
|
|
3836
4169
|
async function requestIntent(input) {
|
|
3837
4170
|
const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
|
|
3838
4171
|
let response;
|
|
@@ -3854,18 +4187,35 @@ async function requestIntent(input) {
|
|
|
3854
4187
|
action: input.action,
|
|
3855
4188
|
yieldseekerUserId: input.userId,
|
|
3856
4189
|
yieldseekerAgentId: input.agentId
|
|
3857
|
-
})
|
|
4190
|
+
}),
|
|
4191
|
+
signal: AbortSignal.timeout(15e3)
|
|
3858
4192
|
});
|
|
3859
4193
|
} catch {
|
|
3860
|
-
throw sponsorError(
|
|
4194
|
+
throw sponsorError(
|
|
4195
|
+
"Withdrawal sponsorship could not be requested.",
|
|
4196
|
+
true,
|
|
4197
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
|
|
4198
|
+
);
|
|
3861
4199
|
}
|
|
3862
4200
|
const envelope = await response.json().catch(() => null);
|
|
3863
4201
|
const intent = envelope?.data;
|
|
3864
4202
|
if (!response.ok || !envelope?.success || !intent?.intent || !["eoa", "smart"].includes(intent.ownerPath)) {
|
|
4203
|
+
const rawCode = typeof envelope?.failureCode === "string" ? envelope.failureCode : envelope?.code;
|
|
4204
|
+
const failureCode = typeof rawCode === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(rawCode) ? rawCode : void 0;
|
|
4205
|
+
if (input.refreshYieldseekerSignature && (failureCode === "YIELDSEEKER_AUTH_REJECTED" || failureCode === "AUTH_REJECTED" || envelope?.sponsorshipStage === "provider-auth" && (envelope.providerStatus === 401 || envelope.providerStatus === 403))) {
|
|
4206
|
+
const signature = await input.refreshYieldseekerSignature(input.yieldseekerSignature);
|
|
4207
|
+
return requestIntent({ ...input, yieldseekerSignature: signature, refreshYieldseekerSignature: void 0 });
|
|
4208
|
+
}
|
|
3865
4209
|
throw sponsorError(
|
|
3866
4210
|
"Withdrawal sponsorship is currently unavailable.",
|
|
3867
4211
|
true,
|
|
3868
|
-
{
|
|
4212
|
+
{
|
|
4213
|
+
statusCode: response.status,
|
|
4214
|
+
phase: "prepare",
|
|
4215
|
+
rejectedBeforeSubmission: true,
|
|
4216
|
+
failureCode: failureCode ?? "INTENT_REJECTED",
|
|
4217
|
+
providerReason: providerReason(envelope?.message)
|
|
4218
|
+
}
|
|
3869
4219
|
);
|
|
3870
4220
|
}
|
|
3871
4221
|
const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
|
|
@@ -3878,19 +4228,55 @@ async function rpc(url, method, params, phase) {
|
|
|
3878
4228
|
response = await fetch(url, {
|
|
3879
4229
|
method: "POST",
|
|
3880
4230
|
headers: { "content-type": "application/json" },
|
|
3881
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
4231
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
4232
|
+
signal: AbortSignal.timeout(15e3)
|
|
3882
4233
|
});
|
|
3883
4234
|
} catch {
|
|
3884
|
-
throw sponsorError(
|
|
4235
|
+
throw sponsorError(
|
|
4236
|
+
"Withdrawal sponsorship request status is unknown.",
|
|
4237
|
+
phase === "prepare",
|
|
4238
|
+
{
|
|
4239
|
+
phase,
|
|
4240
|
+
rpcMethod: method,
|
|
4241
|
+
...phase === "prepare" ? { rejectedBeforeSubmission: true } : {},
|
|
4242
|
+
failureCode: "RPC_NETWORK_ERROR"
|
|
4243
|
+
}
|
|
4244
|
+
);
|
|
3885
4245
|
}
|
|
3886
4246
|
const payload = await response.json().catch(() => null);
|
|
3887
4247
|
if (!response.ok || payload?.error || payload?.result === void 0) {
|
|
3888
4248
|
const rejection = payload?.error;
|
|
3889
|
-
const
|
|
4249
|
+
const diagnostics = rpcDiagnostics(rejection?.data);
|
|
4250
|
+
const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
|
|
4251
|
+
const details = {
|
|
4252
|
+
statusCode: response.status,
|
|
4253
|
+
rpcCode: rejection?.code,
|
|
4254
|
+
rpcMethod: method,
|
|
4255
|
+
phase,
|
|
4256
|
+
providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
|
|
4257
|
+
failureCode: "RPC_REJECTED",
|
|
4258
|
+
...diagnostics
|
|
4259
|
+
};
|
|
4260
|
+
if (phase === "prepare" && executionReverted)
|
|
4261
|
+
throw new OwneyError(
|
|
4262
|
+
"WITHDRAW_FAILED",
|
|
4263
|
+
"Yieldseeker withdrawal simulation reverted before submission.",
|
|
4264
|
+
{
|
|
4265
|
+
...details,
|
|
4266
|
+
failureCode: "SPONSOR_EXECUTION_REVERTED",
|
|
4267
|
+
safeToFallback: false,
|
|
4268
|
+
rejectedBeforeSubmission: true
|
|
4269
|
+
},
|
|
4270
|
+
"yieldseeker"
|
|
4271
|
+
);
|
|
4272
|
+
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 ?? ""));
|
|
3890
4273
|
throw sponsorError(
|
|
3891
4274
|
phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
|
|
3892
4275
|
phase === "prepare" || definitiveRejection,
|
|
3893
|
-
{
|
|
4276
|
+
{
|
|
4277
|
+
...details,
|
|
4278
|
+
...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
|
|
4279
|
+
}
|
|
3894
4280
|
);
|
|
3895
4281
|
}
|
|
3896
4282
|
return payload.result;
|
|
@@ -3910,13 +4296,15 @@ async function submitEoa(input) {
|
|
|
3910
4296
|
let nonce;
|
|
3911
4297
|
let fees;
|
|
3912
4298
|
try {
|
|
3913
|
-
nonce = await
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4299
|
+
[nonce, fees] = await Promise.all([
|
|
4300
|
+
input.client.readContract({
|
|
4301
|
+
address: ENTRY_POINT_V06,
|
|
4302
|
+
abi: ENTRY_POINT_NONCE_ABI,
|
|
4303
|
+
functionName: "getNonce",
|
|
4304
|
+
args: [input.agentWallet, 0n]
|
|
4305
|
+
}),
|
|
4306
|
+
input.client.estimateFeesPerGas()
|
|
4307
|
+
]);
|
|
3920
4308
|
} catch {
|
|
3921
4309
|
throw sponsorError("Could not prepare the sponsored withdrawal.", true);
|
|
3922
4310
|
}
|
|
@@ -3959,9 +4347,10 @@ async function submitEoa(input) {
|
|
|
3959
4347
|
);
|
|
3960
4348
|
if (!final.paymasterAndData?.startsWith("0x"))
|
|
3961
4349
|
throw sponsorError("CDP did not return final paymaster data.", true);
|
|
3962
|
-
operation = { ...operation,
|
|
4350
|
+
operation = { ...operation, ...finalPaymasterFields(final) };
|
|
3963
4351
|
}
|
|
3964
4352
|
const userOpHash = operationHash(operation);
|
|
4353
|
+
await assertOwnerSession(input.state, input.owner);
|
|
3965
4354
|
const wallet = createWalletClient2({
|
|
3966
4355
|
account: input.owner,
|
|
3967
4356
|
chain: base3,
|
|
@@ -3969,24 +4358,47 @@ async function submitEoa(input) {
|
|
|
3969
4358
|
});
|
|
3970
4359
|
const signature = await wallet.signMessage({ message: { raw: userOpHash } });
|
|
3971
4360
|
operation = { ...operation, signature };
|
|
3972
|
-
input.
|
|
4361
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4362
|
+
const userOperation = {
|
|
4363
|
+
sender: operation.sender,
|
|
4364
|
+
nonce: operation.nonce,
|
|
4365
|
+
initCode: operation.initCode,
|
|
4366
|
+
callData: operation.callData,
|
|
4367
|
+
callGasLimit: operation.callGasLimit,
|
|
4368
|
+
verificationGasLimit: operation.verificationGasLimit,
|
|
4369
|
+
preVerificationGas: operation.preVerificationGas,
|
|
4370
|
+
maxFeePerGas: operation.maxFeePerGas,
|
|
4371
|
+
maxPriorityFeePerGas: operation.maxPriorityFeePerGas,
|
|
4372
|
+
paymasterAndData: operation.paymasterAndData
|
|
4373
|
+
};
|
|
4374
|
+
savePending(input.onPending, { userOpHash, userOperation }, true);
|
|
4375
|
+
await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
|
|
3973
4376
|
const submittedHash = await rpc(
|
|
3974
4377
|
input.rpcUrl,
|
|
3975
4378
|
"eth_sendUserOperation",
|
|
3976
4379
|
[operation, ENTRY_POINT_V06],
|
|
3977
4380
|
"send"
|
|
3978
4381
|
);
|
|
3979
|
-
if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4382
|
+
if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
3980
4383
|
throw sponsorError(
|
|
3981
4384
|
"Bundler returned a different UserOperation hash.",
|
|
3982
4385
|
false,
|
|
3983
|
-
{ userOpHash, submittedHash }
|
|
4386
|
+
{ userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
|
|
3984
4387
|
);
|
|
3985
|
-
|
|
3986
|
-
|
|
4388
|
+
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
4389
|
+
let lastReadError;
|
|
4390
|
+
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
4391
|
+
let response;
|
|
4392
|
+
try {
|
|
4393
|
+
response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
|
|
4394
|
+
} catch (error) {
|
|
4395
|
+
lastReadError = error;
|
|
4396
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4397
|
+
continue;
|
|
4398
|
+
}
|
|
3987
4399
|
const txHash = response?.receipt?.transactionHash;
|
|
3988
|
-
if (txHash) {
|
|
3989
|
-
input.onPending
|
|
4400
|
+
if (isHash(txHash)) {
|
|
4401
|
+
savePending(input.onPending, { userOpHash, txHash });
|
|
3990
4402
|
if (response?.success === false)
|
|
3991
4403
|
throw new OwneyError(
|
|
3992
4404
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3996,12 +4408,17 @@ async function submitEoa(input) {
|
|
|
3996
4408
|
);
|
|
3997
4409
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
3998
4410
|
}
|
|
3999
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4411
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4000
4412
|
}
|
|
4001
4413
|
throw sponsorError(
|
|
4002
4414
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4003
4415
|
false,
|
|
4004
|
-
{
|
|
4416
|
+
{
|
|
4417
|
+
userOpHash,
|
|
4418
|
+
phase: "observe",
|
|
4419
|
+
failureCode: "RECEIPT_UNRESOLVED",
|
|
4420
|
+
...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
|
|
4421
|
+
}
|
|
4005
4422
|
);
|
|
4006
4423
|
}
|
|
4007
4424
|
async function submitSmart(input) {
|
|
@@ -4014,14 +4431,14 @@ async function submitSmart(input) {
|
|
|
4014
4431
|
} catch {
|
|
4015
4432
|
throw sponsorError("This account cannot check paymaster support on Base.", true);
|
|
4016
4433
|
}
|
|
4017
|
-
|
|
4434
|
+
const supportsPaymaster = capabilities?.[toHex(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
|
|
4435
|
+
if (supportsPaymaster !== true)
|
|
4018
4436
|
throw sponsorError("This account cannot use a paymaster on Base.", true);
|
|
4437
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4019
4438
|
let sent;
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
|
|
4024
|
-
}
|
|
4439
|
+
const requestedId = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
4440
|
+
savePending(input.onPending, { callsId: requestedId }, true);
|
|
4441
|
+
await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
|
|
4025
4442
|
try {
|
|
4026
4443
|
sent = await input.state.provider.request({
|
|
4027
4444
|
method: "wallet_sendCalls",
|
|
@@ -4029,6 +4446,7 @@ async function submitSmart(input) {
|
|
|
4029
4446
|
version: "2.0.0",
|
|
4030
4447
|
from: input.owner,
|
|
4031
4448
|
chainId: toHex(8453),
|
|
4449
|
+
id: requestedId,
|
|
4032
4450
|
atomicRequired: false,
|
|
4033
4451
|
calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
|
|
4034
4452
|
capabilities: { paymasterService: { url: input.rpcUrl } }
|
|
@@ -4037,17 +4455,27 @@ async function submitSmart(input) {
|
|
|
4037
4455
|
} catch (error) {
|
|
4038
4456
|
if (error?.code === 4001) throw error;
|
|
4039
4457
|
if ([4200, -32601].includes(Number(error?.code)))
|
|
4040
|
-
throw sponsorError(
|
|
4041
|
-
|
|
4458
|
+
throw sponsorError(
|
|
4459
|
+
"This account does not support sponsored calls.",
|
|
4460
|
+
true,
|
|
4461
|
+
{ phase: "send", failureCode: "WALLET_CALLS_UNSUPPORTED" }
|
|
4462
|
+
);
|
|
4463
|
+
throw sponsorError(
|
|
4464
|
+
"Wallet submission status is unknown. Check activity before retrying.",
|
|
4465
|
+
false,
|
|
4466
|
+
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
4467
|
+
);
|
|
4042
4468
|
}
|
|
4043
4469
|
const callsId = typeof sent === "string" ? sent : sent?.id;
|
|
4044
|
-
if (
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4470
|
+
if (typeof callsId !== "string" || callsId.length === 0 || callsId.length > 8194)
|
|
4471
|
+
throw sponsorError(
|
|
4472
|
+
"Wallet returned no calls ID; submission status is unknown.",
|
|
4473
|
+
false,
|
|
4474
|
+
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
4475
|
+
);
|
|
4476
|
+
savePending(input.onPending, { callsId });
|
|
4477
|
+
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
4478
|
+
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
4051
4479
|
let status;
|
|
4052
4480
|
try {
|
|
4053
4481
|
status = await input.state.provider.request({
|
|
@@ -4055,33 +4483,40 @@ async function submitSmart(input) {
|
|
|
4055
4483
|
params: [callsId]
|
|
4056
4484
|
});
|
|
4057
4485
|
} catch {
|
|
4058
|
-
|
|
4486
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4487
|
+
continue;
|
|
4059
4488
|
}
|
|
4060
|
-
if (status?.status
|
|
4489
|
+
if (status?.id !== void 0 && status.id !== callsId || status?.chainId !== void 0 && Number(status.chainId) !== 8453)
|
|
4490
|
+
throw sponsorError(
|
|
4491
|
+
"Wallet returned a different sponsored operation status.",
|
|
4492
|
+
false,
|
|
4493
|
+
{ callsId, phase: "observe", failureCode: "WALLET_STATUS_MISMATCH" }
|
|
4494
|
+
);
|
|
4495
|
+
if (Number(status?.status) === 400 || Number(status?.status) === 500)
|
|
4061
4496
|
throw sponsorError(
|
|
4062
4497
|
"Sponsored withdrawal failed in the wallet.",
|
|
4063
4498
|
false,
|
|
4064
|
-
{ callsId, executionFailed: true }
|
|
4499
|
+
{ callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
|
|
4065
4500
|
);
|
|
4066
|
-
const
|
|
4501
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
|
|
4502
|
+
const txHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4067
4503
|
if (txHash) {
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
"
|
|
4073
|
-
|
|
4074
|
-
|
|
4504
|
+
savePending(input.onPending, { callsId, txHash });
|
|
4505
|
+
if (status?.receipts?.some((receipt) => receipt.transactionHash === txHash && /^(?:0x0+|0|failed|reverted)$/i.test(String(receipt.status))))
|
|
4506
|
+
throw new OwneyError(
|
|
4507
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
4508
|
+
"Sponsored withdrawal execution reverted.",
|
|
4509
|
+
{ callsId, transactionHash: txHash },
|
|
4510
|
+
"yieldseeker"
|
|
4075
4511
|
);
|
|
4076
|
-
}
|
|
4077
4512
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4078
4513
|
}
|
|
4079
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4514
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4080
4515
|
}
|
|
4081
4516
|
throw sponsorError(
|
|
4082
4517
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4083
4518
|
false,
|
|
4084
|
-
{ callsId }
|
|
4519
|
+
{ callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
|
|
4085
4520
|
);
|
|
4086
4521
|
}
|
|
4087
4522
|
async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
@@ -4096,7 +4531,8 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
|
4096
4531
|
agentWallet: input.agentWallet,
|
|
4097
4532
|
asset: input.asset,
|
|
4098
4533
|
callData: input.callData,
|
|
4099
|
-
action: "withdraw"
|
|
4534
|
+
action: "withdraw",
|
|
4535
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4100
4536
|
});
|
|
4101
4537
|
const args = { ...input, owner, rpcUrl };
|
|
4102
4538
|
return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
|
|
@@ -4113,7 +4549,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
|
|
|
4113
4549
|
agentWallet: input.agentWallet,
|
|
4114
4550
|
asset: input.asset,
|
|
4115
4551
|
callData: input.callData,
|
|
4116
|
-
action: "upgrade"
|
|
4552
|
+
action: "upgrade",
|
|
4553
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4117
4554
|
});
|
|
4118
4555
|
if (intent.ownerPath !== "smart")
|
|
4119
4556
|
throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
|
|
@@ -4134,6 +4571,8 @@ var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
|
4134
4571
|
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
4135
4572
|
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
4136
4573
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4574
|
+
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4575
|
+
var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
|
|
4137
4576
|
function generateYieldseekerUsername() {
|
|
4138
4577
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4139
4578
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4144,6 +4583,13 @@ function isUsernameConflict(error) {
|
|
|
4144
4583
|
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
4145
4584
|
}
|
|
4146
4585
|
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
4586
|
+
{
|
|
4587
|
+
type: "function",
|
|
4588
|
+
name: "feeTracker",
|
|
4589
|
+
stateMutability: "view",
|
|
4590
|
+
inputs: [],
|
|
4591
|
+
outputs: [{ name: "", type: "address" }]
|
|
4592
|
+
},
|
|
4147
4593
|
{
|
|
4148
4594
|
type: "function",
|
|
4149
4595
|
name: "withdrawAssetToUser",
|
|
@@ -4166,6 +4612,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
|
4166
4612
|
outputs: []
|
|
4167
4613
|
}
|
|
4168
4614
|
];
|
|
4615
|
+
var YIELDSEEKER_FEE_TRACKER_ABI = [{
|
|
4616
|
+
type: "function",
|
|
4617
|
+
name: "getFeesOwed",
|
|
4618
|
+
stateMutability: "view",
|
|
4619
|
+
inputs: [{ name: "wallet", type: "address" }],
|
|
4620
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
4621
|
+
}];
|
|
4169
4622
|
function query(params) {
|
|
4170
4623
|
const search = new URLSearchParams();
|
|
4171
4624
|
for (const [key2, value] of Object.entries(params)) {
|
|
@@ -4249,17 +4702,22 @@ var YieldseekerAgent = class {
|
|
|
4249
4702
|
getAddress4(state.walletAddress),
|
|
4250
4703
|
YIELDSEEKER_ASSET_METADATA[asset].address
|
|
4251
4704
|
);
|
|
4705
|
+
this.rememberWalletStatus(key2, status);
|
|
4706
|
+
return status;
|
|
4707
|
+
}
|
|
4708
|
+
rememberWalletStatus(key2, status) {
|
|
4252
4709
|
if (status.verified && status.version === "v2") {
|
|
4253
4710
|
this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
|
|
4254
4711
|
if (this.withdrawalVersionCache.size > 128)
|
|
4255
4712
|
this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
|
|
4256
4713
|
} else this.withdrawalVersionCache.delete(key2);
|
|
4257
|
-
return status;
|
|
4258
4714
|
}
|
|
4259
4715
|
/** Explicit owner action. This never runs as a side effect of withdrawal. */
|
|
4260
4716
|
async upgradeWallet(state, chainId, asset, gasMode = "paid") {
|
|
4717
|
+
const generation = this.readGeneration;
|
|
4261
4718
|
const before = await this.getWalletStatus(state, chainId, asset);
|
|
4262
|
-
|
|
4719
|
+
this.assertSession(generation, chainId);
|
|
4720
|
+
if (!before?.verified || before.version === "unknown") {
|
|
4263
4721
|
throw new OwneyError(
|
|
4264
4722
|
"WITHDRAW_FAILED",
|
|
4265
4723
|
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
@@ -4267,48 +4725,77 @@ var YieldseekerAgent = class {
|
|
|
4267
4725
|
this.id
|
|
4268
4726
|
);
|
|
4269
4727
|
}
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
if (!storage5)
|
|
4728
|
+
const versionKey = `${chainId}:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}:${asset}`;
|
|
4729
|
+
const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
|
|
4730
|
+
const confirmUpgrade = async (hash, receipt2) => {
|
|
4731
|
+
this.withdrawalVersionCache.delete(versionKey);
|
|
4732
|
+
const upgradeTopic = encodeEventTopics({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
|
|
4733
|
+
const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
|
|
4734
|
+
const expectedUpgrade = (() => {
|
|
4735
|
+
if (!lastUpgrade || lastUpgrade.removed) return false;
|
|
4736
|
+
try {
|
|
4737
|
+
const event = decodeEventLog2({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
|
|
4738
|
+
return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
|
|
4739
|
+
} catch {
|
|
4740
|
+
return false;
|
|
4741
|
+
}
|
|
4742
|
+
})();
|
|
4743
|
+
if (!receipt2 || receipt2.status !== "success" || receipt2.transactionHash.toLowerCase() !== hash.toLowerCase() || typeof receipt2.blockNumber !== "bigint" || !expectedUpgrade)
|
|
4287
4744
|
throw new OwneyError(
|
|
4288
|
-
"
|
|
4289
|
-
"
|
|
4290
|
-
{
|
|
4745
|
+
"WITHDRAW_FAILED",
|
|
4746
|
+
"Yieldseeker wallet upgrade receipt could not be verified.",
|
|
4747
|
+
{ txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
|
|
4291
4748
|
this.id
|
|
4292
4749
|
);
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4750
|
+
this.assertSession(generation, chainId);
|
|
4751
|
+
const status = {
|
|
4752
|
+
...before,
|
|
4753
|
+
implementation: YIELDSEEKER_V2_IMPLEMENTATION,
|
|
4754
|
+
version: "v2",
|
|
4755
|
+
upgradeAvailable: false
|
|
4756
|
+
};
|
|
4757
|
+
this.rememberWalletStatus(versionKey, status);
|
|
4758
|
+
return { txHash: hash, status };
|
|
4759
|
+
};
|
|
4760
|
+
let storage5;
|
|
4761
|
+
try {
|
|
4762
|
+
storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
4763
|
+
} catch {
|
|
4764
|
+
storage5 = void 0;
|
|
4765
|
+
}
|
|
4766
|
+
const clearConfirmedUpgrade = () => {
|
|
4296
4767
|
try {
|
|
4297
|
-
|
|
4768
|
+
storage5?.removeItem(pendingKey2);
|
|
4298
4769
|
} catch {
|
|
4299
|
-
throw new OwneyError(
|
|
4300
|
-
"SPONSOR_REQUEST_FAILED",
|
|
4301
|
-
"Upgrade status cannot be read in this browser.",
|
|
4302
|
-
{ safeToFallback: true },
|
|
4303
|
-
this.id
|
|
4304
|
-
);
|
|
4305
4770
|
}
|
|
4771
|
+
};
|
|
4772
|
+
if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
|
|
4773
|
+
throw new OwneyError(
|
|
4774
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4775
|
+
"Upgrade status cannot be saved in this browser.",
|
|
4776
|
+
{ safeToFallback: false },
|
|
4777
|
+
this.id
|
|
4778
|
+
);
|
|
4779
|
+
let prior = null;
|
|
4780
|
+
try {
|
|
4781
|
+
const saved = storage5?.getItem(pendingKey2);
|
|
4306
4782
|
try {
|
|
4307
4783
|
prior = JSON.parse(saved ?? "null");
|
|
4308
4784
|
} catch {
|
|
4309
4785
|
prior = { callsId: void 0 };
|
|
4310
4786
|
}
|
|
4311
|
-
|
|
4787
|
+
} catch {
|
|
4788
|
+
if (gasMode === "paid" && before.ownerPath === "eoa") storage5 = void 0;
|
|
4789
|
+
else throw new OwneyError(
|
|
4790
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4791
|
+
"Upgrade status cannot be read in this browser.",
|
|
4792
|
+
{ safeToFallback: false },
|
|
4793
|
+
this.id
|
|
4794
|
+
);
|
|
4795
|
+
}
|
|
4796
|
+
if (prior) {
|
|
4797
|
+
let earlierHash = this.isTransactionHash(prior.txHash) ? prior.txHash : void 0;
|
|
4798
|
+
if (!earlierHash) {
|
|
4312
4799
|
if (!prior.callsId)
|
|
4313
4800
|
throw new OwneyError(
|
|
4314
4801
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4318,10 +4805,7 @@ var YieldseekerAgent = class {
|
|
|
4318
4805
|
);
|
|
4319
4806
|
let callsStatus;
|
|
4320
4807
|
try {
|
|
4321
|
-
callsStatus = await state.provider.request({
|
|
4322
|
-
method: "wallet_getCallsStatus",
|
|
4323
|
-
params: [prior.callsId]
|
|
4324
|
-
});
|
|
4808
|
+
callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
|
|
4325
4809
|
} catch {
|
|
4326
4810
|
throw new OwneyError(
|
|
4327
4811
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4330,8 +4814,15 @@ var YieldseekerAgent = class {
|
|
|
4330
4814
|
this.id
|
|
4331
4815
|
);
|
|
4332
4816
|
}
|
|
4333
|
-
if (callsStatus?.
|
|
4334
|
-
|
|
4817
|
+
if (callsStatus?.id !== void 0 && callsStatus.id !== prior.callsId || callsStatus?.chainId !== void 0 && Number(callsStatus.chainId) !== chainId)
|
|
4818
|
+
throw new OwneyError(
|
|
4819
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4820
|
+
"The earlier upgrade returned a different operation status.",
|
|
4821
|
+
{ safeToFallback: false, callsId: prior.callsId },
|
|
4822
|
+
this.id
|
|
4823
|
+
);
|
|
4824
|
+
if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
|
|
4825
|
+
storage5.removeItem(pendingKey2);
|
|
4335
4826
|
throw new OwneyError(
|
|
4336
4827
|
"SPONSOR_REQUEST_FAILED",
|
|
4337
4828
|
"The earlier sponsored upgrade failed.",
|
|
@@ -4339,22 +4830,39 @@ var YieldseekerAgent = class {
|
|
|
4339
4830
|
this.id
|
|
4340
4831
|
);
|
|
4341
4832
|
}
|
|
4342
|
-
const
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4833
|
+
const hashes = [...new Set((callsStatus?.receipts ?? []).map((value) => value.transactionHash).filter(this.isTransactionHash))];
|
|
4834
|
+
earlierHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4835
|
+
}
|
|
4836
|
+
if (earlierHash) {
|
|
4837
|
+
const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
|
|
4838
|
+
clearConfirmedUpgrade();
|
|
4839
|
+
return recovered;
|
|
4840
|
+
}
|
|
4841
|
+
throw new OwneyError(
|
|
4842
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4843
|
+
"An earlier upgrade is still pending.",
|
|
4844
|
+
{ safeToFallback: false, callsId: prior.callsId },
|
|
4845
|
+
this.id
|
|
4846
|
+
);
|
|
4847
|
+
}
|
|
4848
|
+
if (!before.upgradeAvailable)
|
|
4849
|
+
throw new OwneyError(
|
|
4850
|
+
"WITHDRAW_FAILED",
|
|
4851
|
+
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
4852
|
+
{ version: before.version },
|
|
4853
|
+
this.id
|
|
4854
|
+
);
|
|
4855
|
+
const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
|
|
4856
|
+
let txHash;
|
|
4857
|
+
let receipt;
|
|
4858
|
+
if (gasMode === "sponsored") {
|
|
4859
|
+
if (before.ownerPath !== "smart")
|
|
4351
4860
|
throw new OwneyError(
|
|
4352
4861
|
"SPONSOR_REQUEST_FAILED",
|
|
4353
|
-
"
|
|
4354
|
-
{ safeToFallback:
|
|
4862
|
+
"EOA owners pay Base gas for the V1 upgrade.",
|
|
4863
|
+
{ safeToFallback: true },
|
|
4355
4864
|
this.id
|
|
4356
4865
|
);
|
|
4357
|
-
}
|
|
4358
4866
|
const context = await this.findAgent(state, chainId, asset);
|
|
4359
4867
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
|
|
4360
4868
|
try {
|
|
@@ -4364,14 +4872,15 @@ var YieldseekerAgent = class {
|
|
|
4364
4872
|
apiKey: this.owneyApiKey,
|
|
4365
4873
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4366
4874
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
4875
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4367
4876
|
userId: context.user.userId,
|
|
4368
4877
|
agentId: context.agent.agentId,
|
|
4369
4878
|
agentWallet: before.agentWallet,
|
|
4370
4879
|
asset: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
4371
4880
|
callData: data,
|
|
4372
4881
|
onPending: (pending) => {
|
|
4373
|
-
const current = storage5.getItem(
|
|
4374
|
-
storage5.setItem(
|
|
4882
|
+
const current = storage5.getItem(pendingKey2);
|
|
4883
|
+
storage5.setItem(pendingKey2, JSON.stringify({
|
|
4375
4884
|
startedAt: Date.now(),
|
|
4376
4885
|
...current ? JSON.parse(current) : {},
|
|
4377
4886
|
...pending
|
|
@@ -4379,8 +4888,9 @@ var YieldseekerAgent = class {
|
|
|
4379
4888
|
}
|
|
4380
4889
|
});
|
|
4381
4890
|
txHash = upgraded.txHash;
|
|
4891
|
+
receipt = upgraded.receipt;
|
|
4382
4892
|
} catch (error) {
|
|
4383
|
-
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(
|
|
4893
|
+
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
|
|
4384
4894
|
throw error;
|
|
4385
4895
|
}
|
|
4386
4896
|
} else {
|
|
@@ -4390,20 +4900,13 @@ var YieldseekerAgent = class {
|
|
|
4390
4900
|
data,
|
|
4391
4901
|
value: "0",
|
|
4392
4902
|
chainId
|
|
4903
|
+
}, void 0, (value) => {
|
|
4904
|
+
receipt = value;
|
|
4393
4905
|
});
|
|
4394
4906
|
}
|
|
4395
|
-
const
|
|
4396
|
-
if (
|
|
4397
|
-
|
|
4398
|
-
"WITHDRAW_FAILED",
|
|
4399
|
-
"Yieldseeker wallet upgrade was not confirmed as V2.",
|
|
4400
|
-
{ txHash, version: status?.version ?? "missing" },
|
|
4401
|
-
this.id
|
|
4402
|
-
);
|
|
4403
|
-
}
|
|
4404
|
-
if (gasMode === "sponsored" && typeof window !== "undefined")
|
|
4405
|
-
window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
|
|
4406
|
-
return { txHash, status };
|
|
4907
|
+
const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
|
|
4908
|
+
if (gasMode === "sponsored") clearConfirmedUpgrade();
|
|
4909
|
+
return confirmed;
|
|
4407
4910
|
}
|
|
4408
4911
|
async disconnect() {
|
|
4409
4912
|
this.withdrawalVersionCache.clear();
|
|
@@ -4476,32 +4979,50 @@ var YieldseekerAgent = class {
|
|
|
4476
4979
|
submitted(txHash);
|
|
4477
4980
|
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
4478
4981
|
} else {
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4982
|
+
try {
|
|
4983
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
4984
|
+
from: getAddress4(state.walletAddress),
|
|
4985
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
4986
|
+
data: encodeFunctionData({
|
|
4987
|
+
abi: erc20Abi3,
|
|
4988
|
+
functionName: "transfer",
|
|
4989
|
+
args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
|
|
4990
|
+
}),
|
|
4991
|
+
value: "0",
|
|
4992
|
+
chainId
|
|
4993
|
+
}, submitted, (value) => {
|
|
4994
|
+
receipt = value;
|
|
4995
|
+
});
|
|
4996
|
+
} catch (error) {
|
|
4997
|
+
if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
|
|
4998
|
+
notifyFunding(onProgress, {
|
|
4999
|
+
agentId: this.id,
|
|
5000
|
+
chainId,
|
|
5001
|
+
token: asset,
|
|
5002
|
+
walletAddress: state.walletAddress,
|
|
5003
|
+
agentWallet: context.wallet.walletAddress,
|
|
5004
|
+
intendedAmount: amount,
|
|
5005
|
+
txHash: String(error.details.originalTransactionHash),
|
|
5006
|
+
transfer: "failed",
|
|
5007
|
+
registration: "not-applicable",
|
|
5008
|
+
delivery: "not-applicable"
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
5011
|
+
throw error;
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
if (this.readGeneration === generation) {
|
|
5015
|
+
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
5016
|
+
}
|
|
5017
|
+
const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, confirmedAmount: amount, txHash, transfer: "confirmed", registration: "not-applicable", delivery: "not-applicable" };
|
|
5018
|
+
const evidence = receipt ? inspectFundingReceipt(receipt, base6, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base6;
|
|
5019
|
+
notifyFunding(onProgress, evidence);
|
|
5020
|
+
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
5021
|
+
}
|
|
5022
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
|
|
5023
|
+
let ownerTransferAttempted = false;
|
|
5024
|
+
let evidence = {
|
|
5025
|
+
agentId: this.id,
|
|
4505
5026
|
chainId,
|
|
4506
5027
|
token: asset,
|
|
4507
5028
|
walletAddress: state.walletAddress,
|
|
@@ -4516,10 +5037,10 @@ var YieldseekerAgent = class {
|
|
|
4516
5037
|
notifyFunding(onProgress, evidence);
|
|
4517
5038
|
}, () => {
|
|
4518
5039
|
ownerTransferAttempted = true;
|
|
4519
|
-
}, gasMode);
|
|
5040
|
+
}, gasMode, onBeforeSubmission);
|
|
4520
5041
|
} catch (error) {
|
|
4521
5042
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
|
|
4522
|
-
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)))
|
|
5043
|
+
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)))
|
|
4523
5044
|
notifyFunding(onProgress, {
|
|
4524
5045
|
...evidence,
|
|
4525
5046
|
...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
|
|
@@ -4529,7 +5050,7 @@ var YieldseekerAgent = class {
|
|
|
4529
5050
|
throw error;
|
|
4530
5051
|
}
|
|
4531
5052
|
}
|
|
4532
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
|
|
5053
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
|
|
4533
5054
|
this.assertChain(chainId);
|
|
4534
5055
|
this.assertAsset(asset);
|
|
4535
5056
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -4574,31 +5095,170 @@ var YieldseekerAgent = class {
|
|
|
4574
5095
|
let confirmedMovement = false;
|
|
4575
5096
|
let settlement = null;
|
|
4576
5097
|
try {
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
context
|
|
4581
|
-
);
|
|
5098
|
+
const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
|
|
5099
|
+
this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
|
|
5100
|
+
const portfolioRead = amount === void 0 ? this.loadActivityContext(state, chainId, context, {}).then((value) => ({ positions: value.positions, feeRateBps: 0 })) : this.loadPortfolioContext(state, chainId, context).then((value) => ({ positions: value.positions, feeRateBps: value.snapshot.feeRateBps }));
|
|
4582
5101
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
4583
|
-
const
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
5102
|
+
const agentWallet = getAddress4(context.wallet.walletAddress);
|
|
5103
|
+
const feeTrackerRead = withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5104
|
+
address: agentWallet,
|
|
5105
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5106
|
+
functionName: "feeTracker"
|
|
5107
|
+
}), chainId, "eth_call", this.id);
|
|
5108
|
+
const readOwnerFunds = async () => {
|
|
5109
|
+
const feeTracker = await feeTrackerRead;
|
|
5110
|
+
this.assertSession(generation, chainId, asset);
|
|
5111
|
+
if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
|
|
5112
|
+
throw this.invalidResponse("wallet fee tracker");
|
|
5113
|
+
const [idle2, fees] = await Promise.all([
|
|
5114
|
+
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5115
|
+
address: metadata.address,
|
|
5116
|
+
abi: erc20Abi3,
|
|
5117
|
+
functionName: "balanceOf",
|
|
5118
|
+
args: [agentWallet]
|
|
5119
|
+
}), chainId, "eth_call", this.id),
|
|
5120
|
+
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5121
|
+
address: getAddress4(feeTracker),
|
|
5122
|
+
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5123
|
+
functionName: "getFeesOwed",
|
|
5124
|
+
args: [agentWallet]
|
|
5125
|
+
}), chainId, "eth_call", this.id)
|
|
5126
|
+
]);
|
|
4590
5127
|
this.assertSession(generation, chainId, asset);
|
|
4591
|
-
|
|
5128
|
+
if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
|
|
5129
|
+
throw this.invalidResponse("wallet withdrawal balance");
|
|
5130
|
+
return { idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
|
|
4592
5131
|
};
|
|
4593
|
-
const
|
|
4594
|
-
const
|
|
5132
|
+
const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
|
|
5133
|
+
const idle = initialFunds.idle;
|
|
5134
|
+
let positions = portfolio.positions;
|
|
5135
|
+
let positionsStatus = "empty";
|
|
5136
|
+
const validatePositions = (values) => {
|
|
5137
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5138
|
+
for (const value of values) {
|
|
5139
|
+
const address = value?.yieldOption?.address;
|
|
5140
|
+
if (typeof address !== "string" || !isAddress3(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
|
|
5141
|
+
throw this.invalidResponse("yield positions");
|
|
5142
|
+
seen.add(address.toLowerCase());
|
|
5143
|
+
}
|
|
5144
|
+
return values;
|
|
5145
|
+
};
|
|
5146
|
+
const zeroValuePositions = /* @__PURE__ */ new WeakSet();
|
|
5147
|
+
const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
|
|
5148
|
+
const knownPositions = /* @__PURE__ */ new Map();
|
|
5149
|
+
const rememberPositions = (values) => {
|
|
5150
|
+
for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
|
|
5151
|
+
};
|
|
5152
|
+
const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5153
|
+
let timer;
|
|
5154
|
+
try {
|
|
5155
|
+
return await Promise.race([read2, new Promise((resolve) => {
|
|
5156
|
+
timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
|
|
5157
|
+
})]);
|
|
5158
|
+
} finally {
|
|
5159
|
+
if (timer) clearTimeout(timer);
|
|
5160
|
+
}
|
|
5161
|
+
};
|
|
5162
|
+
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5163
|
+
const verifiedPositions = [];
|
|
5164
|
+
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
5165
|
+
const reads = values.map(async (position2) => {
|
|
5166
|
+
const address = getAddress4(position2.yieldOption.address);
|
|
5167
|
+
try {
|
|
5168
|
+
const [underlying, shares, withdrawable] = await Promise.all([
|
|
5169
|
+
this.getReceiptClient().readContract({ address, abi: erc4626Abi, functionName: "asset", blockNumber }),
|
|
5170
|
+
this.getReceiptClient().readContract({ address, abi: erc20Abi3, functionName: "balanceOf", args: [agentWallet], blockNumber }),
|
|
5171
|
+
this.getReceiptClient().readContract({ address, abi: erc4626Abi, functionName: "maxWithdraw", args: [agentWallet], blockNumber })
|
|
5172
|
+
]);
|
|
5173
|
+
if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
|
|
5174
|
+
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
|
|
5175
|
+
if (shares > 0n && withdrawable === 0n) {
|
|
5176
|
+
let positiveValue = false;
|
|
5177
|
+
const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
|
|
5178
|
+
try {
|
|
5179
|
+
const value = await this.getReceiptClient().readContract({
|
|
5180
|
+
address,
|
|
5181
|
+
abi: erc4626Abi,
|
|
5182
|
+
functionName,
|
|
5183
|
+
args: [shares],
|
|
5184
|
+
blockNumber
|
|
5185
|
+
});
|
|
5186
|
+
if (typeof value !== "bigint" || value < 0n) return null;
|
|
5187
|
+
if (value > 0n && !positiveValue) {
|
|
5188
|
+
positiveValue = true;
|
|
5189
|
+
verifiedPositions.push(verified);
|
|
5190
|
+
}
|
|
5191
|
+
return value;
|
|
5192
|
+
} catch {
|
|
5193
|
+
return null;
|
|
5194
|
+
}
|
|
5195
|
+
}));
|
|
5196
|
+
if (positiveValue) return verified;
|
|
5197
|
+
if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
|
|
5198
|
+
zeroValuePositions.add(verified);
|
|
5199
|
+
}
|
|
5200
|
+
verifiedPositions.push(verified);
|
|
5201
|
+
return verified;
|
|
5202
|
+
} catch {
|
|
5203
|
+
return null;
|
|
5204
|
+
}
|
|
5205
|
+
});
|
|
5206
|
+
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5207
|
+
this.assertSession(generation, chainId, asset);
|
|
5208
|
+
return {
|
|
5209
|
+
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
5210
|
+
positions: [...verifiedPositions]
|
|
5211
|
+
};
|
|
5212
|
+
};
|
|
5213
|
+
const refreshPositions = async () => {
|
|
5214
|
+
this.assertSession(generation, chainId, asset);
|
|
5215
|
+
const response = await this.walletRequest(
|
|
5216
|
+
state,
|
|
5217
|
+
chainId,
|
|
5218
|
+
this.agentPath(context, "yield-positions")
|
|
5219
|
+
);
|
|
5220
|
+
this.assertSession(generation, chainId, asset);
|
|
5221
|
+
if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
|
|
5222
|
+
return validatePositions(response.yieldPositions);
|
|
5223
|
+
};
|
|
5224
|
+
if (amount === void 0) {
|
|
5225
|
+
validatePositions(positions);
|
|
5226
|
+
rememberPositions(positions);
|
|
5227
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5228
|
+
}
|
|
5229
|
+
const deployed = positions.reduce(
|
|
4595
5230
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
4596
5231
|
0n
|
|
4597
5232
|
);
|
|
4598
|
-
const
|
|
5233
|
+
const grossAvailable = idle + deployed;
|
|
5234
|
+
const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
|
|
4599
5235
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4600
5236
|
if (requested === 0n) {
|
|
4601
5237
|
this.invalidateFundingRead(state, chainId, asset);
|
|
5238
|
+
const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
5239
|
+
if (amount === void 0 && zeroPayoutCandidates.length) {
|
|
5240
|
+
const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5241
|
+
const blockNumber = await boundedVerification(
|
|
5242
|
+
Promise.resolve().then(() => this.getReceiptClient().getBlockNumber()).catch(() => void 0)
|
|
5243
|
+
);
|
|
5244
|
+
const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
|
|
5245
|
+
positions = proof.positions;
|
|
5246
|
+
positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
|
|
5247
|
+
if (positionsStatus !== "empty")
|
|
5248
|
+
throw new OwneyError(
|
|
5249
|
+
"WITHDRAW_FAILED",
|
|
5250
|
+
"Yieldseeker vault funds are not currently withdrawable.",
|
|
5251
|
+
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
|
|
5252
|
+
this.id
|
|
5253
|
+
);
|
|
5254
|
+
}
|
|
5255
|
+
if (grossAvailable > 0n && totalAvailable === 0n)
|
|
5256
|
+
throw new OwneyError(
|
|
5257
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
5258
|
+
"No owner-withdrawable Yieldseeker funds remain after reserved fees.",
|
|
5259
|
+
{ asset, available: "0", reason: "YIELDSEEKER_FEES_ONLY", feesReserved: initialFunds.fees.toString() },
|
|
5260
|
+
this.id
|
|
5261
|
+
);
|
|
4602
5262
|
throw new OwneyError(
|
|
4603
5263
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4604
5264
|
"No Yieldseeker funds are available to withdraw.",
|
|
@@ -4618,46 +5278,104 @@ var YieldseekerAgent = class {
|
|
|
4618
5278
|
this.id
|
|
4619
5279
|
);
|
|
4620
5280
|
}
|
|
4621
|
-
|
|
5281
|
+
const requiredIdle = requested + initialFunds.fees;
|
|
5282
|
+
let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
|
|
4622
5283
|
let grossUnwound = 0n;
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
chainId,
|
|
4636
|
-
vaultAddress: position2.yieldOption.address,
|
|
4637
|
-
assetsRaw: assetsRaw.toString()
|
|
5284
|
+
let latestUnwindBlock;
|
|
5285
|
+
const unwind = async (position2, assetsRaw) => {
|
|
5286
|
+
this.assertSession(generation, chainId, asset);
|
|
5287
|
+
let unwindTransactionHash;
|
|
5288
|
+
try {
|
|
5289
|
+
const response = await this.walletRequest(
|
|
5290
|
+
state,
|
|
5291
|
+
chainId,
|
|
5292
|
+
this.agentPath(context, "withdraw-from-position"),
|
|
5293
|
+
{
|
|
5294
|
+
method: "POST",
|
|
5295
|
+
body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
|
|
4638
5296
|
}
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
5297
|
+
);
|
|
5298
|
+
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
5299
|
+
unwindTransactionHash = response.transactionHash;
|
|
5300
|
+
const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
|
|
5301
|
+
if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
|
|
5302
|
+
} catch (error) {
|
|
5303
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
5304
|
+
throw new OwneyError(
|
|
5305
|
+
error instanceof OwneyError ? error.code : "WITHDRAW_FAILED",
|
|
5306
|
+
"Could not confirm the Yieldseeker vault release. No owner transfer was submitted.",
|
|
5307
|
+
{
|
|
5308
|
+
...error instanceof OwneyError ? error.details : {},
|
|
5309
|
+
withdrawalPhase: "unwind",
|
|
5310
|
+
reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
|
|
5311
|
+
vaultAddress: position2.yieldOption.address,
|
|
5312
|
+
unwindTransactionHash,
|
|
5313
|
+
ownerTransferSubmitted: false
|
|
5314
|
+
},
|
|
5315
|
+
this.id
|
|
5316
|
+
);
|
|
4643
5317
|
}
|
|
4644
|
-
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
4645
5318
|
confirmedMovement = true;
|
|
5319
|
+
this.assertSession(generation, chainId, asset);
|
|
4646
5320
|
grossUnwound += assetsRaw;
|
|
4647
|
-
|
|
5321
|
+
};
|
|
5322
|
+
if (amount === void 0) {
|
|
5323
|
+
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
5324
|
+
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
5325
|
+
let unwound = false;
|
|
5326
|
+
for (const position2 of positions) {
|
|
5327
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
5328
|
+
if (available <= 0n) continue;
|
|
5329
|
+
await unwind(position2, available);
|
|
5330
|
+
unwound = true;
|
|
5331
|
+
}
|
|
5332
|
+
if (!unwound) break;
|
|
5333
|
+
const knownBeforeRefresh = new Set(knownPositions.keys());
|
|
5334
|
+
const [catalog, proof] = await Promise.all([
|
|
5335
|
+
boundedVerification(refreshPositions().catch(() => void 0)),
|
|
5336
|
+
verifyKnownPositions(latestUnwindBlock)
|
|
5337
|
+
]);
|
|
5338
|
+
if (catalog) rememberPositions(catalog);
|
|
5339
|
+
this.assertSession(generation, chainId, asset);
|
|
5340
|
+
if (proof.verified) {
|
|
5341
|
+
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
|
|
5342
|
+
} else if (catalog) positions = catalog;
|
|
5343
|
+
else {
|
|
5344
|
+
positionsStatus = "unavailable";
|
|
5345
|
+
break;
|
|
5346
|
+
}
|
|
5347
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5348
|
+
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
|
|
5349
|
+
}
|
|
5350
|
+
} else {
|
|
5351
|
+
for (const position2 of positions) {
|
|
5352
|
+
if (remaining === 0n) break;
|
|
5353
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
5354
|
+
if (available <= 0n) continue;
|
|
5355
|
+
const assetsRaw = available < remaining ? available : remaining;
|
|
5356
|
+
await unwind(position2, assetsRaw);
|
|
5357
|
+
remaining -= assetsRaw;
|
|
5358
|
+
}
|
|
4648
5359
|
}
|
|
4649
|
-
if (remaining > 0n) {
|
|
5360
|
+
if (amount !== void 0 && remaining > 0n) {
|
|
4650
5361
|
throw this.invalidResponse("yield positions", {
|
|
4651
5362
|
reason: "Withdrawable positions could not cover the request.",
|
|
4652
5363
|
remaining: remaining.toString()
|
|
4653
5364
|
});
|
|
4654
5365
|
}
|
|
4655
|
-
const availableForOwner = confirmedMovement ? await
|
|
4656
|
-
const feeRate = portfolio.
|
|
5366
|
+
const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
|
|
5367
|
+
const feeRate = portfolio.feeRateBps;
|
|
4657
5368
|
const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
|
|
4658
5369
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
4659
5370
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
4660
5371
|
this.invalidateFundingRead(state, chainId, asset);
|
|
5372
|
+
if (amount === void 0 && positionsStatus !== "empty")
|
|
5373
|
+
throw new OwneyError(
|
|
5374
|
+
"WITHDRAW_FAILED",
|
|
5375
|
+
"Yieldseeker vault funds could not be cleared for the owner transfer.",
|
|
5376
|
+
{ asset, positionsStatus },
|
|
5377
|
+
this.id
|
|
5378
|
+
);
|
|
4661
5379
|
throw new OwneyError(
|
|
4662
5380
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4663
5381
|
"Yieldseeker funds are not available for the owner transfer.",
|
|
@@ -4666,7 +5384,11 @@ var YieldseekerAgent = class {
|
|
|
4666
5384
|
);
|
|
4667
5385
|
}
|
|
4668
5386
|
const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
|
|
4669
|
-
const plannedMovement =
|
|
5387
|
+
const plannedMovement = amount === void 0 && maxSettlementBasis ? {
|
|
5388
|
+
...maxSettlementBasis,
|
|
5389
|
+
delta: maxSettlementBasis.delta - payout,
|
|
5390
|
+
expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
|
|
5391
|
+
} : this.snapshotMovement(state, chainId, context, -payout);
|
|
4670
5392
|
const account = getAddress4(state.walletAddress);
|
|
4671
5393
|
let receipt;
|
|
4672
5394
|
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" };
|
|
@@ -4679,7 +5401,6 @@ var YieldseekerAgent = class {
|
|
|
4679
5401
|
functionName: "withdrawAssetToUser",
|
|
4680
5402
|
args: [account, metadata.address, payout]
|
|
4681
5403
|
});
|
|
4682
|
-
onOwnerTransferAttempted?.();
|
|
4683
5404
|
let txHash;
|
|
4684
5405
|
if (sponsor) {
|
|
4685
5406
|
const submitted = await submitYieldseekerSponsoredWithdrawal({
|
|
@@ -4688,16 +5409,22 @@ var YieldseekerAgent = class {
|
|
|
4688
5409
|
apiKey: this.owneyApiKey,
|
|
4689
5410
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4690
5411
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5412
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4691
5413
|
userId: context.user.userId,
|
|
4692
5414
|
agentId: context.agent.agentId,
|
|
4693
5415
|
agentWallet: getAddress4(context.wallet.walletAddress),
|
|
4694
5416
|
asset: metadata.address,
|
|
4695
5417
|
callData: data,
|
|
4696
|
-
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending })
|
|
5418
|
+
onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
|
|
5419
|
+
onBeforeSubmission: async (pending) => {
|
|
5420
|
+
await onBeforeSubmission?.({ ...base6, ...pending });
|
|
5421
|
+
onOwnerTransferAttempted?.();
|
|
5422
|
+
}
|
|
4697
5423
|
});
|
|
4698
5424
|
txHash = submitted.txHash;
|
|
4699
5425
|
receipt = submitted.receipt;
|
|
4700
5426
|
} else {
|
|
5427
|
+
onOwnerTransferAttempted?.();
|
|
4701
5428
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4702
5429
|
from: account,
|
|
4703
5430
|
to: getAddress4(context.wallet.walletAddress),
|
|
@@ -4712,6 +5439,46 @@ var YieldseekerAgent = class {
|
|
|
4712
5439
|
settlement = plannedMovement;
|
|
4713
5440
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4714
5441
|
notifyFunding(onProgress, evidence);
|
|
5442
|
+
let completion;
|
|
5443
|
+
if (amount === void 0) {
|
|
5444
|
+
const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
5445
|
+
const knownAtPayout = new Set(knownPositions.keys());
|
|
5446
|
+
const [catalogVerified, knownProof] = await Promise.all([
|
|
5447
|
+
boundedVerification(refreshPositions().then((values) => {
|
|
5448
|
+
rememberPositions(values);
|
|
5449
|
+
return true;
|
|
5450
|
+
}).catch(() => false)),
|
|
5451
|
+
verifyKnownPositions(receipt?.blockNumber)
|
|
5452
|
+
]);
|
|
5453
|
+
const newPositions = [...knownPositions].filter(([address]) => !knownAtPayout.has(address)).map(([, position2]) => position2);
|
|
5454
|
+
const newProof = newPositions.length ? await verifyKnownPositions(receipt?.blockNumber, newPositions, verificationDeadline - Date.now()) : { verified: true, positions: [] };
|
|
5455
|
+
const proof = {
|
|
5456
|
+
verified: catalogVerified === true && knownProof.verified && newProof.verified,
|
|
5457
|
+
positions: [...knownProof.positions, ...newProof.positions]
|
|
5458
|
+
};
|
|
5459
|
+
positions = proof.positions;
|
|
5460
|
+
positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
|
|
5461
|
+
if (positionsStatus === "remaining") {
|
|
5462
|
+
const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
5463
|
+
throw new OwneyError(
|
|
5464
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
5465
|
+
"Yieldseeker MAX could not confirm that all vault positions were cleared.",
|
|
5466
|
+
{
|
|
5467
|
+
reason: "YIELDSEEKER_MAX_INCOMPLETE",
|
|
5468
|
+
asset,
|
|
5469
|
+
positionsStatus,
|
|
5470
|
+
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
5471
|
+
vaultAddress: position2.yieldOption.address,
|
|
5472
|
+
sharesRaw: position2.assetsRaw,
|
|
5473
|
+
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
5474
|
+
})) : void 0,
|
|
5475
|
+
partialResults: { [this.id]: result }
|
|
5476
|
+
},
|
|
5477
|
+
this.id
|
|
5478
|
+
);
|
|
5479
|
+
}
|
|
5480
|
+
if (!proof.verified) completion = "unverified";
|
|
5481
|
+
}
|
|
4715
5482
|
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4716
5483
|
throw new OwneyError(
|
|
4717
5484
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -4719,7 +5486,13 @@ var YieldseekerAgent = class {
|
|
|
4719
5486
|
{ asset, available: "0", txHash },
|
|
4720
5487
|
this.id
|
|
4721
5488
|
);
|
|
4722
|
-
return {
|
|
5489
|
+
return {
|
|
5490
|
+
txHash,
|
|
5491
|
+
type: amount === void 0 && !completion ? "full" : "partial",
|
|
5492
|
+
...completion ? { completion } : {},
|
|
5493
|
+
amount: evidence.confirmedAmount ?? payout.toString(),
|
|
5494
|
+
evidence
|
|
5495
|
+
};
|
|
4723
5496
|
} finally {
|
|
4724
5497
|
if (confirmedMovement && this.readGeneration === generation) {
|
|
4725
5498
|
void this.refreshSnapshotAfterMovement(
|
|
@@ -4766,6 +5539,23 @@ var YieldseekerAgent = class {
|
|
|
4766
5539
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
|
|
4767
5540
|
return context.wallet.walletAddress;
|
|
4768
5541
|
}
|
|
5542
|
+
async hasExistingDepositFunds(state, chainId, asset) {
|
|
5543
|
+
this.assertChain(chainId);
|
|
5544
|
+
this.assertAsset(asset);
|
|
5545
|
+
const generation = this.readGeneration;
|
|
5546
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
5547
|
+
this.assertSession(generation, chainId, asset);
|
|
5548
|
+
if (!context) return void 0;
|
|
5549
|
+
const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5550
|
+
address: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5551
|
+
abi: erc20Abi3,
|
|
5552
|
+
functionName: "balanceOf",
|
|
5553
|
+
args: [getAddress4(context.wallet.walletAddress)]
|
|
5554
|
+
}), chainId, "eth_call", this.id);
|
|
5555
|
+
this.assertSession(generation, chainId, asset);
|
|
5556
|
+
if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
|
|
5557
|
+
return idle > 0n ? true : void 0;
|
|
5558
|
+
}
|
|
4769
5559
|
async getEarnings(state, chainId) {
|
|
4770
5560
|
this.assertChain(chainId);
|
|
4771
5561
|
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
@@ -4796,7 +5586,7 @@ var YieldseekerAgent = class {
|
|
|
4796
5586
|
);
|
|
4797
5587
|
const vaultAddresses = new Set(
|
|
4798
5588
|
catalog.flat().filter(
|
|
4799
|
-
(yieldOption) => yieldOption.chainId === chainId &&
|
|
5589
|
+
(yieldOption) => yieldOption.chainId === chainId && isAddress3(yieldOption.address)
|
|
4800
5590
|
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
4801
5591
|
);
|
|
4802
5592
|
return mapYieldseekerHistory(contexts, {
|
|
@@ -4923,7 +5713,7 @@ var YieldseekerAgent = class {
|
|
|
4923
5713
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
4924
5714
|
).then((walletResponse) => {
|
|
4925
5715
|
this.assertSession(generation, chainId, asset);
|
|
4926
|
-
if (!walletResponse?.agentWallet || !
|
|
5716
|
+
if (!walletResponse?.agentWallet || !isAddress3(walletResponse.agentWallet.walletAddress)) {
|
|
4927
5717
|
throw this.invalidResponse("agent wallet");
|
|
4928
5718
|
}
|
|
4929
5719
|
const context = {
|
|
@@ -5336,7 +6126,7 @@ var YieldseekerAgent = class {
|
|
|
5336
6126
|
this.agentPath(context, "deploy"),
|
|
5337
6127
|
{ method: "POST", body: {} }
|
|
5338
6128
|
);
|
|
5339
|
-
if (!deployed?.agentWallet || !
|
|
6129
|
+
if (!deployed?.agentWallet || !isAddress3(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
5340
6130
|
throw this.invalidResponse("agent deployment", {
|
|
5341
6131
|
reason: "Deploy did not return the expected Agent Wallet."
|
|
5342
6132
|
});
|
|
@@ -5467,25 +6257,79 @@ var YieldseekerAgent = class {
|
|
|
5467
6257
|
value: BigInt(transaction.value)
|
|
5468
6258
|
});
|
|
5469
6259
|
onSubmitted?.(hash);
|
|
6260
|
+
let replacement;
|
|
5470
6261
|
const receipt = await withPaidRpcDiagnostics(
|
|
5471
6262
|
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
5472
6263
|
hash,
|
|
5473
|
-
confirmations: 1
|
|
6264
|
+
confirmations: 1,
|
|
6265
|
+
onReplaced: (value) => {
|
|
6266
|
+
replacement = value;
|
|
6267
|
+
}
|
|
5474
6268
|
}),
|
|
5475
6269
|
8453,
|
|
5476
6270
|
"eth_getTransactionReceipt",
|
|
5477
6271
|
this.id
|
|
5478
6272
|
);
|
|
6273
|
+
const actualHash = receipt.transactionHash;
|
|
6274
|
+
if (!this.isTransactionHash(actualHash))
|
|
6275
|
+
throw new OwneyError(
|
|
6276
|
+
"WITHDRAW_FAILED",
|
|
6277
|
+
"The confirmed transaction hash could not be verified.",
|
|
6278
|
+
{ originalTransactionHash: hash, ownerTransferNotExecuted: false },
|
|
6279
|
+
this.id
|
|
6280
|
+
);
|
|
6281
|
+
if (actualHash.toLowerCase() !== hash.toLowerCase()) {
|
|
6282
|
+
if (!replacement || replacement.transaction.hash.toLowerCase() !== actualHash.toLowerCase()) {
|
|
6283
|
+
try {
|
|
6284
|
+
const [original2, actual2] = await Promise.all([
|
|
6285
|
+
this.getReceiptClient().getTransaction({ hash }),
|
|
6286
|
+
this.getReceiptClient().getTransaction({ hash: actualHash })
|
|
6287
|
+
]);
|
|
6288
|
+
replacement = { replacedTransaction: original2, transaction: actual2 };
|
|
6289
|
+
} catch {
|
|
6290
|
+
replacement = void 0;
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
const original = replacement?.replacedTransaction;
|
|
6294
|
+
const actual = replacement?.transaction;
|
|
6295
|
+
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;
|
|
6296
|
+
if (!sameNonce)
|
|
6297
|
+
throw new OwneyError(
|
|
6298
|
+
"WITHDRAW_FAILED",
|
|
6299
|
+
"The replacement transaction could not be verified.",
|
|
6300
|
+
{
|
|
6301
|
+
originalTransactionHash: hash,
|
|
6302
|
+
replacementTransactionHash: actualHash,
|
|
6303
|
+
reason: "TRANSACTION_REPLACEMENT_UNRESOLVED",
|
|
6304
|
+
ownerTransferNotExecuted: false
|
|
6305
|
+
},
|
|
6306
|
+
this.id
|
|
6307
|
+
);
|
|
6308
|
+
const sameCall = actual.to?.toLowerCase() === transaction.to.toLowerCase() && actual.input?.toLowerCase() === transaction.data.toLowerCase() && actual.value === BigInt(transaction.value);
|
|
6309
|
+
if (!sameCall)
|
|
6310
|
+
throw new OwneyError(
|
|
6311
|
+
"WITHDRAW_FAILED",
|
|
6312
|
+
"The transaction was replaced without executing this withdrawal.",
|
|
6313
|
+
{
|
|
6314
|
+
originalTransactionHash: hash,
|
|
6315
|
+
replacementTransactionHash: actualHash,
|
|
6316
|
+
reason: "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL",
|
|
6317
|
+
ownerTransferNotExecuted: true
|
|
6318
|
+
},
|
|
6319
|
+
this.id
|
|
6320
|
+
);
|
|
6321
|
+
onSubmitted?.(actualHash);
|
|
6322
|
+
}
|
|
5479
6323
|
if (receipt.status !== "success") {
|
|
5480
6324
|
throw new OwneyError(
|
|
5481
6325
|
"AGENT_TRANSACTION_REVERTED",
|
|
5482
|
-
`Yieldseeker transaction reverted (${
|
|
5483
|
-
{ transactionHash:
|
|
6326
|
+
`Yieldseeker transaction reverted (${actualHash}).`,
|
|
6327
|
+
{ transactionHash: actualHash },
|
|
5484
6328
|
this.id
|
|
5485
6329
|
);
|
|
5486
6330
|
}
|
|
5487
6331
|
onReceipt?.(receipt);
|
|
5488
|
-
return
|
|
6332
|
+
return actualHash;
|
|
5489
6333
|
}
|
|
5490
6334
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
5491
6335
|
if (this.unwindReceiptWaiter) {
|
|
@@ -5512,12 +6356,12 @@ var YieldseekerAgent = class {
|
|
|
5512
6356
|
return receipt;
|
|
5513
6357
|
}
|
|
5514
6358
|
assertTransaction(transaction, state, chainId) {
|
|
5515
|
-
if (!transaction || typeof transaction.from !== "string" || !
|
|
6359
|
+
if (!transaction || typeof transaction.from !== "string" || !isAddress3(transaction.from) || typeof transaction.to !== "string" || !isAddress3(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress4(transaction.from) !== getAddress4(state.walletAddress)) {
|
|
5516
6360
|
throw this.invalidResponse("transaction");
|
|
5517
6361
|
}
|
|
5518
6362
|
}
|
|
5519
6363
|
assertAgent(agent) {
|
|
5520
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !
|
|
6364
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress3(agent.assetAddress) || agent.chainId !== 8453) {
|
|
5521
6365
|
throw this.invalidResponse("agent");
|
|
5522
6366
|
}
|
|
5523
6367
|
}
|
|
@@ -5965,6 +6809,18 @@ var planOf = (transfers) => JSON.stringify(
|
|
|
5965
6809
|
amount: BigInt(t.amount).toString()
|
|
5966
6810
|
}))
|
|
5967
6811
|
);
|
|
6812
|
+
var pendingKey = (i) => `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
|
|
6813
|
+
function pendingTokenBatch(i) {
|
|
6814
|
+
try {
|
|
6815
|
+
const saved = read(pendingKey(i));
|
|
6816
|
+
if (!saved) return;
|
|
6817
|
+
const body = JSON.parse(saved);
|
|
6818
|
+
if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
|
|
6819
|
+
return { txHash: keccak256(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
|
|
6820
|
+
} catch {
|
|
6821
|
+
return;
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
5968
6824
|
function read(key2) {
|
|
5969
6825
|
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
5970
6826
|
}
|
|
@@ -5981,11 +6837,11 @@ function clear(key2) {
|
|
|
5981
6837
|
else window.localStorage.removeItem(key2);
|
|
5982
6838
|
}
|
|
5983
6839
|
function sponsorTokenBatch(i) {
|
|
5984
|
-
const key2 =
|
|
6840
|
+
const key2 = pendingKey(i);
|
|
5985
6841
|
const plan = planOf(i.transfers);
|
|
5986
6842
|
const active = inflight.get(key2);
|
|
5987
6843
|
if (active) {
|
|
5988
|
-
if (active.plan !== plan)
|
|
6844
|
+
if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
5989
6845
|
return Promise.reject(
|
|
5990
6846
|
new Error(
|
|
5991
6847
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
@@ -6060,6 +6916,8 @@ async function execute(i, key2, plan) {
|
|
|
6060
6916
|
}
|
|
6061
6917
|
};
|
|
6062
6918
|
const saved = read(key2);
|
|
6919
|
+
if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
6920
|
+
throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
|
|
6063
6921
|
if (saved) {
|
|
6064
6922
|
const previous = JSON.parse(saved);
|
|
6065
6923
|
if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
@@ -6171,7 +7029,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
6171
7029
|
pub,
|
|
6172
7030
|
wallet,
|
|
6173
7031
|
onApproved: deps.onApproved,
|
|
6174
|
-
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
7032
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
|
|
7033
|
+
resumeTxHash: deps.resumeTxHash
|
|
6175
7034
|
});
|
|
6176
7035
|
};
|
|
6177
7036
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -6257,7 +7116,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
6257
7116
|
}
|
|
6258
7117
|
|
|
6259
7118
|
// src/lib/sponsored-calls-deposit.ts
|
|
6260
|
-
import { encodeFunctionData as encodeFunctionData2, erc20Abi as
|
|
7119
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi4, toHex as toHex2 } from "viem";
|
|
6261
7120
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
6262
7121
|
var DEFAULT_MAX_POLLS = 30;
|
|
6263
7122
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -6303,7 +7162,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6303
7162
|
to: token,
|
|
6304
7163
|
value: "0x0",
|
|
6305
7164
|
data: encodeFunctionData2({
|
|
6306
|
-
abi:
|
|
7165
|
+
abi: erc20Abi4,
|
|
6307
7166
|
functionName: "transfer",
|
|
6308
7167
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
6309
7168
|
})
|
|
@@ -6335,50 +7194,73 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6335
7194
|
url.searchParams.append("owneyIntent", intent);
|
|
6336
7195
|
paymasterUrl = url.toString();
|
|
6337
7196
|
}
|
|
6338
|
-
const
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
7197
|
+
const requestedId = toHex2(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
7198
|
+
const progress = (id, transfer) => {
|
|
7199
|
+
try {
|
|
7200
|
+
deps.onCallsProgress?.(id, transfers, transfer);
|
|
7201
|
+
} catch {
|
|
7202
|
+
}
|
|
7203
|
+
};
|
|
7204
|
+
progress(requestedId, "pending");
|
|
7205
|
+
let sendResult;
|
|
7206
|
+
try {
|
|
7207
|
+
sendResult = await deps.provider.request({
|
|
7208
|
+
method: "wallet_sendCalls",
|
|
7209
|
+
params: [
|
|
7210
|
+
{
|
|
7211
|
+
version: "2.0.0",
|
|
7212
|
+
id: requestedId,
|
|
7213
|
+
from: deps.ownerAddress,
|
|
7214
|
+
chainId: toHex2(chainId),
|
|
7215
|
+
atomicRequired: transfers.length > 1,
|
|
7216
|
+
calls,
|
|
7217
|
+
capabilities: {
|
|
7218
|
+
paymasterService: { url: paymasterUrl }
|
|
7219
|
+
}
|
|
6349
7220
|
}
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
})
|
|
6353
|
-
|
|
7221
|
+
]
|
|
7222
|
+
});
|
|
7223
|
+
} catch (error) {
|
|
7224
|
+
if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
|
|
7225
|
+
progress(requestedId, "failed");
|
|
7226
|
+
throw error;
|
|
7227
|
+
}
|
|
7228
|
+
try {
|
|
7229
|
+
deps.onApproved?.();
|
|
7230
|
+
} catch {
|
|
7231
|
+
}
|
|
6354
7232
|
const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
|
|
6355
7233
|
if (!callsId) {
|
|
6356
7234
|
throw new OwneyError(
|
|
6357
7235
|
"SPONSORED_CALLS_NO_ID",
|
|
6358
7236
|
"Wallet accepted wallet_sendCalls but returned no calls id.",
|
|
6359
|
-
{ chainId }
|
|
7237
|
+
{ chainId, callsId: requestedId }
|
|
6360
7238
|
);
|
|
6361
7239
|
}
|
|
7240
|
+
progress(callsId, "pending");
|
|
6362
7241
|
for (let attempt = 0; attempt < maxPolls; attempt += 1) {
|
|
6363
7242
|
const status = await deps.provider.request({
|
|
6364
7243
|
method: "wallet_getCallsStatus",
|
|
6365
7244
|
params: [callsId]
|
|
6366
7245
|
});
|
|
7246
|
+
if (status?.id && status.id !== callsId || status?.chainId && Number(status.chainId) !== chainId)
|
|
7247
|
+
throw new OwneyError("SPONSORED_CALLS_NO_RECEIPT", "Wallet returned status for a different deposit batch.", { chainId, callsId });
|
|
6367
7248
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
6368
|
-
if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
7249
|
+
if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
6369
7250
|
try {
|
|
6370
7251
|
deps.onSubmitted?.(txHash, transfers);
|
|
6371
7252
|
} catch {
|
|
6372
7253
|
}
|
|
6373
7254
|
}
|
|
6374
|
-
if (status?.
|
|
7255
|
+
if (status?.status === 400 || status?.status === 500) {
|
|
7256
|
+
progress(callsId, "failed");
|
|
6375
7257
|
throw new OwneyError(
|
|
6376
7258
|
"SPONSOR_REQUEST_FAILED",
|
|
6377
7259
|
"The sponsored deposit did not complete successfully.",
|
|
6378
7260
|
{ chainId, callsId, safeToFallback: false }
|
|
6379
7261
|
);
|
|
6380
7262
|
}
|
|
6381
|
-
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
7263
|
+
if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
6382
7264
|
if (status?.receipts?.some(
|
|
6383
7265
|
(receipt) => receipt.transactionHash !== txHash
|
|
6384
7266
|
))
|
|
@@ -6594,8 +7476,8 @@ var OwneySDK = class {
|
|
|
6594
7476
|
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
6595
7477
|
}
|
|
6596
7478
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
6597
|
-
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
6598
|
-
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
7479
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7480
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
|
|
6599
7481
|
return this.cachedSponsoredCallback;
|
|
6600
7482
|
const provider = this.requireConnectedProvider();
|
|
6601
7483
|
const owner = this.state.walletAddress;
|
|
@@ -6605,6 +7487,7 @@ var OwneySDK = class {
|
|
|
6605
7487
|
ownerAddress: owner,
|
|
6606
7488
|
onApproved,
|
|
6607
7489
|
onSubmitted,
|
|
7490
|
+
resumeTxHash,
|
|
6608
7491
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
6609
7492
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6610
7493
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -6620,13 +7503,13 @@ var OwneySDK = class {
|
|
|
6620
7503
|
transport: custom4(provider)
|
|
6621
7504
|
})
|
|
6622
7505
|
});
|
|
6623
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
7506
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
|
|
6624
7507
|
return callback;
|
|
6625
7508
|
}
|
|
6626
7509
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
6627
|
-
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
7510
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
|
|
6628
7511
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
6629
|
-
if (!onApproved && !onSubmitted && cached) return cached;
|
|
7512
|
+
if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
|
|
6630
7513
|
const provider = this.requireConnectedProvider();
|
|
6631
7514
|
const callback = makeSponsoredCallsCallback({
|
|
6632
7515
|
apiKey: this.apiKey,
|
|
@@ -6636,9 +7519,10 @@ var OwneySDK = class {
|
|
|
6636
7519
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
6637
7520
|
onApproved,
|
|
6638
7521
|
onSubmitted,
|
|
7522
|
+
onCallsProgress,
|
|
6639
7523
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
6640
7524
|
});
|
|
6641
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
7525
|
+
if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
6642
7526
|
return callback;
|
|
6643
7527
|
}
|
|
6644
7528
|
/**
|
|
@@ -6647,8 +7531,8 @@ var OwneySDK = class {
|
|
|
6647
7531
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
6648
7532
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
6649
7533
|
*/
|
|
6650
|
-
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6651
|
-
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
7534
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7535
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
|
|
6652
7536
|
return this.cachedWethSponsoredCallback;
|
|
6653
7537
|
const provider = this.requireConnectedProvider();
|
|
6654
7538
|
const owner = this.state.walletAddress;
|
|
@@ -6658,6 +7542,7 @@ var OwneySDK = class {
|
|
|
6658
7542
|
ownerAddress: owner,
|
|
6659
7543
|
onApproved,
|
|
6660
7544
|
onSubmitted,
|
|
7545
|
+
resumeTxHash,
|
|
6661
7546
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
6662
7547
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6663
7548
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -6673,7 +7558,7 @@ var OwneySDK = class {
|
|
|
6673
7558
|
transport: custom4(provider)
|
|
6674
7559
|
})
|
|
6675
7560
|
});
|
|
6676
|
-
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
7561
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
|
|
6677
7562
|
return callback;
|
|
6678
7563
|
}
|
|
6679
7564
|
getAgent(agentId) {
|
|
@@ -6982,6 +7867,7 @@ var OwneySDK = class {
|
|
|
6982
7867
|
async deposit(options) {
|
|
6983
7868
|
const { amount, asset, depositCallback, agentId, onApproved } = options;
|
|
6984
7869
|
const prepared = /* @__PURE__ */ new Map();
|
|
7870
|
+
const paidFallbackAgents = /* @__PURE__ */ new Set();
|
|
6985
7871
|
const onProgress = options.onProgress ? (evidence) => {
|
|
6986
7872
|
if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
|
|
6987
7873
|
notifyFunding(options.onProgress, evidence);
|
|
@@ -6992,152 +7878,184 @@ var OwneySDK = class {
|
|
|
6992
7878
|
if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
|
|
6993
7879
|
}
|
|
6994
7880
|
} : void 0;
|
|
7881
|
+
const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
|
|
7882
|
+
for (const destination of destinations) {
|
|
7883
|
+
const evidence = prepared.get(destination.to.toLowerCase());
|
|
7884
|
+
if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
|
|
7885
|
+
}
|
|
7886
|
+
} : void 0;
|
|
6995
7887
|
if (BigInt(amount) === 0n) {
|
|
6996
7888
|
return { agentResults: {} };
|
|
6997
7889
|
}
|
|
6998
7890
|
const state = this.requireState();
|
|
6999
7891
|
const chainId = this.requireChainId();
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
7012
|
-
return this.depositWithFallback(
|
|
7013
|
-
agent,
|
|
7014
|
-
state,
|
|
7015
|
-
chainId,
|
|
7016
|
-
amount,
|
|
7017
|
-
asset,
|
|
7018
|
-
effectiveCallback,
|
|
7019
|
-
depositCallback,
|
|
7020
|
-
onProgress
|
|
7021
|
-
);
|
|
7022
|
-
}
|
|
7023
|
-
const totalAmount = BigInt(amount);
|
|
7024
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
7025
|
-
excludeDisabled: true
|
|
7026
|
-
});
|
|
7027
|
-
const exemptFlags = await Promise.all(
|
|
7028
|
-
eligibleAgents.map(
|
|
7029
|
-
(a) => this.hasExistingBalance(a, state, chainId, asset, true)
|
|
7030
|
-
)
|
|
7031
|
-
);
|
|
7032
|
-
const exempt = new Set(
|
|
7033
|
-
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
7034
|
-
);
|
|
7035
|
-
const agentAmounts = this.splitDepositAmount(
|
|
7036
|
-
totalAmount,
|
|
7037
|
-
eligibleAgents,
|
|
7038
|
-
chainId,
|
|
7039
|
-
asset,
|
|
7040
|
-
exempt
|
|
7041
|
-
);
|
|
7042
|
-
if (agentAmounts.length === 0) {
|
|
7043
|
-
const agentsRequiringActivation = eligibleAgents.filter(
|
|
7044
|
-
(agent) => !exempt.has(agent.id)
|
|
7045
|
-
);
|
|
7046
|
-
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
7047
|
-
(highest, agent) => {
|
|
7048
|
-
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
7049
|
-
return minimum > highest ? minimum : highest;
|
|
7050
|
-
},
|
|
7051
|
-
0n
|
|
7052
|
-
);
|
|
7053
|
-
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7054
|
-
throw new OwneyError(
|
|
7055
|
-
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
7056
|
-
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7057
|
-
{
|
|
7058
|
-
amount,
|
|
7059
|
-
asset,
|
|
7060
|
-
chainId,
|
|
7061
|
-
minDepositAmount: minimumRequired.toString(),
|
|
7062
|
-
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
7063
|
-
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
7064
|
-
agentId: agent.id,
|
|
7065
|
-
minDepositAmount: this.getMinDepositAmount(
|
|
7066
|
-
agent,
|
|
7067
|
-
chainId,
|
|
7068
|
-
asset
|
|
7069
|
-
).toString()
|
|
7070
|
-
}))
|
|
7071
|
-
}
|
|
7072
|
-
);
|
|
7073
|
-
}
|
|
7074
|
-
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
7075
|
-
if (!depositCallback && batchTransfer) {
|
|
7076
|
-
return runAgentDepositBatch(
|
|
7077
|
-
chainId,
|
|
7078
|
-
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
7079
|
-
id: agent.id,
|
|
7080
|
-
amount: amount2,
|
|
7081
|
-
run: (callback) => withFailureReporting(
|
|
7082
|
-
this.apiKey,
|
|
7083
|
-
agent.id,
|
|
7084
|
-
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
7085
|
-
this.routingApiBaseUrl
|
|
7086
|
-
)
|
|
7087
|
-
})),
|
|
7088
|
-
async (cid, transfers) => {
|
|
7089
|
-
try {
|
|
7090
|
-
return await batchTransfer(cid, transfers);
|
|
7091
|
-
} catch (error) {
|
|
7092
|
-
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
7093
|
-
throw error;
|
|
7094
|
-
const requiredAmount = transfers.reduce(
|
|
7095
|
-
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
7096
|
-
0n
|
|
7097
|
-
);
|
|
7098
|
-
await this.approvePermit2(
|
|
7099
|
-
asset,
|
|
7100
|
-
requiredAmount,
|
|
7101
|
-
cid
|
|
7102
|
-
);
|
|
7103
|
-
return batchTransfer(cid, transfers);
|
|
7104
|
-
}
|
|
7892
|
+
if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
|
|
7893
|
+
throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
|
|
7894
|
+
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));
|
|
7895
|
+
try {
|
|
7896
|
+
if (agentId) {
|
|
7897
|
+
if (this.isAgentDisabled(agentId)) {
|
|
7898
|
+
throw new OwneyError(
|
|
7899
|
+
"AGENT_DISABLED",
|
|
7900
|
+
`Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
|
|
7901
|
+
{ agentId }
|
|
7902
|
+
);
|
|
7105
7903
|
}
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
index,
|
|
7111
|
-
{ agent, amount: agentAmount }
|
|
7112
|
-
] of agentAmounts.entries()) {
|
|
7113
|
-
try {
|
|
7114
|
-
agentResults[agent.id] = await this.depositWithFallback(
|
|
7904
|
+
const agent = this.getAgent(agentId);
|
|
7905
|
+
this.validateAssetSupport(agent, chainId, asset);
|
|
7906
|
+
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
7907
|
+
return await this.depositWithFallback(
|
|
7115
7908
|
agent,
|
|
7116
7909
|
state,
|
|
7117
7910
|
chainId,
|
|
7118
|
-
|
|
7911
|
+
amount,
|
|
7119
7912
|
asset,
|
|
7120
7913
|
effectiveCallback,
|
|
7121
7914
|
depositCallback,
|
|
7122
|
-
onProgress
|
|
7915
|
+
onProgress,
|
|
7916
|
+
() => paidFallbackAgents.add(agent.id),
|
|
7917
|
+
!!options.resumeTxHash
|
|
7123
7918
|
);
|
|
7124
|
-
}
|
|
7125
|
-
|
|
7919
|
+
}
|
|
7920
|
+
const totalAmount = BigInt(amount);
|
|
7921
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
7922
|
+
excludeDisabled: true
|
|
7923
|
+
});
|
|
7924
|
+
const prospectiveShare = totalAmount / BigInt(eligibleAgents.length);
|
|
7925
|
+
const meetsEveryMinimum = eligibleAgents.every((agent) => prospectiveShare >= this.getMinDepositAmount(agent, chainId, asset));
|
|
7926
|
+
const exemptFlags = meetsEveryMinimum ? eligibleAgents.map(() => false) : await Promise.all(eligibleAgents.map((agent) => this.hasExistingBalance(agent, state, chainId, asset, true)));
|
|
7927
|
+
const exempt = new Set(
|
|
7928
|
+
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
7929
|
+
);
|
|
7930
|
+
const agentAmounts = this.splitDepositAmount(
|
|
7931
|
+
totalAmount,
|
|
7932
|
+
eligibleAgents,
|
|
7933
|
+
chainId,
|
|
7934
|
+
asset,
|
|
7935
|
+
exempt
|
|
7936
|
+
);
|
|
7937
|
+
if (agentAmounts.length === 0) {
|
|
7938
|
+
const agentsRequiringActivation = eligibleAgents.filter(
|
|
7939
|
+
(agent) => !exempt.has(agent.id)
|
|
7940
|
+
);
|
|
7941
|
+
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
7942
|
+
(highest, agent) => {
|
|
7943
|
+
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
7944
|
+
return minimum > highest ? minimum : highest;
|
|
7945
|
+
},
|
|
7946
|
+
0n
|
|
7947
|
+
);
|
|
7948
|
+
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7126
7949
|
throw new OwneyError(
|
|
7127
|
-
"
|
|
7128
|
-
"
|
|
7950
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
7951
|
+
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7129
7952
|
{
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7953
|
+
amount,
|
|
7954
|
+
asset,
|
|
7955
|
+
chainId,
|
|
7956
|
+
minDepositAmount: minimumRequired.toString(),
|
|
7957
|
+
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
7958
|
+
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
7959
|
+
agentId: agent.id,
|
|
7960
|
+
minDepositAmount: this.getMinDepositAmount(
|
|
7961
|
+
agent,
|
|
7962
|
+
chainId,
|
|
7963
|
+
asset
|
|
7964
|
+
).toString()
|
|
7965
|
+
}))
|
|
7966
|
+
}
|
|
7137
7967
|
);
|
|
7138
7968
|
}
|
|
7969
|
+
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
7970
|
+
if (!depositCallback && batchTransfer) {
|
|
7971
|
+
return await runAgentDepositBatch(
|
|
7972
|
+
chainId,
|
|
7973
|
+
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
7974
|
+
id: agent.id,
|
|
7975
|
+
amount: amount2,
|
|
7976
|
+
run: (callback) => withFailureReporting(
|
|
7977
|
+
this.apiKey,
|
|
7978
|
+
agent.id,
|
|
7979
|
+
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
7980
|
+
this.routingApiBaseUrl
|
|
7981
|
+
)
|
|
7982
|
+
})),
|
|
7983
|
+
async (cid, transfers) => {
|
|
7984
|
+
try {
|
|
7985
|
+
return await batchTransfer(cid, transfers);
|
|
7986
|
+
} catch (error) {
|
|
7987
|
+
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
7988
|
+
throw error;
|
|
7989
|
+
const requiredAmount = transfers.reduce(
|
|
7990
|
+
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
7991
|
+
0n
|
|
7992
|
+
);
|
|
7993
|
+
await this.approvePermit2(
|
|
7994
|
+
asset,
|
|
7995
|
+
requiredAmount,
|
|
7996
|
+
cid
|
|
7997
|
+
);
|
|
7998
|
+
return batchTransfer(cid, transfers);
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
);
|
|
8002
|
+
}
|
|
8003
|
+
const agentResults = {};
|
|
8004
|
+
for (const [
|
|
8005
|
+
index,
|
|
8006
|
+
{ agent, amount: agentAmount }
|
|
8007
|
+
] of agentAmounts.entries()) {
|
|
8008
|
+
try {
|
|
8009
|
+
agentResults[agent.id] = await this.depositWithFallback(
|
|
8010
|
+
agent,
|
|
8011
|
+
state,
|
|
8012
|
+
chainId,
|
|
8013
|
+
agentAmount,
|
|
8014
|
+
asset,
|
|
8015
|
+
effectiveCallback,
|
|
8016
|
+
depositCallback,
|
|
8017
|
+
onProgress,
|
|
8018
|
+
() => paidFallbackAgents.add(agent.id),
|
|
8019
|
+
!!options.resumeTxHash
|
|
8020
|
+
);
|
|
8021
|
+
} catch (error) {
|
|
8022
|
+
if (Object.keys(agentResults).length === 0) throw error;
|
|
8023
|
+
throw new OwneyError(
|
|
8024
|
+
"DEPOSIT_PARTIAL_FAILURE",
|
|
8025
|
+
"Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
|
|
8026
|
+
{
|
|
8027
|
+
agentResults,
|
|
8028
|
+
failedAgentId: agent.id,
|
|
8029
|
+
failedAmount: agentAmount,
|
|
8030
|
+
unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
|
|
8031
|
+
cause: error
|
|
8032
|
+
},
|
|
8033
|
+
agent.id
|
|
8034
|
+
);
|
|
8035
|
+
}
|
|
8036
|
+
}
|
|
8037
|
+
return { agentResults };
|
|
8038
|
+
} catch (error) {
|
|
8039
|
+
if (!depositCallback && onProgress && !options.resumeTxHash) {
|
|
8040
|
+
const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
|
|
8041
|
+
for (const id of ids) {
|
|
8042
|
+
const evidence = [...prepared.values()].find((value) => value.agentId === id);
|
|
8043
|
+
if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
|
|
8044
|
+
const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
|
|
8045
|
+
if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
|
|
8046
|
+
notifyFunding(onProgress, { ...evidence ?? {
|
|
8047
|
+
agentId: id,
|
|
8048
|
+
chainId,
|
|
8049
|
+
token: asset,
|
|
8050
|
+
walletAddress: state.walletAddress,
|
|
8051
|
+
intendedAmount: amount,
|
|
8052
|
+
registration: "unknown",
|
|
8053
|
+
delivery: "not-applicable"
|
|
8054
|
+
}, transfer: "failed" });
|
|
8055
|
+
}
|
|
8056
|
+
}
|
|
8057
|
+
throw error;
|
|
7139
8058
|
}
|
|
7140
|
-
return { agentResults };
|
|
7141
8059
|
}
|
|
7142
8060
|
/**
|
|
7143
8061
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
@@ -7166,7 +8084,7 @@ var OwneySDK = class {
|
|
|
7166
8084
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
7167
8085
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
7168
8086
|
*/
|
|
7169
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
8087
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
|
|
7170
8088
|
const attempt = (callback) => withFailureReporting(
|
|
7171
8089
|
this.apiKey,
|
|
7172
8090
|
agent.id,
|
|
@@ -7178,6 +8096,7 @@ var OwneySDK = class {
|
|
|
7178
8096
|
try {
|
|
7179
8097
|
return await attempt(effectiveCallback);
|
|
7180
8098
|
} catch (error) {
|
|
8099
|
+
if (resumeOnly) throw error;
|
|
7181
8100
|
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
7182
8101
|
approvalAttempted = true;
|
|
7183
8102
|
console.warn(
|
|
@@ -7195,6 +8114,7 @@ var OwneySDK = class {
|
|
|
7195
8114
|
`[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
|
|
7196
8115
|
error
|
|
7197
8116
|
);
|
|
8117
|
+
onPaidFallback?.();
|
|
7198
8118
|
return attempt(void 0);
|
|
7199
8119
|
}
|
|
7200
8120
|
}
|
|
@@ -7277,7 +8197,12 @@ var OwneySDK = class {
|
|
|
7277
8197
|
*/
|
|
7278
8198
|
async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
|
|
7279
8199
|
try {
|
|
7280
|
-
|
|
8200
|
+
if (agent.hasExistingDepositFunds && await agent.hasExistingDepositFunds(
|
|
8201
|
+
state,
|
|
8202
|
+
chainId,
|
|
8203
|
+
asset
|
|
8204
|
+
).catch(() => void 0) === true) return true;
|
|
8205
|
+
const balance = await agent.getBalances(state, chainId, asset);
|
|
7281
8206
|
const target = asset.toLowerCase();
|
|
7282
8207
|
const token = balance.tokens.find(
|
|
7283
8208
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
@@ -7390,7 +8315,7 @@ var OwneySDK = class {
|
|
|
7390
8315
|
if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
|
|
7391
8316
|
const transaction = await rpc2.getTransaction({ hash: evidence.txHash });
|
|
7392
8317
|
assertCurrent();
|
|
7393
|
-
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
|
|
8318
|
+
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address, receipt))
|
|
7394
8319
|
verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
|
|
7395
8320
|
}
|
|
7396
8321
|
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
@@ -7403,7 +8328,8 @@ var OwneySDK = class {
|
|
|
7403
8328
|
return verified;
|
|
7404
8329
|
}
|
|
7405
8330
|
/** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
|
|
7406
|
-
async resolveSponsoredWithdrawalEvidence(evidence) {
|
|
8331
|
+
async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
|
|
8332
|
+
if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
|
|
7407
8333
|
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
7408
8334
|
if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
7409
8335
|
const agent = this.getAgent("yieldseeker");
|
|
@@ -7419,18 +8345,26 @@ var OwneySDK = class {
|
|
|
7419
8345
|
owner: state.walletAddress,
|
|
7420
8346
|
agentWallet: wallet,
|
|
7421
8347
|
asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
|
|
7422
|
-
userOpHash: evidence.userOpHash
|
|
8348
|
+
userOpHash: evidence.userOpHash,
|
|
8349
|
+
...evidence.userOperation ? { userOperation: Object.fromEntries([
|
|
8350
|
+
"sender",
|
|
8351
|
+
"nonce",
|
|
8352
|
+
"initCode",
|
|
8353
|
+
"callData",
|
|
8354
|
+
"callGasLimit",
|
|
8355
|
+
"verificationGasLimit",
|
|
8356
|
+
"preVerificationGas",
|
|
8357
|
+
"maxFeePerGas",
|
|
8358
|
+
"maxPriorityFeePerGas",
|
|
8359
|
+
"paymasterAndData"
|
|
8360
|
+
].map((key2) => [key2, evidence.userOperation[key2]])) } : {},
|
|
8361
|
+
...Number.isFinite(options.submittedAt) && options.submittedAt > 0 ? { submittedAt: options.submittedAt } : {}
|
|
7423
8362
|
})
|
|
7424
8363
|
});
|
|
7425
8364
|
if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
|
|
7426
8365
|
const payload = await response.json();
|
|
7427
8366
|
txHash = payload.data?.txHash ?? void 0;
|
|
7428
8367
|
executionFailed = payload.data?.executionFailed === true;
|
|
7429
|
-
} else if (evidence.callsId) {
|
|
7430
|
-
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
7431
|
-
if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
|
|
7432
|
-
executionFailed = status?.status === 400 || status?.status === 500;
|
|
7433
|
-
txHash = status?.receipts?.[0]?.transactionHash;
|
|
7434
8368
|
}
|
|
7435
8369
|
assertCurrent();
|
|
7436
8370
|
if (executionFailed) return {
|
|
@@ -7441,6 +8375,60 @@ var OwneySDK = class {
|
|
|
7441
8375
|
};
|
|
7442
8376
|
return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
|
|
7443
8377
|
}
|
|
8378
|
+
/** Recover an EIP-5792 deposit or payout using this account's exact batch ID. */
|
|
8379
|
+
async resolveWalletCallsFundingEvidence(evidence, direction) {
|
|
8380
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
8381
|
+
if (!evidence.callsId || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
8382
|
+
const agent = this.getAgent(evidence.agentId);
|
|
8383
|
+
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
8384
|
+
if (!wallet || wallet.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return evidence;
|
|
8385
|
+
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
8386
|
+
assertCurrent();
|
|
8387
|
+
if (status?.id !== void 0 && status.id !== evidence.callsId || status?.chainId !== void 0 && Number(status.chainId) !== chainId) return evidence;
|
|
8388
|
+
const statusCode2 = Number(status?.status);
|
|
8389
|
+
if (statusCode2 === 400 || statusCode2 === 500) return {
|
|
8390
|
+
...evidence,
|
|
8391
|
+
transfer: "failed",
|
|
8392
|
+
delivery: direction === "withdraw" ? "failed" : "not-applicable"
|
|
8393
|
+
};
|
|
8394
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash?.toLowerCase()).filter((hash) => !!hash && /^0x[\da-f]{64}$/i.test(hash)))];
|
|
8395
|
+
if (hashes.length === 1) return { ...evidence, txHash: hashes[0] };
|
|
8396
|
+
if (hashes.length > 16) return evidence;
|
|
8397
|
+
const matches = [];
|
|
8398
|
+
for (const txHash of hashes) {
|
|
8399
|
+
const inspected = await this.inspectFunding({ ...evidence, txHash }, direction);
|
|
8400
|
+
assertCurrent();
|
|
8401
|
+
if (inspected.transfer === "confirmed") matches.push(inspected);
|
|
8402
|
+
}
|
|
8403
|
+
return matches.length === 1 ? matches[0] : evidence;
|
|
8404
|
+
}
|
|
8405
|
+
/** Whether a foreground deposit can resume the same signed batch, never a
|
|
8406
|
+
* fresh authorization. The batch rail independently rechecks the exact plan. */
|
|
8407
|
+
async canResumePendingDeposit(evidences, amount) {
|
|
8408
|
+
if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
|
|
8409
|
+
const first = evidences[0];
|
|
8410
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
|
|
8411
|
+
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
|
|
8412
|
+
if (!token) return false;
|
|
8413
|
+
const pending = pendingTokenBatch({
|
|
8414
|
+
apiKey: this.apiKey,
|
|
8415
|
+
baseUrl: this.routingApiBaseUrl,
|
|
8416
|
+
owner: state.walletAddress,
|
|
8417
|
+
chainId,
|
|
8418
|
+
token: token.address
|
|
8419
|
+
});
|
|
8420
|
+
if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
|
|
8421
|
+
const destinations = /* @__PURE__ */ new Set();
|
|
8422
|
+
for (const evidence of evidences) {
|
|
8423
|
+
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;
|
|
8424
|
+
const agent = this.getAgent(evidence.agentId);
|
|
8425
|
+
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
8426
|
+
if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
|
|
8427
|
+
destinations.add(wallet.toLowerCase());
|
|
8428
|
+
}
|
|
8429
|
+
assertCurrent();
|
|
8430
|
+
return true;
|
|
8431
|
+
}
|
|
7444
8432
|
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
7445
8433
|
endFundingBalanceRefresh(evidence, read2) {
|
|
7446
8434
|
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
@@ -7541,6 +8529,30 @@ var OwneySDK = class {
|
|
|
7541
8529
|
}
|
|
7542
8530
|
};
|
|
7543
8531
|
const token = asset;
|
|
8532
|
+
const agentFailures = {};
|
|
8533
|
+
const recordAgentFailure = (id, error) => {
|
|
8534
|
+
if (!(error instanceof OwneyError)) return;
|
|
8535
|
+
const details = error.details ?? {};
|
|
8536
|
+
const codeValue = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : void 0;
|
|
8537
|
+
const revertNames = ["InsufficientBalance", "InvalidAsset", "Unauthorized", "Error", "Panic", "FailedOp", "FailedOpWithRevert"];
|
|
8538
|
+
const panicCodes = [0, 1, 17, 18, 33, 34, 49, 50, 65, 81];
|
|
8539
|
+
agentFailures[id] = {
|
|
8540
|
+
code: error.code,
|
|
8541
|
+
phase: ["prepare", "send", "observe"].includes(String(details.phase)) ? details.phase : void 0,
|
|
8542
|
+
failureCode: codeValue(details.failureCode),
|
|
8543
|
+
reason: codeValue(details.reason),
|
|
8544
|
+
withdrawalPhase: details.withdrawalPhase === "unwind" ? "unwind" : void 0,
|
|
8545
|
+
statusCode: typeof details.statusCode === "number" ? details.statusCode : void 0,
|
|
8546
|
+
rpcCode: typeof details.rpcCode === "number" ? details.rpcCode : void 0,
|
|
8547
|
+
rpcMethod: typeof details.rpcMethod === "string" && /^[A-Za-z_]{1,80}$/.test(details.rpcMethod) ? details.rpcMethod : void 0,
|
|
8548
|
+
revertSelector: typeof details.revertSelector === "string" && /^0x[0-9a-f]{8}$/i.test(details.revertSelector) ? details.revertSelector : void 0,
|
|
8549
|
+
revertName: typeof details.revertName === "string" && revertNames.includes(details.revertName) ? details.revertName : void 0,
|
|
8550
|
+
aaCode: typeof details.aaCode === "string" && /^AA[0-9]{2}$/.test(details.aaCode) ? details.aaCode : void 0,
|
|
8551
|
+
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,
|
|
8552
|
+
safeToFallback: typeof details.safeToFallback === "boolean" ? details.safeToFallback : void 0,
|
|
8553
|
+
executionFailed: typeof details.executionFailed === "boolean" ? details.executionFailed : void 0
|
|
8554
|
+
};
|
|
8555
|
+
};
|
|
7544
8556
|
const submissionEvidence = /* @__PURE__ */ new Map();
|
|
7545
8557
|
const submit = async (agent, requested2) => {
|
|
7546
8558
|
attempted.add(agent.id);
|
|
@@ -7561,9 +8573,19 @@ var OwneySDK = class {
|
|
|
7561
8573
|
};
|
|
7562
8574
|
progress(evidence);
|
|
7563
8575
|
try {
|
|
8576
|
+
if (options.onBeforeSubmission)
|
|
8577
|
+
return await agent.withdraw(
|
|
8578
|
+
state,
|
|
8579
|
+
chainId,
|
|
8580
|
+
token,
|
|
8581
|
+
requested2,
|
|
8582
|
+
progress,
|
|
8583
|
+
options.gasMode ?? "auto",
|
|
8584
|
+
(next) => options.onBeforeSubmission({ ...evidence, ...next })
|
|
8585
|
+
);
|
|
7564
8586
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
7565
8587
|
} catch (error) {
|
|
7566
|
-
if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
8588
|
+
if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
7567
8589
|
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
7568
8590
|
throw error;
|
|
7569
8591
|
}
|
|
@@ -7594,6 +8616,7 @@ var OwneySDK = class {
|
|
|
7594
8616
|
if (!amount) {
|
|
7595
8617
|
const results2 = {};
|
|
7596
8618
|
const agentErrors2 = {};
|
|
8619
|
+
const incompleteWithdrawals = {};
|
|
7597
8620
|
for (const agent of withdrawalAgents) {
|
|
7598
8621
|
try {
|
|
7599
8622
|
results2[agent.id] = await submit(agent);
|
|
@@ -7602,8 +8625,17 @@ var OwneySDK = class {
|
|
|
7602
8625
|
if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
|
|
7603
8626
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7604
8627
|
if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
|
|
8628
|
+
if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
|
|
8629
|
+
incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
|
|
8630
|
+
const evidence = submissionEvidence.get(agent.id);
|
|
8631
|
+
if (evidence?.agentId === agent.id && evidence.chainId === chainId && evidence.token === token && evidence.walletAddress.toLowerCase() === state.walletAddress.toLowerCase() && evidence.intendedAmount === "MAX" && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && evidence.txHash && /^0x[\da-f]{64}$/i.test(evidence.txHash) && evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) && BigInt(evidence.confirmedAmount) > 0n) {
|
|
8632
|
+
results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
|
|
8633
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
7605
8636
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
7606
8637
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
8638
|
+
recordAgentFailure(agent.id, err);
|
|
7607
8639
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7608
8640
|
void reportAgentFailure(
|
|
7609
8641
|
this.apiKey,
|
|
@@ -7614,17 +8646,23 @@ var OwneySDK = class {
|
|
|
7614
8646
|
}
|
|
7615
8647
|
}
|
|
7616
8648
|
if (Object.keys(results2).length === 0) {
|
|
8649
|
+
if (Object.keys(agentErrors2).length === 0)
|
|
8650
|
+
throw new OwneyError(
|
|
8651
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
8652
|
+
"No funds are currently available to withdraw for this asset.",
|
|
8653
|
+
{ asset, available: "0" }
|
|
8654
|
+
);
|
|
7617
8655
|
throw new OwneyError(
|
|
7618
8656
|
"WITHDRAW_ALL_FAILED",
|
|
7619
8657
|
`Failed to withdraw "${asset}" from all eligible agents.`,
|
|
7620
|
-
{ asset, agentErrors: agentErrors2 }
|
|
8658
|
+
{ asset, agentErrors: agentErrors2, agentFailures }
|
|
7621
8659
|
);
|
|
7622
8660
|
}
|
|
7623
8661
|
if (Object.keys(agentErrors2).length > 0) {
|
|
7624
8662
|
throw new OwneyError(
|
|
7625
8663
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7626
8664
|
"Some agents could not complete the withdrawal.",
|
|
7627
|
-
{ asset, partialResults: results2, agentErrors: agentErrors2 }
|
|
8665
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
7628
8666
|
);
|
|
7629
8667
|
}
|
|
7630
8668
|
const amountsKnown = Object.values(results2).every(
|
|
@@ -7705,6 +8743,7 @@ var OwneySDK = class {
|
|
|
7705
8743
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7706
8744
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
7707
8745
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
8746
|
+
recordAgentFailure(p.agent.id, err);
|
|
7708
8747
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7709
8748
|
void reportAgentFailure(
|
|
7710
8749
|
this.apiKey,
|
|
@@ -7725,7 +8764,8 @@ var OwneySDK = class {
|
|
|
7725
8764
|
{
|
|
7726
8765
|
asset,
|
|
7727
8766
|
amount,
|
|
7728
|
-
agentErrors
|
|
8767
|
+
agentErrors,
|
|
8768
|
+
agentFailures
|
|
7729
8769
|
}
|
|
7730
8770
|
);
|
|
7731
8771
|
}
|
|
@@ -7740,6 +8780,7 @@ var OwneySDK = class {
|
|
|
7740
8780
|
withdrawn: withdrawn.toString(),
|
|
7741
8781
|
partialResults: results,
|
|
7742
8782
|
agentErrors,
|
|
8783
|
+
agentFailures,
|
|
7743
8784
|
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
7744
8785
|
}
|
|
7745
8786
|
);
|