@owney/sdk 0.2.18 → 0.3.0-beta.0

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/README.md CHANGED
@@ -280,18 +280,12 @@ console.log(apy.walletAddress); // "0x..."
280
280
  console.log(apy.weightedApyAfterFee); // 5.2
281
281
  // Per chain + token breakdown, when the agent reports it
282
282
  console.log(apy.weightedApyAfterFeeDetails?.apyPerAsset[8453]?.USDC); // 5.4
283
- // Same per-(chain, asset) breakdown surfaced under a unified key
284
- console.log(apy.apyByChainAndAsset?.[8453]?.USDC); // 5.4
285
283
 
286
284
  // All agents (balance-weighted)
287
285
  const allApy = await sdk.getAccountApy({ days: "30D" });
288
286
  console.log(allApy.totalApy); // "5.6"
289
287
  console.log(allApy.agentApy.zyfai.weightedApyAfterFee); // 6.0
290
288
  console.log(allApy.agentApy.sail.weightedApyAfterFee); // 4.0
291
- // Per (chain, asset), balance-weighted across all agents.
292
- // Cells with 0/missing APY for a given agent are excluded from that cell.
293
- console.log(allApy.apyByChainAndAsset[8453]?.USDC); // 5.3
294
- console.log(allApy.apyByChainAndAsset[42161]?.USDT); // 4.7
295
289
  ```
296
290
 
297
291
  ### `getHistory({ agentId?, filters? })`
@@ -566,21 +560,15 @@ interface OwneyEarnings {
566
560
  agentEarnings: Record<AgentId, AgentEarnings>;
567
561
  }
568
562
 
569
- type ApyByChainAndAsset = Partial<
570
- Record<OwneySupportedChainId, Partial<Record<OwneySupportedTokens, number>>>
571
- >;
572
-
573
563
  interface AccountAgentApy {
574
564
  walletAddress: string;
575
565
  weightedApyAfterFee?: number;
576
566
  weightedApyAfterFeeDetails?: AgentApyDetails;
577
- apyByChainAndAsset?: ApyByChainAndAsset;
578
567
  }
579
568
 
580
569
  interface OwneyAccountApy {
581
570
  totalApy: string;
582
571
  agentApy: Record<AgentId, AccountAgentApy>;
583
- apyByChainAndAsset: ApyByChainAndAsset;
584
572
  }
585
573
 
586
574
  type HistoryAction = "Rebalance" | "Deposit" | "Top up" | "Withdraw" | "Earned";
package/dist/index.cjs CHANGED
@@ -36,7 +36,6 @@ var P_SUPPORTED_CHAINS = {
36
36
  chainIds: [8453, 42161],
37
37
  chains: ["BASE", "ARBITRUM"]
38
38
  };
39
- var SUPPORTED_TOKENS = ["USDC", "USDT"];
40
39
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
41
40
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
42
41
  var SupportedAssets = [
@@ -210,31 +209,10 @@ function mapEarnings(raw, smartWallet) {
210
209
  tokens
211
210
  };
212
211
  }
213
- function mapWeightedApyByChain(raw) {
214
- if (!raw)
215
- return void 0;
216
- const out = {};
217
- for (const [chainKey, tokenApy] of Object.entries(raw)) {
218
- const chainId = Number(chainKey);
219
- if (!SUPPORTED_CHAIN_IDS.includes(chainId))
220
- continue;
221
- const perAsset = {};
222
- for (const [symbol, apy] of Object.entries(tokenApy)) {
223
- const token = symbol;
224
- if (!SUPPORTED_TOKENS.includes(token))
225
- continue;
226
- perAsset[token] = apy;
227
- }
228
- if (Object.keys(perAsset).length > 0)
229
- out[chainId] = perAsset;
230
- }
231
- return Object.keys(out).length > 0 ? out : void 0;
232
- }
233
212
  function mapApyHistory(raw) {
234
213
  return {
235
214
  walletAddress: raw.walletAddress,
236
- weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
237
- apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain)
215
+ weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0
238
216
  };
239
217
  }
240
218
  function mapHistory(raw, chainId) {
@@ -578,6 +556,19 @@ async function fetchAgentKeys(apiKey) {
578
556
  }
579
557
 
580
558
  // src/lib/helpers/withdraw-helper.ts
559
+ var import_viem = require("viem");
560
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
561
+ const target = asset.toUpperCase();
562
+ return agents.map((agent) => {
563
+ const agentBalance = aggregated[agent.id];
564
+ const tokenBalance = agentBalance?.tokens.find(
565
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
566
+ );
567
+ if (!tokenBalance)
568
+ return { agent, balance: 0n };
569
+ return { agent, balance: (0, import_viem.parseUnits)(tokenBalance.amount, decimals) };
570
+ });
571
+ }
581
572
  function planProportionalShares(balances, requested, totalAvailable) {
582
573
  const plans = balances.map(({ agent, balance }) => ({
583
574
  agent,
@@ -602,8 +593,24 @@ function planProportionalShares(balances, requested, totalAvailable) {
602
593
  }
603
594
  return plans;
604
595
  }
605
- function redistributeShare(plans, fromIndex, amount) {
606
- const candidates = plans.slice(fromIndex + 1).filter((c) => c.balance - c.planned > 0n);
596
+ function planDisabledDrain(disabled, requested) {
597
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
598
+ const plans = [];
599
+ let remaining = requested;
600
+ for (const { agent, balance } of sorted) {
601
+ if (remaining === 0n) {
602
+ plans.push({ agent, balance, planned: 0n });
603
+ continue;
604
+ }
605
+ const take = balance < remaining ? balance : remaining;
606
+ plans.push({ agent, balance, planned: take });
607
+ remaining -= take;
608
+ }
609
+ return { plans, remaining };
610
+ }
611
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
612
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
613
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
607
614
  const totalHeadroom = candidates.reduce(
608
615
  (s, c) => s + (c.balance - c.planned),
609
616
  0n
@@ -634,51 +641,16 @@ function sumWithdrawnAmount(results) {
634
641
  return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
635
642
  }
636
643
 
637
- // src/lib/helpers/account-apy-helper.ts
638
- function aggregateApyByChainAndAsset(agentApys, agentBalances) {
639
- const sums = {};
640
- const weights = {};
641
- for (const id of Object.keys(agentApys)) {
642
- const cells = agentApys[id].apyByChainAndAsset;
643
- const balance = agentBalances[id] ?? 0;
644
- if (!cells || balance <= 0)
645
- continue;
646
- for (const [chainKey, perAsset] of Object.entries(cells)) {
647
- if (!perAsset)
648
- continue;
649
- const chainId = Number(chainKey);
650
- for (const [asset, apyValue] of Object.entries(perAsset)) {
651
- const apy = Number(apyValue ?? 0);
652
- if (apy === 0)
653
- continue;
654
- sums[chainId] ??= {};
655
- weights[chainId] ??= {};
656
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
657
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
658
- }
659
- }
660
- }
661
- const out = {};
662
- for (const chainKey of Object.keys(sums)) {
663
- const chainId = Number(chainKey);
664
- const perAssetOut = {};
665
- for (const asset of Object.keys(sums[chainId])) {
666
- const w = weights[chainId][asset];
667
- if (w > 0)
668
- perAssetOut[asset] = sums[chainId][asset] / w;
669
- }
670
- if (Object.keys(perAssetOut).length > 0) {
671
- out[chainId] = perAssetOut;
672
- }
673
- }
674
- return out;
675
- }
676
-
677
644
  // src/client.ts
678
- var import_viem = require("viem");
679
645
  var OwneySDK = class {
680
646
  agents = /* @__PURE__ */ new Map();
681
647
  activeAgents = /* @__PURE__ */ new Set();
648
+ /**
649
+ * Agents whose routing flag is_enabled === false. They remain in
650
+ * `this.agents` and may still be added to `activeAgents` for read +
651
+ * withdraw, but they are excluded from new deposits / activations.
652
+ */
653
+ disabledAgents = /* @__PURE__ */ new Set();
682
654
  state = null;
683
655
  apiKey;
684
656
  zyfaiRpcUrls;
@@ -715,8 +687,12 @@ var OwneySDK = class {
715
687
  await agent.disconnect();
716
688
  }
717
689
  this.activeAgents.clear();
690
+ this.disabledAgents.clear();
718
691
  this.state = null;
719
692
  }
693
+ isAgentDisabled(agentId) {
694
+ return this.disabledAgents.has(agentId);
695
+ }
720
696
  /**
721
697
  * Check if the SDK has an active connection state.
722
698
  * @returns `true` if `connect()` has been called and `disconnect()` has not
@@ -768,11 +744,15 @@ var OwneySDK = class {
768
744
  if (!this.initializingAgentsPromise) {
769
745
  this.initializingAgentsPromise = (async () => {
770
746
  const agentKeys = await fetchAgentKeys(this.apiKey);
771
- for (const { key, agent_type } of agentKeys) {
747
+ this.disabledAgents.clear();
748
+ for (const { key, agent_type, is_enabled } of agentKeys) {
772
749
  const agent = this.createAgent(agent_type, key);
773
750
  if (!agent)
774
751
  continue;
775
752
  this.agents.set(agent_type, agent);
753
+ if (is_enabled === false) {
754
+ this.disabledAgents.add(agent_type);
755
+ }
776
756
  }
777
757
  if (this.agents.size === 0) {
778
758
  throw new OwneyError(
@@ -817,6 +797,16 @@ var OwneySDK = class {
817
797
  }
818
798
  const uniqueIds = [...new Set(agentId)];
819
799
  const agents = uniqueIds.map((id) => this.getAgent(id));
800
+ const disabledRequested = uniqueIds.filter(
801
+ (id) => this.isAgentDisabled(id)
802
+ );
803
+ if (disabledRequested.length > 0) {
804
+ throw new OwneyError(
805
+ "AGENT_DISABLED",
806
+ `Cannot activate disabled agent(s): ${disabledRequested.join(", ")}.`,
807
+ { agentIds: disabledRequested }
808
+ );
809
+ }
820
810
  const incompatibleAgents = agents.filter(
821
811
  (agent) => !agent.supportedChainIds.includes(chainId)
822
812
  ).map((agent) => ({
@@ -855,8 +845,11 @@ var OwneySDK = class {
855
845
  this.activeAgents.add(agent.id);
856
846
  }
857
847
  state.chainId = chainId;
848
+ const enabledCompatible = compatible.filter(
849
+ (agent) => !this.isAgentDisabled(agent.id)
850
+ );
858
851
  Promise.all(
859
- compatible.map(async (agent) => {
852
+ enabledCompatible.map(async (agent) => {
860
853
  await agent.activateAgent(state, chainId);
861
854
  })
862
855
  ).catch((error) => {
@@ -883,13 +876,22 @@ var OwneySDK = class {
883
876
  const state = this.requireState();
884
877
  const chainId = this.requireChainId();
885
878
  if (agentId) {
879
+ if (this.isAgentDisabled(agentId)) {
880
+ throw new OwneyError(
881
+ "AGENT_DISABLED",
882
+ `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
883
+ { agentId }
884
+ );
885
+ }
886
886
  const agent = this.getAgent(agentId);
