@owney/sdk 0.7.0 → 0.7.2

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
@@ -125,6 +125,14 @@ function hexToDecimal(hex, decimals = 6) {
125
125
  const remainder = parsed % divisor;
126
126
  return Number(whole) + Number(remainder) / Number(divisor);
127
127
  }
128
+ function normalizeDecimalPlace(value, decimals = 6) {
129
+ const parsed = BigInt(value);
130
+ if (decimals <= 0) return Number(parsed);
131
+ const divisor = BigInt(10 ** decimals);
132
+ const whole = parsed / divisor;
133
+ const remainder = parsed % divisor;
134
+ return Number(whole) + Number(remainder) / Number(divisor);
135
+ }
128
136
  var DayFilterMapping = {
129
137
  "30D": 30,
130
138
  "14D": 14,
@@ -183,10 +191,10 @@ function mapBalances(raw, _chainId, smartWallet) {
183
191
  const tokens = [];
184
192
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
185
193
  const positionChainId = Number(chainIdKey);
186
- for (const [symbol, { balance, decimals }] of Object.entries(
194
+ for (const [symbol, { balance, balanceWithFee, decimals }] of Object.entries(
187
195
  tokensBySymbol ?? {}
188
196
  )) {
189
- const amount = hexToDecimal(balance || "0", decimals);
197
+ const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
190
198
  totalBalance += amount;
191
199
  tokens.push({
192
200
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
@@ -202,15 +210,23 @@ function mapBalances(raw, _chainId, smartWallet) {
202
210
  protocolId: p.protocol_id,
203
211
  pool: p.pool,
204
212
  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
213
+ // Fee-adjusted underlying amounts are decimal base-unit strings. The
214
+ // legacy `amount` fallback is hex. Runtime
207
215
  // 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.
216
+ // when present, otherwise fall back to the per-symbol lookup.
210
217
  amount: String(
211
- hexToDecimal(
218
+ p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
219
+ p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
220
+ tokenDecimals(
221
+ p.token_symbol,
222
+ p.decimals
223
+ )
224
+ ) : hexToDecimal(
212
225
  p.amount || "0",
213
- tokenDecimals(p.token_symbol, p.decimals)
226
+ tokenDecimals(
227
+ p.token_symbol,
228
+ p.decimals
229
+ )
214
230
  )
215
231
  ),
216
232
  apy: p.pool_apy,
@@ -234,7 +250,7 @@ function sumTokenEarnings(tokens) {
234
250
  return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
235
251
  }
236
252
  function mapEarnings(raw, smartWallet) {
237
- const totalEarningsByChain = raw.data.totalEarningsByChain;
253
+ const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
238
254
  const tokens = [];
239
255
  for (const [chainIdKey, tokensBySymbol] of Object.entries(
240
256
  totalEarningsByChain
@@ -252,7 +268,9 @@ function mapEarnings(raw, smartWallet) {
252
268
  }
253
269
  return {
254
270
  smartWallet,
255
- lifetimeEarnings: sumTokenEarnings(raw.data.totalEarningsByToken),
271
+ lifetimeEarnings: sumTokenEarnings(
272
+ raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
273
+ ),
256
274
  tokens
257
275
  };
258
276
  }
@@ -380,7 +398,9 @@ function mapUserProfile(raw, userAddress) {
380
398
  chains: raw.chains || [],
381
399
  strategy: raw.strategy,
382
400
  hasActiveSessionKey: raw.hasActiveSessionKey || false,
383
- protocols: raw.protocols || []
401
+ protocols: raw.protocols || [],
402
+ splitting: raw.splitting,
403
+ minSplits: raw.minSplits
384
404
  };
385
405
  }
386
406
  function mapApyByStrategy(raw) {
@@ -527,23 +547,23 @@ var readLegacySession = (store, address) => {
527
547
  const legacyKeys = [];
528
548
  try {
529
549
  for (let i = 0; i < store.length; i++) {
530
- const key = store.key(i);
531
- if (key?.startsWith(prefix)) legacyKeys.push(key);
550
+ const key2 = store.key(i);
551
+ if (key2?.startsWith(prefix)) legacyKeys.push(key2);
532
552
  }
533
553
  } catch {
534
554
  return null;
535
555
  }
536
556
  let adopted = null;
537
- for (const key of legacyKeys) {
557
+ for (const key2 of legacyKeys) {
538
558
  if (!adopted) {
539
559
  try {
540
- const parsed = JSON.parse(store.getItem(key) ?? "");
560
+ const parsed = JSON.parse(store.getItem(key2) ?? "");
541
561
  if (isFreshSession(parsed)) adopted = parsed;
542
562
  } catch {
543
563
  }
544
564
  }
545
565
  try {
546
- store.removeItem(key);
566
+ store.removeItem(key2);
547
567
  } catch {
548
568
  }
549
569
  }
@@ -551,11 +571,11 @@ var readLegacySession = (store, address) => {
551
571
  };
552
572
  var readSession = (address, _chainId) => {
553
573
  if (typeof window === "undefined") return null;
554
- const key = buildKey(address);
574
+ const key2 = buildKey(address);
555
575
  const store = storage();
556
576
  let raw = null;
557
577
  try {
558
- raw = store?.getItem(key) ?? null;
578
+ raw = store?.getItem(key2) ?? null;
559
579
  } catch {
560
580
  raw = null;
561
581
  }
@@ -565,9 +585,9 @@ var readSession = (address, _chainId) => {
565
585
  if (isFreshSession(parsed)) return parsed;
566
586
  } catch {
567
587
  }
568
- memorySessions.delete(key);
588
+ memorySessions.delete(key2);
569
589
  try {
570
- store?.removeItem(key);
590
+ store?.removeItem(key2);
571
591
  } catch {
572
592
  }
573
593
  return null;
@@ -577,27 +597,27 @@ var readSession = (address, _chainId) => {
577
597
  writeSession(address, _chainId, legacy);
578
598
  return legacy;
579
599
  }
580
- const cached = memorySessions.get(key);
600
+ const cached = memorySessions.get(key2);
581
601
  if (isFreshSession(cached)) return cached;
582
- if (cached) memorySessions.delete(key);
602
+ if (cached) memorySessions.delete(key2);
583
603
  return null;
584
604
  };
585
605
  var writeSession = (address, _chainId, session) => {
586
606
  if (typeof window === "undefined") return;
587
- const key = buildKey(address);
588
- memorySessions.set(key, session);
607
+ const key2 = buildKey(address);
608
+ memorySessions.set(key2, session);
589
609
  const store = storage();
590
610
  try {
591
- store?.setItem(key, JSON.stringify(session));
611
+ store?.setItem(key2, JSON.stringify(session));
592
612
  } catch {
593
613
  }
594
614
  };
595
615
  var clearSession = (address, _chainId) => {
596
- const key = buildKey(address);
597
- memorySessions.delete(key);
616
+ const key2 = buildKey(address);
617
+ memorySessions.delete(key2);
598
618
  const store = storage();
599
619
  try {
600
- store?.removeItem(key);
620
+ store?.removeItem(key2);
601
621
  } catch {
602
622
  }
603
623
  readLegacySession(store, address);
@@ -628,6 +648,149 @@ function buildProtocolMergeUpdate(available, current, asset) {
628
648
  };
629
649
  }
630
650
 
651
+ // src/agents/zyfai/splitting.ts
652
+ var MAX_MIN_SPLITS = 4;
653
+ var MIN_FORCE_SPLITS = 2;
654
+ var AUTOMATIC_MIN_SPLITS = 1;
655
+ function toZyfaiSplitting(policy) {
656
+ switch (policy.splittingMode) {
657
+ case "none":
658
+ return { splitting: false };
659
+ case "automatic":
660
+ return { splitting: true, minSplits: AUTOMATIC_MIN_SPLITS };
661
+ case "force":
662
+ return {
663
+ splitting: true,
664
+ minSplits: clampForceSplits(policy.minSplits)
665
+ };
666
+ }
667
+ }
668
+ function clampForceSplits(minSplits) {
669
+ if (typeof minSplits !== "number" || !Number.isFinite(minSplits)) {
670
+ return MIN_FORCE_SPLITS;
671
+ }
672
+ const rounded = Math.round(minSplits);
673
+ if (rounded < MIN_FORCE_SPLITS) return MIN_FORCE_SPLITS;
674
+ if (rounded > MAX_MIN_SPLITS) return MAX_MIN_SPLITS;
675
+ return rounded;
676
+ }
677
+ function splittingNeedsUpdate(current, desired) {
678
+ if (!current) return true;
679
+ if (current.splitting !== desired.splitting) return true;
680
+ if (desired.minSplits === void 0) return false;
681
+ return current.minSplits !== desired.minSplits;
682
+ }
683
+
684
+ // src/agents/zyfai/pool-policy.ts
685
+ function toZyfaiCustomizations(policy, availableByProtocol, chainsByProtocol) {
686
+ const selectionByKey = /* @__PURE__ */ new Map();
687
+ for (const selection of policy.selections) {
688
+ selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
689
+ }
690
+ const customizations = [];
691
+ for (const [protocolId, chainIds] of Object.entries(chainsByProtocol)) {
692
+ const available = availableByProtocol.get(protocolId);
693
+ if (available === void 0) {
694
+ continue;
695
+ }
696
+ const availableSet = new Set(available);
697
+ for (const chainId of chainIds) {
698
+ const stored = selectionByKey.get(key(protocolId, chainId));
699
+ if (stored === void 0) {
700
+ customizations.push({
701
+ protocolId,
702
+ chainId,
703
+ pools: [],
704
+ autoselect: true
705
+ });
706
+ continue;
707
+ }
708
+ const pools = stored.pools.filter((pool) => availableSet.has(pool));
709
+ customizations.push({
710
+ protocolId,
711
+ chainId,
712
+ pools,
713
+ autoselect: false
714
+ });
715
+ }
716
+ }
717
+ return customizations;
718
+ }
719
+ function deriveProtocols(policy, universe, chainsByProtocol) {
720
+ if (policy.selections.length === 0) {
721
+ return null;
722
+ }
723
+ const selectionByKey = /* @__PURE__ */ new Map();
724
+ for (const selection of policy.selections) {
725
+ selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
726
+ }
727
+ return universe.filter((protocolId) => {
728
+ const chainIds = chainsByProtocol[protocolId];
729
+ if (chainIds === void 0 || chainIds.length === 0) {
730
+ return true;
731
+ }
732
+ const fullyDisabled = chainIds.every((chainId) => {
733
+ const stored = selectionByKey.get(key(protocolId, chainId));
734
+ return stored !== void 0 && stored.pools.length === 0;
735
+ });
736
+ return !fullyDisabled;
737
+ });
738
+ }
739
+ function key(protocolId, chainId) {
740
+ return `${protocolId}:${chainId}`;
741
+ }
742
+ function buildChainsByProtocol(protocols, dashboardChainIds) {
743
+ const allowed = new Set(dashboardChainIds);
744
+ const result = {};
745
+ for (const protocol of protocols) {
746
+ result[protocol.id] = (protocol.chains ?? []).filter(
747
+ (chainId) => allowed.has(chainId)
748
+ );
749
+ }
750
+ return result;
751
+ }
752
+ function flattenAvailablePools(raw) {
753
+ const byChain = raw ?? {};
754
+ const names = [];
755
+ for (const byToken of Object.values(byChain ?? {})) {
756
+ for (const entry of Object.values(byToken ?? {})) {
757
+ for (const pool of entry?.pools ?? []) {
758
+ if (pool?.name) names.push(pool.name);
759
+ }
760
+ }
761
+ }
762
+ return names;
763
+ }
764
+ function protocolListsEqual(a, b) {
765
+ const left = [...a ?? []].sort();
766
+ const right = [...b ?? []].sort();
767
+ if (left.length !== right.length) return false;
768
+ return left.every((value, index) => value === right[index]);
769
+ }
770
+ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect) {
771
+ if (!current) return true;
772
+ if (current.autoSelectProtocols !== desiredAutoSelect) return true;
773
+ if (desiredProtocols === null) return false;
774
+ return !protocolListsEqual(current.protocols, desiredProtocols);
775
+ }
776
+
777
+ // src/lib/debug.ts
778
+ var configuredDebug = false;
779
+ function setOwneyDebug(enabled) {
780
+ configuredDebug = enabled;
781
+ }
782
+ function isOwneyDebug() {
783
+ return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
784
+ }
785
+ function debugLog(scope, message, data) {
786
+ if (!isOwneyDebug()) return;
787
+ if (data === void 0) {
788
+ console.log(`[${scope}] ${message}`);
789
+ } else {
790
+ console.log(`[${scope}] ${message}`, data);
791
+ }
792
+ }
793
+
631
794
  // src/agents/zyfai/zyfai.agent.ts
632
795
  var DEFAULT_ZYFAI_RPC_URLS = {
633
796
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
@@ -640,6 +803,8 @@ var WETH_ADDRESS_BY_CHAIN = {
640
803
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
641
804
  };
642
805
  var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
806
+ var SPLITTING_ASSETS = ["USDC", "WETH"];
807
+ var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
643
808
  var ZyfaiAgent = class {
644
809
  id = "zyfai";
645
810
  supportedChainIds = [8453, 42161, 1];
@@ -950,6 +1115,176 @@ var ZyfaiAgent = class {
950
1115
  throw error;
951
1116
  }
952
1117
  }
1118
+ /**
1119
+ * Apply the org's capital splitting policy to this user's Zyfai profile.
1120
+ *
1121
+ * Per ASSET, not per chain: Zyfai stores splitting under
1122
+ * `assetTypeSettings[asset]`, so one call would only configure USDC and leave a
1123
+ * WETH holder's real setting untouched.
1124
+ *
1125
+ * Non-fatal throughout. This is a side effect of connecting, so a Zyfai hiccup
1126
+ * must leave the user connected with their profile unchanged, never fail
1127
+ * activation.
1128
+ */
1129
+ async applyOrgPolicy(_state, _chainId, policy) {
1130
+ debugLog("zyfai:splitting", "applying org policy", {
1131
+ policy,
1132
+ // Per asset because Zyfai stores splitting under assetTypeSettings[asset].
1133
+ assets: SPLITTING_ASSETS
1134
+ });
1135
+ await Promise.all(
1136
+ SPLITTING_ASSETS.map((asset) => this.applySplitting(asset, policy))
1137
+ );
1138
+ if (policy.poolPolicy) {
1139
+ await this.applyPoolPolicy(policy.poolPolicy);
1140
+ }
1141
+ }
1142
+ async applySplitting(asset, policy) {
1143
+ try {
1144
+ const desired = toZyfaiSplitting(policy);
1145
+ const current = await this.sdk.getUserDetails(asset);
1146
+ const currentSplitting = {
1147
+ splitting: current?.splitting,
1148
+ minSplits: current?.minSplits
1149
+ };
1150
+ const needsUpdate = splittingNeedsUpdate(current, desired);
1151
+ debugLog("zyfai:splitting", `${asset}: comparing`, {
1152
+ mode: policy.splittingMode,
1153
+ current: currentSplitting,
1154
+ desired,
1155
+ // Spelled out because the "why" is the non-obvious part: minSplits is
1156
+ // what separates automatic (1) from force (2-4), not `splitting`.
1157
+ decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1158
+ });
1159
+ if (!needsUpdate) return;
1160
+ const result = await this.sdk.updateUserProfile({ asset, ...desired });
1161
+ debugLog("zyfai:splitting", `${asset}: wrote updateUserProfile`, {
1162
+ sent: { asset, ...desired },
1163
+ // updateUserProfile re-reads the profile after PATCHing, so this is
1164
+ // Zyfai's own view of the result rather than an echo of what we sent.
1165
+ zyfaiNowReports: {
1166
+ splitting: result?.splitting,
1167
+ minSplits: result?.minSplits
1168
+ }
1169
+ });
1170
+ } catch (error) {
1171
+ console.warn(
1172
+ `[zyfai] applyOrgPolicy(${asset}) failed (non-fatal):`,
1173
+ error instanceof Error ? error.message : String(error)
1174
+ );
1175
+ }
1176
+ }
1177
+ /**
1178
+ * Apply the org's protocol/pool policy to this user's Zyfai profile.
1179
+ *
1180
+ * Two Zyfai writes, in a strict order:
1181
+ * 1. `updateUserProfile({ protocols, autoSelectProtocols })` per asset —
1182
+ * the explicit allow-list. Chain-agnostic, mirrors `applySplitting`.
1183
+ * 2. `customizeBatch(...)` once — per (protocol, chain), asset-agnostic
1184
+ * pool narrowing.
1185
+ *
1186
+ * Order matters: constraining a protocol's pools (step 2) while that
1187
+ * protocol is not yet in the allow-list (step 1 not yet applied) lands the
1188
+ * constraint on something the rebalance engine isn't considering.
1189
+ *
1190
+ * Wrapped so a failure anywhere is non-fatal — this runs as a side effect of
1191
+ * connecting, so a Zyfai hiccup must leave the user connected with their
1192
+ * profile unchanged, never fail activation.
1193
+ */
1194
+ async applyPoolPolicy(policy) {
1195
+ try {
1196
+ const { protocols: universe } = await this.sdk.getAvailableProtocols(
1197
+ this.supportedChainIds[0]
1198
+ );
1199
+ const universeIds = universe.map((p) => p.id);
1200
+ const chainsByProtocol = buildChainsByProtocol(
1201
+ universe,
1202
+ POOL_POLICY_CHAIN_IDS
1203
+ );
1204
+ debugLog("zyfai:pools", "protocol universe", {
1205
+ universeIds,
1206
+ chainsByProtocol
1207
+ });
1208
+ let strategy;
1209
+ const desiredProtocols = deriveProtocols(
1210
+ policy,
1211
+ universeIds,
1212
+ chainsByProtocol
1213
+ );
1214
+ const desiredAutoSelect = policy.autoApproveProtocols;
1215
+ for (const asset of SPLITTING_ASSETS) {
1216
+ const current = await this.sdk.getUserDetails(asset);
1217
+ if (strategy === void 0 && (current.strategy === "conservative" || current.strategy === "aggressive")) {
1218
+ strategy = current.strategy;
1219
+ }
1220
+ const needsUpdate = protocolsPolicyNeedsUpdate(
1221
+ current,
1222
+ desiredProtocols,
1223
+ desiredAutoSelect
1224
+ );
1225
+ debugLog("zyfai:pools", `${asset}: comparing protocols`, {
1226
+ current: {
1227
+ protocols: current.protocols,
1228
+ autoSelectProtocols: current.autoSelectProtocols
1229
+ },
1230
+ desired: {
1231
+ protocols: desiredProtocols,
1232
+ autoSelectProtocols: desiredAutoSelect
1233
+ },
1234
+ decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1235
+ });
1236
+ if (!needsUpdate) continue;
1237
+ const update = {
1238
+ asset,
1239
+ autoSelectProtocols: desiredAutoSelect
1240
+ };
1241
+ if (desiredProtocols !== null) update.protocols = desiredProtocols;
1242
+ await this.sdk.updateUserProfile(update);
1243
+ debugLog("zyfai:pools", `${asset}: wrote protocols update`, update);
1244
+ }
1245
+ const poolResults = await Promise.all(
1246
+ universe.map(async (protocol) => {
1247
+ try {
1248
+ const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1249
+ return [protocol.id, flattenAvailablePools(raw)];
1250
+ } catch (error) {
1251
+ console.warn(
1252
+ `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
1253
+ error instanceof Error ? error.message : String(error)
1254
+ );
1255
+ return null;
1256
+ }
1257
+ })
1258
+ );
1259
+ const availableByProtocol = /* @__PURE__ */ new Map();
1260
+ for (const result of poolResults) {
1261
+ if (result) availableByProtocol.set(result[0], result[1]);
1262
+ }
1263
+ const customizations = toZyfaiCustomizations(
1264
+ policy,
1265
+ availableByProtocol,
1266
+ chainsByProtocol
1267
+ );
1268
+ debugLog("zyfai:pools", "derived customizeBatch payload", {
1269
+ strategy,
1270
+ availableProtocolCount: availableByProtocol.size,
1271
+ customizations
1272
+ });
1273
+ if (customizations.length === 0) {
1274
+ debugLog("zyfai:pools", "skipping customizeBatch \u2014 nothing to send");
1275
+ return;
1276
+ }
1277
+ await this.sdk.customizeBatch(customizations);
1278
+ debugLog("zyfai:pools", "wrote customizeBatch", {
1279
+ count: customizations.length
1280
+ });
1281
+ } catch (error) {
1282
+ console.warn(
1283
+ "[zyfai] applyPoolPolicy failed (non-fatal):",
1284
+ error instanceof Error ? error.message : String(error)
1285
+ );
1286
+ }
1287
+ }
953
1288
  async deposit(state, chainId, amount, asset, depositCallback) {
954
1289
  try {
955
1290
  const validChainId = isValidChainId(chainId);
@@ -1070,6 +1405,16 @@ var ZyfaiAgent = class {
1070
1405
  async getUserProfile(state, chainId) {
1071
1406
  await this.connectAuth(state, chainId);
1072
1407
  const raw = await this.sdk.getUserDetails();
1408
+ debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
1409
+ asset: "USDC (default \u2014 no asset passed)",
1410
+ splitting: raw.splitting,
1411
+ minSplits: raw.minSplits,
1412
+ strategy: raw.strategy,
1413
+ chains: raw.chains,
1414
+ protocolCount: raw.protocols?.length,
1415
+ hasActiveSessionKey: raw.hasActiveSessionKey,
1416
+ smartWallet: raw.smartWallet
1417
+ });
1073
1418
  return mapUserProfile(raw, this.connectedAddress);
1074
1419
  }
1075
1420
  async ensureAutoSelectProtocols(state, chainId, asset) {
@@ -1102,6 +1447,40 @@ var ZyfaiAgent = class {
1102
1447
 
1103
1448
  // src/lib/routing-api.ts
1104
1449
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1450
+ async function fetchOrgAgentConfig(apiKey) {
1451
+ const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
1452
+ try {
1453
+ const res = await fetch(url, {
1454
+ method: "GET",
1455
+ headers: {
1456
+ "Content-Type": "application/json",
1457
+ "x-owney-api-key": `${apiKey}`
1458
+ }
1459
+ });
1460
+ if (!res.ok) {
1461
+ if (res.status !== 404) {
1462
+ console.warn(
1463
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
1464
+ );
1465
+ }
1466
+ return null;
1467
+ }
1468
+ const json = await res.json();
1469
+ const policy = json.success ? json.data ?? null : null;
1470
+ debugLog(
1471
+ "owney-sdk",
1472
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
1473
+ policy ?? void 0
1474
+ );
1475
+ return policy;
1476
+ } catch (error) {
1477
+ console.warn(
1478
+ "[owney-sdk] Could not read org agent config (non-fatal):",
1479
+ error instanceof Error ? error.message : String(error)
1480
+ );
1481
+ return null;
1482
+ }
1483
+ }
1105
1484
  async function fetchAgentKeys(apiKey) {
1106
1485
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
1107
1486
  const res = await fetch(url, {
@@ -1719,6 +2098,12 @@ var OwneySDK = class {
1719
2098
  disabledAgents = /* @__PURE__ */ new Set();
1720
2099
  state = null;
1721
2100
  apiKey;
2101
+ // The org's agent policy, fetched once per client. Applied to each agent on
2102
+ // activation (ROUT-229). `undefined` means "not fetched yet", `null` means
2103
+ // "fetched, and the partner has no policy" — which is a real answer meaning
2104
+ // leave every user's agent profile alone.
2105
+ orgAgentConfig;
2106
+ orgAgentConfigPromise = null;
1722
2107
  zyfaiRpcUrls;
1723
2108
  routingApiBaseUrl;
1724
2109
  referralSource;
@@ -1727,6 +2112,7 @@ var OwneySDK = class {
1727
2112
  initializingAgentsPromise = null;
1728
2113
  constructor(config) {
1729
2114
  this.apiKey = config.apiKey;
2115
+ if (config.debug) setOwneyDebug(true);
1730
2116
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
1731
2117
  this.routingApiBaseUrl = config.routingApiBaseUrl;
1732
2118
  this.referralSource = config.referralSource;
@@ -1888,14 +2274,57 @@ var OwneySDK = class {
1888
2274
  }
1889
2275
  return active;
1890
2276
  }
2277
+ /**
2278
+ * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
2279
+ * throws — it degrades to null — so this cannot break activation.
2280
+ */
2281
+ async getOrgAgentConfig() {
2282
+ if (this.orgAgentConfig !== void 0) return this.orgAgentConfig;
2283
+ if (!this.orgAgentConfigPromise) {
2284
+ this.orgAgentConfigPromise = fetchOrgAgentConfig(this.apiKey).then(
2285
+ (config) => {
2286
+ this.orgAgentConfig = config;
2287
+ return config;
2288
+ }
2289
+ );
2290
+ }
2291
+ return this.orgAgentConfigPromise;
2292
+ }
2293
+ /**
2294
+ * Apply the org policy to one agent, after it has activated.
2295
+ *
2296
+ * Sequenced after activation rather than alongside it because applying the
2297
+ * policy needs the authenticated session activation establishes. Every failure
2298
+ * mode is swallowed: a partner's splitting preference is not worth failing a
2299
+ * user's connect over.
2300
+ */
2301
+ async applyOrgPolicyTo(agent, state, chainId) {
2302
+ if (!agent.applyOrgPolicy) {
2303
+ debugLog(
2304
+ "owney-sdk",
2305
+ `${agent.id}: no applyOrgPolicy \u2014 agent has no org-level knobs, skipping`
2306
+ );
2307
+ return;
2308
+ }
2309
+ try {
2310
+ const policy = await this.getOrgAgentConfig();
2311
+ if (!policy) return;
2312
+ await agent.applyOrgPolicy(state, chainId, policy);
2313
+ } catch (error) {
2314
+ console.warn(
2315
+ `[owney-sdk] applyOrgPolicy(${agent.id}) failed (non-fatal):`,
2316
+ error instanceof Error ? error.message : String(error)
2317
+ );
2318
+ }
2319
+ }
1891
2320
  async ensureAgentsInitialized() {
1892
2321
  if (this.agents.size > 0) return;
1893
2322
  if (!this.initializingAgentsPromise) {
1894
2323
  this.initializingAgentsPromise = (async () => {
1895
2324
  const agentKeys = await fetchAgentKeys(this.apiKey);
1896
2325
  this.disabledAgents.clear();
1897
- for (const { key, agent_type, is_enabled } of agentKeys) {
1898
- const agent = this.createAgent(agent_type, key);
2326
+ for (const { key: key2, agent_type, is_enabled } of agentKeys) {
2327
+ const agent = this.createAgent(agent_type, key2);
1899
2328
  if (!agent) continue;
1900
2329
  this.agents.set(agent_type, agent);
1901
2330
  if (is_enabled === false) {
@@ -1916,9 +2345,9 @@ var OwneySDK = class {
1916
2345
  this.initializingAgentsPromise = null;
1917
2346
  }
1918
2347
  }
1919
- createAgent(agentId, key) {
2348
+ createAgent(agentId, key2) {
1920
2349
  if (agentId === "zyfai") {
1921
- return new ZyfaiAgent(key, this.zyfaiRpcUrls, this.referralSource);
2350
+ return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
1922
2351
  }
1923
2352
  return null;
1924
2353
  }
@@ -1972,6 +2401,7 @@ var OwneySDK = class {
1972
2401
  Promise.all(
1973
2402
  agents.map(async (agent) => {
1974
2403
  await agent.activateAgent(state, chainId);
2404
+ await this.applyOrgPolicyTo(agent, state, chainId);
1975
2405
  })
1976
2406
  ).catch((error) => {
1977
2407
  console.error("activateAgent background init failed:", error);
@@ -1999,6 +2429,7 @@ var OwneySDK = class {
1999
2429
  Promise.all(
2000
2430
  enabledCompatible.map(async (agent) => {
2001
2431
  await agent.activateAgent(state, chainId);
2432
+ await this.applyOrgPolicyTo(agent, state, chainId);
2002
2433
  })
2003
2434
  ).catch((error) => {
2004
2435
  console.error("activateAgent background init failed:", error);
@@ -2813,22 +3244,22 @@ var readLegacySiwxSession = (store, address) => {
2813
3244
  const legacyKeys = [];
2814
3245
  try {
2815
3246
  for (let i = 0; i < store.length; i++) {
2816
- const key = store.key(i);
2817
- if (key?.startsWith(prefix)) legacyKeys.push(key);
3247
+ const key2 = store.key(i);
3248
+ if (key2?.startsWith(prefix)) legacyKeys.push(key2);
2818
3249
  }
2819
3250
  } catch {
2820
3251
  return null;
2821
3252
  }
2822
3253
  let adopted = null;
2823
- for (const key of legacyKeys) {
3254
+ for (const key2 of legacyKeys) {
2824
3255
  if (!adopted) {
2825
3256
  try {
2826
- adopted = JSON.parse(store.getItem(key) ?? "");
3257
+ adopted = JSON.parse(store.getItem(key2) ?? "");
2827
3258
  } catch {
2828
3259
  }
2829
3260
  }
2830
3261
  try {
2831
- store.removeItem(key);
3262
+ store.removeItem(key2);
2832
3263
  } catch {
2833
3264
  }
2834
3265
  }
@@ -2836,11 +3267,11 @@ var readLegacySiwxSession = (store, address) => {
2836
3267
  };
2837
3268
  var readSiwxSession = (address, chainId) => {
2838
3269
  if (typeof window === "undefined") return null;
2839
- const key = buildKey2(address);
3270
+ const key2 = buildKey2(address);
2840
3271
  const store = storage2();
2841
3272
  let raw = null;
2842
3273
  try {
2843
- raw = store?.getItem(key) ?? null;
3274
+ raw = store?.getItem(key2) ?? null;
2844
3275
  } catch {
2845
3276
  raw = null;
2846
3277
  }
@@ -2848,9 +3279,9 @@ var readSiwxSession = (address, chainId) => {
2848
3279
  try {
2849
3280
  return JSON.parse(raw);
2850
3281
  } catch {
2851
- memorySiwxSessions.delete(key);
3282
+ memorySiwxSessions.delete(key2);
2852
3283
  try {
2853
- store?.removeItem(key);
3284
+ store?.removeItem(key2);
2854
3285
  } catch {
2855
3286
  }
2856
3287
  return null;
@@ -2861,24 +3292,24 @@ var readSiwxSession = (address, chainId) => {
2861
3292
  writeSiwxSession(address, chainId, legacy);
2862
3293
  return legacy;
2863
3294
  }
2864
- return memorySiwxSessions.get(key) ?? null;
3295
+ return memorySiwxSessions.get(key2) ?? null;
2865
3296
  };
2866
3297
  var writeSiwxSession = (address, _chainId, session) => {
2867
3298
  if (typeof window === "undefined") return;
2868
- const key = buildKey2(address);
2869
- memorySiwxSessions.set(key, session);
3299
+ const key2 = buildKey2(address);
3300
+ memorySiwxSessions.set(key2, session);
2870
3301
  const store = storage2();
2871
3302
  try {
2872
- store?.setItem(key, JSON.stringify(session));
3303
+ store?.setItem(key2, JSON.stringify(session));
2873
3304
  } catch {
2874
3305
  }
2875
3306
  };
2876
3307
  var clearSiwxSession = (address, _chainId) => {
2877
- const key = buildKey2(address);
2878
- memorySiwxSessions.delete(key);
3308
+ const key2 = buildKey2(address);
3309
+ memorySiwxSessions.delete(key2);
2879
3310
  const store = storage2();
2880
3311
  try {
2881
- store?.removeItem(key);
3312
+ store?.removeItem(key2);
2882
3313
  } catch {
2883
3314
  }
2884
3315
  readLegacySiwxSession(store, address);
@@ -3007,5 +3438,6 @@ export {
3007
3438
  NotConnectedError,
3008
3439
  OwneyError,
3009
3440
  OwneySDK,
3010
- createOwneySIWX
3441
+ createOwneySIWX,
3442
+ setOwneyDebug
3011
3443
  };