@owney/sdk 0.7.16-beta.3 → 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"),
@@ -2956,6 +2970,18 @@ function mapYieldseekerAgentApy(options, days) {
2956
2970
 
2957
2971
  // src/agents/yieldseeker/yieldseeker.agent.ts
2958
2972
  var OWNEY_AGENT_NAME = "owney";
2973
+ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
2974
+ var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
2975
+ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
2976
+ function generateYieldseekerUsername() {
2977
+ const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
2978
+ return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
2979
+ }
2980
+ function isUsernameConflict(error) {
2981
+ if (!(error instanceof YieldseekerApiError)) return false;
2982
+ const code = error.providerCode.toUpperCase();
2983
+ return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
2984
+ }
2959
2985
  var YIELDSEEKER_AGENT_WALLET_ABI = [
2960
2986
  {
2961
2987
  type: "function",
@@ -3295,21 +3321,26 @@ var YieldseekerAgent = class {
3295
3321
  throw this.mapApiError(error);
3296
3322
  }
3297
3323
  let created;
3298
- try {
3299
- created = await this.providerRequest(
3300
- state,
3301
- chainId,
3302
- "/users",
3303
- {
3304
- method: "POST",
3305
- body: {
3306
- walletAddress,
3307
- username: `owney-${walletAddress.slice(2).toLowerCase()}`
3324
+ for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3325
+ try {
3326
+ created = await this.providerRequest(
3327
+ state,
3328
+ chainId,
3329
+ "/users",
3330
+ {
3331
+ method: "POST",
3332
+ body: {
3333
+ walletAddress,
3334
+ username: generateYieldseekerUsername()
3335
+ }
3308
3336
  }
3309
- }
3310
- );
3311
- } catch (createError) {
3312
- throw this.mapApiError(createError);
3337
+ );
3338
+ break;
3339
+ } catch (createError) {
3340
+ const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3341
+ if (canRetry) continue;
3342
+ throw this.mapApiError(createError);
3343
+ }
3313
3344
  }
3314
3345
  user = created?.user ?? null;
3315
3346
  }
@@ -4644,17 +4675,25 @@ var OwneySDK = class {
4644
4675
  * at a time anyway.
4645
4676
  *
4646
4677
  * 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.
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.
4650
4681
  */
4651
4682
  async activateAgentsInTurn(agents, state, chainId, asset) {
4652
4683
  let firstError = null;
4684
+ const activatedAgentIds = [];
4685
+ const failedAgents = [];
4653
4686
  for (const agent of agents) {
4654
4687
  try {
4655
4688
  await agent.activateAgent(state, chainId, asset);
4656
4689
  await this.applyOrgPolicyTo(agent, state, chainId);
4690
+ activatedAgentIds.push(agent.id);
4657
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
+ });
4658
4697
  if (firstError === null) {
4659
4698
  firstError = error;
4660
4699
  } else {
@@ -4662,10 +4701,19 @@ var OwneySDK = class {
4662
4701
  }
4663
4702
  }
4664
4703
  }
4665
- 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
+ );
4666
4714
  }
4667
4715
  /**
4668
- * 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.
4669
4717
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
4670
4718
  * @param options - Deposit parameters
4671
4719
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -4673,7 +4721,8 @@ var OwneySDK = class {
4673
4721
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
4674
4722
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
4675
4723
  * split amount and smart wallet address — expect multiple wallet prompts.
4676
- * @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.
4677
4726
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
4678
4727
  */
4679
4728
  async deposit(options) {
@@ -4725,10 +4774,35 @@ var OwneySDK = class {
4725
4774
  exempt
4726
4775
  );
4727
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);
4728
4788
  throw new OwneyError(
4729
4789
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
4730
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
4731
- { 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
+ }
4732
4806
  );
4733
4807
  }
4734
4808
  const agentResults = {};
@@ -4814,6 +4888,20 @@ var OwneySDK = class {
4814
4888
  }
4815
4889
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
4816
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
+ }
4817
4905
  const perAgent = totalAmount / BigInt(agents.length);
