@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.
Files changed (3) hide show
  1. package/dist/index.cjs +236 -38
  2. package/dist/index.js +236 -38
  3. 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
- function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
47
- if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || transaction.to?.toLowerCase() !== evidence.agentWallet?.toLowerCase() || transaction.from.toLowerCase() !== evidence.walletAddress.toLowerCase()) return false;
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 decoded = (0, import_viem.decodeFunctionData)({ abi: fullWithdrawalAbi, data: transaction.input });
50
- return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
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
- return [
3621
- ...movements.map(
3622
- (movement) => movementEntry(
3623
- movement,
3624
- context.wallet,
3625
- context.agent,
3626
- context.asset,
3627
- options.ownerAddress,
3628
- options.vaultAddresses,
3629
- depositDestination(context, movement)
3630
- )
3631
- ),
3632
- ...(context.actions ?? []).map(actionEntry)
3633
- ].filter((entry) => entry !== void 0);
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 import_account_abstraction = require("viem/account-abstraction");
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, import_account_abstraction.getUserOperationHash)({
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
- const deployed = portfolio.positions.reduce(
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
- for (const position2 of portfolio.positions) {
4633
- if (remaining === 0n) break;
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
- remaining -= assetsRaw;
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
- function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
13
- if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || transaction.to?.toLowerCase() !== evidence.agentWallet?.toLowerCase() || transaction.from.toLowerCase() !== evidence.walletAddress.toLowerCase()) return false;
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 decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data: transaction.input });
16
- return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
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
- return [
3603
- ...movements.map(
3604
- (movement) => movementEntry(
3605
- movement,
3606
- context.wallet,
3607
- context.agent,
3608
- context.asset,
3609
- options.ownerAddress,
3610
- options.vaultAddresses,
3611
- depositDestination(context, movement)
3612
- )
3613
- ),
3614
- ...(context.actions ?? []).map(actionEntry)
3615
- ].filter((entry) => entry !== void 0);
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 getUserOperationHash({
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
- const deployed = portfolio.positions.reduce(
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
- for (const position2 of portfolio.positions) {
4624
- if (remaining === 0n) break;
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
- remaining -= assetsRaw;
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(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.25-beta.13",
3
+ "version": "0.7.25-beta.14",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",