@owney/sdk 0.6.9-beta.8 → 0.7.0-beta.1

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.js CHANGED
@@ -1,17 +1,3 @@
1
- import {
2
- AgentChainIncompatibleError,
3
- AgentNotFoundError,
4
- ERC20_ALLOWANCE_ABI,
5
- MAX_UINT256,
6
- NotConnectedError,
7
- OwneyError,
8
- PERMIT2_ADDRESS,
9
- buildPermitTransferFromTypedData,
10
- randomPermit2Nonce,
11
- readErc20Balance,
12
- readPermit2Allowance
13
- } from "./chunk-PWIKIQOX.js";
14
-
15
1
  // src/agents/zyfai/zyfai.agent.ts
16
2
  import { ZyfaiSDK } from "@zyfai/sdk";
17
3
  import { createPublicClient, http } from "viem";
@@ -70,6 +56,55 @@ var SupportedAssets = [
70
56
  }
71
57
  ];
72
58
 
59
+ // src/errors.ts
60
+ var OwneyError = class extends Error {
61
+ code;
62
+ details;
63
+ agentId;
64
+ constructor(code, message, details, agentId) {
65
+ const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
66
+ super(`${prefix} ${message}`);
67
+ this.name = "OwneyError";
68
+ this.code = code;
69
+ this.details = details;
70
+ this.agentId = agentId;
71
+ }
72
+ };
73
+ var AgentNotFoundError = class extends OwneyError {
74
+ constructor(agentId, available) {
75
+ super(
76
+ "AGENT_NOT_FOUND",
77
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
78
+ { agentId, available },
79
+ agentId
80
+ );
81
+ this.name = "AgentNotFoundError";
82
+ }
83
+ };
84
+ var NotConnectedError = class extends OwneyError {
85
+ constructor() {
86
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
87
+ this.name = "NotConnectedError";
88
+ }
89
+ };
90
+ var AgentChainIncompatibleError = class extends OwneyError {
91
+ incompatibleAgents;
92
+ connectedChainId;
93
+ constructor(incompatibleAgents, connectedChainId) {
94
+ const details = incompatibleAgents.map(
95
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
96
+ ).join("; ");
97
+ super(
98
+ "AGENT_CHAIN_INCOMPATIBLE",
99
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
100
+ { incompatibleAgents, connectedChainId }
101
+ );
102
+ this.name = "AgentChainIncompatibleError";
103
+ this.incompatibleAgents = incompatibleAgents;
104
+ this.connectedChainId = connectedChainId;
105
+ }
106
+ };
107
+
73
108
  // src/lib/utils.ts
74
109
  var isValidChainId = (chainId) => {
75
110
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
@@ -81,12 +116,6 @@ var isValidChainId = (chainId) => {
81
116
  }
82
117
  return chainId;
83
118
  };