4818
4906
  const remainder = totalAmount % BigInt(agents.length);
4819
4907
  const splits = agents.map((agent, i) => ({
@@ -4826,13 +4914,29 @@ var OwneySDK = class {
4826
4914
  if (valid2.length === agents.length) {
4827
4915
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4828
4916
  }
4829
- return this.splitDepositAmount(
4830
- totalAmount,
4831
- valid2.map((s) => s.agent),
4832
- chainId,
4833
- asset,
4834
- 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
4835
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;
4836
4940
  }
4837
4941
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
4838
4942
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -4864,7 +4968,20 @@ var OwneySDK = class {
4864
4968
  const token = balance.tokens.find(
4865
4969
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
4866
4970
  );
4867
- 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;
4868
4985
  } catch {
4869
4986
  return false;
4870
4987
  }
@@ -5087,8 +5204,12 @@ var OwneySDK = class {
5087
5204
  const state = this.requireState();
5088
5205
  const chainId = this.requireChainId();
5089
5206
  if (agentId) {
5090
- const result = await this.getAgent(agentId).getBalances(state, chainId);
5091
- 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
+ };
5092
5213
  }
5093
5214
  let totalBalance = 0;
5094
5215
  const results = {};
@@ -5096,7 +5217,13 @@ var OwneySDK = class {
5096
5217
  const balanceResults = await Promise.allSettled(
5097
5218
  entries.map(async ([id, agent]) => {
5098
5219
  const b = await agent.getBalances(state, chainId);
5099
- return [id, b];
5220
+ return [
5221
+ id,
5222
+ {
5223
+ ...b,
5224
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5225
+ }
5226
+ ];
5100
5227
  })
5101
5228
  );
5102
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"),
@@ -2935,6 +2949,18 @@ function mapYieldseekerAgentApy(options, days) {
2935
2949
 
2936
2950
  // src/agents/yieldseeker/yieldseeker.agent.ts
2937
2951
  var OWNEY_AGENT_NAME = "owney";
2952
+ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
2953
+ var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
2954
+ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
2955
+ function generateYieldseekerUsername() {
2956
+ const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
2957
+ return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
2958
+ }
2959
+ function isUsernameConflict(error) {
2960
+ if (!(error instanceof YieldseekerApiError)) return false;
2961
+ const code = error.providerCode.toUpperCase();
2962
+ return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
2963
+ }
2938
2964
  var YIELDSEEKER_AGENT_WALLET_ABI = [
2939
2965
  {
2940
2966
  type: "function",
@@ -3274,21 +3300,26 @@ var YieldseekerAgent = class {
3274
3300
  throw this.mapApiError(error);
3275
3301
  }
3276
3302
  let created;
3277
- try {
3278
- created = await this.providerRequest(
3279
- state,
3280
- chainId,
3281
- "/users",
3282
- {
3283
- method: "POST",
3284
- body: {
3285
- walletAddress,
3286
- username: `owney-${walletAddress.slice(2).toLowerCase()}`
3303
+ for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3304
+ try {
3305
+ created = await this.providerRequest(
3306
+ state,
3307
+ chainId,
3308
+ "/users",
3309
+ {
3310
+ method: "POST",
3311
+ body: {
3312
+ walletAddress,
3313
+ username: generateYieldseekerUsername()
3314
+ }
3287
3315
  }
3288
- }
3289
- );
3290
- } catch (createError) {
3291
- throw this.mapApiError(createError);
3316
+ );
3317
+ break;
3318
+ } catch (createError) {
3319
+ const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3320
+ if (canRetry) continue;
3321
+ throw this.mapApiError(createError);
3322
+ }
3292
3323
  }
3293
3324
  user = created?.user ?? null;
3294
3325
  }
@@ -4627,17 +4658,25 @@ var OwneySDK = class {
4627
4658
  * at a time anyway.
4628
4659
  *
4629
4660
  * Every agent is attempted even if an earlier one fails, so one declined
4630
- * signature can't deny the remaining agents their turn. The first failure is
4631
- * rethrown (matching the previous `Promise.all` rejection) once all agents
4632
- * 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.
4633
4664
  */
4634
4665
  async activateAgentsInTurn(agents, state, chainId, asset) {
4635
4666
  let firstError = null;
4667
+ const activatedAgentIds = [];
4668
+ const failedAgents = [];
4636
4669
  for (const agent of agents) {
4637
4670
  try {
4638
4671
  await agent.activateAgent(state, chainId, asset);
4639
4672
  await this.applyOrgPolicyTo(agent, state, chainId);
4673
+ activatedAgentIds.push(agent.id);
4640
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
+ });
4641
4680
  if (firstError === null) {
4642
4681
  firstError = error;
4643
4682
  } else {
@@ -4645,10 +4684,19 @@ var OwneySDK = class {
4645
4684
  }
4646
4685
  }
4647
4686
  }
4648
- 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
+ );
4649
4697
  }
4650
4698
  /**
4651
- * 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.
4652
4700
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
4653
4701
  * @param options - Deposit parameters
4654
4702
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -4656,7 +4704,8 @@ var OwneySDK = class {
4656
4704
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
4657
4705
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
4658
4706
  * split amount and smart wallet address — expect multiple wallet prompts.
4659
- * @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.
4660
4709
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
4661
4710
  */
4662
4711
  async deposit(options) {
@@ -4708,10 +4757,35 @@ var OwneySDK = class {
4708
4757
  exempt
4709
4758
  );
4710
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);
4711
4771
  throw new OwneyError(
4712
4772
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
4713
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
4714
- { 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
+ }
4715
4789
  );
4716
4790
  }
4717
4791
  const agentResults = {};
@@ -4797,6 +4871,20 @@ var OwneySDK = class {
4797
4871
  }
4798
4872
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
4799
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
+ }
4800
4888
  const perAgent = totalAmount / BigInt(agents.length);
4801
4889
  const remainder = totalAmount % BigInt(agents.length);
4802
4890
  const splits = agents.map((agent, i) => ({
@@ -4809,13 +4897,29 @@ var OwneySDK = class {
4809
4897
  if (valid2.length === agents.length) {
4810
4898
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4811
4899
  }
4812
- return this.splitDepositAmount(
4813
- totalAmount,
4814
- valid2.map((s) => s.agent),
4815
- chainId,
4816
- asset,
4817
- 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
4818
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;
4819
4923
  }
4820
4924
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
4821
4925
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -4847,7 +4951,20 @@ var OwneySDK = class {
4847
4951
  const token = balance.tokens.find(
4848
4952
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
4849
4953
  );
4850
- 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;
4851
4968
  } catch {
4852
4969
  return false;
4853
4970
  }
@@ -5070,8 +5187,12 @@ var OwneySDK = class {
5070
5187
  const state = this.requireState();
5071
5188
  const chainId = this.requireChainId();
5072
5189
  if (agentId) {
5073
- const result = await this.getAgent(agentId).getBalances(state, chainId);
5074
- 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
+ };
5075
5196
  }
5076
5197
  let totalBalance = 0;
5077
5198
  const results = {};
@@ -5079,7 +5200,13 @@ var OwneySDK = class {
5079
5200
  const balanceResults = await Promise.allSettled(
5080
5201
  entries.map(async ([id, agent]) => {
5081
5202
  const b = await agent.getBalances(state, chainId);
5082
- return [id, b];
5203
+ return [
5204
+ id,
5205
+ {
5206
+ ...b,
5207
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5208
+ }
5209
+ ];
5083
5210
  })
5084
5211
  );
5085
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.3",
3
+ "version": "0.7.16-beta.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",