887
887
  this.validateAssetSupport(agent, chainId, asset);
888
888
  this.validateMinDepositAmount(agent, chainId, asset, amount);
889
889
  return agent.deposit(state, chainId, amount, depositCallback);
890
890
  }
891
891
  const totalAmount = BigInt(amount);
892
- const eligibleAgents = this.getEligibleAgents(chainId, asset);
892
+ const eligibleAgents = this.getEligibleAgents(chainId, asset, {
893
+ excludeDisabled: true
894
+ });
893
895
  const agentAmounts = this.splitDepositAmount(
894
896
  totalAmount,
895
897
  eligibleAgents,
@@ -973,8 +975,11 @@ var OwneySDK = class {
973
975
  );
974
976
  }
975
977
  }
976
- getEligibleAgents(chainId, asset) {
978
+ getEligibleAgents(chainId, asset, opts) {
977
979
  const eligible = [...this.agents.values()].filter((agent) => {
980
+ if (opts?.excludeDisabled && this.isAgentDisabled(agent.id)) {
981
+ return false;
982
+ }
978
983
  const chainAssets = agent.supportedAssets.find(
979
984
  (sa) => sa.chainId === chainId
980
985
  );
@@ -1041,16 +1046,13 @@ var OwneySDK = class {
1041
1046
  }
1042
1047
  const requested = BigInt(amount);
1043
1048
  const aggregated = await this.getBalances();
1044
- const balances = eligibleAgents.map((agent) => {
1045
- const agentBalance = aggregated.agentBalances[agent.id];
1046
- const tokenBalance = agentBalance?.tokens.find(
1047
- (t) => t.chainId === chainId && t.asset.toUpperCase() === asset.toUpperCase()
1048
- );
1049
- if (!tokenBalance)
1050
- return { agent, balance: 0n };
1051
- const balance = (0, import_viem.parseUnits)(tokenBalance.amount, assetInfo.decimals);
1052
- return { agent, balance };
1053
- });
1049
+ const balances = projectAgentBalancesForAsset(
1050
+ eligibleAgents,
1051
+ aggregated.agentBalances,
1052
+ chainId,
1053
+ asset,
1054
+ assetInfo.decimals
1055
+ );
1054
1056
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
1055
1057
  if (totalAvailable < requested) {
1056
1058
  throw new OwneyError(
@@ -1063,7 +1065,26 @@ var OwneySDK = class {
1063
1065
  }
1064
1066
  );
1065
1067
  }
1066
- const plans = planProportionalShares(balances, requested, totalAvailable);
1068
+ const disabledBalances = balances.filter(
1069
+ (b) => this.isAgentDisabled(b.agent.id)
1070
+ );
1071
+ const enabledBalances = balances.filter(
1072
+ (b) => !this.isAgentDisabled(b.agent.id)
1073
+ );
1074
+ const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
1075
+ disabledBalances,
1076
+ requested
1077
+ );
1078
+ const enabledTotal = enabledBalances.reduce(
1079
+ (s, b) => s + b.balance,
1080
+ 0n
1081
+ );
1082
+ const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
1083
+ agent,
1084
+ balance,
1085
+ planned: 0n
1086
+ }));
1087
+ const plans = [...disabledPlans, ...enabledPlans];
1067
1088
  const results = {};