84
- function isSameAsset(tokenSymbol, asset) {
85
- const token = tokenSymbol.toLowerCase();
86
- const target = asset.toLowerCase();
87
- if (token === target) return true;
88
- return target === "weth" && token === "eth" || target === "eth" && token === "weth";
89
- }
90
119
  function hexToDecimal(hex, decimals = 6) {
91
120
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
92
121
  const parsed = BigInt(normalized);
@@ -150,46 +179,46 @@ function resolveChainId(chain) {
150
179
  function mapBalances(raw, _chainId, smartWallet) {
151
180
  const portfolio = raw.portfolio;
152
181
  const portfolioByChain = portfolio.portfolioByChain ?? {};
153
- const rawPositions = portfolio.positions ?? [];
154
- const detailByKey = /* @__PURE__ */ new Map();
155
- for (const p of rawPositions) {
156
- const cid = resolveChainId(p.chain ?? "");
157
- const symbol = (p.token_symbol ?? "").toUpperCase();
158
- if (Number.isFinite(cid) && symbol) detailByKey.set(`${cid}:${symbol}`, p);
159
- }
160
182
  let totalBalance = 0;
161
183
  const tokens = [];
162
- const positions = [];
163
184
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
164
185
  const positionChainId = Number(chainIdKey);
165
- const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
166
186
  for (const [symbol, { balance, decimals }] of Object.entries(
167
187
  tokensBySymbol ?? {}
168
188
  )) {
169
- const amount = hexToDecimal(balance || "0", tokenDecimals(symbol, decimals));
189
+ const amount = hexToDecimal(balance || "0", decimals);
170
190
  totalBalance += amount;
171
191
  tokens.push({
172
- chain: chainName,
192
+ chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
173
193
  chainId: positionChainId,
174
194
  asset: symbol,
175
195
  amount: String(amount)
176
196
  });
177
- const detail = detailByKey.get(`${positionChainId}:${symbol.toUpperCase()}`);
178
- positions.push({
179
- chain: chainName,
180
- protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
181
- protocolId: detail?.protocol_id,
182
- pool: detail?.pool,
183
- asset: symbol,
184
- amount: String(amount),
185
- apy: detail?.pool_apy,
186
- tvl: detail?.pool_tvl,
187
- // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
188
- // structural read keeps it undefined today and auto-populates later.
189
- liquidity: detail?.liquidity
190
- });
191
197
  }
192
198
  }
199
+ const positions = (portfolio.positions ?? []).map((p) => ({
200
+ chain: p.chain ?? "",
201
+ protocol: p.protocol_name ?? p.protocol_id ?? "",
202
+ protocolId: p.protocol_id,
203
+ pool: p.pool,
204
+ asset: p.token_symbol ?? "",
205
+ // Position amount is hex-encoded base units (like the token balances
206
+ // above). `PositionSlot` doesn't type a `decimals` field, but runtime
207
+ // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
208
+ // when present, otherwise fall back to the per-symbol lookup so
209
+ // non-6-decimal assets like WETH don't render 1e12 too large.
210
+ amount: String(
211
+ hexToDecimal(
212
+ p.amount || "0",
213
+ tokenDecimals(p.token_symbol, p.decimals)
214
+ )
215
+ ),
216
+ apy: p.pool_apy,
217
+ tvl: p.pool_tvl,
218
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
219
+ // structural read keeps it undefined today and auto-populates later.
220
+ liquidity: p.liquidity
221
+ }));
193
222
  return {
194
223
  smartWallet,
195
224
  totalBalance: String(totalBalance),
@@ -351,9 +380,7 @@ function mapUserProfile(raw, userAddress) {
351
380
  chains: raw.chains || [],
352
381
  strategy: raw.strategy,
353
382
  hasActiveSessionKey: raw.hasActiveSessionKey || false,
354
- protocols: raw.protocols || [],
355
- splitting: raw.splitting,
356
- minSplits: raw.minSplits
383
+ protocols: raw.protocols || []
357
384
  };
358
385
  }
359
386
  function mapApyByStrategy(raw) {
@@ -500,23 +527,23 @@ var readLegacySession = (store, address) => {
500
527
  const legacyKeys = [];
501
528
  try {
502
529
  for (let i = 0; i < store.length; i++) {
503
- const key2 = store.key(i);
504
- if (key2?.startsWith(prefix)) legacyKeys.push(key2);
530
+ const key = store.key(i);
531
+ if (key?.startsWith(prefix)) legacyKeys.push(key);
505
532
  }
506
533
  } catch {
507
534
  return null;
508
535
  }
509
536
  let adopted = null;
510
- for (const key2 of legacyKeys) {
537
+ for (const key of legacyKeys) {
511
538
  if (!adopted) {
512
539
  try {
513
- const parsed = JSON.parse(store.getItem(key2) ?? "");
540
+ const parsed = JSON.parse(store.getItem(key) ?? "");
514
541
  if (isFreshSession(parsed)) adopted = parsed;
515
542
  } catch {
516
543
  }
517
544
  }
518
545
  try {
519
- store.removeItem(key2);
546
+ store.removeItem(key);
520
547
  } catch {
521
548
  }
522
549
  }
@@ -524,11 +551,11 @@ var readLegacySession = (store, address) => {
524
551
  };
525
552
  var readSession = (address, _chainId) => {
526
553
  if (typeof window === "undefined") return null;
527
- const key2 = buildKey(address);
554
+ const key = buildKey(address);
528
555
  const store = storage();
529
556
  let raw = null;
530
557
  try {
531
- raw = store?.getItem(key2) ?? null;
558
+ raw = store?.getItem(key) ?? null;
532
559
  } catch {
533
560
  raw = null;
534
561
  }
@@ -538,9 +565,9 @@ var readSession = (address, _chainId) => {
538
565
  if (isFreshSession(parsed)) return parsed;
539
566
  } catch {
540
567
  }
541
- memorySessions.delete(key2);
568
+ memorySessions.delete(key);
542
569
  try {
543
- store?.removeItem(key2);
570
+ store?.removeItem(key);
544
571
  } catch {
545
572
  }
546
573
  return null;
@@ -550,27 +577,27 @@ var readSession = (address, _chainId) => {
550
577
  writeSession(address, _chainId, legacy);
551
578
  return legacy;
552
579
  }
553
- const cached = memorySessions.get(key2);
580
+ const cached = memorySessions.get(key);
554
581
  if (isFreshSession(cached)) return cached;
555
- if (cached) memorySessions.delete(key2);
582
+ if (cached) memorySessions.delete(key);
556
583
  return null;
557
584
  };
558
585
  var writeSession = (address, _chainId, session) => {
559
586
  if (typeof window === "undefined") return;
560
- const key2 = buildKey(address);
561
- memorySessions.set(key2, session);
587
+ const key = buildKey(address);
588
+ memorySessions.set(key, session);
562
589
  const store = storage();
563
590
  try {
564
- store?.setItem(key2, JSON.stringify(session));
591
+ store?.setItem(key, JSON.stringify(session));
565
592
  } catch {
566
593
  }
567
594
  };
568
595
  var clearSession = (address, _chainId) => {
569
- const key2 = buildKey(address);
570
- memorySessions.delete(key2);
596
+ const key = buildKey(address);
597
+ memorySessions.delete(key);
571
598
  const store = storage();
572
599
  try {
573
- store?.removeItem(key2);
600
+ store?.removeItem(key);
574
601
  } catch {
575
602
  }
576
603
  readLegacySession(store, address);
@@ -601,149 +628,6 @@ function buildProtocolMergeUpdate(available, current, asset) {
601
628
  };
602
629
  }
603
630
 
604
- // src/agents/zyfai/splitting.ts
605
- var MAX_MIN_SPLITS = 4;
606
- var MIN_FORCE_SPLITS = 2;
607
- var AUTOMATIC_MIN_SPLITS = 1;
608
- function toZyfaiSplitting(policy) {
609
- switch (policy.splittingMode) {
610
- case "none":
611
- return { splitting: false };
612
- case "automatic":
613
- return { splitting: true, minSplits: AUTOMATIC_MIN_SPLITS };
614
- case "force":
615
- return {
616
- splitting: true,
617
- minSplits: clampForceSplits(policy.minSplits)
618
- };
619
- }
620
- }
621
- function clampForceSplits(minSplits) {
622
- if (typeof minSplits !== "number" || !Number.isFinite(minSplits)) {
623
- return MIN_FORCE_SPLITS;
624
- }
625
- const rounded = Math.round(minSplits);
626
- if (rounded < MIN_FORCE_SPLITS) return MIN_FORCE_SPLITS;
627
- if (rounded > MAX_MIN_SPLITS) return MAX_MIN_SPLITS;
628
- return rounded;
629
- }
630
- function splittingNeedsUpdate(current, desired) {
631
- if (!current) return true;
632
- if (current.splitting !== desired.splitting) return true;
633
- if (desired.minSplits === void 0) return false;
634
- return current.minSplits !== desired.minSplits;
635
- }
636
-
637
- // src/agents/zyfai/pool-policy.ts
638
- function toZyfaiCustomizations(policy, availableByProtocol, chainsByProtocol) {
639
- const selectionByKey = /* @__PURE__ */ new Map();
640
- for (const selection of policy.selections) {
641
- selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
642
- }
643
- const customizations = [];
644
- for (const [protocolId, chainIds] of Object.entries(chainsByProtocol)) {
645
- const available = availableByProtocol.get(protocolId);
646
- if (available === void 0) {
647
- continue;
648
- }
649
- const availableSet = new Set(available);
650
- for (const chainId of chainIds) {
651
- const stored = selectionByKey.get(key(protocolId, chainId));
652
- if (stored === void 0) {
653
- customizations.push({
654
- protocolId,
655
- chainId,
656
- pools: [],
657
- autoselect: true
658
- });
659
- continue;
660
- }
661
- const pools = stored.pools.filter((pool) => availableSet.has(pool));
662
- customizations.push({
663
- protocolId,
664
- chainId,
665
- pools,
666
- autoselect: false
667
- });
668
- }
669
- }
670
- return customizations;
671
- }
672
- function deriveProtocols(policy, universe, chainsByProtocol) {
673
- if (policy.selections.length === 0) {
674
- return null;
675
- }
676
- const selectionByKey = /* @__PURE__ */ new Map();
677
- for (const selection of policy.selections) {
678
- selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
679
- }
680
- return universe.filter((protocolId) => {
681
- const chainIds = chainsByProtocol[protocolId];
682
- if (chainIds === void 0 || chainIds.length === 0) {
683
- return true;
684
- }
685
- const fullyDisabled = chainIds.every((chainId) => {
686
- const stored = selectionByKey.get(key(protocolId, chainId));
687
- return stored !== void 0 && stored.pools.length === 0;
688
- });
689
- return !fullyDisabled;
690
- });
691
- }
692
- function key(protocolId, chainId) {
693
- return `${protocolId}:${chainId}`;
694
- }
695
- function buildChainsByProtocol(protocols, dashboardChainIds) {
696
- const allowed = new Set(dashboardChainIds);
697
- const result = {};
698
- for (const protocol of protocols) {
699
- result[protocol.id] = (protocol.chains ?? []).filter(
700
- (chainId) => allowed.has(chainId)
701
- );
702
- }
703
- return result;
704
- }
705
- function flattenAvailablePools(raw) {
706
- const byChain = raw ?? {};
707
- const names = [];
708
- for (const byToken of Object.values(byChain ?? {})) {
709
- for (const entry of Object.values(byToken ?? {})) {
710
- for (const pool of entry?.pools ?? []) {
711
- if (pool?.name) names.push(pool.name);
712
- }
713
- }
714
- }
715
- return names;
716
- }
717
- function protocolListsEqual(a, b) {
718
- const left = [...a ?? []].sort();
719
- const right = [...b ?? []].sort();
720
- if (left.length !== right.length) return false;
721
- return left.every((value, index) => value === right[index]);
722
- }
723
- function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect) {
724
- if (!current) return true;
725
- if (current.autoSelectProtocols !== desiredAutoSelect) return true;
726
- if (desiredProtocols === null) return false;
727
- return !protocolListsEqual(current.protocols, desiredProtocols);
728
- }
729
-
730
- // src/lib/debug.ts
731
- var configuredDebug = false;
732
- function setOwneyDebug(enabled) {
733
- configuredDebug = enabled;
734
- }
735
- function isOwneyDebug() {
736
- return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
737
- }
738
- function debugLog(scope, message, data) {
739
- if (!isOwneyDebug()) return;
740
- if (data === void 0) {
741
- console.log(`[${scope}] ${message}`);
742
- } else {
743
- console.log(`[${scope}] ${message}`, data);
744
- }
745
- }
746
-
747
631
  // src/agents/zyfai/zyfai.agent.ts
748
632
  var DEFAULT_ZYFAI_RPC_URLS = {
749
633
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
@@ -756,12 +640,6 @@ var WETH_ADDRESS_BY_CHAIN = {
756
640
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
757
641
  };
758
642
  var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
759
- var SPLITTING_ASSETS = ["USDC", "WETH"];
760
- var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
761
- function isAlreadyActiveSessionKeyError(error) {
762
- const message = error instanceof Error ? error.message : String(error);
763
- return /already has an active session key/i.test(message);
764
- }
765
643
  var ZyfaiAgent = class {
766
644
  id = "zyfai";
767
645
  supportedChainIds = [8453, 42161, 1];
@@ -988,71 +866,21 @@ var ZyfaiAgent = class {
988
866
  return await this.sdk.connectAccount(provider, chainId);
989
867
  }
990
868
  }
991
- /** Full connect: SIWE auth + Safe deploy check + session key. */
992
- async connectFull(state, chainId) {
993
- await this.connectAuth(state, chainId);
994
- const validChainId = isValidChainId(chainId);
995
- const address = this.getAddress();
996
- const wallet = await this.sdk.getSmartWalletAddress(address, validChainId);
997
- this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
998
- if (!wallet.isDeployed) {
999
- await this.sdk.deploySafe(address, validChainId, "aggressive", false);
1000
- try {
1001
- if (validChainId === 42161) {
1002
- await this.sdk.updateUserProfile({
1003
- asset: "WETH",
1004
- chains: [validChainId],
1005
- protocols: ["c24a33fa-4a87-4376-b756-93bd3957d3ea"],
1006
- // AAVE protocol ID
1007
- autoSelectProtocols: false
1008
- });
1009
- }
1010
- } catch (err) {
1011
- console.warn(
1012
- "Failed to configure AAVE protocol after Safe deployment:",
1013
- err instanceof Error ? err.message : String(err)
1014
- );
1015
- }
1016
- }
1017
- await this.ensureSessionKey(state, address, validChainId);
1018
- }
1019
869
  /**
1020
- * Ensures the user has an active session key, creating one if absent.
1021
- *
1022
- * The Zyfai session key is a single **per-user** record, not per-chain: its
1023
- * config (`/session-keys/config`) spans every supported chain and is signed
1024
- * in one shot, and the backend rejects `POST /session-keys/add` with 400
1025
- * "User already has an active session key" if one exists. So we must NOT try
1026
- * to (re)create a key per chain — that 400s and blocks the deposit. We only
1027
- * create when the user has none.
1028
- *
1029
- * The one wrapper-side hardening that IS needed: a chain/account switch can
1030
- * fire the provider's `accountsChanged` → `disconnectAccount`, tearing down
1031
- * the zyfai connection (walletClient + authenticatedUserId both null) while
1032
- * our own `connectAuth` guard still believes this chain is connected and
1033
- * early-returns without reconnecting. `createSessionKey` then fails with
1034
- * "No account connected". Detect that and reconnect first.
1035
- *
1036
- * NOTE: whether a globally-active session key actually authorizes activity on
1037
- * a freshly-deployed Safe on a new chain is a backend/zyf question — the
1038
- * wrapper cannot force per-chain activation given the global 400 guard.
1039
- * See [[zyfai-per-chain-session-key]].
870
+ * Reconnect if the Zyfai connection was torn down behind our back: a
871
+ * chain/account switch fires the provider's `accountsChanged` →
872
+ * `disconnectAccount` (nulls walletClient + authenticatedUserId) while our
873
+ * own `connectAuth` guard still believes this chain is connected and
874
+ * early-returns. Without this, authenticated calls (depositFunds,
875
+ * withdrawFunds) fail with "No account connected".
1040
876
  */
1041
- async ensureSessionKey(state, address, chainId) {
877
+ async ensureConnected(state, chainId) {
878
+ await this.connectAuth(state, chainId);
1042
879
  const internals = this.sdk;
1043
880
  if (!internals.walletClient || internals.authenticatedUserId === null) {
1044
881
  this.connectedChainId = null;
1045
882
  await this.connectAuth(state, chainId);
1046
883
  }
1047
- const user = await this.sdk.getUserDetails();
1048
- if (!user.hasActiveSessionKey) {
1049
- try {
1050
- await this.sdk.createSessionKey(address, chainId);
1051
- } catch (error) {
1052
- if (!isAlreadyActiveSessionKeyError(error)) throw error;
1053
- internals.hasActiveSessionKey = true;
1054
- }
1055
- }
1056
884
  }
1057
885
  /**
1058
886
  * Sync newly-available protocols onto an existing user's profile at wallet
@@ -1122,211 +950,10 @@ var ZyfaiAgent = class {
1122
950
  throw error;
1123
951
  }
1124
952
  }
1125
- /**
1126
- * Apply the org's capital splitting policy to this user's Zyfai profile.
1127
- *
1128
- * Per ASSET, not per chain: Zyfai stores splitting under
1129
- * `assetTypeSettings[asset]`, so one call would only configure USDC and leave a
1130
- * WETH holder's real setting untouched.
1131
- *
1132
- * Non-fatal throughout. This is a side effect of connecting, so a Zyfai hiccup
1133
- * must leave the user connected with their profile unchanged, never fail
1134
- * activation.
1135
- */
1136
- async applyOrgPolicy(_state, _chainId, policy) {
1137
- debugLog("zyfai:splitting", "applying org policy", {
1138
- policy,
1139
- // Per asset because Zyfai stores splitting under assetTypeSettings[asset].
1140
- assets: SPLITTING_ASSETS
1141
- });
1142
- await Promise.all(
1143
- SPLITTING_ASSETS.map((asset) => this.applySplitting(asset, policy))
1144
- );
1145
- if (policy.poolPolicy) {
1146
- await this.applyPoolPolicy(policy.poolPolicy);
1147
- }
1148
- }
1149
- async applySplitting(asset, policy) {
1150
- try {
1151
- const desired = toZyfaiSplitting(policy);
1152
- const current = await this.sdk.getUserDetails(asset);
1153
- const currentSplitting = {
1154
- splitting: current?.splitting,
1155
- minSplits: current?.minSplits
1156
- };
1157
- const needsUpdate = splittingNeedsUpdate(current, desired);
1158
- debugLog("zyfai:splitting", `${asset}: comparing`, {
1159
- mode: policy.splittingMode,
1160
- current: currentSplitting,
1161
- desired,
1162
- // Spelled out because the "why" is the non-obvious part: minSplits is
1163
- // what separates automatic (1) from force (2-4), not `splitting`.
1164
- decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1165
- });
1166
- if (!needsUpdate) return;
1167
- const result = await this.sdk.updateUserProfile({ asset, ...desired });
1168
- debugLog("zyfai:splitting", `${asset}: wrote updateUserProfile`, {
1169
- sent: { asset, ...desired },
1170
- // updateUserProfile re-reads the profile after PATCHing, so this is
1171
- // Zyfai's own view of the result rather than an echo of what we sent.
1172
- zyfaiNowReports: {
1173
- splitting: result?.splitting,
1174
- minSplits: result?.minSplits
1175
- }
1176
- });
1177
- } catch (error) {
1178
- console.warn(
1179
- `[zyfai] applyOrgPolicy(${asset}) failed (non-fatal):`,
1180
- error instanceof Error ? error.message : String(error)
1181
- );
1182
- }
1183
- }
1184
- /**
1185
- * Apply the org's protocol/pool policy to this user's Zyfai profile.
1186
- *
1187
- * Two Zyfai writes, in a strict order:
1188
- * 1. `updateUserProfile({ protocols, autoSelectProtocols })` per asset —
1189
- * the explicit allow-list. Chain-agnostic, mirrors `applySplitting`.
1190
- * 2. `customizeBatch(...)` once — per (protocol, chain), asset-agnostic
1191
- * pool narrowing.
1192
- *
1193
- * Order matters: constraining a protocol's pools (step 2) while that
1194
- * protocol is not yet in the allow-list (step 1 not yet applied) lands the
1195
- * constraint on something the rebalance engine isn't considering.
1196
- *
1197
- * Wrapped so a failure anywhere is non-fatal — this runs as a side effect of
1198
- * connecting, so a Zyfai hiccup must leave the user connected with their
1199
- * profile unchanged, never fail activation.
1200
- */
1201
- async applyPoolPolicy(policy) {
1202
- try {
1203
- const { protocols: universe } = await this.sdk.getAvailableProtocols(
1204
- this.supportedChainIds[0]
1205
- );
1206
- const universeIds = universe.map((p) => p.id);
1207
- const chainsByProtocol = buildChainsByProtocol(
1208
- universe,
1209
- POOL_POLICY_CHAIN_IDS
1210
- );
1211
- debugLog("zyfai:pools", "protocol universe", {
1212
- universeIds,
1213
- chainsByProtocol
1214
- });
1215
- let strategy;
1216
- const desiredProtocols = deriveProtocols(
1217
- policy,
1218
- universeIds,
1219
- chainsByProtocol
1220
- );
1221
- const desiredAutoSelect = policy.autoApproveProtocols;
1222
- for (const asset of SPLITTING_ASSETS) {
1223
- const current = await this.sdk.getUserDetails(asset);
1224
- if (strategy === void 0 && (current.strategy === "conservative" || current.strategy === "aggressive")) {
1225
- strategy = current.strategy;
1226
- }
1227
- const needsUpdate = protocolsPolicyNeedsUpdate(
1228
- current,
1229
- desiredProtocols,
1230
- desiredAutoSelect
1231
- );
1232
- debugLog("zyfai:pools", `${asset}: comparing protocols`, {
1233
- current: {
1234
- protocols: current.protocols,
1235
- autoSelectProtocols: current.autoSelectProtocols
1236
- },
1237
- desired: {
1238
- protocols: desiredProtocols,
1239
- autoSelectProtocols: desiredAutoSelect
1240
- },
1241
- decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1242
- });
1243
- if (!needsUpdate) continue;
1244
- const update = {
1245
- asset,
1246
- autoSelectProtocols: desiredAutoSelect
1247
- };
1248
- if (desiredProtocols !== null) update.protocols = desiredProtocols;
1249
- await this.sdk.updateUserProfile(update);
1250
- debugLog("zyfai:pools", `${asset}: wrote protocols update`, update);
1251
- }
1252
- const poolResults = await Promise.all(
1253
- universe.map(async (protocol) => {
1254
- try {
1255
- const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1256
- return [protocol.id, flattenAvailablePools(raw)];
1257
- } catch (error) {
1258
- console.warn(
1259
- `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
1260
- error instanceof Error ? error.message : String(error)
1261
- );
1262
- return null;
1263
- }
1264
- })
1265
- );
1266
- const availableByProtocol = /* @__PURE__ */ new Map();
1267
- for (const result of poolResults) {
1268
- if (result) availableByProtocol.set(result[0], result[1]);
1269
- }
1270
- const customizations = toZyfaiCustomizations(
1271
- policy,
1272
- availableByProtocol,
1273
- chainsByProtocol
1274
- );
1275
- debugLog("zyfai:pools", "derived customizeBatch payload", {
1276
- strategy,
1277
- availableProtocolCount: availableByProtocol.size,
1278
- customizations
1279
- });
1280
- if (customizations.length === 0) {
1281
- debugLog("zyfai:pools", "skipping customizeBatch \u2014 nothing to send");
1282
- return;
1283
- }
1284
- await this.sdk.customizeBatch(customizations);
1285
- debugLog("zyfai:pools", "wrote customizeBatch", {
1286
- count: customizations.length
1287
- });
1288
- } catch (error) {
1289
- console.warn(
1290
- "[zyfai] applyPoolPolicy failed (non-fatal):",
1291
- error instanceof Error ? error.message : String(error)
1292
- );
1293
- }
1294
- }
1295
- /**
1296
- * Records the deposit with Zyfai, retrying transient failures.
1297
- *
1298
- * This runs AFTER the transfer has already landed on-chain, so it must never
1299
- * fail the deposit — the funds moved. But it is also the ONLY source of the
1300
- * "Top up wallet" entry the history is built from: Zyfai auto-deploys the
1301
- * balance it detects either way, so when this call is lost the user's deposit
1302
- * never appears in Activity (an earlier withdrawal stays the newest row) and
1303
- * nothing ever backfills it. Retry, then log loudly enough to be recoverable.
1304
- */
1305
- async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
1306
- const ATTEMPTS = 3;
1307
- const RETRY_DELAY_MS = 1e3;
1308
- for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
1309
- try {
1310
- await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
1311
- return;
1312
- } catch (logError) {
1313
- if (attempt === ATTEMPTS) {
1314
- console.error(
1315
- "[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
1316
- { txHash, chainId, amount, tokenAddress, error: logError }
1317
- );
1318
- return;
1319
- }
1320
- await new Promise(
1321
- (resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
1322
- );
1323
- }
1324
- }
1325
- }
1326
953
  async deposit(state, chainId, amount, asset, depositCallback) {
1327
954
  try {
1328
- await this.connectFull(state, chainId);
1329
955
  const validChainId = isValidChainId(chainId);
956
+ await this.ensureConnected(state, validChainId);
1330
957
  if (depositCallback) {
1331
958
  const wallet = await this.sdk.getSmartWalletAddress(
1332
959
  this.getAddress(),
@@ -1338,19 +965,22 @@ var ZyfaiAgent = class {
1338
965
  amount
1339
966
  );
1340
967
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1341
- await this.logDepositWithRetry(
1342
- validChainId,
1343
- txHash,
1344
- amount,
1345
- tokenAddress
1346
- );
968
+ try {
969
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
970
+ } catch (logError) {
971
+ console.warn(
972
+ "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
973
+ logError
974
+ );
975
+ }
1347
976
  return { txHash, smartWallet: wallet.address, amount };
1348
977
  }
1349
978
  const raw = await this.sdk.depositFunds(
1350
979
  this.getAddress(),
1351
980
  validChainId,
1352
981
  amount,
1353
- asset
982
+ asset,
983
+ "aggressive"
1354
984
  );
1355
985
  return mapDeposit(raw);
1356
986
  } catch (error) {
@@ -1359,8 +989,8 @@ var ZyfaiAgent = class {
1359
989
  }
1360
990
  // --- IAgent: Fund operations ---
1361
991
  async withdraw(state, chainId, token, amount) {
1362
- await this.connectFull(state, chainId);
1363
992
  const validChainId = isValidChainId(chainId);
993
+ await this.ensureConnected(state, validChainId);
1364
994
  const raw = await this.sdk.withdrawFunds(
1365
995
  this.getAddress(),
1366
996
  validChainId,
@@ -1439,19 +1069,7 @@ var ZyfaiAgent = class {
1439
1069
  }
1440
1070
  async getUserProfile(state, chainId) {
1441
1071
  await this.connectAuth(state, chainId);
1442
- const validChainId = isValidChainId(chainId);
1443
1072
  const raw = await this.sdk.getUserDetails();
1444
- debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
1445
- asset: "USDC (default \u2014 no asset passed)",
1446
- splitting: raw.splitting,
1447
- minSplits: raw.minSplits,
1448
- strategy: raw.strategy,
1449
- chains: raw.chains,
1450
- protocolCount: raw.protocols?.length,
1451
- hasActiveSessionKey: raw.hasActiveSessionKey,
1452
- smartWallet: raw.smartWallet
1453
- });
1454
- await this.ensureSessionKey(state, this.getAddress(), validChainId);
1455
1073
  return mapUserProfile(raw, this.connectedAddress);
1456
1074
  }
1457
1075
  async ensureAutoSelectProtocols(state, chainId, asset) {
@@ -1484,40 +1102,6 @@ var ZyfaiAgent = class {
1484
1102
 
1485
1103
  // src/lib/routing-api.ts
1486
1104
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1487
- async function fetchOrgAgentConfig(apiKey) {
1488
- const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
1489
- try {
1490
- const res = await fetch(url, {
1491
- method: "GET",
1492
- headers: {
1493
- "Content-Type": "application/json",
1494
- "x-owney-api-key": `${apiKey}`
1495
- }
1496
- });
1497
- if (!res.ok) {
1498
- if (res.status !== 404) {
1499
- console.warn(
1500
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
1501
- );
1502
- }
1503
- return null;
1504
- }
1505
- const json = await res.json();
1506
- const policy = json.success ? json.data ?? null : null;
1507
- debugLog(
1508
- "owney-sdk",
1509
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
1510
- policy ?? void 0
1511
- );
1512
- return policy;
1513
- } catch (error) {
1514
- console.warn(
1515
- "[owney-sdk] Could not read org agent config (non-fatal):",
1516
- error instanceof Error ? error.message : String(error)
1517
- );
1518
- return null;
1519
- }
1520
- }
1521
1105
  async function fetchAgentKeys(apiKey) {
1522
1106
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
1523
1107
  const res = await fetch(url, {
@@ -1582,10 +1166,11 @@ async function withFailureReporting(apiKey, agentType, fn) {
1582
1166
  // src/lib/helpers/withdraw-helper.ts
1583
1167
  import { parseUnits } from "viem";
1584
1168
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1169
+ const target = asset.toUpperCase();
1585
1170
  return agents.map((agent) => {
1586
1171
  const agentBalance = aggregated[agent.id];
1587
1172
  const tokenBalance = agentBalance?.tokens.find(
1588
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
1173
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1589
1174
  );
1590
1175
  if (!tokenBalance) return { agent, balance: 0n };
1591
1176
  return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
@@ -1855,6 +1440,84 @@ async function getSponsorRelayerAddress(input) {
1855
1440
  return parsed.data.relayer;
1856
1441
  }
1857
1442
 
1443
+ // src/lib/permit2.ts
1444
+ import { bytesToHex as bytesToHex2 } from "viem";
1445
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
1446
+ var MAX_UINT256 = 2n ** 256n - 1n;
1447
+ var ERC20_ALLOWANCE_ABI = [
1448
+ {
1449
+ type: "function",
1450
+ name: "allowance",
1451
+ stateMutability: "view",
1452
+ inputs: [
1453
+ { name: "owner", type: "address" },
1454
+ { name: "spender", type: "address" }
1455
+ ],
1456
+ outputs: [{ name: "", type: "uint256" }]
1457
+ },
1458
+ {
1459
+ type: "function",
1460
+ name: "approve",
1461
+ stateMutability: "nonpayable",
1462
+ inputs: [
1463
+ { name: "spender", type: "address" },
1464
+ { name: "amount", type: "uint256" }
1465
+ ],
1466
+ outputs: [{ name: "", type: "bool" }]
1467
+ },
1468
+ {
1469
+ type: "function",
1470
+ name: "balanceOf",
1471
+ stateMutability: "view",
1472
+ inputs: [{ name: "account", type: "address" }],
1473
+ outputs: [{ name: "", type: "uint256" }]
1474
+ }
1475
+ ];
1476
+ function buildPermitTransferFromTypedData(input) {
1477
+ return {
1478
+ domain: {
1479
+ name: "Permit2",
1480
+ chainId: input.chainId,
1481
+ verifyingContract: PERMIT2_ADDRESS
1482
+ },
1483
+ types: {
1484
+ PermitTransferFrom: [
1485
+ { name: "permitted", type: "TokenPermissions" },
1486
+ { name: "spender", type: "address" },
1487
+ { name: "nonce", type: "uint256" },
1488
+ { name: "deadline", type: "uint256" }
1489
+ ],
1490
+ TokenPermissions: [
1491
+ { name: "token", type: "address" },
1492
+ { name: "amount", type: "uint256" }
1493
+ ]
1494
+ },
1495
+ primaryType: "PermitTransferFrom",
1496
+ message: input.message
1497
+ };
1498
+ }
1499
+ function randomPermit2Nonce() {
1500
+ const bytes = new Uint8Array(32);
1501
+ globalThis.crypto.getRandomValues(bytes);
1502
+ return BigInt(bytesToHex2(bytes));
1503
+ }
1504
+ async function readPermit2Allowance(publicClient, token, owner) {
1505
+ return publicClient.readContract({
1506
+ address: token,
1507
+ abi: ERC20_ALLOWANCE_ABI,
1508
+ functionName: "allowance",
1509
+ args: [owner, PERMIT2_ADDRESS]
1510
+ });
1511
+ }
1512
+ async function readErc20Balance(publicClient, token, owner) {
1513
+ return publicClient.readContract({
1514
+ address: token,
1515
+ abi: ERC20_ALLOWANCE_ABI,
1516
+ functionName: "balanceOf",
1517
+ args: [owner]
1518
+ });
1519
+ }
1520
+
1858
1521
  // src/lib/sponsored-deposit.ts
1859
1522
  var AUTH_WINDOW_SECONDS = 15 * 60;
1860
1523
  function makeSponsoredDepositCallback(deps) {
@@ -2037,7 +1700,6 @@ var VIEM_CHAIN2 = {
2037
1700
  42161: arbitrum2,
2038
1701
  1: mainnet2
2039
1702
  };
2040
- var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2041
1703
  var SPONSORED_WETH_BY_CHAIN = {
2042
1704
  8453: "0x4200000000000000000000000000000000000006",
2043
1705
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2057,26 +1719,17 @@ var OwneySDK = class {
2057
1719
  disabledAgents = /* @__PURE__ */ new Set();
2058
1720
  state = null;
2059
1721
  apiKey;
2060
- // The org's agent policy, fetched once per client. Applied to each agent on
2061
- // activation (ROUT-229). `undefined` means "not fetched yet", `null` means
2062
- // "fetched, and the partner has no policy" — which is a real answer meaning
2063
- // leave every user's agent profile alone.
2064
- orgAgentConfig;
2065
- orgAgentConfigPromise = null;
2066
1722
  zyfaiRpcUrls;
2067
1723
  routingApiBaseUrl;
2068
1724
  referralSource;
2069
- surfliquidApiBaseUrl;
2070
1725
  cachedSponsoredCallback = null;
2071
1726
  cachedWethSponsoredCallback = null;
2072
1727
  initializingAgentsPromise = null;
2073
1728
  constructor(config) {
2074
1729
  this.apiKey = config.apiKey;
2075
- if (config.debug) setOwneyDebug(true);
2076
1730
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2077
1731
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2078
1732
  this.referralSource = config.referralSource;
2079
- this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2080
1733
  }
2081
1734
  /**
2082
1735
  * Establish connection state for all subsequent SDK calls.
@@ -2235,57 +1888,14 @@ var OwneySDK = class {
2235
1888
  }
2236
1889
  return active;
2237
1890
  }
2238
- /**
2239
- * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
2240
- * throws — it degrades to null — so this cannot break activation.
2241
- */
2242
- async getOrgAgentConfig() {
2243
- if (this.orgAgentConfig !== void 0) return this.orgAgentConfig;
2244
- if (!this.orgAgentConfigPromise) {
2245
- this.orgAgentConfigPromise = fetchOrgAgentConfig(this.apiKey).then(
2246
- (config) => {
2247
- this.orgAgentConfig = config;
2248
- return config;
2249
- }
2250
- );
2251
- }
2252
- return this.orgAgentConfigPromise;
2253
- }
2254
- /**
2255
- * Apply the org policy to one agent, after it has activated.
2256
- *
2257
- * Sequenced after activation rather than alongside it because applying the
2258
- * policy needs the authenticated session activation establishes. Every failure
2259
- * mode is swallowed: a partner's splitting preference is not worth failing a
2260
- * user's connect over.
2261
- */
2262
- async applyOrgPolicyTo(agent, state, chainId) {
2263
- if (!agent.applyOrgPolicy) {
2264
- debugLog(
2265
- "owney-sdk",
2266
- `${agent.id}: no applyOrgPolicy \u2014 agent has no org-level knobs, skipping`
2267
- );
2268
- return;
2269
- }
2270
- try {
2271
- const policy = await this.getOrgAgentConfig();
2272
- if (!policy) return;
2273
- await agent.applyOrgPolicy(state, chainId, policy);
2274
- } catch (error) {
2275
- console.warn(
2276
- `[owney-sdk] applyOrgPolicy(${agent.id}) failed (non-fatal):`,
2277
- error instanceof Error ? error.message : String(error)
2278
- );
2279
- }
2280
- }
2281
1891
  async ensureAgentsInitialized() {
2282
1892
  if (this.agents.size > 0) return;
2283
1893
  if (!this.initializingAgentsPromise) {
2284
1894
  this.initializingAgentsPromise = (async () => {
2285
1895
  const agentKeys = await fetchAgentKeys(this.apiKey);
2286
1896
  this.disabledAgents.clear();
2287
- for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2288
- const agent = await this.createAgent(agent_type, key2, project_name);
1897
+ for (const { key, agent_type, is_enabled } of agentKeys) {
1898
+ const agent = this.createAgent(agent_type, key);
2289
1899
  if (!agent) continue;
2290
1900
  this.agents.set(agent_type, agent);
2291
1901
  if (is_enabled === false) {
@@ -2306,21 +1916,9 @@ var OwneySDK = class {
2306
1916
  this.initializingAgentsPromise = null;
2307
1917
  }
2308
1918
  }
2309
- async createAgent(agentId, key2, projectName) {
1919
+ createAgent(agentId, key) {
2310
1920
  if (agentId === "zyfai") {
2311
- return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2312
- }
2313
- if (agentId === "surfliquid") {
2314
- if (!projectName) return null;
2315
- const { SurfLiquidAgent } = await import("./surfliquid.agent-VNGQUSXN.js");
2316
- return new SurfLiquidAgent({
2317
- projectName,
2318
- appId: key2,
2319
- // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
2320
- // which WebKit (iOS) discards when it is third-party. See
2321
- // OwneySDKConfig.surfliquidApiBaseUrl.
2322
- apiBaseUrl: this.surfliquidApiBaseUrl
2323
- });
1921
+ return new ZyfaiAgent(key, this.zyfaiRpcUrls, this.referralSource);
2324
1922
  }
2325
1923
  return null;
2326
1924
  }
@@ -2374,7 +1972,6 @@ var OwneySDK = class {
2374
1972
  Promise.all(
2375
1973
  agents.map(async (agent) => {
2376
1974
  await agent.activateAgent(state, chainId);
2377
- await this.applyOrgPolicyTo(agent, state, chainId);
2378
1975
  })
2379
1976
  ).catch((error) => {
2380
1977
  console.error("activateAgent background init failed:", error);
@@ -2402,7 +1999,6 @@ var OwneySDK = class {
2402
1999
  Promise.all(
2403
2000
  enabledCompatible.map(async (agent) => {
2404
2001
  await agent.activateAgent(state, chainId);
2405
- await this.applyOrgPolicyTo(agent, state, chainId);
2406
2002
  })
2407
2003
  ).catch((error) => {
2408
2004
  console.error("activateAgent background init failed:", error);
@@ -2476,30 +2072,18 @@ var OwneySDK = class {
2476
2072
  );
2477
2073
  }
2478
2074
  const agentResults = {};
2479
- const agentErrors = {};
2480
2075
  for (const { agent, amount: agentAmount } of agentAmounts) {
2481
- try {
2482
- agentResults[agent.id] = await this.depositWithFallback(
2483
- agent,
2484
- state,
2485
- chainId,
2486
- agentAmount,
2487
- asset,
2488
- effectiveCallback,
2489
- depositCallback
2490
- );
2491
- } catch (error) {
2492
- agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
2493
- }
2494
- }
2495
- if (Object.keys(agentResults).length === 0) {
2496
- throw new OwneyError(
2497
- "DEPOSIT_ALL_FAILED",
2498
- `All ${agentAmounts.length} agent deposit(s) failed.`,
2499
- { agentErrors }
2076
+ agentResults[agent.id] = await this.depositWithFallback(
2077
+ agent,
2078
+ state,
2079
+ chainId,
2080
+ agentAmount,
2081
+ asset,
2082
+ effectiveCallback,
2083
+ depositCallback
2500
2084
  );
2501
2085
  }
2502
- return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2086
+ return { agentResults };
2503
2087
  }
2504
2088
  /**
2505
2089
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2615,8 +2199,9 @@ var OwneySDK = class {
2615
2199
  async hasExistingBalance(agent, state, chainId, asset) {
2616
2200
  try {
2617
2201
  const balance = await agent.getBalances(state, chainId);
2202
+ const target = asset.toLowerCase();
2618
2203
  const token = balance.tokens.find(
2619
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
2204
+ (t) => t.chainId === chainId && t.asset.toLowerCase() === target
2620
2205
  );
2621
2206
  return !!token && Number(token.amount) > 0;
2622
2207
  } catch {
@@ -2655,34 +2240,6 @@ var OwneySDK = class {
2655
2240
  }
2656
2241
  return eligible;
2657
2242
  }
2658
- /**
2659
- * Agent ids the routing API provisioned for this org that support the given
2660
- * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
2661
- * surfliquid first). Returns `[]` when the org has no compatible agent — never
2662
- * throws on an empty org. Loads agent keys on first call (apiKey only, no
2663
- * wallet), so the UI can resolve which agent to use before the user connects.
2664
- *
2665
- * This is the source of truth for agent availability: an agent appears here
2666
- * iff the routing API returned its key. No per-app feature flags.
2667
- */
2668
- async getEligibleAgentIds(chainId, asset) {
2669
- try {
2670
- await this.ensureAgentsInitialized();
2671
- } catch (error) {
2672
- if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
2673
- return [];
2674
- }
2675
- throw error;
2676
- }
2677
- return [...this.agents.values()].filter((agent) => {
2678
- const chainAssets = agent.supportedAssets.find(
2679
- (sa) => sa.chainId === chainId
2680
- );
2681
- return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
2682
- }).map((agent) => agent.id).sort(
2683
- (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
2684
- );
2685
- }
2686
2243
  // --- Fund operations ---
2687
2244
  /**
2688
2245
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2718,17 +2275,9 @@ var OwneySDK = class {
2718
2275
  }
2719
2276
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2720
2277
  if (!amount) {
2721
- const aggregated2 = await this.getBalances();
2722
- const funded = projectAgentBalancesForAsset(
2723
- eligibleAgents,
2724
- aggregated2.agentBalances,
2725
- chainId,
2726
- asset,
2727
- assetInfo.decimals
2728
- ).filter((b) => b.balance > 0n);
2729
2278
  const results2 = {};
2730
2279
  const agentErrors2 = {};
2731
- for (const { agent } of funded) {
2280
+ for (const agent of eligibleAgents) {
2732
2281
  try {
2733
2282
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2734
2283
  } catch (err) {
@@ -2745,10 +2294,9 @@ var OwneySDK = class {
2745
2294
  { asset, agentErrors: agentErrors2 }
2746
2295
  );
2747
2296
  }
2748
- const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2749
2297
  return {
2750
2298
  agentResult: results2,
2751
- totalWithdrawn: totalWithdrawn.toString()
2299
+ totalWithdrawn: sumWithdrawnAmount(results2).toString()
2752
2300
  };
2753
2301
  }
2754
2302
  const requested = BigInt(amount);
@@ -3265,22 +2813,22 @@ var readLegacySiwxSession = (store, address) => {
3265
2813
  const legacyKeys = [];
3266
2814
  try {
3267
2815
  for (let i = 0; i < store.length; i++) {
3268
- const key2 = store.key(i);
3269
- if (key2?.startsWith(prefix)) legacyKeys.push(key2);
2816
+ const key = store.key(i);
2817
+ if (key?.startsWith(prefix)) legacyKeys.push(key);
3270
2818
  }
3271
2819
  } catch {
3272
2820
  return null;
3273
2821
  }
3274
2822
  let adopted = null;
3275
- for (const key2 of legacyKeys) {
2823
+ for (const key of legacyKeys) {
3276
2824
  if (!adopted) {
3277
2825
  try {
3278
- adopted = JSON.parse(store.getItem(key2) ?? "");
2826
+ adopted = JSON.parse(store.getItem(key) ?? "");
3279
2827
  } catch {
3280
2828
  }
3281
2829
  }
3282
2830
  try {
3283
- store.removeItem(key2);
2831
+ store.removeItem(key);
3284
2832
  } catch {
3285
2833
  }
3286
2834
  }
@@ -3288,11 +2836,11 @@ var readLegacySiwxSession = (store, address) => {
3288
2836
  };
3289
2837
  var readSiwxSession = (address, chainId) => {
3290
2838
  if (typeof window === "undefined") return null;
3291
- const key2 = buildKey2(address);
2839
+ const key = buildKey2(address);
3292
2840
  const store = storage2();
3293
2841
  let raw = null;
3294
2842
  try {
3295
- raw = store?.getItem(key2) ?? null;
2843
+ raw = store?.getItem(key) ?? null;
3296
2844
  } catch {
3297
2845
  raw = null;
3298
2846
  }
@@ -3300,9 +2848,9 @@ var readSiwxSession = (address, chainId) => {
3300
2848
  try {
3301
2849
  return JSON.parse(raw);
3302
2850
  } catch {
3303
- memorySiwxSessions.delete(key2);
2851
+ memorySiwxSessions.delete(key);
3304
2852
  try {
3305
- store?.removeItem(key2);
2853
+ store?.removeItem(key);
3306
2854
  } catch {
3307
2855
  }
3308
2856
  return null;
@@ -3313,24 +2861,24 @@ var readSiwxSession = (address, chainId) => {
3313
2861
  writeSiwxSession(address, chainId, legacy);
3314
2862
  return legacy;
3315
2863
  }
3316
- return memorySiwxSessions.get(key2) ?? null;
2864
+ return memorySiwxSessions.get(key) ?? null;
3317
2865
  };
3318
2866
  var writeSiwxSession = (address, _chainId, session) => {
3319
2867
  if (typeof window === "undefined") return;
3320
- const key2 = buildKey2(address);
3321
- memorySiwxSessions.set(key2, session);
2868
+ const key = buildKey2(address);
2869
+ memorySiwxSessions.set(key, session);
3322
2870
  const store = storage2();
3323
2871
  try {
3324
- store?.setItem(key2, JSON.stringify(session));
2872
+ store?.setItem(key, JSON.stringify(session));
3325
2873
  } catch {
3326
2874
  }
3327
2875
  };
3328
2876
  var clearSiwxSession = (address, _chainId) => {
3329
- const key2 = buildKey2(address);
3330
- memorySiwxSessions.delete(key2);
2877
+ const key = buildKey2(address);
2878
+ memorySiwxSessions.delete(key);
3331
2879
  const store = storage2();
3332
2880
  try {
3333
- store?.removeItem(key2);
2881
+ store?.removeItem(key);
3334
2882
  } catch {
3335
2883
  }
3336
2884
  readLegacySiwxSession(store, address);
@@ -3459,6 +3007,5 @@ export {
3459
3007
  NotConnectedError,
3460
3008
  OwneyError,
3461
3009
  OwneySDK,
3462
- createOwneySIWX,
3463
- setOwneyDebug
3010
+ createOwneySIWX
3464
3011
  };