@owney/sdk 0.6.9-beta.9 → 0.7.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/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);
@@ -112,10 +141,6 @@ var DECIMALS_BY_SYMBOL = {
112
141
  wbtc: 8,
113
142
  cbbtc: 8
114
143
  };
115
- function canonicalSymbol(symbol) {
116
- const upper = symbol.toUpperCase();
117
- return upper === "ETH" ? "WETH" : upper;
118
- }
119
144
  function tokenDecimals(symbol, explicit) {
120
145
  if (typeof explicit === "number" && Number.isFinite(explicit))
121
146
  return explicit;
@@ -154,50 +179,46 @@ function resolveChainId(chain) {
154
179
  function mapBalances(raw, _chainId, smartWallet) {
155
180
  const portfolio = raw.portfolio;
156
181
  const portfolioByChain = portfolio.portfolioByChain ?? {};
157
- const rawPositions = portfolio.positions ?? [];
158
- const detailByKey = /* @__PURE__ */ new Map();
159
- for (const p of rawPositions) {
160
- const cid = resolveChainId(p.chain ?? "");
161
- const symbol = canonicalSymbol(p.token_symbol ?? "");
162
- if (Number.isFinite(cid) && symbol) detailByKey.set(`${cid}:${symbol}`, p);
163
- }
164
182
  let totalBalance = 0;
165
183
  const tokens = [];
166
- const positions = [];
167
184
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
168
185
  const positionChainId = Number(chainIdKey);
169
- const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
170
186
  for (const [symbol, { balance, decimals }] of Object.entries(
171
187
  tokensBySymbol ?? {}
172
188
  )) {
173
- const amount = hexToDecimal(balance || "0", tokenDecimals(symbol, decimals));
189
+ const amount = hexToDecimal(balance || "0", decimals);
174
190
  totalBalance += amount;
175
191
  tokens.push({
176
- chain: chainName,
192
+ chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
177
193
  chainId: positionChainId,
178
194
  asset: symbol,
179
195
  amount: String(amount)
180
196
  });
181
- const detail = detailByKey.get(
182
- `${positionChainId}:${canonicalSymbol(symbol)}`
183
- );
184
- positions.push({
185
- chain: chainName,
186
- protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
187
- protocolId: detail?.protocol_id,
188
- pool: detail?.pool,
189
- // Report the token the way the rest of owney spells it ("WETH", as the
190
- // pre-portfolioByChain mapper did), not Zyfai's internal "eth".
191
- asset: canonicalSymbol(symbol),
192
- amount: String(amount),
193
- apy: detail?.pool_apy,
194
- tvl: detail?.pool_tvl,
195
- // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
196
- // structural read keeps it undefined today and auto-populates later.
197
- liquidity: detail?.liquidity
198
- });
199
197
  }
200
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
+ }));
201
222
  return {
202
223
  smartWallet,
203
224
  totalBalance: String(totalBalance),
@@ -359,9 +380,7 @@ function mapUserProfile(raw, userAddress) {
359
380
  chains: raw.chains || [],
360
381
  strategy: raw.strategy,
361
382
  hasActiveSessionKey: raw.hasActiveSessionKey || false,
362
- protocols: raw.protocols || [],
363
- splitting: raw.splitting,
364
- minSplits: raw.minSplits
383
+ protocols: raw.protocols || []
365
384
  };
366
385
  }
