@owney/sdk 0.7.25-beta.13 → 0.7.25-beta.14
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 +236 -38
- package/dist/index.js +236 -38
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
34
34
|
|
|
35
35
|
// src/lib/funding-evidence.ts
|
|
36
36
|
var import_viem = require("viem");
|
|
37
|
+
var import_account_abstraction = require("viem/account-abstraction");
|
|
37
38
|
function notifyFunding(callback, evidence) {
|
|
38
39
|
try {
|
|
39
40
|
void Promise.resolve(callback?.({ ...evidence })).catch(() => {
|
|
@@ -43,11 +44,53 @@ function notifyFunding(callback, evidence) {
|
|
|
43
44
|
}
|
|
44
45
|
var fullWithdrawalAbi = (0, import_viem.parseAbi)(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
45
46
|
var withdrawalEventAbi = (0, import_viem.parseAbi)(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
var accountExecutionAbi = (0, import_viem.parseAbi)([
|
|
48
|
+
"function execute(address target,uint256 value,bytes data)",
|
|
49
|
+
"function executeBatch((address target,uint256 value,bytes data)[] calls)"
|
|
50
|
+
]);
|
|
51
|
+
var entryPoint06 = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
|
|
52
|
+
var entryPoint07 = "0x0000000071727de22e5e9d8baf0edac6f37da032";
|
|
53
|
+
function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receipt) {
|
|
54
|
+
if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
|
|
55
|
+
const matchesFullCall = (data) => {
|
|
56
|
+
try {
|
|
57
|
+
const decoded = (0, import_viem.decodeFunctionData)({ abi: fullWithdrawalAbi, data });
|
|
58
|
+
return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (transaction.to?.toLowerCase() === evidence.agentWallet.toLowerCase() && transaction.from.toLowerCase() === evidence.walletAddress.toLowerCase()) return matchesFullCall(transaction.input);
|
|
48
64
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
65
|
+
const address = transaction.to?.toLowerCase();
|
|
66
|
+
if (address !== entryPoint06 && address !== entryPoint07) return false;
|
|
67
|
+
const version = address === entryPoint06 ? "0.6" : "0.7";
|
|
68
|
+
const decoded = (0, import_viem.decodeFunctionData)({ abi: version === "0.6" ? import_account_abstraction.entryPoint06Abi : import_account_abstraction.entryPoint07Abi, data: transaction.input });
|
|
69
|
+
if (decoded.functionName !== "handleOps") return false;
|
|
70
|
+
const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
|
|
71
|
+
if (operations.length !== 1) return false;
|
|
72
|
+
const operation = operations[0];
|
|
73
|
+
const userOperation = (0, import_account_abstraction.toUserOperation)(operation);
|
|
74
|
+
const hash = (0, import_account_abstraction.getUserOperationHash)({
|
|
75
|
+
chainId: evidence.chainId,
|
|
76
|
+
entryPointAddress: transaction.to,
|
|
77
|
+
entryPointVersion: version,
|
|
78
|
+
userOperation
|
|
79
|
+
});
|
|
80
|
+
if (evidence.userOpHash && hash.toLowerCase() !== evidence.userOpHash.toLowerCase()) return false;
|
|
81
|
+
if (!receipt?.logs.some((log) => {
|
|
82
|
+
if (log.address.toLowerCase() !== address) return false;
|
|
83
|
+
try {
|
|
84
|
+
const event = (0, import_viem.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
|
|
85
|
+
return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
})) return false;
|
|
90
|
+
if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
|
|
91
|
+
const execution = (0, import_viem.decodeFunctionData)({ abi: accountExecutionAbi, data: operation.callData });
|
|
92
|
+
const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
|
|
93
|
+
return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
|
|
51
94
|
} catch {
|
|
52
95
|
return false;
|
|
53
96
|
}
|
|
@@ -3550,6 +3593,49 @@ function actionEntry(action) {
|
|
|
3550
3593
|
rebalanceLog: []
|
|
3551
3594
|
};
|
|
3552
3595
|
}
|
|
3596
|
+
function allocationEntry(action, context) {
|
|
3597
|
+
if (action.actionType.toLowerCase() !== "autoseek") return void 0;
|
|
3598
|
+
const hash = action.details?.depositTransactionHash;
|
|
3599
|
+
const deposits = action.details?.deposits;
|
|
3600
|
+
if (typeof hash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash) || !Array.isArray(deposits) || deposits.length === 0) return void 0;
|
|
3601
|
+
const positions = [];
|
|
3602
|
+
let amountRaw = 0n;
|
|
3603
|
+
for (const deposit of deposits) {
|
|
3604
|
+
if (!deposit || typeof deposit !== "object") return void 0;
|
|
3605
|
+
const leg = deposit;
|
|
3606
|
+
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
3607
|
+
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
3608
|
+
if (typeof leg.vaultAddress !== "string" || !(0, import_viem7.isAddress)(leg.vaultAddress)) return void 0;
|
|
3609
|
+
const rawAmount = typeof leg.rawAmount === "string" && /^\d+$/.test(leg.rawAmount) ? BigInt(leg.rawAmount) : typeof leg.rawAmount === "number" && Number.isSafeInteger(leg.rawAmount) ? BigInt(leg.rawAmount) : void 0;
|
|
3610
|
+
if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
|
|
3611
|
+
const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
|
|
3612
|
+
positions.push({
|
|
3613
|
+
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
3614
|
+
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
3615
|
+
tokenSymbol: context.asset,
|
|
3616
|
+
amount: (0, import_viem7.formatUnits)(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3617
|
+
});
|
|
3618
|
+
amountRaw += rawAmount;
|
|
3619
|
+
}
|
|
3620
|
+
return {
|
|
3621
|
+
agent: "yieldseeker",
|
|
3622
|
+
action: "Deposit",
|
|
3623
|
+
date: action.createdDate,
|
|
3624
|
+
oldApy: null,
|
|
3625
|
+
newApy: null,
|
|
3626
|
+
positions,
|
|
3627
|
+
rebalanceLog: [],
|
|
3628
|
+
transactions: [{
|
|
3629
|
+
txHashes: [hash],
|
|
3630
|
+
chainId: context.agent.chainId,
|
|
3631
|
+
tokenAddress: context.agent.assetAddress,
|
|
3632
|
+
tokenSymbol: context.asset,
|
|
3633
|
+
agentWallet: context.wallet.walletAddress,
|
|
3634
|
+
amountRaw: String(amountRaw),
|
|
3635
|
+
amount: (0, import_viem7.formatUnits)(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3636
|
+
}]
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3553
3639
|
function depositDestination(context, movement) {
|
|
3554
3640
|
const to = movement.toAddress.toLowerCase();
|
|
3555
3641
|
const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
|
|
@@ -3617,20 +3703,30 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3617
3703
|
seenMovements.add(key2);
|
|
3618
3704
|
return true;
|
|
3619
3705
|
});
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3706
|
+
const movementEntries = movements.map(
|
|
3707
|
+
(movement) => movementEntry(
|
|
3708
|
+
movement,
|
|
3709
|
+
context.wallet,
|
|
3710
|
+
context.agent,
|
|
3711
|
+
context.asset,
|
|
3712
|
+
options.ownerAddress,
|
|
3713
|
+
options.vaultAddresses,
|
|
3714
|
+
depositDestination(context, movement)
|
|
3715
|
+
)
|
|
3716
|
+
).filter((entry) => entry !== void 0);
|
|
3717
|
+
const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
|
|
3718
|
+
const actions = (context.actions ?? []).flatMap((action) => {
|
|
3719
|
+
const allocation = allocationEntry(action, context);
|
|
3720
|
+
if (allocation) {
|
|
3721
|
+
const hash = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
3722
|
+
if (deposits.has(hash)) return [];
|
|
3723
|
+
deposits.add(hash);
|
|
3724
|
+
return [allocation];
|
|
3725
|
+
}
|
|
3726
|
+
const entry = actionEntry(action);
|
|
3727
|
+
return entry ? [entry] : [];
|
|
3728
|
+
});
|
|
3729
|
+
return [...movementEntries, ...actions];
|
|
3634
3730
|
});
|
|
3635
3731
|
const grouped = /* @__PURE__ */ new Map();
|
|
3636
3732
|
const ungrouped = [];
|
|
@@ -3801,7 +3897,7 @@ var YIELDSEEKER_UPGRADE_ABI = walletAbi;
|
|
|
3801
3897
|
|
|
3802
3898
|
// src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
|
|
3803
3899
|
var import_viem9 = require("viem");
|
|
3804
|
-
var
|
|
3900
|
+
var import_account_abstraction2 = require("viem/account-abstraction");
|
|
3805
3901
|
var import_accounts = require("viem/accounts");
|
|
3806
3902
|
var import_chains3 = require("viem/chains");
|
|
3807
3903
|
var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
@@ -3815,7 +3911,7 @@ var ENTRY_POINT_NONCE_ABI = [
|
|
|
3815
3911
|
var DEFAULT_ROUTING_API = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3816
3912
|
var SIMULATION_ACCOUNT = (0, import_accounts.privateKeyToAccount)((0, import_accounts.generatePrivateKey)());
|
|
3817
3913
|
function operationHash(operation) {
|
|
3818
|
-
return (0,
|
|
3914
|
+
return (0, import_account_abstraction2.getUserOperationHash)({
|
|
3819
3915
|
chainId: 8453,
|
|
3820
3916
|
entryPointAddress: ENTRY_POINT_V06,
|
|
3821
3917
|
entryPointVersion: "0.6",
|
|
@@ -4143,6 +4239,7 @@ var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
|
4143
4239
|
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
4144
4240
|
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
4145
4241
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4242
|
+
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4146
4243
|
function generateYieldseekerUsername() {
|
|
4147
4244
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4148
4245
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4583,6 +4680,7 @@ var YieldseekerAgent = class {
|
|
|
4583
4680
|
let confirmedMovement = false;
|
|
4584
4681
|
let settlement = null;
|
|
4585
4682
|
try {
|
|
4683
|
+
if (amount === void 0) this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
|
|
4586
4684
|
const portfolio = await this.loadPortfolioContext(
|
|
4587
4685
|
state,
|
|
4588
4686
|
chainId,
|
|
@@ -4600,7 +4698,35 @@ var YieldseekerAgent = class {
|
|
|
4600
4698
|
return balance;
|
|
4601
4699
|
};
|
|
4602
4700
|
const idle = await readIdle();
|
|
4603
|
-
|
|
4701
|
+
let positions = portfolio.positions;
|
|
4702
|
+
let positionsStatus = "empty";
|
|
4703
|
+
const validatePositions = (values) => {
|
|
4704
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4705
|
+
for (const value of values) {
|
|
4706
|
+
const address = value?.yieldOption?.address;
|
|
4707
|
+
if (typeof address !== "string" || !(0, import_viem10.isAddress)(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
|
|
4708
|
+
throw this.invalidResponse("yield positions");
|
|
4709
|
+
seen.add(address.toLowerCase());
|
|
4710
|
+
}
|
|
4711
|
+
return values;
|
|
4712
|
+
};
|
|
4713
|
+
const hasPositions = (values) => values.some((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
4714
|
+
const refreshPositions = async () => {
|
|
4715
|
+
this.assertSession(generation, chainId, asset);
|
|
4716
|
+
const response = await this.walletRequest(
|
|
4717
|
+
state,
|
|
4718
|
+
chainId,
|
|
4719
|
+
this.agentPath(context, "yield-positions")
|
|
4720
|
+
);
|
|
4721
|
+
this.assertSession(generation, chainId, asset);
|
|
4722
|
+
if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
|
|
4723
|
+
return validatePositions(response.yieldPositions);
|
|
4724
|
+
};
|
|
4725
|
+
if (amount === void 0) {
|
|
4726
|
+
validatePositions(positions);
|
|
4727
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4728
|
+
}
|
|
4729
|
+
const deployed = positions.reduce(
|
|
4604
4730
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
4605
4731
|
0n
|
|
4606
4732
|
);
|
|
@@ -4608,6 +4734,13 @@ var YieldseekerAgent = class {
|
|
|
4608
4734
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4609
4735
|
if (requested === 0n) {
|
|
4610
4736
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4737
|
+
if (amount === void 0 && positionsStatus === "remaining")
|
|
4738
|
+
throw new OwneyError(
|
|
4739
|
+
"WITHDRAW_FAILED",
|
|
4740
|
+
"Yieldseeker vault funds are not currently withdrawable.",
|
|
4741
|
+
{ asset, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
|
|
4742
|
+
this.id
|
|
4743
|
+
);
|
|
4611
4744
|
throw new OwneyError(
|
|
4612
4745
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4613
4746
|
"No Yieldseeker funds are available to withdraw.",
|
|
@@ -4629,33 +4762,55 @@ var YieldseekerAgent = class {
|
|
|
4629
4762
|
}
|
|
4630
4763
|
let remaining = requested > idle ? requested - idle : 0n;
|
|
4631
4764
|
let grossUnwound = 0n;
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
4635
|
-
if (available <= 0n) continue;
|
|
4636
|
-
const assetsRaw = available < remaining ? available : remaining;
|
|
4765
|
+
const unwind = async (position2, assetsRaw) => {
|
|
4766
|
+
this.assertSession(generation, chainId, asset);
|
|
4637
4767
|
const response = await this.walletRequest(
|
|
4638
4768
|
state,
|
|
4639
4769
|
chainId,
|
|
4640
4770
|
this.agentPath(context, "withdraw-from-position"),
|
|
4641
4771
|
{
|
|
4642
4772
|
method: "POST",
|
|
4643
|
-
body: {
|
|
4644
|
-
chainId,
|
|
4645
|
-
vaultAddress: position2.yieldOption.address,
|
|
4646
|
-
assetsRaw: assetsRaw.toString()
|
|
4647
|
-
}
|
|
4773
|
+
body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
|
|
4648
4774
|
}
|
|
4649
4775
|
);
|
|
4650
|
-
if (!this.isTransactionHash(response?.transactionHash))
|
|
4651
|
-
throw this.invalidResponse("position withdrawal");
|
|
4652
|
-
}
|
|
4776
|
+
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
4653
4777
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
4654
4778
|
confirmedMovement = true;
|
|
4779
|
+
this.assertSession(generation, chainId, asset);
|
|
4655
4780
|
grossUnwound += assetsRaw;
|
|
4656
|
-
|
|
4781
|
+
};
|
|
4782
|
+
if (amount === void 0) {
|
|
4783
|
+
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
4784
|
+
const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.assetsRaw)]));
|
|
4785
|
+
let unwound = false;
|
|
4786
|
+
for (const position2 of positions) {
|
|
4787
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
4788
|
+
if (available <= 0n) continue;
|
|
4789
|
+
await unwind(position2, available);
|
|
4790
|
+
unwound = true;
|
|
4791
|
+
}
|
|
4792
|
+
if (!unwound) break;
|
|
4793
|
+
try {
|
|
4794
|
+
positions = await refreshPositions();
|
|
4795
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4796
|
+
} catch {
|
|
4797
|
+
this.assertSession(generation, chainId, asset);
|
|
4798
|
+
positionsStatus = "unavailable";
|
|
4799
|
+
break;
|
|
4800
|
+
}
|
|
4801
|
+
if (positions.some((position2) => BigInt(position2.assetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.assetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
|
|
4802
|
+
}
|
|
4803
|
+
} else {
|
|
4804
|
+
for (const position2 of positions) {
|
|
4805
|
+
if (remaining === 0n) break;
|
|
4806
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
4807
|
+
if (available <= 0n) continue;
|
|
4808
|
+
const assetsRaw = available < remaining ? available : remaining;
|
|
4809
|
+
await unwind(position2, assetsRaw);
|
|
4810
|
+
remaining -= assetsRaw;
|
|
4811
|
+
}
|
|
4657
4812
|
}
|
|
4658
|
-
if (remaining > 0n) {
|
|
4813
|
+
if (amount !== void 0 && remaining > 0n) {
|
|
4659
4814
|
throw this.invalidResponse("yield positions", {
|
|
4660
4815
|
reason: "Withdrawable positions could not cover the request.",
|
|
4661
4816
|
remaining: remaining.toString()
|
|
@@ -4667,6 +4822,13 @@ var YieldseekerAgent = class {
|
|
|
4667
4822
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
4668
4823
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
4669
4824
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4825
|
+
if (amount === void 0 && positionsStatus !== "empty")
|
|
4826
|
+
throw new OwneyError(
|
|
4827
|
+
"WITHDRAW_FAILED",
|
|
4828
|
+
"Yieldseeker vault funds could not be cleared for the owner transfer.",
|
|
4829
|
+
{ asset, positionsStatus },
|
|
4830
|
+
this.id
|
|
4831
|
+
);
|
|
4670
4832
|
throw new OwneyError(
|
|
4671
4833
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4672
4834
|
"Yieldseeker funds are not available for the owner transfer.",
|
|
@@ -4721,6 +4883,33 @@ var YieldseekerAgent = class {
|
|
|
4721
4883
|
settlement = plannedMovement;
|
|
4722
4884
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4723
4885
|
notifyFunding(onProgress, evidence);
|
|
4886
|
+
if (amount === void 0) {
|
|
4887
|
+
try {
|
|
4888
|
+
positions = await refreshPositions();
|
|
4889
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4890
|
+
} catch {
|
|
4891
|
+
positionsStatus = "unavailable";
|
|
4892
|
+
}
|
|
4893
|
+
if (positionsStatus !== "empty") {
|
|
4894
|
+
const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
4895
|
+
throw new OwneyError(
|
|
4896
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
4897
|
+
"Yieldseeker MAX could not confirm that all vault positions were cleared.",
|
|
4898
|
+
{
|
|
4899
|
+
reason: "YIELDSEEKER_MAX_INCOMPLETE",
|
|
4900
|
+
asset,
|
|
4901
|
+
positionsStatus,
|
|
4902
|
+
remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
|
|
4903
|
+
vaultAddress: position2.yieldOption.address,
|
|
4904
|
+
sharesRaw: position2.assetsRaw,
|
|
4905
|
+
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
4906
|
+
})) : void 0,
|
|
4907
|
+
partialResults: { [this.id]: result }
|
|
4908
|
+
},
|
|
4909
|
+
this.id
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4724
4913
|
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4725
4914
|
throw new OwneyError(
|
|
4726
4915
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -7391,7 +7580,7 @@ var OwneySDK = class {
|
|
|
7391
7580
|
if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
|
|
7392
7581
|
const transaction = await rpc2.getTransaction({ hash: evidence.txHash });
|
|
7393
7582
|
assertCurrent();
|
|
7394
|
-
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
|
|
7583
|
+
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address, receipt))
|
|
7395
7584
|
verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
|
|
7396
7585
|
}
|
|
7397
7586
|
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
@@ -7595,6 +7784,7 @@ var OwneySDK = class {
|
|
|
7595
7784
|
if (!amount) {
|
|
7596
7785
|
const results2 = {};
|
|
7597
7786
|
const agentErrors2 = {};
|
|
7787
|
+
const incompleteWithdrawals = {};
|
|
7598
7788
|
for (const agent of withdrawalAgents) {
|
|
7599
7789
|
try {
|
|
7600
7790
|
results2[agent.id] = await submit(agent);
|
|
@@ -7603,6 +7793,14 @@ var OwneySDK = class {
|
|
|
7603
7793
|
if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
|
|
7604
7794
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7605
7795
|
if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
|
|
7796
|
+
if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
|
|
7797
|
+
incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
|
|
7798
|
+
const evidence = submissionEvidence.get(agent.id);
|
|
7799
|
+
if (evidence?.agentId === agent.id && evidence.chainId === chainId && evidence.token === token && evidence.walletAddress.toLowerCase() === state.walletAddress.toLowerCase() && evidence.intendedAmount === "MAX" && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && evidence.txHash && /^0x[\da-f]{64}$/i.test(evidence.txHash) && evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) && BigInt(evidence.confirmedAmount) > 0n) {
|
|
7800
|
+
results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
|
|
7801
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7606
7804
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
7607
7805
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
7608
7806
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -7625,7 +7823,7 @@ var OwneySDK = class {
|
|
|
7625
7823
|
throw new OwneyError(
|
|
7626
7824
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7627
7825
|
"Some agents could not complete the withdrawal.",
|
|
7628
|
-
{ asset, partialResults: results2, agentErrors: agentErrors2 }
|
|
7826
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
7629
7827
|
);
|
|
7630
7828
|
}
|
|
7631
7829
|
const amountsKnown = Object.values(results2).every(
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/lib/funding-evidence.ts
|
|
2
2
|
import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
|
|
3
|
+
import { entryPoint06Abi, entryPoint07Abi, getUserOperationHash, toUserOperation } from "viem/account-abstraction";
|
|
3
4
|
function notifyFunding(callback, evidence) {
|
|
4
5
|
try {
|
|
5
6
|
void Promise.resolve(callback?.({ ...evidence })).catch(() => {
|
|
@@ -9,11 +10,53 @@ function notifyFunding(callback, evidence) {
|
|
|
9
10
|
}
|
|
10
11
|
var fullWithdrawalAbi = parseAbi(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
11
12
|
var withdrawalEventAbi = parseAbi(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
var accountExecutionAbi = parseAbi([
|
|
14
|
+
"function execute(address target,uint256 value,bytes data)",
|
|
15
|
+
"function executeBatch((address target,uint256 value,bytes data)[] calls)"
|
|
16
|
+
]);
|
|
17
|
+
var entryPoint06 = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
|
|
18
|
+
var entryPoint07 = "0x0000000071727de22e5e9d8baf0edac6f37da032";
|
|
19
|
+
function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receipt) {
|
|
20
|
+
if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
|
|
21
|
+
const matchesFullCall = (data) => {
|
|
22
|
+
try {
|
|
23
|
+
const decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data });
|
|
24
|
+
return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
if (transaction.to?.toLowerCase() === evidence.agentWallet.toLowerCase() && transaction.from.toLowerCase() === evidence.walletAddress.toLowerCase()) return matchesFullCall(transaction.input);
|
|
14
30
|
try {
|
|
15
|
-
const
|
|
16
|
-
|
|
31
|
+
const address = transaction.to?.toLowerCase();
|
|
32
|
+
if (address !== entryPoint06 && address !== entryPoint07) return false;
|
|
33
|
+
const version = address === entryPoint06 ? "0.6" : "0.7";
|
|
34
|
+
const decoded = decodeFunctionData({ abi: version === "0.6" ? entryPoint06Abi : entryPoint07Abi, data: transaction.input });
|
|
35
|
+
if (decoded.functionName !== "handleOps") return false;
|
|
36
|
+
const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
|
|
37
|
+
if (operations.length !== 1) return false;
|
|
38
|
+
const operation = operations[0];
|
|
39
|
+
const userOperation = toUserOperation(operation);
|
|
40
|
+
const hash = getUserOperationHash({
|
|
41
|
+
chainId: evidence.chainId,
|
|
42
|
+
entryPointAddress: transaction.to,
|
|
43
|
+
entryPointVersion: version,
|
|
44
|
+
userOperation
|
|
45
|
+
});
|
|
46
|
+
if (evidence.userOpHash && hash.toLowerCase() !== evidence.userOpHash.toLowerCase()) return false;
|
|
47
|
+
if (!receipt?.logs.some((log) => {
|
|
48
|
+
if (log.address.toLowerCase() !== address) return false;
|
|
49
|
+
try {
|
|
50
|
+
const event = decodeEventLog({ abi: entryPoint06Abi, data: log.data, topics: log.topics });
|
|
51
|
+
return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
})) return false;
|
|
56
|
+
if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
|
|
57
|
+
const execution = decodeFunctionData({ abi: accountExecutionAbi, data: operation.callData });
|
|
58
|
+
const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
|
|
59
|
+
return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
|
|
17
60
|
} catch {
|
|
18
61
|
return false;
|
|
19
62
|
}
|
|
@@ -3532,6 +3575,49 @@ function actionEntry(action) {
|
|
|
3532
3575
|
rebalanceLog: []
|
|
3533
3576
|
};
|
|
3534
3577
|
}
|
|
3578
|
+
function allocationEntry(action, context) {
|
|
3579
|
+
if (action.actionType.toLowerCase() !== "autoseek") return void 0;
|
|
3580
|
+
const hash = action.details?.depositTransactionHash;
|
|
3581
|
+
const deposits = action.details?.deposits;
|
|
3582
|
+
if (typeof hash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash) || !Array.isArray(deposits) || deposits.length === 0) return void 0;
|
|
3583
|
+
const positions = [];
|
|
3584
|
+
let amountRaw = 0n;
|
|
3585
|
+
for (const deposit of deposits) {
|
|
3586
|
+
if (!deposit || typeof deposit !== "object") return void 0;
|
|
3587
|
+
const leg = deposit;
|
|
3588
|
+
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
3589
|
+
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
3590
|
+
if (typeof leg.vaultAddress !== "string" || !isAddress(leg.vaultAddress)) return void 0;
|
|
3591
|
+
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
|
+
if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
|
|
3593
|
+
const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
|
|
3594
|
+
positions.push({
|
|
3595
|
+
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
3596
|
+
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
3597
|
+
tokenSymbol: context.asset,
|
|
3598
|
+
amount: formatUnits2(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3599
|
+
});
|
|
3600
|
+
amountRaw += rawAmount;
|
|
3601
|
+
}
|
|
3602
|
+
return {
|
|
3603
|
+
agent: "yieldseeker",
|
|
3604
|
+
action: "Deposit",
|
|
3605
|
+
date: action.createdDate,
|
|
3606
|
+
oldApy: null,
|
|
3607
|
+
newApy: null,
|
|
3608
|
+
positions,
|
|
3609
|
+
rebalanceLog: [],
|
|
3610
|
+
transactions: [{
|
|
3611
|
+
txHashes: [hash],
|
|
3612
|
+
chainId: context.agent.chainId,
|
|
3613
|
+
tokenAddress: context.agent.assetAddress,
|
|
3614
|
+
tokenSymbol: context.asset,
|
|
3615
|
+
agentWallet: context.wallet.walletAddress,
|
|
3616
|
+
amountRaw: String(amountRaw),
|
|
3617
|
+
amount: formatUnits2(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
3618
|
+
}]
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3535
3621
|
function depositDestination(context, movement) {
|
|
3536
3622
|
const to = movement.toAddress.toLowerCase();
|
|
3537
3623
|
const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
|
|
@@ -3599,20 +3685,30 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3599
3685
|
seenMovements.add(key2);
|
|
3600
3686
|
return true;
|
|
3601
3687
|
});
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3688
|
+
const movementEntries = movements.map(
|
|
3689
|
+
(movement) => movementEntry(
|
|
3690
|
+
movement,
|
|
3691
|
+
context.wallet,
|
|
3692
|
+
context.agent,
|
|
3693
|
+
context.asset,
|
|
3694
|
+
options.ownerAddress,
|
|
3695
|
+
options.vaultAddresses,
|
|
3696
|
+
depositDestination(context, movement)
|
|
3697
|
+
)
|
|
3698
|
+
).filter((entry) => entry !== void 0);
|
|
3699
|
+
const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
|
|
3700
|
+
const actions = (context.actions ?? []).flatMap((action) => {
|
|
3701
|
+
const allocation = allocationEntry(action, context);
|
|
3702
|
+
if (allocation) {
|
|
3703
|
+
const hash = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
3704
|
+
if (deposits.has(hash)) return [];
|
|
3705
|
+
deposits.add(hash);
|
|
3706
|
+
return [allocation];
|
|
3707
|
+
}
|
|
3708
|
+
const entry = actionEntry(action);
|
|
3709
|
+
return entry ? [entry] : [];
|
|
3710
|
+
});
|
|
3711
|
+
return [...movementEntries, ...actions];
|
|
3616
3712
|
});
|
|
3617
3713
|
const grouped = /* @__PURE__ */ new Map();
|
|
3618
3714
|
const ungrouped = [];
|
|
@@ -3792,7 +3888,7 @@ import {
|
|
|
3792
3888
|
getAddress as getAddress3,
|
|
3793
3889
|
toHex
|
|
3794
3890
|
} from "viem";
|
|
3795
|
-
import { getUserOperationHash } from "viem/account-abstraction";
|
|
3891
|
+
import { getUserOperationHash as getUserOperationHash2 } from "viem/account-abstraction";
|
|
3796
3892
|
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
3797
3893
|
import { base as base3 } from "viem/chains";
|
|
3798
3894
|
var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
@@ -3806,7 +3902,7 @@ var ENTRY_POINT_NONCE_ABI = [
|
|
|
3806
3902
|
var DEFAULT_ROUTING_API = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3807
3903
|
var SIMULATION_ACCOUNT = privateKeyToAccount(generatePrivateKey());
|
|
3808
3904
|
function operationHash(operation) {
|
|
3809
|
-
return
|
|
3905
|
+
return getUserOperationHash2({
|
|
3810
3906
|
chainId: 8453,
|
|
3811
3907
|
entryPointAddress: ENTRY_POINT_V06,
|
|
3812
3908
|
entryPointVersion: "0.6",
|
|
@@ -4134,6 +4230,7 @@ var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
|
4134
4230
|
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
4135
4231
|
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
4136
4232
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
4233
|
+
var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
|
|
4137
4234
|
function generateYieldseekerUsername() {
|
|
4138
4235
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
4139
4236
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -4574,6 +4671,7 @@ var YieldseekerAgent = class {
|
|
|
4574
4671
|
let confirmedMovement = false;
|
|
4575
4672
|
let settlement = null;
|
|
4576
4673
|
try {
|
|
4674
|
+
if (amount === void 0) this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
|
|
4577
4675
|
const portfolio = await this.loadPortfolioContext(
|
|
4578
4676
|
state,
|
|
4579
4677
|
chainId,
|
|
@@ -4591,7 +4689,35 @@ var YieldseekerAgent = class {
|
|
|
4591
4689
|
return balance;
|
|
4592
4690
|
};
|
|
4593
4691
|
const idle = await readIdle();
|
|
4594
|
-
|
|
4692
|
+
let positions = portfolio.positions;
|
|
4693
|
+
let positionsStatus = "empty";
|
|
4694
|
+
const validatePositions = (values) => {
|
|
4695
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4696
|
+
for (const value of values) {
|
|
4697
|
+
const address = value?.yieldOption?.address;
|
|
4698
|
+
if (typeof address !== "string" || !isAddress2(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
|
|
4699
|
+
throw this.invalidResponse("yield positions");
|
|
4700
|
+
seen.add(address.toLowerCase());
|
|
4701
|
+
}
|
|
4702
|
+
return values;
|
|
4703
|
+
};
|
|
4704
|
+
const hasPositions = (values) => values.some((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
4705
|
+
const refreshPositions = async () => {
|
|
4706
|
+
this.assertSession(generation, chainId, asset);
|
|
4707
|
+
const response = await this.walletRequest(
|
|
4708
|
+
state,
|
|
4709
|
+
chainId,
|
|
4710
|
+
this.agentPath(context, "yield-positions")
|
|
4711
|
+
);
|
|
4712
|
+
this.assertSession(generation, chainId, asset);
|
|
4713
|
+
if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
|
|
4714
|
+
return validatePositions(response.yieldPositions);
|
|
4715
|
+
};
|
|
4716
|
+
if (amount === void 0) {
|
|
4717
|
+
validatePositions(positions);
|
|
4718
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4719
|
+
}
|
|
4720
|
+
const deployed = positions.reduce(
|
|
4595
4721
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
4596
4722
|
0n
|
|
4597
4723
|
);
|
|
@@ -4599,6 +4725,13 @@ var YieldseekerAgent = class {
|
|
|
4599
4725
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4600
4726
|
if (requested === 0n) {
|
|
4601
4727
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4728
|
+
if (amount === void 0 && positionsStatus === "remaining")
|
|
4729
|
+
throw new OwneyError(
|
|
4730
|
+
"WITHDRAW_FAILED",
|
|
4731
|
+
"Yieldseeker vault funds are not currently withdrawable.",
|
|
4732
|
+
{ asset, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
|
|
4733
|
+
this.id
|
|
4734
|
+
);
|
|
4602
4735
|
throw new OwneyError(
|
|
4603
4736
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4604
4737
|
"No Yieldseeker funds are available to withdraw.",
|
|
@@ -4620,33 +4753,55 @@ var YieldseekerAgent = class {
|
|
|
4620
4753
|
}
|
|
4621
4754
|
let remaining = requested > idle ? requested - idle : 0n;
|
|
4622
4755
|
let grossUnwound = 0n;
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
4626
|
-
if (available <= 0n) continue;
|
|
4627
|
-
const assetsRaw = available < remaining ? available : remaining;
|
|
4756
|
+
const unwind = async (position2, assetsRaw) => {
|
|
4757
|
+
this.assertSession(generation, chainId, asset);
|
|
4628
4758
|
const response = await this.walletRequest(
|
|
4629
4759
|
state,
|
|
4630
4760
|
chainId,
|
|
4631
4761
|
this.agentPath(context, "withdraw-from-position"),
|
|
4632
4762
|
{
|
|
4633
4763
|
method: "POST",
|
|
4634
|
-
body: {
|
|
4635
|
-
chainId,
|
|
4636
|
-
vaultAddress: position2.yieldOption.address,
|
|
4637
|
-
assetsRaw: assetsRaw.toString()
|
|
4638
|
-
}
|
|
4764
|
+
body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
|
|
4639
4765
|
}
|
|
4640
4766
|
);
|
|
4641
|
-
if (!this.isTransactionHash(response?.transactionHash))
|
|
4642
|
-
throw this.invalidResponse("position withdrawal");
|
|
4643
|
-
}
|
|
4767
|
+
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
4644
4768
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
4645
4769
|
confirmedMovement = true;
|
|
4770
|
+
this.assertSession(generation, chainId, asset);
|
|
4646
4771
|
grossUnwound += assetsRaw;
|
|
4647
|
-
|
|
4772
|
+
};
|
|
4773
|
+
if (amount === void 0) {
|
|
4774
|
+
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.assetsRaw)]));
|
|
4776
|
+
let unwound = false;
|
|
4777
|
+
for (const position2 of positions) {
|
|
4778
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
4779
|
+
if (available <= 0n) continue;
|
|
4780
|
+
await unwind(position2, available);
|
|
4781
|
+
unwound = true;
|
|
4782
|
+
}
|
|
4783
|
+
if (!unwound) break;
|
|
4784
|
+
try {
|
|
4785
|
+
positions = await refreshPositions();
|
|
4786
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4787
|
+
} catch {
|
|
4788
|
+
this.assertSession(generation, chainId, asset);
|
|
4789
|
+
positionsStatus = "unavailable";
|
|
4790
|
+
break;
|
|
4791
|
+
}
|
|
4792
|
+
if (positions.some((position2) => BigInt(position2.assetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.assetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
|
|
4793
|
+
}
|
|
4794
|
+
} else {
|
|
4795
|
+
for (const position2 of positions) {
|
|
4796
|
+
if (remaining === 0n) break;
|
|
4797
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
4798
|
+
if (available <= 0n) continue;
|
|
4799
|
+
const assetsRaw = available < remaining ? available : remaining;
|
|
4800
|
+
await unwind(position2, assetsRaw);
|
|
4801
|
+
remaining -= assetsRaw;
|
|
4802
|
+
}
|
|
4648
4803
|
}
|
|
4649
|
-
if (remaining > 0n) {
|
|
4804
|
+
if (amount !== void 0 && remaining > 0n) {
|
|
4650
4805
|
throw this.invalidResponse("yield positions", {
|
|
4651
4806
|
reason: "Withdrawable positions could not cover the request.",
|
|
4652
4807
|
remaining: remaining.toString()
|
|
@@ -4658,6 +4813,13 @@ var YieldseekerAgent = class {
|
|
|
4658
4813
|
const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
|
|
4659
4814
|
if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
|
|
4660
4815
|
this.invalidateFundingRead(state, chainId, asset);
|
|
4816
|
+
if (amount === void 0 && positionsStatus !== "empty")
|
|
4817
|
+
throw new OwneyError(
|
|
4818
|
+
"WITHDRAW_FAILED",
|
|
4819
|
+
"Yieldseeker vault funds could not be cleared for the owner transfer.",
|
|
4820
|
+
{ asset, positionsStatus },
|
|
4821
|
+
this.id
|
|
4822
|
+
);
|
|
4661
4823
|
throw new OwneyError(
|
|
4662
4824
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4663
4825
|
"Yieldseeker funds are not available for the owner transfer.",
|
|
@@ -4712,6 +4874,33 @@ var YieldseekerAgent = class {
|
|
|
4712
4874
|
settlement = plannedMovement;
|
|
4713
4875
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4714
4876
|
notifyFunding(onProgress, evidence);
|
|
4877
|
+
if (amount === void 0) {
|
|
4878
|
+
try {
|
|
4879
|
+
positions = await refreshPositions();
|
|
4880
|
+
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
4881
|
+
} catch {
|
|
4882
|
+
positionsStatus = "unavailable";
|
|
4883
|
+
}
|
|
4884
|
+
if (positionsStatus !== "empty") {
|
|
4885
|
+
const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
4886
|
+
throw new OwneyError(
|
|
4887
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
4888
|
+
"Yieldseeker MAX could not confirm that all vault positions were cleared.",
|
|
4889
|
+
{
|
|
4890
|
+
reason: "YIELDSEEKER_MAX_INCOMPLETE",
|
|
4891
|
+
asset,
|
|
4892
|
+
positionsStatus,
|
|
4893
|
+
remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
|
|
4894
|
+
vaultAddress: position2.yieldOption.address,
|
|
4895
|
+
sharesRaw: position2.assetsRaw,
|
|
4896
|
+
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
4897
|
+
})) : void 0,
|
|
4898
|
+
partialResults: { [this.id]: result }
|
|
4899
|
+
},
|
|
4900
|
+
this.id
|
|
4901
|
+
);
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4715
4904
|
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4716
4905
|
throw new OwneyError(
|
|
4717
4906
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -7390,7 +7579,7 @@ var OwneySDK = class {
|
|
|
7390
7579
|
if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
|
|
7391
7580
|
const transaction = await rpc2.getTransaction({ hash: evidence.txHash });
|
|
7392
7581
|
assertCurrent();
|
|
7393
|
-
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
|
|
7582
|
+
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address, receipt))
|
|
7394
7583
|
verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
|
|
7395
7584
|
}
|
|
7396
7585
|
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
@@ -7594,6 +7783,7 @@ var OwneySDK = class {
|
|
|
7594
7783
|
if (!amount) {
|
|
7595
7784
|
const results2 = {};
|
|
7596
7785
|
const agentErrors2 = {};
|
|
7786
|
+
const incompleteWithdrawals = {};
|
|
7597
7787
|
for (const agent of withdrawalAgents) {
|
|
7598
7788
|
try {
|
|
7599
7789
|
results2[agent.id] = await submit(agent);
|
|
@@ -7602,6 +7792,14 @@ var OwneySDK = class {
|
|
|
7602
7792
|
if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
|
|
7603
7793
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
7604
7794
|
if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
|
|
7795
|
+
if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
|
|
7796
|
+
incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
|
|
7797
|
+
const evidence = submissionEvidence.get(agent.id);
|
|
7798
|
+
if (evidence?.agentId === agent.id && evidence.chainId === chainId && evidence.token === token && evidence.walletAddress.toLowerCase() === state.walletAddress.toLowerCase() && evidence.intendedAmount === "MAX" && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && evidence.txHash && /^0x[\da-f]{64}$/i.test(evidence.txHash) && evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) && BigInt(evidence.confirmedAmount) > 0n) {
|
|
7799
|
+
results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
|
|
7800
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7605
7803
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
7606
7804
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
7607
7805
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -7624,7 +7822,7 @@ var OwneySDK = class {
|
|
|
7624
7822
|
throw new OwneyError(
|
|
7625
7823
|
"WITHDRAW_PARTIAL_FAILURE",
|
|
7626
7824
|
"Some agents could not complete the withdrawal.",
|
|
7627
|
-
{ asset, partialResults: results2, agentErrors: agentErrors2 }
|
|
7825
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
|
|
7628
7826
|
);
|
|
7629
7827
|
}
|
|
7630
7828
|
const amountsKnown = Object.values(results2).every(
|