1068
1089
  const agentErrors = {};
1069
1090
  for (let i = 0; i < plans.length; i++) {
@@ -1080,7 +1101,9 @@ var OwneySDK = class {
1080
1101
  } catch (err) {
1081
1102
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
1082
1103
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
1083
- redistributeShare(plans, i, p.planned);
1104
+ const failedAmount = p.planned;
1105
+ p.planned = 0n;
1106
+ redistributeShare(plans, i, failedAmount);
1084
1107
  }
1085
1108
  }
1086
1109
  if (Object.keys(results).length === 0) {
@@ -1246,11 +1269,9 @@ var OwneySDK = class {
1246
1269
  totalApy += agentApy * weight;
1247
1270
  }
1248
1271
  }
1249
- const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
1250
1272
  return {
1251
1273
  totalApy: String(totalApy),
1252
- agentApy: results,
1253
- apyByChainAndAsset
1274
+ agentApy: results
1254
1275
  };
1255
1276
  }
1256
1277
  /**
package/dist/index.d.cts CHANGED
@@ -139,17 +139,14 @@ interface OwneyEarnings {
139
139
  totalEarnings: string;
140
140
  agentEarnings: Record<AgentId, AgentEarnings>;
141
141
  }
142
- type ApyByChainAndAsset = Partial<Record<OwneySupportedChainId, Partial<Record<OwneySupportedTokens, number>>>>;
143
142
  interface AccountAgentApy {
144
143
  walletAddress: string;
145
144
  weightedApyAfterFee?: number;
146
145
  weightedApyAfterFeeDetails?: AgentApyDetails;
147
- apyByChainAndAsset?: ApyByChainAndAsset;
148
146
  }
149
147
  interface OwneyAccountApy {
150
148
  totalApy: string;
151
149
  agentApy: Record<AgentId, AccountAgentApy>;
152
- apyByChainAndAsset: ApyByChainAndAsset;
153
150
  }
154
151
  type HistoryAction = "Rebalance" | "Deposit" | "Top up" | "Withdraw" | "Earned";
155
152
  interface HistoryTransaction {
@@ -211,6 +208,12 @@ interface OwneyAgentAllocation {
211
208
  declare class OwneySDK {
212
209
  private agents;
213
210
  private activeAgents;
211
+ /**
212
+ * Agents whose routing flag is_enabled === false. They remain in
213
+ * `this.agents` and may still be added to `activeAgents` for read +
214
+ * withdraw, but they are excluded from new deposits / activations.
215
+ */
216
+ private disabledAgents;
214
217
  private state;
