@owney/sdk 0.7.16-beta.4 → 0.7.16-beta.5

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 CHANGED
@@ -2673,19 +2673,27 @@ function assetAddressValue(record, address) {
2673
2673
  );
2674
2674
  return entry?.[1] ?? "0";
2675
2675
  }
2676
- function position(value, asset) {
2676
+ function position(value, asset, baseAssetDecimals) {
2677
2677
  const option = value?.yieldOption;
2678
2678
  if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
2679
2679
  return invalid("yield positions", "missing vault metadata");
2680
2680
  }
2681
- const decimals = YIELDSEEKER_ASSET_METADATA[asset].decimals;
2682
2681
  return {
2683
2682
  chain: "BASE",
2684
2683
  protocol: option.provider,
2685
2684
  protocolId: option.address,
2686
2685
  pool: option.name,
2687
2686
  asset,
2688
- amount: decimal(value.assetsRaw, decimals, "yield positions"),
2687
+ // `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
2688
+ // differ from the underlying asset. Yieldseeker already converts it to
2689
+ // underlying base-asset units in `assetsBase`; pair that value with the
2690
+ // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2691
+ // share quantity separately because withdraw-from-position expects it.
2692
+ amount: decimal(
2693
+ value.assetsBase,
2694
+ baseAssetDecimals,
2695
+ "yield positions"
2696
+ ),
2689
2697
  amountRaw: String(value.assetsRaw),
2690
2698
  apy: percent(option.riskAdjustedApy),
2691
2699
  tvl: Number(option.totalDepositsUsd),
@@ -2709,7 +2717,13 @@ function mapYieldseekerBalances(contexts) {
2709
2717
  amount: decimal(idle, metadata.decimals, "snapshot")
2710
2718
  });
2711
2719
  positions.push(
2712
- ...context.positions.map((entry) => position(entry, context.asset))
2720
+ ...context.positions.map(
2721
+ (entry) => position(
2722
+ entry,
2723
+ context.asset,
2724
+ context.snapshot.baseAssetDecimals
2725
+ )
2726
+ )
2713
2727
  );
2714
2728
  totalUsd += usd(
2715
2729
  raw(context.snapshot.totalValueBase, "snapshot"),
@@ -4661,17 +4675,25 @@ var OwneySDK = class {
4661
4675
  * at a time anyway.
4662
4676
  *
4663
4677
  * Every agent is attempted even if an earlier one fails, so one declined
4664
- * signature can't deny the remaining agents their turn. The first failure is
4665
- * rethrown (matching the previous `Promise.all` rejection) once all agents
4666
- * have had a chance to activate.
4678
+ * signature can't deny the remaining agents their turn. Once all agents have
4679
+ * had a chance, a partial failure identifies the agents that still need a
4680
+ * retry; if none activated, the original provider error is preserved.
4667
4681
  */
4668
4682
  async activateAgentsInTurn(agents, state, chainId, asset) {
4669
4683
  let firstError = null;
4684
+ const activatedAgentIds = [];
4685
+ const failedAgents = [];
4670
4686
  for (const agent of agents) {
4671
4687
  try {
4672
4688
  await agent.activateAgent(state, chainId, asset);
4673
4689
  await this.applyOrgPolicyTo(agent, state, chainId);
4690
+ activatedAgentIds.push(agent.id);
4674
4691
  } catch (error) {
4692
+ failedAgents.push({
4693
+ agentId: agent.id,
4694
+ code: error instanceof OwneyError ? error.code : void 0,
4695
+ message: error instanceof Error ? error.message : String(error)
4696
+ });
4675
4697
  if (firstError === null) {
4676
4698
  firstError = error;
4677
4699
  } else {
@@ -4679,10 +4701,19 @@ var OwneySDK = class {
4679
4701
  }
4680
4702
  }
4681
4703
  }
4682
- if (firstError !== null) throw firstError;
4704
+ if (firstError === null) return;
4705
+ if (activatedAgentIds.length === 0) throw firstError;
4706
+ const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
4707
+ const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
4708
+ const remainingNames = failedAgentIds.map(this.formatAgentName).join(", ");
4709
+ throw new OwneyError(
4710
+ "AGENT_ACTIVATION_PARTIAL_FAILURE",
4711
+ `${activeNames} activated, but ${remainingNames} still needs activation. Try again and approve the remaining wallet request.`,
4712
+ { activatedAgentIds, failedAgentIds, failures: failedAgents }
4713
+ );
4683
4714
  }
4684
4715
  /**
4685
- * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
4716
+ * Deposit funds into a specific agent, or distribute across all agents if agentId is omitted.
4686
4717
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
4687
4718
  * @param options - Deposit parameters
4688
4719
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -4690,7 +4721,8 @@ var OwneySDK = class {
4690
4721
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
4691
4722
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
4692
4723
  * split amount and smart wallet address — expect multiple wallet prompts.
4693
- * @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
4724
+ * @param options.agentId - Optional. Target agent. Omit to fund all eligible agents. Deposits
4725
+ * are equal once every agent is funded; first/recovery deposits also honor each agent's floor.
4694
4726
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
4695
4727
  */
4696
4728
  async deposit(options) {
@@ -4742,10 +4774,35 @@ var OwneySDK = class {
4742
4774
  exempt
4743
4775
  );
4744
4776
  if (agentAmounts.length === 0) {
4777
+ const agentsRequiringActivation = eligibleAgents.filter(
4778
+ (agent) => !exempt.has(agent.id)
4779
+ );
4780
+ const perAgentMinimum = agentsRequiringActivation.reduce(
4781
+ (highest, agent) => {
4782
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
4783
+ return minimum > highest ? minimum : highest;
4784
+ },
4785
+ 0n
4786
+ );
4787
+ const minimumRequired = perAgentMinimum * BigInt(agentsRequiringActivation.length);
4745
4788
  throw new OwneyError(
4746
4789
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
4747
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
4748
- { amount }
4790
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
4791
+ {
4792
+ amount,
4793
+ asset,
4794
+ chainId,
4795
+ minDepositAmount: minimumRequired.toString(),
4796
+ perAgentDepositAmount: perAgentMinimum.toString(),
4797
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
4798
+ agentId: agent.id,
4799
+ minDepositAmount: this.getMinDepositAmount(
4800
+ agent,
4801
+ chainId,
4802
+ asset
4803
+ ).toString()
4804
+ }))
4805
+ }
4749
4806
  );
4750
4807
  }
4751
4808
  const agentResults = {};
@@ -4831,6 +4888,20 @@ var OwneySDK = class {
4831
4888
  }
4832
4889
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
4833
4890
  if (agents.length === 0) return [];
4891
+ const agentsRequiringActivation = agents.filter(
4892
+ (agent) => !exempt.has(agent.id)
4893
+ );
4894
+ if (agentsRequiringActivation.length === agents.length) {
4895
+ const highestMinimum2 = agents.reduce(
4896
+ (highest, agent) => {
4897
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
4898
+ return minimum > highest ? minimum : highest;
4899
+ },
4900
+ 0n
4901
+ );
4902
+ const minimumRequired2 = highestMinimum2 * BigInt(agents.length);
4903
+ if (totalAmount < minimumRequired2) return [];
4904
+ }
4834
4905
  const perAgent = totalAmount / BigInt(agents.length);
4835
4906
  const remainder = totalAmount % BigInt(agents.length);
4836
4907
  const splits = agents.map((agent, i) => ({
@@ -4843,13 +4914,29 @@ var OwneySDK = class {
4843
4914
  if (valid2.length === agents.length) {
4844
4915
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4845
4916
  }
4846
- return this.splitDepositAmount(
4847
- totalAmount,
4848
- valid2.map((s) => s.agent),
4849
- chainId,
4850
- asset,
4851
- exempt
4917
+ const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : valid2.map((s) => s.agent);
4918
+ const highestMinimum = targets.reduce(
4919
+ (highest, agent) => {
4920
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
4921
+ return minimum > highest ? minimum : highest;
4922
+ },
4923
+ 0n
4852
4924
  );
4925
+ const minimumRequired = highestMinimum * BigInt(targets.length);
4926
+ if (totalAmount < minimumRequired) return [];
4927
+ const targetShare = totalAmount / BigInt(targets.length);
4928
+ const targetRemainder = totalAmount % BigInt(targets.length);
4929
+ return targets.map((agent, index) => ({
4930
+ agent,
4931
+ amount: String(
4932
+ targetShare + (index === targets.length - 1 ? targetRemainder : 0n)
4933
+ )
4934
+ }));
4935
+ }
4936
+ formatAgentName(agentId) {
4937
+ if (agentId === "zyfai") return "Zyfai";
4938
+ if (agentId === "yieldseeker") return "Yieldseeker";
4939
+ return agentId;
4853
4940
  }
4854
4941
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
4855
4942
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -4881,7 +4968,20 @@ var OwneySDK = class {
4881
4968
  const token = balance.tokens.find(
4882
4969
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
4883
4970
  );
4884
- return !!token && Number(token.amount) > 0;
4971
+ const chainNameById = {
4972
+ 1: "ETHEREUM",
4973
+ 8453: "BASE",
4974
+ 42161: "ARBITRUM"
4975
+ };
4976
+ const targetChainName = chainNameById[chainId];
4977
+ const position2 = (balance.positions ?? []).find(
4978
+ (p) => {
4979
+ const positionChain = p.chain.trim().toUpperCase();
4980
+ const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
4981
+ return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
4982
+ }
4983
+ );
4984
+ return !!token && Number(token.amount) > 0 || !!position2;
4885
4985
  } catch {
4886
4986
  return false;
4887
4987
  }
@@ -5104,8 +5204,12 @@ var OwneySDK = class {
5104
5204
  const state = this.requireState();
5105
5205
  const chainId = this.requireChainId();
5106
5206
  if (agentId) {
5107
- const result = await this.getAgent(agentId).getBalances(state, chainId);
5108
- return result;
5207
+ const agent = this.getAgent(agentId);
5208
+ const result = await agent.getBalances(state, chainId);
5209
+ return {
5210
+ ...result,
5211
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5212
+ };
5109
5213
  }
5110
5214
  let totalBalance = 0;
5111
5215
  const results = {};
@@ -5113,7 +5217,13 @@ var OwneySDK = class {
5113
5217
  const balanceResults = await Promise.allSettled(
5114
5218
  entries.map(async ([id, agent]) => {
5115
5219
  const b = await agent.getBalances(state, chainId);
5116
- return [id, b];
5220
+ return [
5221
+ id,
5222
+ {
5223
+ ...b,
5224
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5225
+ }
5226
+ ];
5117
5227
  })
5118
5228
  );
5119
5229
  let successCount = 0;
package/dist/index.d.cts CHANGED
@@ -245,6 +245,13 @@ interface AgentBalance {
245
245
  totalBalance: string;
246
246
  /** Asset that `totalBalance` is denominated in. Currently always `"usdc"`. */
247
247
  totalBalanceAsset: string;
248
+ /**
249
+ * Describes whether `tokens` already includes deployed `positions`.
250
+ * Consumers must add matching positions only for `tokens-plus-positions`;
251
+ * doing so for Zyfai would double-count, while omitting it for Yieldseeker
252
+ * makes its balance disappear as soon as idle funds enter a vault.
253
+ */
254
+ balanceComposition?: "tokens-include-positions" | "tokens-plus-positions";
248
255
  tokens: OwneyToken[];
249
256
  /**
250
257
  * Per-protocol/pool positions when the agent's portfolio payload includes
@@ -578,13 +585,13 @@ declare class OwneySDK {
578
585
  * at a time anyway.
579
586
  *
580
587
  * Every agent is attempted even if an earlier one fails, so one declined
581
- * signature can't deny the remaining agents their turn. The first failure is
582
- * rethrown (matching the previous `Promise.all` rejection) once all agents
583
- * have had a chance to activate.
588
+ * signature can't deny the remaining agents their turn. Once all agents have
589
+ * had a chance, a partial failure identifies the agents that still need a
590
+ * retry; if none activated, the original provider error is preserved.
584
591
  */
585
592
  private activateAgentsInTurn;
586
593
  /**
587
- * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
594
+ * Deposit funds into a specific agent, or distribute across all agents if agentId is omitted.
588
595
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
589
596
  * @param options - Deposit parameters
590
597
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -592,7 +599,8 @@ declare class OwneySDK {
592
599
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
593
600
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
594
601
  * split amount and smart wallet address — expect multiple wallet prompts.
595
- * @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
602
+ * @param options.agentId - Optional. Target agent. Omit to fund all eligible agents. Deposits
603
+ * are equal once every agent is funded; first/recovery deposits also honor each agent's floor.
596
604
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
597
605
  */
598
606
  deposit(options: DepositOptions): Promise<OwneyDepositResult | OwneyMultiDepositResult>;
@@ -624,6 +632,7 @@ declare class OwneySDK {
624
632
  private depositWithFallback;
625
633
  private getMinDepositAmount;
626
634
  private splitDepositAmount;
635
+ private formatAgentName;
627
636
  private validateMinDepositAmount;
628
637
  /**
629
638
  * Whether the user already holds a non-zero balance with `agent` for the
@@ -819,7 +828,7 @@ declare class YieldseekerAgent implements IAgent {
819
828
  private invalidResponse;
820
829
  }
821
830
 
822
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_BALANCE_UNAVAILABLE" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "AGENT_API_ERROR" | "AGENT_AUTH_FAILED" | "AGENT_INVALID_RESPONSE" | "AGENT_TIMEOUT" | "AGENT_TRANSACTION_REVERTED" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
831
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "AGENT_ACTIVATION_PARTIAL_FAILURE" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_BALANCE_UNAVAILABLE" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "AGENT_API_ERROR" | "AGENT_AUTH_FAILED" | "AGENT_INVALID_RESPONSE" | "AGENT_TIMEOUT" | "AGENT_TRANSACTION_REVERTED" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
823
832
  declare class OwneyError extends Error {
824
833
  readonly code: OwneyErrorCode;
825
834
  readonly details?: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -245,6 +245,13 @@ interface AgentBalance {
245
245
  totalBalance: string;
246
246
  /** Asset that `totalBalance` is denominated in. Currently always `"usdc"`. */
247
247
  totalBalanceAsset: string;
248
+ /**
249
+ * Describes whether `tokens` already includes deployed `positions`.
250
+ * Consumers must add matching positions only for `tokens-plus-positions`;
251
+ * doing so for Zyfai would double-count, while omitting it for Yieldseeker
252
+ * makes its balance disappear as soon as idle funds enter a vault.
253
+ */
254
+ balanceComposition?: "tokens-include-positions" | "tokens-plus-positions";
248
255
  tokens: OwneyToken[];
249
256
  /**
250
257
  * Per-protocol/pool positions when the agent's portfolio payload includes
@@ -578,13 +585,13 @@ declare class OwneySDK {
578
585
  * at a time anyway.
579
586
  *
580
587
  * Every agent is attempted even if an earlier one fails, so one declined
581
- * signature can't deny the remaining agents their turn. The first failure is
582
- * rethrown (matching the previous `Promise.all` rejection) once all agents
583
- * have had a chance to activate.
588
+ * signature can't deny the remaining agents their turn. Once all agents have
589
+ * had a chance, a partial failure identifies the agents that still need a
590
+ * retry; if none activated, the original provider error is preserved.
584
591
  */
585
592
  private activateAgentsInTurn;
586
593
  /**
587
- * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
594
+ * Deposit funds into a specific agent, or distribute across all agents if agentId is omitted.
588
595
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
589
596
  * @param options - Deposit parameters
590
597
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -592,7 +599,8 @@ declare class OwneySDK {
592
599
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
593
600
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
594
601
  * split amount and smart wallet address — expect multiple wallet prompts.
595
- * @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
602
+ * @param options.agentId - Optional. Target agent. Omit to fund all eligible agents. Deposits
603
+ * are equal once every agent is funded; first/recovery deposits also honor each agent's floor.
596
604
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
597
605
  */
598
606
  deposit(options: DepositOptions): Promise<OwneyDepositResult | OwneyMultiDepositResult>;
@@ -624,6 +632,7 @@ declare class OwneySDK {
624
632
  private depositWithFallback;
625
633
  private getMinDepositAmount;
626
634
  private splitDepositAmount;
635
+ private formatAgentName;
627
636
  private validateMinDepositAmount;
628
637
  /**
629
638
  * Whether the user already holds a non-zero balance with `agent` for the
@@ -819,7 +828,7 @@ declare class YieldseekerAgent implements IAgent {
819
828
  private invalidResponse;
820
829
  }
821
830
 
822
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_BALANCE_UNAVAILABLE" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "AGENT_API_ERROR" | "AGENT_AUTH_FAILED" | "AGENT_INVALID_RESPONSE" | "AGENT_TIMEOUT" | "AGENT_TRANSACTION_REVERTED" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
831
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "AGENT_ACTIVATION_PARTIAL_FAILURE" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_BALANCE_UNAVAILABLE" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "AGENT_API_ERROR" | "AGENT_AUTH_FAILED" | "AGENT_INVALID_RESPONSE" | "AGENT_TIMEOUT" | "AGENT_TRANSACTION_REVERTED" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
823
832
  declare class OwneyError extends Error {
824
833
  readonly code: OwneyErrorCode;
825
834
  readonly details?: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -2652,19 +2652,27 @@ function assetAddressValue(record, address) {
2652
2652
  );
2653
2653
  return entry?.[1] ?? "0";
2654
2654
  }
2655
- function position(value, asset) {
2655
+ function position(value, asset, baseAssetDecimals) {
2656
2656
  const option = value?.yieldOption;
2657
2657
  if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
2658
2658
  return invalid("yield positions", "missing vault metadata");
2659
2659
  }
2660
- const decimals = YIELDSEEKER_ASSET_METADATA[asset].decimals;
2661
2660
  return {
2662
2661
  chain: "BASE",
2663
2662
  protocol: option.provider,
2664
2663
  protocolId: option.address,
2665
2664
  pool: option.name,
2666
2665
  asset,
2667
- amount: decimal(value.assetsRaw, decimals, "yield positions"),
2666
+ // `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
2667
+ // differ from the underlying asset. Yieldseeker already converts it to
2668
+ // underlying base-asset units in `assetsBase`; pair that value with the
2669
+ // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2670
+ // share quantity separately because withdraw-from-position expects it.
2671
+ amount: decimal(
2672
+ value.assetsBase,
2673
+ baseAssetDecimals,
2674
+ "yield positions"
2675
+ ),
2668
2676
  amountRaw: String(value.assetsRaw),
2669
2677
  apy: percent(option.riskAdjustedApy),
2670
2678
  tvl: Number(option.totalDepositsUsd),
@@ -2688,7 +2696,13 @@ function mapYieldseekerBalances(contexts) {
2688
2696
  amount: decimal(idle, metadata.decimals, "snapshot")
2689
2697
  });
2690
2698
  positions.push(
2691
- ...context.positions.map((entry) => position(entry, context.asset))
2699
+ ...context.positions.map(
2700
+ (entry) => position(
2701
+ entry,
2702
+ context.asset,
2703
+ context.snapshot.baseAssetDecimals
2704
+ )
2705
+ )
2692
2706
  );
2693
2707
  totalUsd += usd(
2694
2708
  raw(context.snapshot.totalValueBase, "snapshot"),
@@ -4644,17 +4658,25 @@ var OwneySDK = class {
4644
4658
  * at a time anyway.
4645
4659
  *
4646
4660
  * Every agent is attempted even if an earlier one fails, so one declined
4647
- * signature can't deny the remaining agents their turn. The first failure is
4648
- * rethrown (matching the previous `Promise.all` rejection) once all agents
4649
- * have had a chance to activate.
4661
+ * signature can't deny the remaining agents their turn. Once all agents have
4662
+ * had a chance, a partial failure identifies the agents that still need a
4663
+ * retry; if none activated, the original provider error is preserved.
4650
4664
  */
4651
4665
  async activateAgentsInTurn(agents, state, chainId, asset) {
4652
4666
  let firstError = null;
4667
+ const activatedAgentIds = [];
4668
+ const failedAgents = [];
4653
4669
  for (const agent of agents) {
4654
4670
  try {
4655
4671
  await agent.activateAgent(state, chainId, asset);
4656
4672
  await this.applyOrgPolicyTo(agent, state, chainId);
4673
+ activatedAgentIds.push(agent.id);
4657
4674
  } catch (error) {
4675
+ failedAgents.push({
4676
+ agentId: agent.id,
4677
+ code: error instanceof OwneyError ? error.code : void 0,
4678
+ message: error instanceof Error ? error.message : String(error)
4679
+ });
4658
4680
  if (firstError === null) {
4659
4681
  firstError = error;
4660
4682
  } else {
@@ -4662,10 +4684,19 @@ var OwneySDK = class {
4662
4684
  }
4663
4685
  }
4664
4686
  }
4665
- if (firstError !== null) throw firstError;
4687
+ if (firstError === null) return;
4688
+ if (activatedAgentIds.length === 0) throw firstError;
4689
+ const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
4690
+ const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
4691
+ const remainingNames = failedAgentIds.map(this.formatAgentName).join(", ");
4692
+ throw new OwneyError(
4693
+ "AGENT_ACTIVATION_PARTIAL_FAILURE",
4694
+ `${activeNames} activated, but ${remainingNames} still needs activation. Try again and approve the remaining wallet request.`,
4695
+ { activatedAgentIds, failedAgentIds, failures: failedAgents }
4696
+ );
4666
4697
  }
4667
4698
  /**
4668
- * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
4699
+ * Deposit funds into a specific agent, or distribute across all agents if agentId is omitted.
4669
4700
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
4670
4701
  * @param options - Deposit parameters
4671
4702
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -4673,7 +4704,8 @@ var OwneySDK = class {
4673
4704
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
4674
4705
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
4675
4706
  * split amount and smart wallet address — expect multiple wallet prompts.
4676
- * @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
4707
+ * @param options.agentId - Optional. Target agent. Omit to fund all eligible agents. Deposits
4708
+ * are equal once every agent is funded; first/recovery deposits also honor each agent's floor.
4677
4709
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
4678
4710
  */
4679
4711
  async deposit(options) {
@@ -4725,10 +4757,35 @@ var OwneySDK = class {
4725
4757
  exempt
4726
4758
  );
4727
4759
  if (agentAmounts.length === 0) {
4760
+ const agentsRequiringActivation = eligibleAgents.filter(
4761
+ (agent) => !exempt.has(agent.id)
4762
+ );
4763
+ const perAgentMinimum = agentsRequiringActivation.reduce(
4764
+ (highest, agent) => {
4765
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
4766
+ return minimum > highest ? minimum : highest;
4767
+ },
4768
+ 0n
4769
+ );
4770
+ const minimumRequired = perAgentMinimum * BigInt(agentsRequiringActivation.length);
4728
4771
  throw new OwneyError(
4729
4772
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
4730
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
4731
- { amount }
4773
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
4774
+ {
4775
+ amount,
4776
+ asset,
4777
+ chainId,
4778
+ minDepositAmount: minimumRequired.toString(),
4779
+ perAgentDepositAmount: perAgentMinimum.toString(),
4780
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
4781
+ agentId: agent.id,
4782
+ minDepositAmount: this.getMinDepositAmount(
4783
+ agent,
4784
+ chainId,
4785
+ asset
4786
+ ).toString()
4787
+ }))
4788
+ }
4732
4789
  );
4733
4790
  }
4734
4791
  const agentResults = {};
@@ -4814,6 +4871,20 @@ var OwneySDK = class {
4814
4871
  }
4815
4872
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
4816
4873
  if (agents.length === 0) return [];
4874
+ const agentsRequiringActivation = agents.filter(
4875
+ (agent) => !exempt.has(agent.id)
4876
+ );
4877
+ if (agentsRequiringActivation.length === agents.length) {
4878
+ const highestMinimum2 = agents.reduce(
4879
+ (highest, agent) => {
4880
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
4881
+ return minimum > highest ? minimum : highest;
4882
+ },
4883
+ 0n
4884
+ );
4885
+ const minimumRequired2 = highestMinimum2 * BigInt(agents.length);
4886
+ if (totalAmount < minimumRequired2) return [];
4887
+ }
4817
4888
  const perAgent = totalAmount / BigInt(agents.length);
4818
4889
  const remainder = totalAmount % BigInt(agents.length);
4819
4890
  const splits = agents.map((agent, i) => ({
@@ -4826,13 +4897,29 @@ var OwneySDK = class {
4826
4897
  if (valid2.length === agents.length) {
4827
4898
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4828
4899
  }
4829
- return this.splitDepositAmount(
4830
- totalAmount,
4831
- valid2.map((s) => s.agent),
4832
- chainId,
4833
- asset,
4834
- exempt
4900
+ const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : valid2.map((s) => s.agent);
4901
+ const highestMinimum = targets.reduce(
4902
+ (highest, agent) => {
4903
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
4904
+ return minimum > highest ? minimum : highest;
4905
+ },
4906
+ 0n
4835
4907
  );
4908
+ const minimumRequired = highestMinimum * BigInt(targets.length);
4909
+ if (totalAmount < minimumRequired) return [];
4910
+ const targetShare = totalAmount / BigInt(targets.length);
4911
+ const targetRemainder = totalAmount % BigInt(targets.length);
4912
+ return targets.map((agent, index) => ({
4913
+ agent,
4914
+ amount: String(
4915
+ targetShare + (index === targets.length - 1 ? targetRemainder : 0n)
4916
+ )
4917
+ }));
4918
+ }
4919
+ formatAgentName(agentId) {
4920
+ if (agentId === "zyfai") return "Zyfai";
4921
+ if (agentId === "yieldseeker") return "Yieldseeker";
4922
+ return agentId;
4836
4923
  }
4837
4924
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
4838
4925
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -4864,7 +4951,20 @@ var OwneySDK = class {
4864
4951
  const token = balance.tokens.find(
4865
4952
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
4866
4953
  );
4867
- return !!token && Number(token.amount) > 0;
4954
+ const chainNameById = {
4955
+ 1: "ETHEREUM",
4956
+ 8453: "BASE",
4957
+ 42161: "ARBITRUM"
4958
+ };
4959
+ const targetChainName = chainNameById[chainId];
4960
+ const position2 = (balance.positions ?? []).find(
4961
+ (p) => {
4962
+ const positionChain = p.chain.trim().toUpperCase();
4963
+ const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
4964
+ return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
4965
+ }
4966
+ );
4967
+ return !!token && Number(token.amount) > 0 || !!position2;
4868
4968
  } catch {
4869
4969
  return false;
4870
4970
  }
@@ -5087,8 +5187,12 @@ var OwneySDK = class {
5087
5187
  const state = this.requireState();
5088
5188
  const chainId = this.requireChainId();
5089
5189
  if (agentId) {
5090
- const result = await this.getAgent(agentId).getBalances(state, chainId);
5091
- return result;
5190
+ const agent = this.getAgent(agentId);
5191
+ const result = await agent.getBalances(state, chainId);
5192
+ return {
5193
+ ...result,
5194
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5195
+ };
5092
5196
  }
5093
5197
  let totalBalance = 0;
5094
5198
  const results = {};
@@ -5096,7 +5200,13 @@ var OwneySDK = class {
5096
5200
  const balanceResults = await Promise.allSettled(
5097
5201
  entries.map(async ([id, agent]) => {
5098
5202
  const b = await agent.getBalances(state, chainId);
5099
- return [id, b];
5203
+ return [
5204
+ id,
5205
+ {
5206
+ ...b,
5207
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5208
+ }
5209
+ ];
5100
5210
  })
5101
5211
  );
5102
5212
  let successCount = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.16-beta.4",
3
+ "version": "0.7.16-beta.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",