367
386
  function mapApyByStrategy(raw) {
@@ -508,23 +527,23 @@ var readLegacySession = (store, address) => {
508
527
  const legacyKeys = [];
509
528
  try {
510
529
  for (let i = 0; i < store.length; i++) {
511
- const key2 = store.key(i);
512
- if (key2?.startsWith(prefix)) legacyKeys.push(key2);
530
+ const key = store.key(i);
531
+ if (key?.startsWith(prefix)) legacyKeys.push(key);
513
532
  }
514
533
  } catch {
515
534
  return null;
516
535
  }
517
536
  let adopted = null;
518
- for (const key2 of legacyKeys) {
537
+ for (const key of legacyKeys) {
519
538
  if (!adopted) {
520
539
  try {
521
- const parsed = JSON.parse(store.getItem(key2) ?? "");
540
+ const parsed = JSON.parse(store.getItem(key) ?? "");
522
541
  if (isFreshSession(parsed)) adopted = parsed;
523
542
  } catch {
524
543
  }
525
544
  }
526
545
  try {
527
- store.removeItem(key2);
546
+ store.removeItem(key);
528
547
  } catch {
529
548
  }
530
549
  }
@@ -532,11 +551,11 @@ var readLegacySession = (store, address) => {
532
551
  };
533
552
  var readSession = (address, _chainId) => {
534
553
  if (typeof window === "undefined") return null;
535
- const key2 = buildKey(address);
554
+ const key = buildKey(address);
536
555
  const store = storage();
537
556
  let raw = null;
538
557
  try {
539
- raw = store?.getItem(key2) ?? null;
558
+ raw = store?.getItem(key) ?? null;
540
559
  } catch {
541
560
  raw = null;
542
561
  }
@@ -546,9 +565,9 @@ var readSession = (address, _chainId) => {
546
565
  if (isFreshSession(parsed)) return parsed;
547
566
  } catch {
548
567
  }
549
- memorySessions.delete(key2);
568
+ memorySessions.delete(key);
550
569
  try {
551
- store?.removeItem(key2);
570
+ store?.removeItem(key);
552
571
  } catch {
553
572
  }
554
573
  return null;
@@ -558,27 +577,27 @@ var readSession = (address, _chainId) => {
558
577
  writeSession(address, _chainId, legacy);
559
578
  return legacy;
560
579
  }
561
- const cached = memorySessions.get(key2);
580
+ const cached = memorySessions.get(key);
562
581
  if (isFreshSession(cached)) return cached;
563
- if (cached) memorySessions.delete(key2);
582
+ if (cached) memorySessions.delete(key);
564
583
  return null;
565
584
  };
566
585
  var writeSession = (address, _chainId, session) => {
567
586
  if (typeof window === "undefined") return;
568
- const key2 = buildKey(address);
569
- memorySessions.set(key2, session);
587
+ const key = buildKey(address);
588
+ memorySessions.set(key, session);
570
589
  const store = storage();
571
590
  try {
572
- store?.setItem(key2, JSON.stringify(session));
591
+ store?.setItem(key, JSON.stringify(session));
573
592
  } catch {
574
593
  }
575
594
  };
576
595
  var clearSession = (address, _chainId) => {
577
- const key2 = buildKey(address);
578
- memorySessions.delete(key2);
596
+ const key = buildKey(address);
597
+ memorySessions.delete(key);
579
598
  const store = storage();
580
599
  try {
581
- store?.removeItem(key2);
600
+ store?.removeItem(key);
582
601
  } catch {
583
602
  }
584
603
  readLegacySession(store, address);
@@ -609,149 +628,6 @@ function buildProtocolMergeUpdate(available, current, asset) {
609
628
  };
610
629
  }
611
630
 
612
- // src/agents/zyfai/splitting.ts
613
- var MAX_MIN_SPLITS = 4;
614
- var MIN_FORCE_SPLITS = 2;
615
- var AUTOMATIC_MIN_SPLITS = 1;
616
- function toZyfaiSplitting(policy) {
617
- switch (policy.splittingMode) {
618
- case "none":
619
- return { splitting: false };
620
- case "automatic":
621
- return { splitting: true, minSplits: AUTOMATIC_MIN_SPLITS };
622
- case "force":
623
- return {
624
- splitting: true,
625
- minSplits: clampForceSplits(policy.minSplits)
626
- };
627
- }
628
- }
629
- function clampForceSplits(minSplits) {
630
- if (typeof minSplits !== "number" || !Number.isFinite(minSplits)) {
631
- return MIN_FORCE_SPLITS;
632
- }
633
- const rounded = Math.round(minSplits);
634
- if (rounded < MIN_FORCE_SPLITS) return MIN_FORCE_SPLITS;
635
- if (rounded > MAX_MIN_SPLITS) return MAX_MIN_SPLITS;
636
- return rounded;
637
- }
638
- function splittingNeedsUpdate(current, desired) {
639
- if (!current) return true;
640
- if (current.splitting !== desired.splitting) return true;
641
- if (desired.minSplits === void 0) return false;
642
- return current.minSplits !== desired.minSplits;
643
- }
644
-
645
- // src/agents/zyfai/pool-policy.ts
646
- function toZyfaiCustomizations(policy, availableByProtocol, chainsByProtocol) {
647
- const selectionByKey = /* @__PURE__ */ new Map();
648
- for (const selection of policy.selections) {
649
- selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
650
- }
651
- const customizations = [];
652
- for (const [protocolId, chainIds] of Object.entries(chainsByProtocol)) {
653
- const available = availableByProtocol.get(protocolId);
654
- if (available === void 0) {
655
- continue;
656
- }
657
- const availableSet = new Set(available);
658
- for (const chainId of chainIds) {
659
- const stored = selectionByKey.get(key(protocolId, chainId));
660
- if (stored === void 0) {
661
- customizations.push({
662
- protocolId,
663
- chainId,
664
- pools: [],
665
- autoselect: true
666
- });
667
- continue;
668
- }
669
- const pools = stored.pools.filter((pool) => availableSet.has(pool));
670
- customizations.push({
671
- protocolId,
672
- chainId,
673
- pools,
674
- autoselect: false
675
- });
676
- }
677
- }
678
- return customizations;
679
- }
680
- function deriveProtocols(policy, universe, chainsByProtocol) {
681
- if (policy.selections.length === 0) {
682
- return null;
683
- }
684
- const selectionByKey = /* @__PURE__ */ new Map();
685
- for (const selection of policy.selections) {
686
- selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
687
- }
688
- return universe.filter((protocolId) => {
689
- const chainIds = chainsByProtocol[protocolId];
690
- if (chainIds === void 0 || chainIds.length === 0) {
691
- return true;
692
- }
693
- const fullyDisabled = chainIds.every((chainId) => {
694
- const stored = selectionByKey.get(key(protocolId, chainId));
695
- return stored !== void 0 && stored.pools.length === 0;
696
- });
697
- return !fullyDisabled;
698
- });
699
- }
700
- function key(protocolId, chainId) {
701
- return `${protocolId}:${chainId}`;
702
- }
703
- function buildChainsByProtocol(protocols, dashboardChainIds) {
704
- const allowed = new Set(dashboardChainIds);
705
- const result = {};
706
- for (const protocol of protocols) {
707
- result[protocol.id] = (protocol.chains ?? []).filter(
708
- (chainId) => allowed.has(chainId)
709
- );
710
- }
711
- return result;
712
- }
713
- function flattenAvailablePools(raw) {
714
- const byChain = raw ?? {};
715
- const names = [];
716
- for (const byToken of Object.values(byChain ?? {})) {
717
- for (const entry of Object.values(byToken ?? {})) {
718
- for (const pool of entry?.pools ?? []) {
719
- if (pool?.name) names.push(pool.name);
720
- }
721
- }
722
- }
723
- return names;
724
- }
725
- function protocolListsEqual(a, b) {
726
- const left = [...a ?? []].sort();
727
- const right = [...b ?? []].sort();
728
- if (left.length !== right.length) return false;
729
- return left.every((value, index) => value === right[index]);
730
- }
731
- function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect) {
732
- if (!current) return true;
733
- if (current.autoSelectProtocols !== desiredAutoSelect) return true;
734
- if (desiredProtocols === null) return false;
735
- return !protocolListsEqual(current.protocols, desiredProtocols);
736
- }
737
-
738
- // src/lib/debug.ts
739
- var configuredDebug = false;
740
- function setOwneyDebug(enabled) {
741
- configuredDebug = enabled;
742
- }
743
- function isOwneyDebug() {
744
- return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
745
- }
746
- function debugLog(scope, message, data) {
747
- if (!isOwneyDebug()) return;
748
- if (data === void 0) {
749
- console.log(`[${scope}] ${message}`);
750
- } else {
751
- console.log(`[${scope}] ${message}`, data);
752
- }
753
- }
754
-
755
631
  // src/agents/zyfai/zyfai.agent.ts
756
632
  var DEFAULT_ZYFAI_RPC_URLS = {
757
633
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
@@ -764,12 +640,6 @@ var WETH_ADDRESS_BY_CHAIN = {
764
640
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
765
641
  };
766
642
  var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
767
- var SPLITTING_ASSETS = ["USDC", "WETH"];
768
- var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
769
- function isAlreadyActiveSessionKeyError(error) {
770
- const message = error instanceof Error ? error.message : String(error);
771
- return /already has an active session key/i.test(message);
772
- }
773
643
  var ZyfaiAgent = class {
774
644
  id = "zyfai";
775
645
  supportedChainIds = [8453, 42161, 1];
@@ -996,71 +866,21 @@ var ZyfaiAgent = class {
996
866
  return await this.sdk.connectAccount(provider, chainId);
997
867
  }
998
868
  }
999
- /** Full connect: SIWE auth + Safe deploy check + session key. */
1000
- async connectFull(state, chainId) {
1001
- await this.connectAuth(state, chainId);
1002
- const validChainId = isValidChainId(chainId);
1003
- const address = this.getAddress();
1004
- const wallet = await this.sdk.getSmartWalletAddress(address, validChainId);
1005
- this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
1006
- if (!wallet.isDeployed) {
1007
- await this.sdk.deploySafe(address, validChainId, "aggressive", false);
1008
- try {
1009
- if (validChainId === 42161) {
1010
- await this.sdk.updateUserProfile({
1011
- asset: "WETH",
1012
- chains: [validChainId],
1013
- protocols: ["c24a33fa-4a87-4376-b756-93bd3957d3ea"],
1014
- // AAVE protocol ID
1015
- autoSelectProtocols: false
1016
- });
1017
- }
1018
- } catch (err) {
1019
- console.warn(
1020
- "Failed to configure AAVE protocol after Safe deployment:",
1021
- err instanceof Error ? err.message : String(err)
1022
- );
1023
- }
1024
- }
1025
- await this.ensureSessionKey(state, address, validChainId);
1026
- }
1027
869
  /**
1028
- * Ensures the user has an active session key, creating one if absent.
1029
- *
1030
- * The Zyfai session key is a single **per-user** record, not per-chain: its
1031
- * config (`/session-keys/config`) spans every supported chain and is signed
1032
- * in one shot, and the backend rejects `POST /session-keys/add` with 400
1033
- * "User already has an active session key" if one exists. So we must NOT try
1034
- * to (re)create a key per chain — that 400s and blocks the deposit. We only
1035
- * create when the user has none.
1036
- *
1037
- * The one wrapper-side hardening that IS needed: a chain/account switch can
1038
- * fire the provider's `accountsChanged` → `disconnectAccount`, tearing down
1039
- * the zyfai connection (walletClient + authenticatedUserId both null) while
1040
- * our own `connectAuth` guard still believes this chain is connected and
1041
- * early-returns without reconnecting. `createSessionKey` then fails with
1042
- * "No account connected". Detect that and reconnect first.
1043
- *
1044
- * NOTE: whether a globally-active session key actually authorizes activity on
1045
- * a freshly-deployed Safe on a new chain is a backend/zyf question — the
1046
- * wrapper cannot force per-chain activation given the global 400 guard.
1047
- * 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".
1048
876
  */
1049
- async ensureSessionKey(state, address, chainId) {
877
+ async ensureConnected(state, chainId) {
878
+ await this.connectAuth(state, chainId);
1050
879
  const internals = this.sdk;
1051
880
  if (!internals.walletClient || internals.authenticatedUserId === null) {
1052
881
  this.connectedChainId = null;
1053
882
  await this.connectAuth(state, chainId);
1054
883
  }
1055
- const user = await this.sdk.getUserDetails();
1056
- if (!user.hasActiveSessionKey) {
1057
- try {
1058
- await this.sdk.createSessionKey(address, chainId);
1059
- } catch (error) {
1060
- if (!isAlreadyActiveSessionKeyError(error)) throw error;
1061
- internals.hasActiveSessionKey = true;
1062
- }
1063
- }
1064
884
  }
1065
885
  /**
1066
886
  * Sync newly-available protocols onto an existing user's profile at wallet
@@ -1130,211 +950,10 @@ var ZyfaiAgent = class {
1130
950
  throw error;
1131
951
  }
1132
952
  }
1133
- /**
1134
- * Apply the org's capital splitting policy to this user's Zyfai profile.
1135
- *
1136
- * Per ASSET, not per chain: Zyfai stores splitting under
1137
- * `assetTypeSettings[asset]`, so one call would only configure USDC and leave a
1138
- * WETH holder's real setting untouched.
1139
- *
1140
- * Non-fatal throughout. This is a side effect of connecting, so a Zyfai hiccup
1141
- * must leave the user connected with their profile unchanged, never fail
1142
- * activation.
1143
- */
1144
- async applyOrgPolicy(_state, _chainId, policy) {
1145
- debugLog("zyfai:splitting", "applying org policy", {
1146
- policy,
1147
- // Per asset because Zyfai stores splitting under assetTypeSettings[asset].
1148
- assets: SPLITTING_ASSETS
1149
- });
1150
- await Promise.all(
1151
- SPLITTING_ASSETS.map((asset) => this.applySplitting(asset, policy))
1152
- );
1153
- if (policy.poolPolicy) {
1154
- await this.applyPoolPolicy(policy.poolPolicy);
1155
- }
1156
- }
1157
- async applySplitting(asset, policy) {
1158
- try {
1159
- const desired = toZyfaiSplitting(policy);
1160
- const current = await this.sdk.getUserDetails(asset);
1161
- const currentSplitting = {
1162
- splitting: current?.splitting,
1163
- minSplits: current?.minSplits
1164
- };
1165
- const needsUpdate = splittingNeedsUpdate(current, desired);
1166
- debugLog("zyfai:splitting", `${asset}: comparing`, {
1167
- mode: policy.splittingMode,
1168
- current: currentSplitting,
1169
- desired,
1170
- // Spelled out because the "why" is the non-obvious part: minSplits is
1171
- // what separates automatic (1) from force (2-4), not `splitting`.
1172
- decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1173
- });
1174
- if (!needsUpdate) return;
1175
- const result = await this.sdk.updateUserProfile({ asset, ...desired });
1176
- debugLog("zyfai:splitting", `${asset}: wrote updateUserProfile`, {
1177
- sent: { asset, ...desired },
1178
- // updateUserProfile re-reads the profile after PATCHing, so this is
1179
- // Zyfai's own view of the result rather than an echo of what we sent.
1180
- zyfaiNowReports: {
1181
- splitting: result?.splitting,
1182
- minSplits: result?.minSplits
1183
- }
1184
- });
1185
- } catch (error) {
1186
- console.warn(
1187
- `[zyfai] applyOrgPolicy(${asset}) failed (non-fatal):`,
1188
- error instanceof Error ? error.message : String(error)
1189
- );
1190
- }
1191
- }
1192
- /**
1193
- * Apply the org's protocol/pool policy to this user's Zyfai profile.
1194
- *
1195
- * Two Zyfai writes, in a strict order:
1196
- * 1. `updateUserProfile({ protocols, autoSelectProtocols })` per asset —
1197
- * the explicit allow-list. Chain-agnostic, mirrors `applySplitting`.
1198
- * 2. `customizeBatch(...)` once — per (protocol, chain), asset-agnostic
1199
- * pool narrowing.
1200
- *
1201
- * Order matters: constraining a protocol's pools (step 2) while that
1202
- * protocol is not yet in the allow-list (step 1 not yet applied) lands the
1203
- * constraint on something the rebalance engine isn't considering.
1204
- *
1205
- * Wrapped so a failure anywhere is non-fatal — this runs as a side effect of
1206
- * connecting, so a Zyfai hiccup must leave the user connected with their
1207
- * profile unchanged, never fail activation.
1208
- */
1209
- async applyPoolPolicy(policy) {
1210
- try {
1211
- const { protocols: universe } = await this.sdk.getAvailableProtocols(
1212
- this.supportedChainIds[0]
1213
- );
1214
- const universeIds = universe.map((p) => p.id);
1215
- const chainsByProtocol = buildChainsByProtocol(
1216
- universe,
1217
- POOL_POLICY_CHAIN_IDS
1218
- );
1219
- debugLog("zyfai:pools", "protocol universe", {
1220
- universeIds,
1221
- chainsByProtocol
1222
- });
1223
- let strategy;
1224
- const desiredProtocols = deriveProtocols(
1225
- policy,
1226
- universeIds,
1227
- chainsByProtocol
1228
- );
1229
- const desiredAutoSelect = policy.autoApproveProtocols;
1230
- for (const asset of SPLITTING_ASSETS) {
1231
- const current = await this.sdk.getUserDetails(asset);
1232
- if (strategy === void 0 && (current.strategy === "conservative" || current.strategy === "aggressive")) {
1233
- strategy = current.strategy;
1234
- }
1235
- const needsUpdate = protocolsPolicyNeedsUpdate(
1236
- current,
1237
- desiredProtocols,
1238
- desiredAutoSelect
1239
- );
1240
- debugLog("zyfai:pools", `${asset}: comparing protocols`, {
1241
- current: {
1242
- protocols: current.protocols,
1243
- autoSelectProtocols: current.autoSelectProtocols
1244
- },
1245
- desired: {
1246
- protocols: desiredProtocols,
1247
- autoSelectProtocols: desiredAutoSelect
1248
- },
1249
- decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1250
- });
1251
- if (!needsUpdate) continue;
1252
- const update = {
1253
- asset,
1254
- autoSelectProtocols: desiredAutoSelect
1255
- };
1256
- if (desiredProtocols !== null) update.protocols = desiredProtocols;
1257
- await this.sdk.updateUserProfile(update);
1258
- debugLog("zyfai:pools", `${asset}: wrote protocols update`, update);
1259
- }
1260
- const poolResults = await Promise.all(
1261
- universe.map(async (protocol) => {
1262
- try {
1263
- const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1264
- return [protocol.id, flattenAvailablePools(raw)];
1265
- } catch (error) {
1266
- console.warn(
1267
- `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
1268
- error instanceof Error ? error.message : String(error)
1269
- );
1270
- return null;
1271
- }
1272
- })
1273
- );
1274
- const availableByProtocol = /* @__PURE__ */ new Map();
1275
- for (const result of poolResults) {
1276
- if (result) availableByProtocol.set(result[0], result[1]);
1277
- }
1278
- const customizations = toZyfaiCustomizations(
1279
- policy,
1280
- availableByProtocol,
1281
- chainsByProtocol
1282
- );
1283
- debugLog("zyfai:pools", "derived customizeBatch payload", {
1284
- strategy,
1285
- availableProtocolCount: availableByProtocol.size,
1286
- customizations
1287
- });
1288
- if (customizations.length === 0) {
1289
- debugLog("zyfai:pools", "skipping customizeBatch \u2014 nothing to send");
1290
- return;
1291
- }
1292
- await this.sdk.customizeBatch(customizations);
1293
- debugLog("zyfai:pools", "wrote customizeBatch", {
1294
- count: customizations.length
1295
- });
1296
- } catch (error) {
1297
- console.warn(
1298
- "[zyfai] applyPoolPolicy failed (non-fatal):",
1299
- error instanceof Error ? error.message : String(error)
1300
- );
1301
- }
1302
- }
1303
- /**
1304
- * Records the deposit with Zyfai, retrying transient failures.
1305
- *
1306
- * This runs AFTER the transfer has already landed on-chain, so it must never
1307
- * fail the deposit — the funds moved. But it is also the ONLY source of the
1308
- * "Top up wallet" entry the history is built from: Zyfai auto-deploys the
1309
- * balance it detects either way, so when this call is lost the user's deposit
1310
- * never appears in Activity (an earlier withdrawal stays the newest row) and
1311
- * nothing ever backfills it. Retry, then log loudly enough to be recoverable.
1312
- */
1313
- async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
1314
- const ATTEMPTS = 3;
1315
- const RETRY_DELAY_MS = 1e3;
1316
- for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
1317
- try {
1318
- await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
1319
- return;
1320
- } catch (logError) {
1321
- if (attempt === ATTEMPTS) {
1322
- console.error(
1323
- "[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
1324
- { txHash, chainId, amount, tokenAddress, error: logError }
1325
- );
1326
- return;
1327
- }
1328
- await new Promise(
1329
- (resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
1330
- );
1331
- }
1332
- }
1333
- }
1334
953
  async deposit(state, chainId, amount, asset, depositCallback) {
1335
954
  try {
1336
- await this.connectFull(state, chainId);
1337
955
  const validChainId = isValidChainId(chainId);
956
+ await this.ensureConnected(state, validChainId);
1338
957
  if (depositCallback) {
1339
958
  const wallet = await this.sdk.getSmartWalletAddress(
1340
959
  this.getAddress(),
@@ -1346,19 +965,22 @@ var ZyfaiAgent = class {
1346
965
  amount
1347
966
  );
1348
967
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1349
- await this.logDepositWithRetry(
1350
- validChainId,
1351
- txHash,
1352
- amount,
1353
- tokenAddress
1354
- );
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
+ }
1355
976
  return { txHash, smartWallet: wallet.address, amount };
1356
977
  }
1357
978
  const raw = await this.sdk.depositFunds(
1358
979
  this.getAddress(),
1359
980
  validChainId,
1360
981
  amount,
1361
- asset
982
+ asset,
983
+ "aggressive"
1362
984
  );
1363
985
  return mapDeposit(raw);
1364
986
  } catch (error) {
@@ -1367,8 +989,8 @@ var ZyfaiAgent = class {
1367
989
  }
1368
990
  // --- IAgent: Fund operations ---
1369
991
  async withdraw(state, chainId, token, amount) {
1370
- await this.connectFull(state, chainId);
1371
992
  const validChainId = isValidChainId(chainId);
993
+ await this.ensureConnected(state, validChainId);
1372
994
  const raw = await this.sdk.withdrawFunds(
1373
995
  this.getAddress(),
1374
996
  validChainId,
@@ -1447,19 +1069,7 @@ var ZyfaiAgent = class {
1447
1069
  }
1448
1070
  async getUserProfile(state, chainId) {
1449
1071
  await this.connectAuth(state, chainId);
1450
- const validChainId = isValidChainId(chainId);
1451
1072
  const raw = await this.sdk.getUserDetails();
1452
- debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
1453
- asset: "USDC (default \u2014 no asset passed)",
1454
- splitting: raw.splitting,
1455
- minSplits: raw.minSplits,
1456
- strategy: raw.strategy,
1457
- chains: raw.chains,
1458
- protocolCount: raw.protocols?.length,
1459
- hasActiveSessionKey: raw.hasActiveSessionKey,
1460
- smartWallet: raw.smartWallet
1461
- });
1462
- await this.ensureSessionKey(state, this.getAddress(), validChainId);
1463
1073
  return mapUserProfile(raw, this.connectedAddress);
1464
1074
  }
1465
1075
  async ensureAutoSelectProtocols(state, chainId, asset) {
@@ -1492,40 +1102,6 @@ var ZyfaiAgent = class {
1492
1102
 
1493
1103
  // src/lib/routing-api.ts
1494
1104
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1495
- async function fetchOrgAgentConfig(apiKey) {
1496
- const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
1497
- try {
1498
- const res = await fetch(url, {
1499
- method: "GET",
1500
- headers: {
1501
- "Content-Type": "application/json",
1502
- "x-owney-api-key": `${apiKey}`
1503
- }
1504
- });
1505
- if (!res.ok) {
1506
- if (res.status !== 404) {
1507
- console.warn(
1508
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
1509
- );
1510
- }
1511
- return null;
1512
- }
1513
- const json = await res.json();
1514
- const policy = json.success ? json.data ?? null : null;
1515
- debugLog(
1516
- "owney-sdk",
1517
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
1518
- policy ?? void 0
1519
- );
1520
- return policy;
1521
- } catch (error) {
1522
- console.warn(
1523
- "[owney-sdk] Could not read org agent config (non-fatal):",
1524
- error instanceof Error ? error.message : String(error)
1525
- );
1526
- return null;
1527
- }
1528
- }
1529
1105
  async function fetchAgentKeys(apiKey) {
1530
1106
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
1531
1107
  const res = await fetch(url, {
@@ -1590,10 +1166,11 @@ async function withFailureReporting(apiKey, agentType, fn) {
1590
1166
  // src/lib/helpers/withdraw-helper.ts
1591
1167
  import { parseUnits } from "viem";
1592
1168
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1169
+ const target = asset.toUpperCase();
1593
1170
  return agents.map((agent) => {
1594
1171
  const agentBalance = aggregated[agent.id];
1595
1172
  const tokenBalance = agentBalance?.tokens.find(
1596
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
1173
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1597
1174
  );
1598
1175
  if (!tokenBalance) return { agent, balance: 0n };
1599
1176
  return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
@@ -1863,6 +1440,84 @@ async function getSponsorRelayerAddress(input) {
1863
1440
  return parsed.data.relayer;
1864
1441
  }
1865
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
+
1866
1521
  // src/lib/sponsored-deposit.ts
1867
1522
  var AUTH_WINDOW_SECONDS = 15 * 60;
1868
1523
  function makeSponsoredDepositCallback(deps) {
@@ -2045,7 +1700,6 @@ var VIEM_CHAIN2 = {
2045
1700
  42161: arbitrum2,
2046
1701
  1: mainnet2
2047
1702
  };
2048
- var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2049
1703
  var SPONSORED_WETH_BY_CHAIN = {
2050
1704
  8453: "0x4200000000000000000000000000000000000006",
2051
1705
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2065,26 +1719,17 @@ var OwneySDK = class {
2065
1719
  disabledAgents = /* @__PURE__ */ new Set();
2066
1720
  state = null;
2067
1721
  apiKey;
2068
- // The org's agent policy, fetched once per client. Applied to each agent on
2069
- // activation (ROUT-229). `undefined` means "not fetched yet", `null` means
2070
- // "fetched, and the partner has no policy" — which is a real answer meaning
2071
- // leave every user's agent profile alone.
2072
- orgAgentConfig;
2073
- orgAgentConfigPromise = null;
2074
1722
  zyfaiRpcUrls;
2075
1723
  routingApiBaseUrl;
2076
1724
  referralSource;
2077
- surfliquidApiBaseUrl;
2078
1725
  cachedSponsoredCallback = null;
2079
1726
  cachedWethSponsoredCallback = null;
2080
1727
  initializingAgentsPromise = null;
2081
1728
  constructor(config) {
2082
1729
  this.apiKey = config.apiKey;
2083
- if (config.debug) setOwneyDebug(true);
2084
1730
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2085
1731
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2086
1732
  this.referralSource = config.referralSource;
2087
- this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2088
1733
  }
2089
1734
  /**
2090
1735
  * Establish connection state for all subsequent SDK calls.
@@ -2243,57 +1888,14 @@ var OwneySDK = class {
2243
1888
  }
2244
1889
  return active;
2245
1890
  }
2246
- /**
2247
- * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
2248
- * throws — it degrades to null — so this cannot break activation.
2249
- */
2250
- async getOrgAgentConfig() {
2251
- if (this.orgAgentConfig !== void 0) return this.orgAgentConfig;
2252
- if (!this.orgAgentConfigPromise) {
2253
- this.orgAgentConfigPromise = fetchOrgAgentConfig(this.apiKey).then(
2254
- (config) => {
2255
- this.orgAgentConfig = config;
2256
- return config;
2257
- }
2258
- );
2259
- }
2260
- return this.orgAgentConfigPromise;
2261
- }
2262
- /**
2263
- * Apply the org policy to one agent, after it has activated.
2264
- *
2265
- * Sequenced after activation rather than alongside it because applying the
2266
- * policy needs the authenticated session activation establishes. Every failure
2267
- * mode is swallowed: a partner's splitting preference is not worth failing a
2268
- * user's connect over.
2269
- */
2270
- async applyOrgPolicyTo(agent, state, chainId) {
2271
- if (!agent.applyOrgPolicy) {
2272
- debugLog(
2273
- "owney-sdk",
2274
- `${agent.id}: no applyOrgPolicy \u2014 agent has no org-level knobs, skipping`
2275
- );
2276
- return;
2277
- }
2278
- try {
2279
- const policy = await this.getOrgAgentConfig();
2280
- if (!policy) return;
2281
- await agent.applyOrgPolicy(state, chainId, policy);
2282
- } catch (error) {
2283
- console.warn(
2284
- `[owney-sdk] applyOrgPolicy(${agent.id}) failed (non-fatal):`,
2285
- error instanceof Error ? error.message : String(error)
2286
- );
2287
- }
2288
- }
2289
1891
  async ensureAgentsInitialized() {
2290
1892
  if (this.agents.size > 0) return;
2291
1893
  if (!this.initializingAgentsPromise) {
2292
1894
  this.initializingAgentsPromise = (async () => {
2293
1895
  const agentKeys = await fetchAgentKeys(this.apiKey);
2294
1896
  this.disabledAgents.clear();
2295
- for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2296
- 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);
2297
1899
  if (!agent) continue;
2298
1900
  this.agents.set(agent_type, agent);
2299
1901
  if (is_enabled === false) {
@@ -2314,21 +1916,9 @@ var OwneySDK = class {
2314
1916
  this.initializingAgentsPromise = null;
2315
1917
  }
2316
1918
  }
2317
- async createAgent(agentId, key2, projectName) {
1919
+ createAgent(agentId, key) {
2318
1920
  if (agentId === "zyfai") {
2319
- return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2320
- }
2321
- if (agentId === "surfliquid") {
2322
- if (!projectName) return null;
2323
- const { SurfLiquidAgent } = await import("./surfliquid.agent-VNGQUSXN.js");
2324
- return new SurfLiquidAgent({
2325
- projectName,
2326
- appId: key2,
2327
- // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
2328
- // which WebKit (iOS) discards when it is third-party. See
2329
- // OwneySDKConfig.surfliquidApiBaseUrl.
2330
- apiBaseUrl: this.surfliquidApiBaseUrl
2331
- });
1921
+ return new ZyfaiAgent(key, this.zyfaiRpcUrls, this.referralSource);
2332
1922
  }
2333
1923
  return null;
2334
1924
  }
@@ -2382,7 +1972,6 @@ var OwneySDK = class {
2382
1972
  Promise.all(
2383
1973
  agents.map(async (agent) => {
2384
1974
  await agent.activateAgent(state, chainId);
2385
- await this.applyOrgPolicyTo(agent, state, chainId);
2386
1975
  })
2387
1976
  ).catch((error) => {
2388
1977
  console.error("activateAgent background init failed:", error);
@@ -2410,7 +1999,6 @@ var OwneySDK = class {
2410
1999
  Promise.all(
2411
2000
  enabledCompatible.map(async (agent) => {
2412
2001
  await agent.activateAgent(state, chainId);
2413
- await this.applyOrgPolicyTo(agent, state, chainId);
2414
2002
  })
2415
2003
  ).catch((error) => {
2416
2004
  console.error("activateAgent background init failed:", error);
@@ -2484,30 +2072,18 @@ var OwneySDK = class {
2484
2072
  );
2485
2073
  }
2486
2074
  const agentResults = {};
2487
- const agentErrors = {};
2488
2075
  for (const { agent, amount: agentAmount } of agentAmounts) {
2489
- try {
2490
- agentResults[agent.id] = await this.depositWithFallback(
2491
- agent,
2492
- state,
2493
- chainId,
2494
- agentAmount,
2495
- asset,
2496
- effectiveCallback,
2497
- depositCallback
2498
- );
2499
- } catch (error) {
2500
- agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
2501
- }
2502
- }
2503
- if (Object.keys(agentResults).length === 0) {
2504
- throw new OwneyError(
2505
- "DEPOSIT_ALL_FAILED",
2506
- `All ${agentAmounts.length} agent deposit(s) failed.`,
2507
- { agentErrors }
2076
+ agentResults[agent.id] = await this.depositWithFallback(
2077
+ agent,
2078
+ state,
2079
+ chainId,
2080
+ agentAmount,
2081
+ asset,
2082
+ effectiveCallback,
2083
+ depositCallback
2508
2084
  );
2509
2085
  }
2510
- return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2086
+ return { agentResults };
2511
2087
  }
2512
2088
  /**
2513
2089
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2623,8 +2199,9 @@ var OwneySDK = class {
2623
2199
  async hasExistingBalance(agent, state, chainId, asset) {
2624
2200
  try {
2625
2201
  const balance = await agent.getBalances(state, chainId);
2202
+ const target = asset.toLowerCase();
2626
2203
  const token = balance.tokens.find(
2627
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
2204
+ (t) => t.chainId === chainId && t.asset.toLowerCase() === target
2628
2205
  );
2629
2206
  return !!token && Number(token.amount) > 0;
2630
2207
  } catch {
@@ -2663,34 +2240,6 @@ var OwneySDK = class {
2663
2240
  }
2664
2241
  return eligible;
2665
2242
  }
2666
- /**
2667
- * Agent ids the routing API provisioned for this org that support the given
2668
- * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
2669
- * surfliquid first). Returns `[]` when the org has no compatible agent — never
2670
- * throws on an empty org. Loads agent keys on first call (apiKey only, no
2671
- * wallet), so the UI can resolve which agent to use before the user connects.
2672
- *
2673
- * This is the source of truth for agent availability: an agent appears here
2674
- * iff the routing API returned its key. No per-app feature flags.
2675
- */
2676
- async getEligibleAgentIds(chainId, asset) {
2677
- try {
2678
- await this.ensureAgentsInitialized();
2679
- } catch (error) {
2680
- if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
2681
- return [];
2682
- }
2683
- throw error;
2684
- }
2685
- return [...this.agents.values()].filter((agent) => {
2686
- const chainAssets = agent.supportedAssets.find(
2687
- (sa) => sa.chainId === chainId
2688
- );
2689
- return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
2690
- }).map((agent) => agent.id).sort(
2691
- (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
2692
- );
2693
- }
2694
2243
  // --- Fund operations ---
2695
2244
  /**
2696
2245
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2726,17 +2275,9 @@ var OwneySDK = class {
2726
2275
  }
2727
2276
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2728
2277
  if (!amount) {
2729
- const aggregated2 = await this.getBalances();
2730
- const funded = projectAgentBalancesForAsset(
2731
- eligibleAgents,
2732
- aggregated2.agentBalances,
2733
- chainId,
2734
- asset,
2735
- assetInfo.decimals
2736
- ).filter((b) => b.balance > 0n);
2737
2278
  const results2 = {};
2738
2279
  const agentErrors2 = {};
2739
- for (const { agent } of funded) {
2280
+ for (const agent of eligibleAgents) {
2740
2281
  try {
2741
2282
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2742
2283
  } catch (err) {
@@ -2753,10 +2294,9 @@ var OwneySDK = class {
2753
2294
  { asset, agentErrors: agentErrors2 }
2754
2295
  );
2755
2296
  }
2756
- const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2757
2297
  return {
2758
2298
  agentResult: results2,
2759
- totalWithdrawn: totalWithdrawn.toString()
2299
+ totalWithdrawn: sumWithdrawnAmount(results2).toString()
2760
2300
  };
2761
2301
  }
2762
2302
  const requested = BigInt(amount);
@@ -3273,22 +2813,22 @@ var readLegacySiwxSession = (store, address) => {
3273
2813
  const legacyKeys = [];
3274
2814
  try {
3275
2815
  for (let i = 0; i < store.length; i++) {
3276
- const key2 = store.key(i);
3277
- if (key2?.startsWith(prefix)) legacyKeys.push(key2);
2816
+ const key = store.key(i);
2817
+ if (key?.startsWith(prefix)) legacyKeys.push(key);
3278
2818
  }
3279
2819
  } catch {
3280
2820
  return null;
3281
2821
  }
3282
2822
  let adopted = null;
3283
- for (const key2 of legacyKeys) {
2823
+ for (const key of legacyKeys) {
3284
2824
  if (!adopted) {
3285
2825
  try {
3286
- adopted = JSON.parse(store.getItem(key2) ?? "");
2826
+ adopted = JSON.parse(store.getItem(key) ?? "");
3287
2827
  } catch {
3288
2828
  }
3289
2829
  }
3290
2830
  try {
3291
- store.removeItem(key2);
2831
+ store.removeItem(key);
3292
2832
  } catch {
3293
2833
  }
3294
2834
  }
@@ -3296,11 +2836,11 @@ var readLegacySiwxSession = (store, address) => {
3296
2836
  };
3297
2837
  var readSiwxSession = (address, chainId) => {
3298
2838
  if (typeof window === "undefined") return null;
3299
- const key2 = buildKey2(address);
2839
+ const key = buildKey2(address);
3300
2840
  const store = storage2();
3301
2841
  let raw = null;
3302
2842
  try {
3303
- raw = store?.getItem(key2) ?? null;
2843
+ raw = store?.getItem(key) ?? null;
3304
2844
  } catch {
3305
2845
  raw = null;
3306
2846
  }
@@ -3308,9 +2848,9 @@ var readSiwxSession = (address, chainId) => {
3308
2848
  try {
3309
2849
  return JSON.parse(raw);
3310
2850
  } catch {
3311
- memorySiwxSessions.delete(key2);
2851
+ memorySiwxSessions.delete(key);
3312
2852
  try {
3313
- store?.removeItem(key2);
2853
+ store?.removeItem(key);
3314
2854
  } catch {
3315
2855
  }
3316
2856
  return null;
@@ -3321,24 +2861,24 @@ var readSiwxSession = (address, chainId) => {
3321
2861
  writeSiwxSession(address, chainId, legacy);
3322
2862
  return legacy;
3323
2863
  }
3324
- return memorySiwxSessions.get(key2) ?? null;
2864
+ return memorySiwxSessions.get(key) ?? null;
3325
2865
  };
3326
2866
  var writeSiwxSession = (address, _chainId, session) => {
3327
2867
  if (typeof window === "undefined") return;
3328
- const key2 = buildKey2(address);
3329
- memorySiwxSessions.set(key2, session);
2868
+ const key = buildKey2(address);
2869
+ memorySiwxSessions.set(key, session);
3330
2870
  const store = storage2();
3331
2871
  try {
3332
- store?.setItem(key2, JSON.stringify(session));
2872
+ store?.setItem(key, JSON.stringify(session));
3333
2873
  } catch {
3334
2874
  }
3335
2875
  };
3336
2876
  var clearSiwxSession = (address, _chainId) => {
3337
- const key2 = buildKey2(address);
3338
- memorySiwxSessions.delete(key2);
2877
+ const key = buildKey2(address);
2878
+ memorySiwxSessions.delete(key);
3339
2879
  const store = storage2();
3340
2880
  try {
3341
- store?.removeItem(key2);
2881
+ store?.removeItem(key);
3342
2882
  } catch {
3343
2883
  }
3344
2884
  readLegacySiwxSession(store, address);
@@ -3467,6 +3007,5 @@ export {
3467
3007
  NotConnectedError,
3468
3008
  OwneyError,
3469
3009
  OwneySDK,
3470
- createOwneySIWX,
3471
- setOwneyDebug
3010
+ createOwneySIWX
3472
3011
  };