215
218
  private apiKey;
216
219
  private zyfaiRpcUrls?;
@@ -227,6 +230,7 @@ declare class OwneySDK {
227
230
  * After calling this, `connect()` must be called again before using agent methods.
228
231
  */
229
232
  disconnect(): Promise<void>;
233
+ private isAgentDisabled;
230
234
  /**
231
235
  * Check if the SDK has an active connection state.
232
236
  * @returns `true` if `connect()` has been called and `disconnect()` has not
@@ -349,7 +353,7 @@ declare class OwneySDK {
349
353
  getAgentAllocation(): Promise<OwneyAgentAllocation>;
350
354
  }
351
355
 
352
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
356
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
353
357
  declare class OwneyError extends Error {
354
358
  readonly code: OwneyErrorCode;
355
359
  readonly details?: Record<string, unknown>;
@@ -374,4 +378,4 @@ declare class AgentChainIncompatibleError extends OwneyError {
374
378
  }>, connectedChainId: number);
375
379
  }
376
380
 
377
- export { AccountAgentApy, AccountApyOptions, AgentApy, AgentApyDetails, AgentBalance, AgentChainIncompatibleError, AgentEarnings, AgentHistoryEntry, AgentId, AgentNotFoundError, AgentSupportedAsset, AgentSupportedAssets, AgentUserProfile, AgentWithdrawResult, AgentsApyOptions, ApyByChainAndAsset, Asset, ConnectionState, DailyApyDays, DepositCallback, DepositOptions, HistoryAction, HistoryFilters, HistoryOptions, HistoryTransaction, IAgent, NotConnectedError, OwneyAccountApy, OwneyAgentAllocation, OwneyAgentApy, OwneyAgentHistory, OwneyAgentStatus, OwneyBalances, OwneyDepositResult, OwneyEarnings, OwneyError, OwneyErrorCode, OwneyMultiDepositResult, OwneySDK, OwneySDKConfig, OwneySupportedChainId, OwneySupportedChains, OwneySupportedTokens, OwneyToken, OwneyUserProfile, OwneyWithdrawResult, RebalanceLog, WithdrawOptions };
381
+ export { AccountAgentApy, AccountApyOptions, AgentApy, AgentApyDetails, AgentBalance, AgentChainIncompatibleError, AgentEarnings, AgentHistoryEntry, AgentId, AgentNotFoundError, AgentSupportedAsset, AgentSupportedAssets, AgentUserProfile, AgentWithdrawResult, AgentsApyOptions, Asset, ConnectionState, DailyApyDays, DepositCallback, DepositOptions, HistoryAction, HistoryFilters, HistoryOptions, HistoryTransaction, IAgent, NotConnectedError, OwneyAccountApy, OwneyAgentAllocation, OwneyAgentApy, OwneyAgentHistory, OwneyAgentStatus, OwneyBalances, OwneyDepositResult, OwneyEarnings, OwneyError, OwneyErrorCode, OwneyMultiDepositResult, OwneySDK, OwneySDKConfig, OwneySupportedChainId, OwneySupportedChains, OwneySupportedTokens, OwneyToken, OwneyUserProfile, OwneyWithdrawResult, RebalanceLog, WithdrawOptions };
package/dist/index.d.ts CHANGED
@@ -139,17 +139,14 @@ interface OwneyEarnings {
139
139
  totalEarnings: string;
140
140
  agentEarnings: Record<AgentId, AgentEarnings>;
141
141
  }
142
- type ApyByChainAndAsset = Partial<Record<OwneySupportedChainId, Partial<Record<OwneySupportedTokens, number>>>>;
143
142
  interface AccountAgentApy {
144
143
  walletAddress: string;
145
144
  weightedApyAfterFee?: number;
146
145
  weightedApyAfterFeeDetails?: AgentApyDetails;
147
- apyByChainAndAsset?: ApyByChainAndAsset;
148
146
  }
149
147
  interface OwneyAccountApy {
150
148
  totalApy: string;
151
149
  agentApy: Record<AgentId, AccountAgentApy>;
152
- apyByChainAndAsset: ApyByChainAndAsset;
153
150
  }
154
151
  type HistoryAction = "Rebalance" | "Deposit" | "Top up" | "Withdraw" | "Earned";
155
152
  interface HistoryTransaction {
@@ -211,6 +208,12 @@ interface OwneyAgentAllocation {
211
208
  declare class OwneySDK {
212
209
  private agents;
213
210
  private activeAgents;
211
+ /**
212
+ * Agents whose routing flag is_enabled === false. They remain in
213
+ * `this.agents` and may still be added to `activeAgents` for read +
214
+ * withdraw, but they are excluded from new deposits / activations.
215
+ */
216
+ private disabledAgents;
214
217
  private state;
215
218
  private apiKey;
216
219
  private zyfaiRpcUrls?;
@@ -227,6 +230,7 @@ declare class OwneySDK {
227
230
  * After calling this, `connect()` must be called again before using agent methods.
228
231
  */
229
232
  disconnect(): Promise<void>;
233
+ private isAgentDisabled;
230
234
  /**
231
235
  * Check if the SDK has an active connection state.
232
236
  * @returns `true` if `connect()` has been called and `disconnect()` has not
@@ -349,7 +353,7 @@ declare class OwneySDK {
349
353
  getAgentAllocation(): Promise<OwneyAgentAllocation>;
350
354
  }
351
355
 
352
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
356
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
353
357
  declare class OwneyError extends Error {
354
358
  readonly code: OwneyErrorCode;
355
359
  readonly details?: Record<string, unknown>;
@@ -374,4 +378,4 @@ declare class AgentChainIncompatibleError extends OwneyError {
374
378
  }>, connectedChainId: number);
375
379
  }
376
380
 
377
- export { AccountAgentApy, AccountApyOptions, AgentApy, AgentApyDetails, AgentBalance, AgentChainIncompatibleError, AgentEarnings, AgentHistoryEntry, AgentId, AgentNotFoundError, AgentSupportedAsset, AgentSupportedAssets, AgentUserProfile, AgentWithdrawResult, AgentsApyOptions, ApyByChainAndAsset, Asset, ConnectionState, DailyApyDays, DepositCallback, DepositOptions, HistoryAction, HistoryFilters, HistoryOptions, HistoryTransaction, IAgent, NotConnectedError, OwneyAccountApy, OwneyAgentAllocation, OwneyAgentApy, OwneyAgentHistory, OwneyAgentStatus, OwneyBalances, OwneyDepositResult, OwneyEarnings, OwneyError, OwneyErrorCode, OwneyMultiDepositResult, OwneySDK, OwneySDKConfig, OwneySupportedChainId, OwneySupportedChains, OwneySupportedTokens, OwneyToken, OwneyUserProfile, OwneyWithdrawResult, RebalanceLog, WithdrawOptions };
381
+ export { AccountAgentApy, AccountApyOptions, AgentApy, AgentApyDetails, AgentBalance, AgentChainIncompatibleError, AgentEarnings, AgentHistoryEntry, AgentId, AgentNotFoundError, AgentSupportedAsset, AgentSupportedAssets, AgentUserProfile, AgentWithdrawResult, AgentsApyOptions, Asset, ConnectionState, DailyApyDays, DepositCallback, DepositOptions, HistoryAction, HistoryFilters, HistoryOptions, HistoryTransaction, IAgent, NotConnectedError, OwneyAccountApy, OwneyAgentAllocation, OwneyAgentApy, OwneyAgentHistory, OwneyAgentStatus, OwneyBalances, OwneyDepositResult, OwneyEarnings, OwneyError, OwneyErrorCode, OwneyMultiDepositResult, OwneySDK, OwneySDKConfig, OwneySupportedChainId, OwneySupportedChains, OwneySupportedTokens, OwneyToken, OwneyUserProfile, OwneyWithdrawResult, RebalanceLog, WithdrawOptions };
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ var P_SUPPORTED_CHAINS = {
6
6
  chainIds: [8453, 42161],
7
7
  chains: ["BASE", "ARBITRUM"]
8
8
  };
9
- var SUPPORTED_TOKENS = ["USDC", "USDT"];
10
9
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
11
10
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
12
11
  var SupportedAssets = [
@@ -180,31 +179,10 @@ function mapEarnings(raw, smartWallet) {
180
179
  tokens
181
180
  };
182
181
  }
183
- function mapWeightedApyByChain(raw) {
184
- if (!raw)
185
- return void 0;
186
- const out = {};
187
- for (const [chainKey, tokenApy] of Object.entries(raw)) {
188
- const chainId = Number(chainKey);
189
- if (!SUPPORTED_CHAIN_IDS.includes(chainId))
190
- continue;
191
- const perAsset = {};
192
- for (const [symbol, apy] of Object.entries(tokenApy)) {
193
- const token = symbol;
194
- if (!SUPPORTED_TOKENS.includes(token))
195
- continue;
196
- perAsset[token] = apy;
197
- }
198
- if (Object.keys(perAsset).length > 0)
199
- out[chainId] = perAsset;
200
- }
201
- return Object.keys(out).length > 0 ? out : void 0;
202
- }
203
182
  function mapApyHistory(raw) {
204
183
  return {
205
184
  walletAddress: raw.walletAddress,
206
- weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
207
- apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain)
185
+ weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0
208
186
  };
209
187
  }
210
188
  function mapHistory(raw, chainId) {
@@ -548,6 +526,19 @@ async function fetchAgentKeys(apiKey) {
548
526
  }
549
527
 
550
528
  // src/lib/helpers/withdraw-helper.ts
529
+ import { parseUnits } from "viem";
530
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
531
+ const target = asset.toUpperCase();
532
+ return agents.map((agent) => {
533
+ const agentBalance = aggregated[agent.id];
534
+ const tokenBalance = agentBalance?.tokens.find(
535
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
536
+ );
537
+ if (!tokenBalance)
538
+ return { agent, balance: 0n };
539
+ return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
540
+ });
541
+ }
551
542
  function planProportionalShares(balances, requested, totalAvailable) {
552
543
  const plans = balances.map(({ agent, balance }) => ({
553
544
  agent,
@@ -572,8 +563,24 @@ function planProportionalShares(balances, requested, totalAvailable) {
572
563
  }
573
564
  return plans;
574
565
  }
575
- function redistributeShare(plans, fromIndex, amount) {
576
- const candidates = plans.slice(fromIndex + 1).filter((c) => c.balance - c.planned > 0n);
566
+ function planDisabledDrain(disabled, requested) {
567
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
568
+ const plans = [];
569
+ let remaining = requested;
570
+ for (const { agent, balance } of sorted) {
571
+ if (remaining === 0n) {
572
+ plans.push({ agent, balance, planned: 0n });
573
+ continue;
574
+ }
575
+ const take = balance < remaining ? balance : remaining;
576
+ plans.push({ agent, balance, planned: take });
577
+ remaining -= take;
578
+ }
579
+ return { plans, remaining };
580
+ }
581
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
582
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
583
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
577
584
  const totalHeadroom = candidates.reduce(
578
585
  (s, c) => s + (c.balance - c.planned),
579
586
  0n
@@ -604,51 +611,16 @@ function sumWithdrawnAmount(results) {
604
611
  return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
605
612
  }
606
613
 
607
- // src/lib/helpers/account-apy-helper.ts
608
- function aggregateApyByChainAndAsset(agentApys, agentBalances) {
609
- const sums = {};
610
- const weights = {};
611
- for (const id of Object.keys(agentApys)) {
612
- const cells = agentApys[id].apyByChainAndAsset;
613
- const balance = agentBalances[id] ?? 0;
614
- if (!cells || balance <= 0)
615
- continue;
616
- for (const [chainKey, perAsset] of Object.entries(cells)) {
617
- if (!perAsset)
618
- continue;
619
- const chainId = Number(chainKey);
620
- for (const [asset, apyValue] of Object.entries(perAsset)) {
621
- const apy = Number(apyValue ?? 0);
622
- if (apy === 0)
623
- continue;
624
- sums[chainId] ??= {};
625
- weights[chainId] ??= {};
626
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
627
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
628
- }
629
- }
630
- }
631
- const out = {};
632
- for (const chainKey of Object.keys(sums)) {
633
- const chainId = Number(chainKey);
634
- const perAssetOut = {};
635
- for (const asset of Object.keys(sums[chainId])) {
636
- const w = weights[chainId][asset];
637
- if (w > 0)
638
- perAssetOut[asset] = sums[chainId][asset] / w;
639
- }
640
- if (Object.keys(perAssetOut).length > 0) {
641
- out[chainId] = perAssetOut;
642
- }
643
- }
644
- return out;
645
- }
646
-
647
614
  // src/client.ts
648
- import { parseUnits } from "viem";
649
615
  var OwneySDK = class {
650
616
  agents = /* @__PURE__ */ new Map();
651
617
  activeAgents = /* @__PURE__ */ new Set();
618
+ /**
619
+ * Agents whose routing flag is_enabled === false. They remain in
620
+ * `this.agents` and may still be added to `activeAgents` for read +
621
+ * withdraw, but they are excluded from new deposits / activations.
622
+ */
623
+ disabledAgents = /* @__PURE__ */ new Set();
652
624
  state = null;
653
625
  apiKey;
654
626
  zyfaiRpcUrls;
@@ -685,8 +657,12 @@ var OwneySDK = class {
685
657
  await agent.disconnect();
686
658
  }
687
659
  this.activeAgents.clear();
660
+ this.disabledAgents.clear();
688
661
  this.state = null;
689
662
  }
663
+ isAgentDisabled(agentId) {
664
+ return this.disabledAgents.has(agentId);
665
+ }
690
666
  /**
691
667
  * Check if the SDK has an active connection state.
692
668
  * @returns `true` if `connect()` has been called and `disconnect()` has not
@@ -738,11 +714,15 @@ var OwneySDK = class {
738
714
  if (!this.initializingAgentsPromise) {
739
715
  this.initializingAgentsPromise = (async () => {
740
716
  const agentKeys = await fetchAgentKeys(this.apiKey);
741
- for (const { key, agent_type } of agentKeys) {
717
+ this.disabledAgents.clear();
718
+ for (const { key, agent_type, is_enabled } of agentKeys) {
742
719
  const agent = this.createAgent(agent_type, key);
743
720
  if (!agent)
744
721
  continue;
745
722
  this.agents.set(agent_type, agent);
723
+ if (is_enabled === false) {
724
+ this.disabledAgents.add(agent_type);
725
+ }
746
726
  }
747
727
  if (this.agents.size === 0) {
748
728
  throw new OwneyError(
@@ -787,6 +767,16 @@ var OwneySDK = class {
787
767
  }
788
768
  const uniqueIds = [...new Set(agentId)];
789
769
  const agents = uniqueIds.map((id) => this.getAgent(id));
770
+ const disabledRequested = uniqueIds.filter(
771
+ (id) => this.isAgentDisabled(id)
772
+ );
773
+ if (disabledRequested.length > 0) {
774
+ throw new OwneyError(
775
+ "AGENT_DISABLED",
776
+ `Cannot activate disabled agent(s): ${disabledRequested.join(", ")}.`,
777
+ { agentIds: disabledRequested }
778
+ );
779
+ }
790
780
  const incompatibleAgents = agents.filter(
791
781
  (agent) => !agent.supportedChainIds.includes(chainId)
792
782
  ).map((agent) => ({
@@ -825,8 +815,11 @@ var OwneySDK = class {
825
815
  this.activeAgents.add(agent.id);
826
816
  }
827
817
  state.chainId = chainId;
818
+ const enabledCompatible = compatible.filter(
819
+ (agent) => !this.isAgentDisabled(agent.id)
820
+ );
828
821
  Promise.all(
829
- compatible.map(async (agent) => {
822
+ enabledCompatible.map(async (agent) => {
830
823
  await agent.activateAgent(state, chainId);
831
824
  })
832
825
  ).catch((error) => {
@@ -853,13 +846,22 @@ var OwneySDK = class {
853
846
  const state = this.requireState();
854
847
  const chainId = this.requireChainId();
855
848
  if (agentId) {
849
+ if (this.isAgentDisabled(agentId)) {
850
+ throw new OwneyError(
851
+ "AGENT_DISABLED",
852
+ `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
853
+ { agentId }
854
+ );
855
+ }
856
856
  const agent = this.getAgent(agentId);
857
857
  this.validateAssetSupport(agent, chainId, asset);
858
858
  this.validateMinDepositAmount(agent, chainId, asset, amount);
859
859
  return agent.deposit(state, chainId, amount, depositCallback);
860
860
  }
861
861
  const totalAmount = BigInt(amount);
862
- const eligibleAgents = this.getEligibleAgents(chainId, asset);
862
+ const eligibleAgents = this.getEligibleAgents(chainId, asset, {
863
+ excludeDisabled: true
864
+ });
863
865
  const agentAmounts = this.splitDepositAmount(
864
866
  totalAmount,
865
867
  eligibleAgents,
@@ -943,8 +945,11 @@ var OwneySDK = class {
943
945
  );
944
946
  }
945
947
  }
946
- getEligibleAgents(chainId, asset) {
948
+ getEligibleAgents(chainId, asset, opts) {
947
949
  const eligible = [...this.agents.values()].filter((agent) => {
950
+ if (opts?.excludeDisabled && this.isAgentDisabled(agent.id)) {
951
+ return false;
952
+ }
948
953
  const chainAssets = agent.supportedAssets.find(
949
954
  (sa) => sa.chainId === chainId
950
955
  );
@@ -1011,16 +1016,13 @@ var OwneySDK = class {
1011
1016
  }
1012
1017
  const requested = BigInt(amount);
1013
1018
  const aggregated = await this.getBalances();
1014
- const balances = eligibleAgents.map((agent) => {
1015
- const agentBalance = aggregated.agentBalances[agent.id];
1016
- const tokenBalance = agentBalance?.tokens.find(
1017
- (t) => t.chainId === chainId && t.asset.toUpperCase() === asset.toUpperCase()
1018
- );
1019
- if (!tokenBalance)
1020
- return { agent, balance: 0n };
1021
- const balance = parseUnits(tokenBalance.amount, assetInfo.decimals);
1022
- return { agent, balance };
1023
- });
1019
+ const balances = projectAgentBalancesForAsset(
1020
+ eligibleAgents,
1021
+ aggregated.agentBalances,
1022
+ chainId,
1023
+ asset,
1024
+ assetInfo.decimals
1025
+ );
1024
1026
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
1025
1027
  if (totalAvailable < requested) {
1026
1028
  throw new OwneyError(
@@ -1033,7 +1035,26 @@ var OwneySDK = class {
1033
1035
  }
1034
1036
  );
1035
1037
  }
1036
- const plans = planProportionalShares(balances, requested, totalAvailable);
1038
+ const disabledBalances = balances.filter(
1039
+ (b) => this.isAgentDisabled(b.agent.id)
1040
+ );
1041
+ const enabledBalances = balances.filter(
1042
+ (b) => !this.isAgentDisabled(b.agent.id)
1043
+ );
1044
+ const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
1045
+ disabledBalances,
1046
+ requested
1047
+ );
1048
+ const enabledTotal = enabledBalances.reduce(
1049
+ (s, b) => s + b.balance,
1050
+ 0n
1051
+ );
1052
+ const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
1053
+ agent,
1054
+ balance,
1055
+ planned: 0n
1056
+ }));
1057
+ const plans = [...disabledPlans, ...enabledPlans];
1037
1058
  const results = {};
1038
1059
  const agentErrors = {};
1039
1060
  for (let i = 0; i < plans.length; i++) {
@@ -1050,7 +1071,9 @@ var OwneySDK = class {
1050
1071
  } catch (err) {
1051
1072
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
1052
1073
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
1053
- redistributeShare(plans, i, p.planned);
1074
+ const failedAmount = p.planned;
1075
+ p.planned = 0n;
1076
+ redistributeShare(plans, i, failedAmount);
1054
1077
  }
1055
1078
  }
1056
1079
  if (Object.keys(results).length === 0) {
@@ -1216,11 +1239,9 @@ var OwneySDK = class {
1216
1239
  totalApy += agentApy * weight;
1217
1240
  }
1218
1241
  }
1219
- const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
1220
1242
  return {
1221
1243
  totalApy: String(totalApy),
1222
- agentApy: results,
1223
- apyByChainAndAsset
1244
+ agentApy: results
1224
1245
  };
1225
1246
  }
1226
1247
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.2.18",
3
+ "version": "0.3.0-beta.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -17,16 +17,19 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build": "tsup",
20
- "dev": "tsup --watch"
20
+ "dev": "tsup --watch",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest"
21
23
  },
22
24
  "dependencies": {
23
- "@zyfai/sdk": "^0.2.34",
25
+ "@zyfai/sdk": "^0.2.33",
24
26
  "viem": "^2.48.1"
25
27
  },
26
28
  "devDependencies": {
27
29
  "dotenv": "^17.3.1",
28
30
  "tsup": "^7.0.0",
29
- "typescript": "^5.0.0"
31
+ "typescript": "^5.0.0",
32
+ "vitest": "^4.1.5"
30
33
  },
31
34
  "publishConfig": {
32
35
  "access": "public"