@owney/sdk 0.7.25-beta.14 → 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 +1366 -526
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +1286 -443
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -68,6 +68,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
68
68
|
const attributed = /* @__PURE__ */ new Map();
|
|
69
69
|
const agent = evidence.agentWallet?.toLowerCase();
|
|
70
70
|
const owner = evidence.walletAddress.toLowerCase();
|
|
71
|
+
const source = direction === "deposit" ? owner : agent;
|
|
72
|
+
const destination = direction === "deposit" ? agent : owner;
|
|
71
73
|
const logIndexes = [];
|
|
72
74
|
const transferLogs = [];
|
|
73
75
|
const zeroTransferLogs = [];
|
|
@@ -82,17 +84,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
|
82
84
|
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
83
85
|
zeroTransferLogs.push(log.logIndex);
|
|
84
86
|
let matched = 0n;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (sender !== agent) attributed.set(sender, available - carried);
|
|
93
|
-
if (recipient === owner) matched = carried;
|
|
94
|
-
else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
95
|
-
}
|
|
87
|
+
const sender = from.toLowerCase();
|
|
88
|
+
const recipient = to.toLowerCase();
|
|
89
|
+
const available = sender === source ? value : attributed.get(sender) ?? 0n;
|
|
90
|
+
const carried = available < value ? available : value;
|
|
91
|
+
if (sender !== source) attributed.set(sender, available - carried);
|
|
92
|
+
if (recipient === destination) matched = carried;
|
|
93
|
+
else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
96
94
|
if (matched === 0n) continue;
|
|
97
95
|
amount += matched;
|
|
98
96
|
transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
|
|
@@ -384,8 +382,8 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
384
382
|
return await original.apply(this, args);
|
|
385
383
|
} catch (error) {
|
|
386
384
|
const message = error instanceof Error ? error.message : "";
|
|
387
|
-
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";
|
|
388
|
-
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");
|
|
389
387
|
throw error;
|
|
390
388
|
} finally {
|
|
391
389
|
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
@@ -408,9 +406,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
|
408
406
|
} };
|
|
409
407
|
}
|
|
410
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
|
+
|
|
411
449
|
// src/agents/zyfai/zyfai.agent.ts
|
|
412
450
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
413
|
-
import { formatUnits, parseAbi as parseAbi2 } from "viem";
|
|
451
|
+
import { erc20Abi as erc20Abi2, formatUnits as formatUnits2, isAddress, parseAbi as parseAbi2 } from "viem";
|
|
414
452
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
415
453
|
|
|
416
454
|
// src/types/config.ts
|
|
@@ -497,14 +535,6 @@ function hexToDecimal(hex, decimals = 6) {
|
|
|
497
535
|
const remainder = parsed % divisor;
|
|
498
536
|
return Number(whole) + Number(remainder) / Number(divisor);
|
|
499
537
|
}
|
|
500
|
-
function normalizeDecimalPlace(value, decimals = 6) {
|
|
501
|
-
const parsed = BigInt(value);
|
|
502
|
-
if (decimals <= 0) return Number(parsed);
|
|
503
|
-
const divisor = BigInt(10 ** decimals);
|
|
504
|
-
const whole = parsed / divisor;
|
|
505
|
-
const remainder = parsed % divisor;
|
|
506
|
-
return Number(whole) + Number(remainder) / Number(divisor);
|
|
507
|
-
}
|
|
508
538
|
var DayFilterMapping = {
|
|
509
539
|
"30D": 30,
|
|
510
540
|
"14D": 14,
|
|
@@ -512,6 +542,8 @@ var DayFilterMapping = {
|
|
|
512
542
|
};
|
|
513
543
|
|
|
514
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);
|
|
515
547
|
var DECIMALS_BY_SYMBOL = {
|
|
516
548
|
usdc: 6,
|
|
517
549
|
weth: 18,
|
|
@@ -566,15 +598,15 @@ function mapPendingAllocations(raw2) {
|
|
|
566
598
|
if (typeof e.balance !== "string") continue;
|
|
567
599
|
let amount;
|
|
568
600
|
try {
|
|
569
|
-
|
|
601
|
+
if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
|
|
602
|
+
amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
|
|
570
603
|
} catch {
|
|
571
604
|
continue;
|
|
572
605
|
}
|
|
573
|
-
if (!Number.isFinite(amount) || amount <= 0) continue;
|
|
574
606
|
pending.push({
|
|
575
607
|
chainId: e.chainId,
|
|
576
608
|
asset: e.tokenSymbol,
|
|
577
|
-
amount
|
|
609
|
+
amount,
|
|
578
610
|
isPending: e.isPending === true,
|
|
579
611
|
...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
|
|
580
612
|
});
|
|
@@ -597,13 +629,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
597
629
|
{ balance, balanceWithFee, decimals }
|
|
598
630
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
599
631
|
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
600
|
-
const amount =
|
|
601
|
-
totalBalance += amount;
|
|
632
|
+
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
|
|
633
|
+
totalBalance += Number(amount);
|
|
602
634
|
tokens.push({
|
|
603
635
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
604
636
|
chainId: positionChainId,
|
|
605
637
|
asset: symbol,
|
|
606
|
-
amount
|
|
638
|
+
amount
|
|
607
639
|
});
|
|
608
640
|
}
|
|
609
641
|
}
|
|
@@ -617,19 +649,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
617
649
|
// legacy `amount` fallback is hex. Runtime
|
|
618
650
|
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
619
651
|
// when present, otherwise fall back to the per-symbol lookup.
|
|
620
|
-
amount:
|
|
621
|
-
p.underlyingAmountWithFee ?? p.underlyingAmount
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
p.decimals
|
|
632
|
-
)
|
|
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
|
|
633
663
|
)
|
|
634
664
|
),
|
|
635
665
|
apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
|
|
@@ -1573,7 +1603,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1573
1603
|
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
1574
1604
|
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
1575
1605
|
Object.assign(tx, {
|
|
1576
|
-
amount:
|
|
1606
|
+
amount: formatUnits2(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
1577
1607
|
amountRaw: evidence.confirmedAmount,
|
|
1578
1608
|
blockNumber: evidence.blockNumber,
|
|
1579
1609
|
logIndexes: evidence.logIndexes,
|
|
@@ -2300,6 +2330,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2300
2330
|
evidence2 = { ...evidence2, transfer: "unknown" };
|
|
2301
2331
|
}
|
|
2302
2332
|
notifyFunding(onProgress, evidence2);
|
|
2333
|
+
if (evidence2.transfer === "failed")
|
|
2334
|
+
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
|
|
2303
2335
|
try {
|
|
2304
2336
|
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
2305
2337
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
@@ -2338,12 +2370,77 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2338
2370
|
} catch {
|
|
2339
2371
|
}
|
|
2340
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);
|
|
2341
2375
|
return { ...mapDeposit(raw2), evidence };
|
|
2342
2376
|
} catch (error) {
|
|
2343
2377
|
throw error;
|
|
2344
2378
|
}
|
|
2345
2379
|
}
|
|
2346
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
|
+
}
|
|
2347
2444
|
async withdraw(state, chainId, token, amount, onProgress) {
|
|
2348
2445
|
const validChainId = isValidChainId(chainId);
|
|
2349
2446
|
try {
|
|
@@ -2361,11 +2458,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2361
2458
|
});
|
|
2362
2459
|
throw error;
|
|
2363
2460
|
}
|
|
2364
|
-
|
|
2461
|
+
let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2365
2462
|
const authToken = this.sdk.httpClient?.authToken;
|
|
2463
|
+
const owner = this.getAddress();
|
|
2366
2464
|
let raw2;
|
|
2367
2465
|
try {
|
|
2368
|
-
|
|
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
|
+
);
|
|
2369
2490
|
} catch (error) {
|
|
2370
2491
|
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
2371
2492
|
if (observation.attempted() !== void 0) {
|
|
@@ -2648,10 +2769,13 @@ import {
|
|
|
2648
2769
|
createPublicClient as createPublicClient3,
|
|
2649
2770
|
createWalletClient as createWalletClient3,
|
|
2650
2771
|
custom as custom3,
|
|
2772
|
+
decodeEventLog as decodeEventLog2,
|
|
2773
|
+
encodeEventTopics,
|
|
2651
2774
|
encodeFunctionData,
|
|
2652
|
-
erc20Abi as
|
|
2775
|
+
erc20Abi as erc20Abi3,
|
|
2776
|
+
erc4626Abi,
|
|
2653
2777
|
getAddress as getAddress4,
|
|
2654
|
-
isAddress as
|
|
2778
|
+
isAddress as isAddress3
|
|
2655
2779
|
} from "viem";
|
|
2656
2780
|
import { base as base4 } from "viem/chains";
|
|
2657
2781
|
|
|
@@ -3270,7 +3394,7 @@ var YieldseekerApiClient = class {
|
|
|
3270
3394
|
};
|
|
3271
3395
|
|
|
3272
3396
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
3273
|
-
import { formatUnits as
|
|
3397
|
+
import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
|
|
3274
3398
|
|
|
3275
3399
|
// src/lib/helpers/snapshot-apy.ts
|
|
3276
3400
|
var DAY_MS = 864e5;
|
|
@@ -3337,10 +3461,10 @@ function raw(value, endpoint) {
|
|
|
3337
3461
|
return BigInt(value);
|
|
3338
3462
|
}
|
|
3339
3463
|
function decimal(value, decimals, endpoint) {
|
|
3340
|
-
return
|
|
3464
|
+
return formatUnits3(raw(value, endpoint), decimals);
|
|
3341
3465
|
}
|
|
3342
3466
|
function usd(rawAmount, decimals, price) {
|
|
3343
|
-
return Number(
|
|
3467
|
+
return Number(formatUnits3(rawAmount, decimals)) * price;
|
|
3344
3468
|
}
|
|
3345
3469
|
function percent(value) {
|
|
3346
3470
|
const result = Number(value);
|
|
@@ -3366,7 +3490,7 @@ function assetAddressValue(record, address) {
|
|
|
3366
3490
|
}
|
|
3367
3491
|
function position(value, asset, baseAssetDecimals) {
|
|
3368
3492
|
const option = value?.yieldOption;
|
|
3369
|
-
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)) {
|
|
3370
3494
|
return invalid("yield positions", "missing vault metadata");
|
|
3371
3495
|
}
|
|
3372
3496
|
return {
|
|
@@ -3458,7 +3582,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
3458
3582
|
chain: "BASE",
|
|
3459
3583
|
chainId: 8453,
|
|
3460
3584
|
asset: context.asset,
|
|
3461
|
-
amount:
|
|
3585
|
+
amount: formatUnits3(amount, context.snapshot.baseAssetDecimals)
|
|
3462
3586
|
});
|
|
3463
3587
|
lifetimeEarnings += usd(
|
|
3464
3588
|
amount,
|
|
@@ -3587,7 +3711,7 @@ function allocationEntry(action, context) {
|
|
|
3587
3711
|
const leg = deposit;
|
|
3588
3712
|
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
3589
3713
|
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
3590
|
-
if (typeof leg.vaultAddress !== "string" || !
|
|
3714
|
+
if (typeof leg.vaultAddress !== "string" || !isAddress2(leg.vaultAddress)) return void 0;
|
|
3591
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;
|
|
3592
3716
|
if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
|
|
3593
3717
|
const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
|
|
@@ -3595,7 +3719,7 @@ function allocationEntry(action, context) {
|
|
|
3595
3719
|
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
3596
3720
|
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
3597
3721
|
tokenSymbol: context.asset,
|
|
3598
|
-
amount:
|
|
3722
|
+
amount: formatUnits3(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3599
3723
|
});
|
|
3600
3724
|
amountRaw += rawAmount;
|
|
3601
3725
|
}
|
|
@@ -3614,7 +3738,7 @@ function allocationEntry(action, context) {
|
|
|
3614
3738
|
tokenSymbol: context.asset,
|
|
3615
3739
|
agentWallet: context.wallet.walletAddress,
|
|
3616
3740
|
amountRaw: String(amountRaw),
|
|
3617
|
-
amount:
|
|
3741
|
+
amount: formatUnits3(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3618
3742
|
}]
|
|
3619
3743
|
};
|
|
3620
3744
|
}
|
|
@@ -3738,7 +3862,7 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3738
3862
|
const known = new Set(target.logIndexes ?? []);
|
|
3739
3863
|
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
3740
3864
|
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
3741
|
-
target.amount =
|
|
3865
|
+
target.amount = formatUnits3(
|
|
3742
3866
|
BigInt(target.amountRaw),
|
|
3743
3867
|
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
3744
3868
|
);
|
|
@@ -3833,7 +3957,8 @@ var walletAbi = parseAbi3([
|
|
|
3833
3957
|
"function ownerAgentIndex() view returns (uint256)",
|
|
3834
3958
|
"function baseAsset() view returns (address)",
|
|
3835
3959
|
"function FACTORY() view returns (address)",
|
|
3836
|
-
"function upgradeToLatest()"
|
|
3960
|
+
"function upgradeToLatest()",
|
|
3961
|
+
"event Upgraded(address indexed implementation)"
|
|
3837
3962
|
]);
|
|
3838
3963
|
var factoryAbi = parseAbi3([
|
|
3839
3964
|
"function userWallets(address owner, uint256 index) view returns (address)",
|
|
@@ -3842,19 +3967,19 @@ var factoryAbi = parseAbi3([
|
|
|
3842
3967
|
function yieldseekerOwnerPath(code) {
|
|
3843
3968
|
return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
|
|
3844
3969
|
}
|
|
3845
|
-
async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
3970
|
+
async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
|
|
3846
3971
|
const agentWallet = getAddress2(wallet);
|
|
3847
3972
|
const expectedOwner = getAddress2(owner);
|
|
3848
3973
|
const expectedAsset = getAddress2(asset);
|
|
3849
3974
|
const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
|
|
3850
|
-
client.getBytecode({ address: agentWallet }),
|
|
3851
|
-
client.getBytecode({ address: expectedOwner }),
|
|
3852
|
-
client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
|
|
3853
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
|
|
3854
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
|
|
3855
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
|
|
3856
|
-
client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
|
|
3857
|
-
client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
|
|
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 })
|
|
3858
3983
|
]);
|
|
3859
3984
|
if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
|
|
3860
3985
|
throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
|
|
@@ -3864,7 +3989,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
|
|
|
3864
3989
|
address: YIELDSEEKER_FACTORY,
|
|
3865
3990
|
abi: factoryAbi,
|
|
3866
3991
|
functionName: "userWallets",
|
|
3867
|
-
args: [expectedOwner, index]
|
|
3992
|
+
args: [expectedOwner, index],
|
|
3993
|
+
blockNumber
|
|
3868
3994
|
});
|
|
3869
3995
|
const verified = isAddressEqual(actualOwner, expectedOwner) && isAddressEqual(actualAsset, expectedAsset) && isAddressEqual(actualFactory, YIELDSEEKER_FACTORY) && isAddressEqual(mapped, agentWallet);
|
|
3870
3996
|
const version = !verified ? "unknown" : isAddressEqual(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : isAddressEqual(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
|
|
@@ -3893,6 +4019,8 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
|
3893
4019
|
import { base as base3 } from "viem/chains";
|
|
3894
4020
|
var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
3895
4021
|
var USER_OP_FEE_HEADROOM = 2n;
|
|
4022
|
+
var OBSERVATION_WINDOW_MS = 6e4;
|
|
4023
|
+
var OBSERVATION_POLL_MS = 1500;
|
|
3896
4024
|
var ENTRY_POINT_NONCE_ABI = [
|
|
3897
4025
|
{ type: "function", name: "getNonce", stateMutability: "view", inputs: [
|
|
3898
4026
|
{ name: "sender", type: "address" },
|
|
@@ -3925,10 +4053,119 @@ function sponsorError(message, safeToFallback, details = {}) {
|
|
|
3925
4053
|
return new OwneyError(
|
|
3926
4054
|
"SPONSOR_REQUEST_FAILED",
|
|
3927
4055
|
message,
|
|
3928
|
-
{
|
|
4056
|
+
{
|
|
4057
|
+
safeToFallback,
|
|
4058
|
+
phase: safeToFallback ? "prepare" : "observe",
|
|
4059
|
+
failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
|
|
4060
|
+
...safeToFallback ? { rejectedBeforeSubmission: true } : {},
|
|
4061
|
+
...details
|
|
4062
|
+
},
|
|
3929
4063
|
"yieldseeker"
|
|
3930
4064
|
);
|
|
3931
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
|
+
}
|
|
3932
4169
|
async function requestIntent(input) {
|
|
3933
4170
|
const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
|
|
3934
4171
|
let response;
|
|
@@ -3950,18 +4187,35 @@ async function requestIntent(input) {
|
|
|
3950
4187
|
action: input.action,
|
|
3951
4188
|
yieldseekerUserId: input.userId,
|
|
3952
4189
|
yieldseekerAgentId: input.agentId
|
|
3953
|
-
})
|
|
4190
|
+
}),
|
|
4191
|
+
signal: AbortSignal.timeout(15e3)
|
|
3954
4192
|
});
|
|
3955
4193
|
} catch {
|
|
3956
|
-
throw sponsorError(
|
|
4194
|
+
throw sponsorError(
|
|
4195
|
+
"Withdrawal sponsorship could not be requested.",
|
|
4196
|
+
true,
|
|
4197
|
+
{ phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
|
|
4198
|
+
);
|
|
3957
4199
|
}
|
|
3958
4200
|
const envelope = await response.json().catch(() => null);
|
|
3959
4201
|
const intent = envelope?.data;
|
|
3960
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
|
+
}
|
|
3961
4209
|
throw sponsorError(
|
|
3962
4210
|
"Withdrawal sponsorship is currently unavailable.",
|
|
3963
4211
|
true,
|
|
3964
|
-
{
|
|
4212
|
+
{
|
|
4213
|
+
statusCode: response.status,
|
|
4214
|
+
phase: "prepare",
|
|
4215
|
+
rejectedBeforeSubmission: true,
|
|
4216
|
+
failureCode: failureCode ?? "INTENT_REJECTED",
|
|
4217
|
+
providerReason: providerReason(envelope?.message)
|
|
4218
|
+
}
|
|
3965
4219
|
);
|
|
3966
4220
|
}
|
|
3967
4221
|
const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
|
|
@@ -3974,19 +4228,55 @@ async function rpc(url, method, params, phase) {
|
|
|
3974
4228
|
response = await fetch(url, {
|
|
3975
4229
|
method: "POST",
|
|
3976
4230
|
headers: { "content-type": "application/json" },
|
|
3977
|
-
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)
|
|
3978
4233
|
});
|
|
3979
4234
|
} catch {
|
|
3980
|
-
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
|
+
);
|
|
3981
4245
|
}
|
|
3982
4246
|
const payload = await response.json().catch(() => null);
|
|
3983
4247
|
if (!response.ok || payload?.error || payload?.result === void 0) {
|
|
3984
4248
|
const rejection = payload?.error;
|
|
3985
|
-
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 ?? ""));
|
|
3986
4273
|
throw sponsorError(
|
|
3987
4274
|
phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
|
|
3988
4275
|
phase === "prepare" || definitiveRejection,
|
|
3989
|
-
{
|
|
4276
|
+
{
|
|
4277
|
+
...details,
|
|
4278
|
+
...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
|
|
4279
|
+
}
|
|
3990
4280
|
);
|
|
3991
4281
|
}
|
|
3992
4282
|
return payload.result;
|
|
@@ -4006,13 +4296,15 @@ async function submitEoa(input) {
|
|
|
4006
4296
|
let nonce;
|
|
4007
4297
|
let fees;
|
|
4008
4298
|
try {
|
|
4009
|
-
nonce = await
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
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
|
+
]);
|
|
4016
4308
|
} catch {
|
|
4017
4309
|
throw sponsorError("Could not prepare the sponsored withdrawal.", true);
|
|
4018
4310
|
}
|
|
@@ -4055,9 +4347,10 @@ async function submitEoa(input) {
|
|
|
4055
4347
|
);
|
|
4056
4348
|
if (!final.paymasterAndData?.startsWith("0x"))
|
|
4057
4349
|
throw sponsorError("CDP did not return final paymaster data.", true);
|
|
4058
|
-
operation = { ...operation,
|
|
4350
|
+
operation = { ...operation, ...finalPaymasterFields(final) };
|
|
4059
4351
|
}
|
|
4060
4352
|
const userOpHash = operationHash(operation);
|
|
4353
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4061
4354
|
const wallet = createWalletClient2({
|
|
4062
4355
|
account: input.owner,
|
|
4063
4356
|
chain: base3,
|
|
@@ -4065,24 +4358,47 @@ async function submitEoa(input) {
|
|
|
4065
4358
|
});
|
|
4066
4359
|
const signature = await wallet.signMessage({ message: { raw: userOpHash } });
|
|
4067
4360
|
operation = { ...operation, signature };
|
|
4068
|
-
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 });
|
|
4069
4376
|
const submittedHash = await rpc(
|
|
4070
4377
|
input.rpcUrl,
|
|
4071
4378
|
"eth_sendUserOperation",
|
|
4072
4379
|
[operation, ENTRY_POINT_V06],
|
|
4073
4380
|
"send"
|
|
4074
4381
|
);
|
|
4075
|
-
if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4382
|
+
if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
4076
4383
|
throw sponsorError(
|
|
4077
4384
|
"Bundler returned a different UserOperation hash.",
|
|
4078
4385
|
false,
|
|
4079
|
-
{ userOpHash, submittedHash }
|
|
4386
|
+
{ userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
|
|
4080
4387
|
);
|
|
4081
|
-
|
|
4082
|
-
|
|
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
|
+
}
|
|
4083
4399
|
const txHash = response?.receipt?.transactionHash;
|
|
4084
|
-
if (txHash) {
|
|
4085
|
-
input.onPending
|
|
4400
|
+
if (isHash(txHash)) {
|
|
4401
|
+
savePending(input.onPending, { userOpHash, txHash });
|
|
4086
4402
|
if (response?.success === false)
|
|
4087
4403
|
throw new OwneyError(
|
|
4088
4404
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -4092,12 +4408,17 @@ async function submitEoa(input) {
|
|
|
4092
4408
|
);
|
|
4093
4409
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4094
4410
|
}
|
|
4095
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4411
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4096
4412
|
}
|
|
4097
4413
|
throw sponsorError(
|
|
4098
4414
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4099
4415
|
false,
|
|
4100
|
-
{
|
|
4416
|
+
{
|
|
4417
|
+
userOpHash,
|
|
4418
|
+
phase: "observe",
|
|
4419
|
+
failureCode: "RECEIPT_UNRESOLVED",
|
|
4420
|
+
...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
|
|
4421
|
+
}
|
|
4101
4422
|
);
|
|
4102
4423
|
}
|
|
4103
4424
|
async function submitSmart(input) {
|
|
@@ -4110,14 +4431,14 @@ async function submitSmart(input) {
|
|
|
4110
4431
|
} catch {
|
|
4111
4432
|
throw sponsorError("This account cannot check paymaster support on Base.", true);
|
|
4112
4433
|
}
|
|
4113
|
-
|
|
4434
|
+
const supportsPaymaster = capabilities?.[toHex(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
|
|
4435
|
+
if (supportsPaymaster !== true)
|
|
4114
4436
|
throw sponsorError("This account cannot use a paymaster on Base.", true);
|
|
4437
|
+
await assertOwnerSession(input.state, input.owner);
|
|
4115
4438
|
let sent;
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
|
|
4120
|
-
}
|
|
4439
|
+
const requestedId = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
4440
|
+
savePending(input.onPending, { callsId: requestedId }, true);
|
|
4441
|
+
await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
|
|
4121
4442
|
try {
|
|
4122
4443
|
sent = await input.state.provider.request({
|
|
4123
4444
|
method: "wallet_sendCalls",
|
|
@@ -4125,6 +4446,7 @@ async function submitSmart(input) {
|
|
|
4125
4446
|
version: "2.0.0",
|
|
4126
4447
|
from: input.owner,
|
|
4127
4448
|
chainId: toHex(8453),
|
|
4449
|
+
id: requestedId,
|
|
4128
4450
|
atomicRequired: false,
|
|
4129
4451
|
calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
|
|
4130
4452
|
capabilities: { paymasterService: { url: input.rpcUrl } }
|
|
@@ -4133,17 +4455,27 @@ async function submitSmart(input) {
|
|
|
4133
4455
|
} catch (error) {
|
|
4134
4456
|
if (error?.code === 4001) throw error;
|
|
4135
4457
|
if ([4200, -32601].includes(Number(error?.code)))
|
|
4136
|
-
throw sponsorError(
|
|
4137
|
-
|
|
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
|
+
);
|
|
4138
4468
|
}
|
|
4139
4469
|
const callsId = typeof sent === "string" ? sent : sent?.id;
|
|
4140
|
-
if (
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
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++) {
|
|
4147
4479
|
let status;
|
|
4148
4480
|
try {
|
|
4149
4481
|
status = await input.state.provider.request({
|
|
@@ -4151,33 +4483,40 @@ async function submitSmart(input) {
|
|
|
4151
4483
|
params: [callsId]
|
|
4152
4484
|
});
|
|
4153
4485
|
} catch {
|
|
4154
|
-
|
|
4486
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
4487
|
+
continue;
|
|
4155
4488
|
}
|
|
4156
|
-
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)
|
|
4157
4496
|
throw sponsorError(
|
|
4158
4497
|
"Sponsored withdrawal failed in the wallet.",
|
|
4159
4498
|
false,
|
|
4160
|
-
{ callsId, executionFailed: true }
|
|
4499
|
+
{ callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
|
|
4161
4500
|
);
|
|
4162
|
-
const
|
|
4501
|
+
const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
|
|
4502
|
+
const txHash = hashes.length === 1 ? hashes[0] : void 0;
|
|
4163
4503
|
if (txHash) {
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
"
|
|
4169
|
-
|
|
4170
|
-
|
|
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"
|
|
4171
4511
|
);
|
|
4172
|
-
}
|
|
4173
4512
|
return { txHash, receipt: await waitForTransaction(input.client, txHash) };
|
|
4174
4513
|
}
|
|
4175
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4514
|
+
await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
|
|
4176
4515
|
}
|
|
4177
4516
|
throw sponsorError(
|
|
4178
4517
|
"Sponsored withdrawal is still pending; check status before retrying.",
|
|
4179
4518
|
false,
|
|
4180
|
-
{ callsId }
|
|
4519
|
+
{ callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
|
|
4181
4520
|
);
|
|
4182
4521
|
}
|
|
4183
4522
|
async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
@@ -4192,7 +4531,8 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
|
4192
4531
|
agentWallet: input.agentWallet,
|
|
4193
4532
|
asset: input.asset,
|
|
4194
4533
|
callData: input.callData,
|
|
4195
|
-
action: "withdraw"
|
|
4534
|
+
action: "withdraw",
|
|
4535
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4196
4536
|
});
|
|
4197
4537
|
const args = { ...input, owner, rpcUrl };
|
|
4198
4538
|
return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
|
|
@@ -4209,7 +4549,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
|
|
|
4209
4549
|
agentWallet: input.agentWallet,
|
|
4210
4550
|
asset: input.asset,
|
|
4211
4551
|
callData: input.callData,
|
|
4212
|
-
action: "upgrade"
|
|
4552
|
+
action: "upgrade",
|
|
4553
|
+
refreshYieldseekerSignature: input.refreshYieldseekerSignature
|
|
4213
4554
|
});
|
|
4214
4555
|
if (intent.ownerPath !== "smart")
|
|
4215
4556
|
throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
|
|
@@ -4231,6 +4572,7 @@ var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
|
4231
4572
|
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
4232
4573
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4233
4574
|
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4575
|
+
var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
|
|
4234
4576
|
function generateYieldseekerUsername() {
|
|
4235
4577
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4236
4578
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4241,6 +4583,13 @@ function isUsernameConflict(error) {
|
|
|
4241
4583
|
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
4242
4584
|
}
|
|
4243
4585
|
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
4586
|
+
{
|
|
4587
|
+
type: "function",
|
|
4588
|
+
name: "feeTracker",
|
|
4589
|
+
stateMutability: "view",
|
|
4590
|
+
inputs: [],
|
|
4591
|
+
outputs: [{ name: "", type: "address" }]
|
|
4592
|
+
},
|
|
4244
4593
|
{
|
|
4245
4594
|
type: "function",
|
|
4246
4595
|
name: "withdrawAssetToUser",
|
|
@@ -4263,6 +4612,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
|
4263
4612
|
outputs: []
|
|
4264
4613
|
}
|
|
4265
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
|
+
}];
|
|
4266
4622
|
function query(params) {
|
|
4267
4623
|
const search = new URLSearchParams();
|
|
4268
4624
|
for (const [key2, value] of Object.entries(params)) {
|
|
@@ -4346,17 +4702,22 @@ var YieldseekerAgent = class {
|
|
|
4346
4702
|
getAddress4(state.walletAddress),
|
|
4347
4703
|
YIELDSEEKER_ASSET_METADATA[asset].address
|
|
4348
4704
|
);
|
|
4705
|
+
this.rememberWalletStatus(key2, status);
|
|
4706
|
+
return status;
|
|
4707
|
+
}
|
|
4708
|
+
rememberWalletStatus(key2, status) {
|
|
4349
4709
|
if (status.verified && status.version === "v2") {
|
|
4350
4710
|
this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
|
|
4351
4711
|
if (this.withdrawalVersionCache.size > 128)
|
|
4352
4712
|
this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
|
|
4353
4713
|
} else this.withdrawalVersionCache.delete(key2);
|
|
4354
|
-
return status;
|
|
4355
4714
|
}
|
|
4356
4715
|
/** Explicit owner action. This never runs as a side effect of withdrawal. */
|
|
4357
4716
|
async upgradeWallet(state, chainId, asset, gasMode = "paid") {
|
|
4717
|
+
const generation = this.readGeneration;
|
|
4358
4718
|
const before = await this.getWalletStatus(state, chainId, asset);
|
|
4359
|
-
|
|
4719
|
+
this.assertSession(generation, chainId);
|
|
4720
|
+
if (!before?.verified || before.version === "unknown") {
|
|
4360
4721
|
throw new OwneyError(
|
|
4361
4722
|
"WITHDRAW_FAILED",
|
|
4362
4723
|
"This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
|
|
@@ -4364,48 +4725,77 @@ var YieldseekerAgent = class {
|
|
|
4364
4725
|
this.id
|
|
4365
4726
|
);
|
|
4366
4727
|
}
|
|
4367
|
-
const
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
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)
|
|
4384
4744
|
throw new OwneyError(
|
|
4385
|
-
"
|
|
4386
|
-
"
|
|
4387
|
-
{
|
|
4745
|
+
"WITHDRAW_FAILED",
|
|
4746
|
+
"Yieldseeker wallet upgrade receipt could not be verified.",
|
|
4747
|
+
{ txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
|
|
4388
4748
|
this.id
|
|
4389
4749
|
);
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
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 = () => {
|
|
4393
4767
|
try {
|
|
4394
|
-
|
|
4768
|
+
storage5?.removeItem(pendingKey2);
|
|
4395
4769
|
} catch {
|
|
4396
|
-
throw new OwneyError(
|
|
4397
|
-
"SPONSOR_REQUEST_FAILED",
|
|
4398
|
-
"Upgrade status cannot be read in this browser.",
|
|
4399
|
-
{ safeToFallback: true },
|
|
4400
|
-
this.id
|
|
4401
|
-
);
|
|
4402
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);
|
|
4403
4782
|
try {
|
|
4404
4783
|
prior = JSON.parse(saved ?? "null");
|
|
4405
4784
|
} catch {
|
|
4406
4785
|
prior = { callsId: void 0 };
|
|
4407
4786
|
}
|
|
4408
|
-
|
|
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) {
|
|
4409
4799
|
if (!prior.callsId)
|
|
4410
4800
|
throw new OwneyError(
|
|
4411
4801
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4415,10 +4805,7 @@ var YieldseekerAgent = class {
|
|
|
4415
4805
|
);
|
|
4416
4806
|
let callsStatus;
|
|
4417
4807
|
try {
|
|
4418
|
-
callsStatus = await state.provider.request({
|
|
4419
|
-
method: "wallet_getCallsStatus",
|
|
4420
|
-
params: [prior.callsId]
|
|
4421
|
-
});
|
|
4808
|
+
callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
|
|
4422
4809
|
} catch {
|
|
4423
4810
|
throw new OwneyError(
|
|
4424
4811
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4427,8 +4814,15 @@ var YieldseekerAgent = class {
|
|
|
4427
4814
|
this.id
|
|
4428
4815
|
);
|
|
4429
4816
|
}
|
|
4430
|
-
if (callsStatus?.
|
|
4431
|
-
|
|
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);
|
|
4432
4826
|
throw new OwneyError(
|
|
4433
4827
|
"SPONSOR_REQUEST_FAILED",
|
|
4434
4828
|
"The earlier sponsored upgrade failed.",
|
|
@@ -4436,22 +4830,39 @@ var YieldseekerAgent = class {
|
|
|
4436
4830
|
this.id
|
|
4437
4831
|
);
|
|
4438
4832
|
}
|
|
4439
|
-
const
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
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")
|
|
4448
4860
|
throw new OwneyError(
|
|
4449
4861
|
"SPONSOR_REQUEST_FAILED",
|
|
4450
|
-
"
|
|
4451
|
-
{ safeToFallback:
|
|
4862
|
+
"EOA owners pay Base gas for the V1 upgrade.",
|
|
4863
|
+
{ safeToFallback: true },
|
|
4452
4864
|
this.id
|
|
4453
4865
|
);
|
|
4454
|
-
}
|
|
4455
4866
|
const context = await this.findAgent(state, chainId, asset);
|
|
4456
4867
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
|
|
4457
4868
|
try {
|
|
@@ -4461,14 +4872,15 @@ var YieldseekerAgent = class {
|
|
|
4461
4872
|
apiKey: this.owneyApiKey,
|
|
4462
4873
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4463
4874
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
4875
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4464
4876
|
userId: context.user.userId,
|
|
4465
4877
|
agentId: context.agent.agentId,
|
|
4466
4878
|
agentWallet: before.agentWallet,
|
|
4467
4879
|
asset: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
4468
4880
|
callData: data,
|
|
4469
4881
|
onPending: (pending) => {
|
|
4470
|
-
const current = storage5.getItem(
|
|
4471
|
-
storage5.setItem(
|
|
4882
|
+
const current = storage5.getItem(pendingKey2);
|
|
4883
|
+
storage5.setItem(pendingKey2, JSON.stringify({
|
|
4472
4884
|
startedAt: Date.now(),
|
|
4473
4885
|
...current ? JSON.parse(current) : {},
|
|
4474
4886
|
...pending
|
|
@@ -4476,8 +4888,9 @@ var YieldseekerAgent = class {
|
|
|
4476
4888
|
}
|
|
4477
4889
|
});
|
|
4478
4890
|
txHash = upgraded.txHash;
|
|
4891
|
+
receipt = upgraded.receipt;
|
|
4479
4892
|
} catch (error) {
|
|
4480
|
-
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(
|
|
4893
|
+
if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
|
|
4481
4894
|
throw error;
|
|
4482
4895
|
}
|
|
4483
4896
|
} else {
|
|
@@ -4487,20 +4900,13 @@ var YieldseekerAgent = class {
|
|
|
4487
4900
|
data,
|
|
4488
4901
|
value: "0",
|
|
4489
4902
|
chainId
|
|
4903
|
+
}, void 0, (value) => {
|
|
4904
|
+
receipt = value;
|
|
4490
4905
|
});
|
|
4491
4906
|
}
|
|
4492
|
-
const
|
|
4493
|
-
if (
|
|
4494
|
-
|
|
4495
|
-
"WITHDRAW_FAILED",
|
|
4496
|
-
"Yieldseeker wallet upgrade was not confirmed as V2.",
|
|
4497
|
-
{ txHash, version: status?.version ?? "missing" },
|
|
4498
|
-
this.id
|
|
4499
|
-
);
|
|
4500
|
-
}
|
|
4501
|
-
if (gasMode === "sponsored" && typeof window !== "undefined")
|
|
4502
|
-
window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
|
|
4503
|
-
return { txHash, status };
|
|
4907
|
+
const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
|
|
4908
|
+
if (gasMode === "sponsored") clearConfirmedUpgrade();
|
|
4909
|
+
return confirmed;
|
|
4504
4910
|
}
|
|
4505
4911
|
async disconnect() {
|
|
4506
4912
|
this.withdrawalVersionCache.clear();
|
|
@@ -4573,19 +4979,37 @@ var YieldseekerAgent = class {
|
|
|
4573
4979
|
submitted(txHash);
|
|
4574
4980
|
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
4575
4981
|
} else {
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
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
|
+
}
|
|
4589
5013
|
}
|
|
4590
5014
|
if (this.readGeneration === generation) {
|
|
4591
5015
|
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
@@ -4595,7 +5019,7 @@ var YieldseekerAgent = class {
|
|
|
4595
5019
|
notifyFunding(onProgress, evidence);
|
|
4596
5020
|
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
4597
5021
|
}
|
|
4598
|
-
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto") {
|
|
5022
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
|
|
4599
5023
|
let ownerTransferAttempted = false;
|
|
4600
5024
|
let evidence = {
|
|
4601
5025
|
agentId: this.id,
|
|
@@ -4613,10 +5037,10 @@ var YieldseekerAgent = class {
|
|
|
4613
5037
|
notifyFunding(onProgress, evidence);
|
|
4614
5038
|
}, () => {
|
|
4615
5039
|
ownerTransferAttempted = true;
|
|
4616
|
-
}, gasMode);
|
|
5040
|
+
}, gasMode, onBeforeSubmission);
|
|
4617
5041
|
} catch (error) {
|
|
4618
5042
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
|
|
4619
|
-
if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
|
|
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)))
|
|
4620
5044
|
notifyFunding(onProgress, {
|
|
4621
5045
|
...evidence,
|
|
4622
5046
|
...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
|
|
@@ -4626,7 +5050,7 @@ var YieldseekerAgent = class {
|
|
|
4626
5050
|
throw error;
|
|
4627
5051
|
}
|
|
4628
5052
|
}
|
|
4629
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
|
|
5053
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
|
|
4630
5054
|
this.assertChain(chainId);
|
|
4631
5055
|
this.assertAsset(asset);
|
|
4632
5056
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -4671,37 +5095,121 @@ var YieldseekerAgent = class {
|
|
|
4671
5095
|
let confirmedMovement = false;
|
|
4672
5096
|
let settlement = null;
|
|
4673
5097
|
try {
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
chainId,
|
|
4678
|
-
context
|
|
4679
|
-
);
|
|
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 }));
|
|
4680
5101
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
4681
|
-
const
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
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;
|
|
4688
5110
|
this.assertSession(generation, chainId, asset);
|
|
4689
|
-
|
|
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
|
+
]);
|
|
5127
|
+
this.assertSession(generation, chainId, asset);
|
|
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 };
|
|
4690
5131
|
};
|
|
4691
|
-
const
|
|
5132
|
+
const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
|
|
5133
|
+
const idle = initialFunds.idle;
|
|
4692
5134
|
let positions = portfolio.positions;
|
|
4693
5135
|
let positionsStatus = "empty";
|
|
4694
5136
|
const validatePositions = (values) => {
|
|
4695
5137
|
const seen = /* @__PURE__ */ new Set();
|
|
4696
5138
|
for (const value of values) {
|
|
4697
5139
|
const address = value?.yieldOption?.address;
|
|
4698
|
-
if (typeof address !== "string" || !
|
|
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))
|
|
4699
5141
|
throw this.invalidResponse("yield positions");
|
|
4700
5142
|
seen.add(address.toLowerCase());
|
|
4701
5143
|
}
|
|
4702
5144
|
return values;
|
|
4703
5145
|
};
|
|
4704
|
-
const
|
|
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
|
+
};
|
|
4705
5213
|
const refreshPositions = async () => {
|
|
4706
5214
|
this.assertSession(generation, chainId, asset);
|
|
4707
5215
|
const response = await this.walletRequest(
|
|
@@ -4715,21 +5223,40 @@ var YieldseekerAgent = class {
|
|
|
4715
5223
|
};
|
|
4716
5224
|
if (amount === void 0) {
|
|
4717
5225
|
validatePositions(positions);
|
|
5226
|
+
rememberPositions(positions);
|
|
4718
5227
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4719
5228
|
}
|
|
4720
5229
|
const deployed = positions.reduce(
|
|
4721
5230
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
4722
5231
|
0n
|
|
4723
5232
|
);
|
|
4724
|
-
const
|
|
5233
|
+
const grossAvailable = idle + deployed;
|
|
5234
|
+
const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
|
|
4725
5235
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4726
5236
|
if (requested === 0n) {
|
|
4727
5237
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4728
|
-
|
|
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)
|
|
4729
5256
|
throw new OwneyError(
|
|
4730
|
-
"
|
|
4731
|
-
"Yieldseeker
|
|
4732
|
-
{ asset,
|
|
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() },
|
|
4733
5260
|
this.id
|
|
4734
5261
|
);
|
|
4735
5262
|
throw new OwneyError(
|
|
@@ -4751,28 +5278,50 @@ var YieldseekerAgent = class {
|
|
|
4751
5278
|
this.id
|
|
4752
5279
|
);
|
|
4753
5280
|
}
|
|
4754
|
-
|
|
5281
|
+
const requiredIdle = requested + initialFunds.fees;
|
|
5282
|
+
let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
|
|
4755
5283
|
let grossUnwound = 0n;
|
|
5284
|
+
let latestUnwindBlock;
|
|
4756
5285
|
const unwind = async (position2, assetsRaw) => {
|
|
4757
5286
|
this.assertSession(generation, chainId, asset);
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
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() }
|
|
5296
|
+
}
|
|
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
|
+
);
|
|
5317
|
+
}
|
|
4769
5318
|
confirmedMovement = true;
|
|
4770
5319
|
this.assertSession(generation, chainId, asset);
|
|
4771
5320
|
grossUnwound += assetsRaw;
|
|
4772
5321
|
};
|
|
4773
5322
|
if (amount === void 0) {
|
|
4774
5323
|
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
4775
|
-
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.
|
|
5324
|
+
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
4776
5325
|
let unwound = false;
|
|
4777
5326
|
for (const position2 of positions) {
|
|
4778
5327
|
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
@@ -4781,15 +5330,22 @@ var YieldseekerAgent = class {
|
|
|
4781
5330
|
unwound = true;
|
|
4782
5331
|
}
|
|
4783
5332
|
if (!unwound) break;
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
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 {
|
|
4789
5344
|
positionsStatus = "unavailable";
|
|
4790
5345
|
break;
|
|
4791
5346
|
}
|
|
4792
|
-
|
|
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;
|
|
4793
5349
|
}
|
|
4794
5350
|
} else {
|
|
4795
5351
|
for (const position2 of positions) {
|
|
@@ -4807,8 +5363,8 @@ var YieldseekerAgent = class {
|
|
|
4807
5363
|
remaining: remaining.toString()
|
|
4808
5364
|
});
|
|
4809
5365
|
}
|
|
4810
|
-
const availableForOwner = confirmedMovement ? await
|
|
4811
|
-
const feeRate = portfolio.
|
|
5366
|
+
const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
|
|
5367
|
+
const feeRate = portfolio.feeRateBps;
|
|
4812
5368
|
const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
|
|
4813
5369
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
4814
5370
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
@@ -4828,7 +5384,11 @@ var YieldseekerAgent = class {
|
|
|
4828
5384
|
);
|
|
4829
5385
|
}
|
|
4830
5386
|
const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
|
|
4831
|
-
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);
|
|
4832
5392
|
const account = getAddress4(state.walletAddress);
|
|
4833
5393
|
let receipt;
|
|
4834
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" };
|
|
@@ -4841,7 +5401,6 @@ var YieldseekerAgent = class {
|
|
|
4841
5401
|
functionName: "withdrawAssetToUser",
|
|
4842
5402
|
args: [account, metadata.address, payout]
|
|
4843
5403
|
});
|
|
4844
|
-
onOwnerTransferAttempted?.();
|
|
4845
5404
|
let txHash;
|
|
4846
5405
|
if (sponsor) {
|
|
4847
5406
|
const submitted = await submitYieldseekerSponsoredWithdrawal({
|
|
@@ -4850,16 +5409,22 @@ var YieldseekerAgent = class {
|
|
|
4850
5409
|
apiKey: this.owneyApiKey,
|
|
4851
5410
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4852
5411
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5412
|
+
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
4853
5413
|
userId: context.user.userId,
|
|
4854
5414
|
agentId: context.agent.agentId,
|
|
4855
5415
|
agentWallet: getAddress4(context.wallet.walletAddress),
|
|
4856
5416
|
asset: metadata.address,
|
|
4857
5417
|
callData: data,
|
|
4858
|
-
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
|
+
}
|
|
4859
5423
|
});
|
|
4860
5424
|
txHash = submitted.txHash;
|
|
4861
5425
|
receipt = submitted.receipt;
|
|
4862
5426
|
} else {
|
|
5427
|
+
onOwnerTransferAttempted?.();
|
|
4863
5428
|
txHash = await this.submitTransaction(state, chainId, {
|
|
4864
5429
|
from: account,
|
|
4865
5430
|
to: getAddress4(context.wallet.walletAddress),
|
|
@@ -4874,14 +5439,26 @@ var YieldseekerAgent = class {
|
|
|
4874
5439
|
settlement = plannedMovement;
|
|
4875
5440
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4876
5441
|
notifyFunding(onProgress, evidence);
|
|
5442
|
+
let completion;
|
|
4877
5443
|
if (amount === void 0) {
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
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") {
|
|
4885
5462
|
const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
4886
5463
|
throw new OwneyError(
|
|
4887
5464
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
@@ -4890,7 +5467,7 @@ var YieldseekerAgent = class {
|
|
|
4890
5467
|
reason: "YIELDSEEKER_MAX_INCOMPLETE",
|
|
4891
5468
|
asset,
|
|
4892
5469
|
positionsStatus,
|
|
4893
|
-
remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
|
|
5470
|
+
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
4894
5471
|
vaultAddress: position2.yieldOption.address,
|
|
4895
5472
|
sharesRaw: position2.assetsRaw,
|
|
4896
5473
|
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
@@ -4900,6 +5477,7 @@ var YieldseekerAgent = class {
|
|
|
4900
5477
|
this.id
|
|
4901
5478
|
);
|
|
4902
5479
|
}
|
|
5480
|
+
if (!proof.verified) completion = "unverified";
|
|
4903
5481
|
}
|
|
4904
5482
|
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4905
5483
|
throw new OwneyError(
|
|
@@ -4908,7 +5486,13 @@ var YieldseekerAgent = class {
|
|
|
4908
5486
|
{ asset, available: "0", txHash },
|
|
4909
5487
|
this.id
|
|
4910
5488
|
);
|
|
4911
|
-
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
|
+
};
|
|
4912
5496
|
} finally {
|
|
4913
5497
|
if (confirmedMovement && this.readGeneration === generation) {
|
|
4914
5498
|
void this.refreshSnapshotAfterMovement(
|
|
@@ -4955,6 +5539,23 @@ var YieldseekerAgent = class {
|
|
|
4955
5539
|
if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
|
|
4956
5540
|
return context.wallet.walletAddress;
|
|
4957
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
|
+
}
|
|
4958
5559
|
async getEarnings(state, chainId) {
|
|
4959
5560
|
this.assertChain(chainId);
|
|
4960
5561
|
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
@@ -4985,7 +5586,7 @@ var YieldseekerAgent = class {
|
|
|
4985
5586
|
);
|
|
4986
5587
|
const vaultAddresses = new Set(
|
|
4987
5588
|
catalog.flat().filter(
|
|
4988
|
-
(yieldOption) => yieldOption.chainId === chainId &&
|
|
5589
|
+
(yieldOption) => yieldOption.chainId === chainId && isAddress3(yieldOption.address)
|
|
4989
5590
|
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
4990
5591
|
);
|
|
4991
5592
|
return mapYieldseekerHistory(contexts, {
|
|
@@ -5112,7 +5713,7 @@ var YieldseekerAgent = class {
|
|
|
5112
5713
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
5113
5714
|
).then((walletResponse) => {
|
|
5114
5715
|
this.assertSession(generation, chainId, asset);
|
|
5115
|
-
if (!walletResponse?.agentWallet || !
|
|
5716
|
+
if (!walletResponse?.agentWallet || !isAddress3(walletResponse.agentWallet.walletAddress)) {
|
|
5116
5717
|
throw this.invalidResponse("agent wallet");
|
|
5117
5718
|
}
|
|
5118
5719
|
const context = {
|
|
@@ -5525,7 +6126,7 @@ var YieldseekerAgent = class {
|
|
|
5525
6126
|
this.agentPath(context, "deploy"),
|
|
5526
6127
|
{ method: "POST", body: {} }
|
|
5527
6128
|
);
|
|
5528
|
-
if (!deployed?.agentWallet || !
|
|
6129
|
+
if (!deployed?.agentWallet || !isAddress3(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
5529
6130
|
throw this.invalidResponse("agent deployment", {
|
|
5530
6131
|
reason: "Deploy did not return the expected Agent Wallet."
|
|
5531
6132
|
});
|
|
@@ -5656,25 +6257,79 @@ var YieldseekerAgent = class {
|
|
|
5656
6257
|
value: BigInt(transaction.value)
|
|
5657
6258
|
});
|
|
5658
6259
|
onSubmitted?.(hash);
|
|
6260
|
+
let replacement;
|
|
5659
6261
|
const receipt = await withPaidRpcDiagnostics(
|
|
5660
6262
|
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
5661
6263
|
hash,
|
|
5662
|
-
confirmations: 1
|
|
6264
|
+
confirmations: 1,
|
|
6265
|
+
onReplaced: (value) => {
|
|
6266
|
+
replacement = value;
|
|
6267
|
+
}
|
|
5663
6268
|
}),
|
|
5664
6269
|
8453,
|
|
5665
6270
|
"eth_getTransactionReceipt",
|
|
5666
6271
|
this.id
|
|
5667
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
|
+
}
|
|
5668
6323
|
if (receipt.status !== "success") {
|
|
5669
6324
|
throw new OwneyError(
|
|
5670
6325
|
"AGENT_TRANSACTION_REVERTED",
|
|
5671
|
-
`Yieldseeker transaction reverted (${
|
|
5672
|
-
{ transactionHash:
|
|
6326
|
+
`Yieldseeker transaction reverted (${actualHash}).`,
|
|
6327
|
+
{ transactionHash: actualHash },
|
|
5673
6328
|
this.id
|
|
5674
6329
|
);
|
|
5675
6330
|
}
|
|
5676
6331
|
onReceipt?.(receipt);
|
|
5677
|
-
return
|
|
6332
|
+
return actualHash;
|
|
5678
6333
|
}
|
|
5679
6334
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
5680
6335
|
if (this.unwindReceiptWaiter) {
|
|
@@ -5701,12 +6356,12 @@ var YieldseekerAgent = class {
|
|
|
5701
6356
|
return receipt;
|
|
5702
6357
|
}
|
|
5703
6358
|
assertTransaction(transaction, state, chainId) {
|
|
5704
|
-
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)) {
|
|
5705
6360
|
throw this.invalidResponse("transaction");
|
|
5706
6361
|
}
|
|
5707
6362
|
}
|
|
5708
6363
|
assertAgent(agent) {
|
|
5709
|
-
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) {
|
|
5710
6365
|
throw this.invalidResponse("agent");
|
|
5711
6366
|
}
|
|
5712
6367
|
}
|
|
@@ -6154,6 +6809,18 @@ var planOf = (transfers) => JSON.stringify(
|
|
|
6154
6809
|
amount: BigInt(t.amount).toString()
|
|
6155
6810
|
}))
|
|
6156
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
|
+
}
|
|
6157
6824
|
function read(key2) {
|
|
6158
6825
|
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
6159
6826
|
}
|
|
@@ -6170,11 +6837,11 @@ function clear(key2) {
|
|
|
6170
6837
|
else window.localStorage.removeItem(key2);
|
|
6171
6838
|
}
|
|
6172
6839
|
function sponsorTokenBatch(i) {
|
|
6173
|
-
const key2 =
|
|
6840
|
+
const key2 = pendingKey(i);
|
|
6174
6841
|
const plan = planOf(i.transfers);
|
|
6175
6842
|
const active = inflight.get(key2);
|
|
6176
6843
|
if (active) {
|
|
6177
|
-
if (active.plan !== plan)
|
|
6844
|
+
if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
6178
6845
|
return Promise.reject(
|
|
6179
6846
|
new Error(
|
|
6180
6847
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
@@ -6249,6 +6916,8 @@ async function execute(i, key2, plan) {
|
|
|
6249
6916
|
}
|
|
6250
6917
|
};
|
|
6251
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.");
|
|
6252
6921
|
if (saved) {
|
|
6253
6922
|
const previous = JSON.parse(saved);
|
|
6254
6923
|
if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
@@ -6360,7 +7029,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
6360
7029
|
pub,
|
|
6361
7030
|
wallet,
|
|
6362
7031
|
onApproved: deps.onApproved,
|
|
6363
|
-
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
7032
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
|
|
7033
|
+
resumeTxHash: deps.resumeTxHash
|
|
6364
7034
|
});
|
|
6365
7035
|
};
|
|
6366
7036
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -6446,7 +7116,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
6446
7116
|
}
|
|
6447
7117
|
|
|
6448
7118
|
// src/lib/sponsored-calls-deposit.ts
|
|
6449
|
-
import { encodeFunctionData as encodeFunctionData2, erc20Abi as
|
|
7119
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi4, toHex as toHex2 } from "viem";
|
|
6450
7120
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
6451
7121
|
var DEFAULT_MAX_POLLS = 30;
|
|
6452
7122
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -6492,7 +7162,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6492
7162
|
to: token,
|
|
6493
7163
|
value: "0x0",
|
|
6494
7164
|
data: encodeFunctionData2({
|
|
6495
|
-
abi:
|
|
7165
|
+
abi: erc20Abi4,
|
|
6496
7166
|
functionName: "transfer",
|
|
6497
7167
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
6498
7168
|
})
|
|
@@ -6524,50 +7194,73 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
6524
7194
|
url.searchParams.append("owneyIntent", intent);
|
|
6525
7195
|
paymasterUrl = url.toString();
|
|
6526
7196
|
}
|
|
6527
|
-
const
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
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
|
+
}
|
|
6538
7220
|
}
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
})
|
|
6542
|
-
|
|
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
|
+
}
|
|
6543
7232
|
const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
|
|
6544
7233
|
if (!callsId) {
|
|
6545
7234
|
throw new OwneyError(
|
|
6546
7235
|
"SPONSORED_CALLS_NO_ID",
|
|
6547
7236
|
"Wallet accepted wallet_sendCalls but returned no calls id.",
|
|
6548
|
-
{ chainId }
|
|
7237
|
+
{ chainId, callsId: requestedId }
|
|
6549
7238
|
);
|
|
6550
7239
|
}
|
|
7240
|
+
progress(callsId, "pending");
|
|
6551
7241
|
for (let attempt = 0; attempt < maxPolls; attempt += 1) {
|
|
6552
7242
|
const status = await deps.provider.request({
|
|
6553
7243
|
method: "wallet_getCallsStatus",
|
|
6554
7244
|
params: [callsId]
|
|
6555
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 });
|
|
6556
7248
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
6557
|
-
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)) {
|
|
6558
7250
|
try {
|
|
6559
7251
|
deps.onSubmitted?.(txHash, transfers);
|
|
6560
7252
|
} catch {
|
|
6561
7253
|
}
|
|
6562
7254
|
}
|
|
6563
|
-
if (status?.
|
|
7255
|
+
if (status?.status === 400 || status?.status === 500) {
|
|
7256
|
+
progress(callsId, "failed");
|
|
6564
7257
|
throw new OwneyError(
|
|
6565
7258
|
"SPONSOR_REQUEST_FAILED",
|
|
6566
7259
|
"The sponsored deposit did not complete successfully.",
|
|
6567
7260
|
{ chainId, callsId, safeToFallback: false }
|
|
6568
7261
|
);
|
|
6569
7262
|
}
|
|
6570
|
-
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")) {
|
|
6571
7264
|
if (status?.receipts?.some(
|
|
6572
7265
|
(receipt) => receipt.transactionHash !== txHash
|
|
6573
7266
|
))
|
|
@@ -6783,8 +7476,8 @@ var OwneySDK = class {
|
|
|
6783
7476
|
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
6784
7477
|
}
|
|
6785
7478
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
6786
|
-
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
6787
|
-
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
7479
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7480
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
|
|
6788
7481
|
return this.cachedSponsoredCallback;
|
|
6789
7482
|
const provider = this.requireConnectedProvider();
|
|
6790
7483
|
const owner = this.state.walletAddress;
|
|
@@ -6794,6 +7487,7 @@ var OwneySDK = class {
|
|
|
6794
7487
|
ownerAddress: owner,
|
|
6795
7488
|
onApproved,
|
|
6796
7489
|
onSubmitted,
|
|
7490
|
+
resumeTxHash,
|
|
6797
7491
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
6798
7492
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6799
7493
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -6809,13 +7503,13 @@ var OwneySDK = class {
|
|
|
6809
7503
|
transport: custom4(provider)
|
|
6810
7504
|
})
|
|
6811
7505
|
});
|
|
6812
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
7506
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
|
|
6813
7507
|
return callback;
|
|
6814
7508
|
}
|
|
6815
7509
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
6816
|
-
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
7510
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
|
|
6817
7511
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
6818
|
-
if (!onApproved && !onSubmitted && cached) return cached;
|
|
7512
|
+
if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
|
|
6819
7513
|
const provider = this.requireConnectedProvider();
|
|
6820
7514
|
const callback = makeSponsoredCallsCallback({
|
|
6821
7515
|
apiKey: this.apiKey,
|
|
@@ -6825,9 +7519,10 @@ var OwneySDK = class {
|
|
|
6825
7519
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
6826
7520
|
onApproved,
|
|
6827
7521
|
onSubmitted,
|
|
7522
|
+
onCallsProgress,
|
|
6828
7523
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
6829
7524
|
});
|
|
6830
|
-
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
7525
|
+
if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
6831
7526
|
return callback;
|
|
6832
7527
|
}
|
|
6833
7528
|
/**
|
|
@@ -6836,8 +7531,8 @@ var OwneySDK = class {
|
|
|
6836
7531
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
6837
7532
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
6838
7533
|
*/
|
|
6839
|
-
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6840
|
-
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
7534
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
|
|
7535
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
|
|
6841
7536
|
return this.cachedWethSponsoredCallback;
|
|
6842
7537
|
const provider = this.requireConnectedProvider();
|
|
6843
7538
|
const owner = this.state.walletAddress;
|
|
@@ -6847,6 +7542,7 @@ var OwneySDK = class {
|
|
|
6847
7542
|
ownerAddress: owner,
|
|
6848
7543
|
onApproved,
|
|
6849
7544
|
onSubmitted,
|
|
7545
|
+
resumeTxHash,
|
|
6850
7546
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
6851
7547
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
6852
7548
|
// PublicClient/WalletClient param types — structurally identical at
|
|
@@ -6862,7 +7558,7 @@ var OwneySDK = class {
|
|
|
6862
7558
|
transport: custom4(provider)
|
|
6863
7559
|
})
|
|
6864
7560
|
});
|
|
6865
|
-
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
7561
|
+
if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
|
|
6866
7562
|
return callback;
|
|
6867
7563
|
}
|
|
6868
7564
|
getAgent(agentId) {
|
|
@@ -7171,6 +7867,7 @@ var OwneySDK = class {
|
|
|
7171
7867
|
async deposit(options) {
|
|
7172
7868
|
const { amount, asset, depositCallback, agentId, onApproved } = options;
|
|
7173
7869
|
const prepared = /* @__PURE__ */ new Map();
|
|
7870
|
+
const paidFallbackAgents = /* @__PURE__ */ new Set();
|
|
7174
7871
|
const onProgress = options.onProgress ? (evidence) => {
|
|
7175
7872
|
if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
|
|
7176
7873
|
notifyFunding(options.onProgress, evidence);
|
|
@@ -7181,152 +7878,184 @@ var OwneySDK = class {
|
|
|
7181
7878
|
if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
|
|
7182
7879
|
}
|
|
7183
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;
|
|
7184
7887
|
if (BigInt(amount) === 0n) {
|
|
7185
7888
|
return { agentResults: {} };
|
|
7186
7889
|
}
|
|
7187
7890
|
const state = this.requireState();
|
|
7188
7891
|
const chainId = this.requireChainId();
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
7201
|
-
return this.depositWithFallback(
|
|
7202
|
-
agent,
|
|
7203
|
-
state,
|
|
7204
|
-
chainId,
|
|
7205
|
-
amount,
|
|
7206
|
-
asset,
|
|
7207
|
-
effectiveCallback,
|
|
7208
|
-
depositCallback,
|
|
7209
|
-
onProgress
|
|
7210
|
-
);
|
|
7211
|
-
}
|
|
7212
|
-
const totalAmount = BigInt(amount);
|
|
7213
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset, {
|
|
7214
|
-
excludeDisabled: true
|
|
7215
|
-
});
|
|
7216
|
-
const exemptFlags = await Promise.all(
|
|
7217
|
-
eligibleAgents.map(
|
|
7218
|
-
(a) => this.hasExistingBalance(a, state, chainId, asset, true)
|
|
7219
|
-
)
|
|
7220
|
-
);
|
|
7221
|
-
const exempt = new Set(
|
|
7222
|
-
eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
|
|
7223
|
-
);
|
|
7224
|
-
const agentAmounts = this.splitDepositAmount(
|
|
7225
|
-
totalAmount,
|
|
7226
|
-
eligibleAgents,
|
|
7227
|
-
chainId,
|
|
7228
|
-
asset,
|
|
7229
|
-
exempt
|
|
7230
|
-
);
|
|
7231
|
-
if (agentAmounts.length === 0) {
|
|
7232
|
-
const agentsRequiringActivation = eligibleAgents.filter(
|
|
7233
|
-
(agent) => !exempt.has(agent.id)
|
|
7234
|
-
);
|
|
7235
|
-
const perAgentMinimum = agentsRequiringActivation.reduce(
|
|
7236
|
-
(highest, agent) => {
|
|
7237
|
-
const minimum = this.getMinDepositAmount(agent, chainId, asset);
|
|
7238
|
-
return minimum > highest ? minimum : highest;
|
|
7239
|
-
},
|
|
7240
|
-
0n
|
|
7241
|
-
);
|
|
7242
|
-
const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
|
|
7243
|
-
throw new OwneyError(
|
|
7244
|
-
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
7245
|
-
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7246
|
-
{
|
|
7247
|
-
amount,
|
|
7248
|
-
asset,
|
|
7249
|
-
chainId,
|
|
7250
|
-
minDepositAmount: minimumRequired.toString(),
|
|
7251
|
-
perAgentDepositAmount: perAgentMinimum.toString(),
|
|
7252
|
-
agentMinimums: agentsRequiringActivation.map((agent) => ({
|
|
7253
|
-
agentId: agent.id,
|
|
7254
|
-
minDepositAmount: this.getMinDepositAmount(
|
|
7255
|
-
agent,
|
|
7256
|
-
chainId,
|
|
7257
|
-
asset
|
|
7258
|
-
).toString()
|
|
7259
|
-
}))
|
|
7260
|
-
}
|
|
7261
|
-
);
|
|
7262
|
-
}
|
|
7263
|
-
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
7264
|
-
if (!depositCallback && batchTransfer) {
|
|
7265
|
-
return runAgentDepositBatch(
|
|
7266
|
-
chainId,
|
|
7267
|
-
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
7268
|
-
id: agent.id,
|
|
7269
|
-
amount: amount2,
|
|
7270
|
-
run: (callback) => withFailureReporting(
|
|
7271
|
-
this.apiKey,
|
|
7272
|
-
agent.id,
|
|
7273
|
-
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
7274
|
-
this.routingApiBaseUrl
|
|
7275
|
-
)
|
|
7276
|
-
})),
|
|
7277
|
-
async (cid, transfers) => {
|
|
7278
|
-
try {
|
|
7279
|
-
return await batchTransfer(cid, transfers);
|
|
7280
|
-
} catch (error) {
|
|
7281
|
-
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
7282
|
-
throw error;
|
|
7283
|
-
const requiredAmount = transfers.reduce(
|
|
7284
|
-
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
7285
|
-
0n
|
|
7286
|
-
);
|
|
7287
|
-
await this.approvePermit2(
|
|
7288
|
-
asset,
|
|
7289
|
-
requiredAmount,
|
|
7290
|
-
cid
|
|
7291
|
-
);
|
|
7292
|
-
return batchTransfer(cid, transfers);
|
|
7293
|
-
}
|
|
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
|
+
);
|
|
7294
7903
|
}
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
index,
|
|
7300
|
-
{ agent, amount: agentAmount }
|
|
7301
|
-
] of agentAmounts.entries()) {
|
|
7302
|
-
try {
|
|
7303
|
-
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(
|
|
7304
7908
|
agent,
|
|
7305
7909
|
state,
|
|
7306
7910
|
chainId,
|
|
7307
|
-
|
|
7911
|
+
amount,
|
|
7308
7912
|
asset,
|
|
7309
7913
|
effectiveCallback,
|
|
7310
7914
|
depositCallback,
|
|
7311
|
-
onProgress
|
|
7915
|
+
onProgress,
|
|
7916
|
+
() => paidFallbackAgents.add(agent.id),
|
|
7917
|
+
!!options.resumeTxHash
|
|
7312
7918
|
);
|
|
7313
|
-
}
|
|
7314
|
-
|
|
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);
|
|
7315
7949
|
throw new OwneyError(
|
|
7316
|
-
"
|
|
7317
|
-
"
|
|
7950
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
7951
|
+
`Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
|
|
7318
7952
|
{
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
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
|
+
}
|
|
7326
7967
|
);
|
|
7327
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;
|
|
7328
8058
|
}
|
|
7329
|
-
return { agentResults };
|
|
7330
8059
|
}
|
|
7331
8060
|
/**
|
|
7332
8061
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
@@ -7355,7 +8084,7 @@ var OwneySDK = class {
|
|
|
7355
8084
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
7356
8085
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
7357
8086
|
*/
|
|
7358
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
8087
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
|
|
7359
8088
|
const attempt = (callback) => withFailureReporting(
|
|
7360
8089
|
this.apiKey,
|
|
7361
8090
|
agent.id,
|
|
@@ -7367,6 +8096,7 @@ var OwneySDK = class {
|
|
|
7367
8096
|
try {
|
|
7368
8097
|
return await attempt(effectiveCallback);
|
|
7369
8098
|
} catch (error) {
|
|
8099
|
+
if (resumeOnly) throw error;
|
|
7370
8100
|
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
7371
8101
|
approvalAttempted = true;
|
|
7372
8102
|
console.warn(
|
|
@@ -7384,6 +8114,7 @@ var OwneySDK = class {
|
|
|
7384
8114
|
`[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
|
|
7385
8115
|
error
|
|
7386
8116
|
);
|
|
8117
|
+
onPaidFallback?.();
|
|
7387
8118
|
return attempt(void 0);
|
|
7388
8119
|
}
|
|
7389
8120
|
}
|
|
@@ -7466,7 +8197,12 @@ var OwneySDK = class {
|
|
|
7466
8197
|
*/
|
|
7467
8198
|
async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
|
|
7468
8199
|
try {
|
|
7469
|
-
|
|
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);
|
|
7470
8206
|
const target = asset.toLowerCase();
|
|
7471
8207
|
const token = balance.tokens.find(
|
|
7472
8208
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
@@ -7592,7 +8328,8 @@ var OwneySDK = class {
|
|
|
7592
8328
|
return verified;
|
|
7593
8329
|
}
|
|
7594
8330
|
/** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
|
|
7595
|
-
async resolveSponsoredWithdrawalEvidence(evidence) {
|
|
8331
|
+
async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
|
|
8332
|
+
if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
|
|
7596
8333
|
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
7597
8334
|
if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
|
|
7598
8335
|
const agent = this.getAgent("yieldseeker");
|
|
@@ -7608,18 +8345,26 @@ var OwneySDK = class {
|
|
|
7608
8345
|
owner: state.walletAddress,
|
|
7609
8346
|
agentWallet: wallet,
|
|
7610
8347
|
asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
|
|
7611
|
-
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 } : {}
|
|
7612
8362
|
})
|
|
7613
8363
|
});
|
|
7614
8364
|
if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
|
|
7615
8365
|
const payload = await response.json();
|
|
7616
8366
|
txHash = payload.data?.txHash ?? void 0;
|
|
7617
8367
|
executionFailed = payload.data?.executionFailed === true;
|
|
7618
|
-
} else if (evidence.callsId) {
|
|
7619
|
-
const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
|
|
7620
|
-
if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
|
|
7621
|
-
executionFailed = status?.status === 400 || status?.status === 500;
|
|
7622
|
-
txHash = status?.receipts?.[0]?.transactionHash;
|
|
7623
8368
|
}
|
|
7624
8369
|
assertCurrent();
|
|
7625
8370
|
if (executionFailed) return {
|
|
@@ -7630,6 +8375,60 @@ var OwneySDK = class {
|
|
|
7630
8375
|
};
|
|
7631
8376
|
return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
|
|
7632
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
|
+
}
|
|
7633
8432
|
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
7634
8433
|
endFundingBalanceRefresh(evidence, read2) {
|
|
7635
8434
|
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
@@ -7730,6 +8529,30 @@ var OwneySDK = class {
|
|
|
7730
8529
|
}
|
|
7731
8530
|
};
|
|
7732
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
|
+
};
|
|
7733
8556
|
const submissionEvidence = /* @__PURE__ */ new Map();
|
|
7734
8557
|
const submit = async (agent, requested2) => {
|
|
7735
8558
|
attempted.add(agent.id);
|
|
@@ -7750,9 +8573,19 @@ var OwneySDK = class {
|
|
|
7750
8573
|
};
|
|
7751
8574
|
progress(evidence);
|
|
7752
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
|
+
);
|
|
7753
8586
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
7754
8587
|
} catch (error) {
|
|
7755
|
-
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 : "")))
|
|
7756
8589
|
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
7757
8590
|
throw error;
|
|
7758
8591
|
}
|
|
@@ -7802,6 +8635,7 @@ var OwneySDK = class {
|
|
|
7802
8635
|
}
|
|
7803
8636
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
7804
8637
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
8638
|
+
recordAgentFailure(agent.id, err);
|
|
7805
8639
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7806
8640
|
void reportAgentFailure(
|
|
7807
8641
|
this.apiKey,
|
|
@@ -7812,17 +8646,23 @@ var OwneySDK = class {
|
|
|
7812
8646
|
}
|
|
7813
8647
|
}
|
|
7814
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
|
+
);
|
|
7815
8655
|
throw new OwneyError(
|
|
7816
8656
|
"WITHDRAW_ALL_FAILED",
|
|
7817
8657
|
`Failed to withdraw "${asset}" from all eligible agents.`,
|
|
7818
|
-
{ asset, agentErrors: agentErrors2 }
|
|
8658
|
+
{ asset, agentErrors: agentErrors2, agentFailures }
|
|
7819
8659
|
);
|
|
7820
8660
|
}
|
|
7821
8661
|
if (Object.keys(agentErrors2).length > 0) {
|
|
7822
8662
|
throw new OwneyError(
|
|
7823
8663
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7824
8664
|
"Some agents could not complete the withdrawal.",
|
|
7825
|
-
{ asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
8665
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
7826
8666
|
);
|
|
7827
8667
|
}
|
|
7828
8668
|
const amountsKnown = Object.values(results2).every(
|
|
@@ -7903,6 +8743,7 @@ var OwneySDK = class {
|
|
|
7903
8743
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7904
8744
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
7905
8745
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
8746
|
+
recordAgentFailure(p.agent.id, err);
|
|
7906
8747
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
7907
8748
|
void reportAgentFailure(
|
|
7908
8749
|
this.apiKey,
|
|
@@ -7923,7 +8764,8 @@ var OwneySDK = class {
|
|
|
7923
8764
|
{
|
|
7924
8765
|
asset,
|
|
7925
8766
|
amount,
|
|
7926
|
-
agentErrors
|
|
8767
|
+
agentErrors,
|
|
8768
|
+
agentFailures
|
|
7927
8769
|
}
|
|
7928
8770
|
);
|
|
7929
8771
|
}
|
|
@@ -7938,6 +8780,7 @@ var OwneySDK = class {
|
|
|
7938
8780
|
withdrawn: withdrawn.toString(),
|
|
7939
8781
|
partialResults: results,
|
|
7940
8782
|
agentErrors,
|
|
8783
|
+
agentFailures,
|
|
7941
8784
|
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
7942
8785
|
}
|
|
7943
8786
|
);
|