@owney/sdk 0.6.9-beta.4 → 0.6.9-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,20 +3,500 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res, err) => function __init() {
7
+ if (err) throw err[0];
8
+ try {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ } catch (e) {
11
+ throw err = [e], e;
12
+ }
13
+ };
6
14
  var __export = (target, all) => {
7
15
  for (var name in all)
8
16
  __defProp(target, name, { get: all[name], enumerable: true });
9
17
  };
10
18
  var __copyProps = (to, from, except, desc) => {
11
19
  if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ for (let key2 of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
22
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
15
23
  }
16
24
  return to;
17
25
  };
18
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
27
 
28
+ // src/errors.ts
29
+ var OwneyError, AgentNotFoundError, NotConnectedError, AgentChainIncompatibleError;
30
+ var init_errors = __esm({
31
+ "src/errors.ts"() {
32
+ "use strict";
33
+ OwneyError = class extends Error {
34
+ code;
35
+ details;
36
+ agentId;
37
+ constructor(code, message, details, agentId) {
38
+ const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
39
+ super(`${prefix} ${message}`);
40
+ this.name = "OwneyError";
41
+ this.code = code;
42
+ this.details = details;
43
+ this.agentId = agentId;
44
+ }
45
+ };
46
+ AgentNotFoundError = class extends OwneyError {
47
+ constructor(agentId, available) {
48
+ super(
49
+ "AGENT_NOT_FOUND",
50
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
51
+ { agentId, available },
52
+ agentId
53
+ );
54
+ this.name = "AgentNotFoundError";
55
+ }
56
+ };
57
+ NotConnectedError = class extends OwneyError {
58
+ constructor() {
59
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
60
+ this.name = "NotConnectedError";
61
+ }
62
+ };
63
+ AgentChainIncompatibleError = class extends OwneyError {
64
+ incompatibleAgents;
65
+ connectedChainId;
66
+ constructor(incompatibleAgents, connectedChainId) {
67
+ const details = incompatibleAgents.map(
68
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
69
+ ).join("; ");
70
+ super(
71
+ "AGENT_CHAIN_INCOMPATIBLE",
72
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
73
+ { incompatibleAgents, connectedChainId }
74
+ );
75
+ this.name = "AgentChainIncompatibleError";
76
+ this.incompatibleAgents = incompatibleAgents;
77
+ this.connectedChainId = connectedChainId;
78
+ }
79
+ };
80
+ }
81
+ });
82
+
83
+ // src/agents/surfliquid/surfliquid.constants.ts
84
+ var SURFLIQUID_CHAIN_ID, SURFLIQUID_CHAIN_NAME, SURFLIQUID_USDC_ADDRESS, SURFLIQUID_USDC_DECIMALS, SURFLIQUID_MIN_DEPOSIT, SURFLIQUID_SUPPORTED_ASSETS, SURFLIQUID_ACTION_MAP;
85
+ var init_surfliquid_constants = __esm({
86
+ "src/agents/surfliquid/surfliquid.constants.ts"() {
87
+ "use strict";
88
+ SURFLIQUID_CHAIN_ID = 8453;
89
+ SURFLIQUID_CHAIN_NAME = "BASE";
90
+ SURFLIQUID_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
91
+ SURFLIQUID_USDC_DECIMALS = 6;
92
+ SURFLIQUID_MIN_DEPOSIT = "0";
93
+ SURFLIQUID_SUPPORTED_ASSETS = [
94
+ {
95
+ chainId: SURFLIQUID_CHAIN_ID,
96
+ chain: SURFLIQUID_CHAIN_NAME,
97
+ assets: [{ symbol: "USDC", minDepositAmount: SURFLIQUID_MIN_DEPOSIT }]
98
+ }
99
+ ];
100
+ SURFLIQUID_ACTION_MAP = {
101
+ // INITIAL_DEPOSIT is the user's real first deposit, so surface it like any
102
+ // other funding ("Top up"). The mini-app intentionally hides the "Deposit"
103
+ // action (it marks Zyfai's smart-account-creation event), which would
104
+ // otherwise drop a SurfLiquid user's only history row.
105
+ INITIAL_DEPOSIT: "Top up",
106
+ DEPOSIT: "Top up",
107
+ USER_DEPOSIT: "Top up",
108
+ WITHDRAWAL: "Withdraw",
109
+ USER_WITHDRAWAL: "Withdraw",
110
+ REBALANCE: "Rebalance",
111
+ REBALANCE_COMPLETED: "Rebalance",
112
+ CROSS_CHAIN_REBALANCE: "Rebalance",
113
+ MERKL_CLAIM: "Earned"
114
+ };
115
+ }
116
+ });
117
+
118
+ // src/agents/surfliquid/surfliquid.mapper.ts
119
+ function mapMessage(message) {
120
+ const action = SURFLIQUID_ACTION_MAP[message.transactionType];
121
+ if (!action) return null;
122
+ return {
123
+ agent: "surfliquid",
124
+ action,
125
+ date: message.timestamp,
126
+ oldApy: message.apyBefore != null ? String(message.apyBefore) : null,
127
+ newApy: message.apyAfter != null ? String(message.apyAfter) : null,
128
+ transactions: [
129
+ {
130
+ txHashes: [message.txHash],
131
+ chainId: message.chainId,
132
+ tokenSymbol: message.token ?? void 0,
133
+ amount: message.amount != null ? String(message.amount) : void 0
134
+ }
135
+ ],
136
+ rebalanceLog: []
137
+ };
138
+ }
139
+ function mapHistory(result, _chainId) {
140
+ const data = result.messages.map(mapMessage).filter((entry) => entry !== null);
141
+ const hasMore = result.page < result.pages;
142
+ return {
143
+ data,
144
+ hasMore,
145
+ nextCursor: hasMore ? String(result.page + 1) : void 0
146
+ };
147
+ }
148
+ function mapBalances2(vault, chainId, morphoStats) {
149
+ const assets = (vault.assets ?? []).filter(
150
+ (asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId
151
+ );
152
+ const tokens = assets.map((asset) => ({
153
+ chain: SURFLIQUID_CHAIN_NAME,
154
+ chainId: SURFLIQUID_CHAIN_ID,
155
+ asset: "USDC",
156
+ amount: String(asset.currentValueUSD)
157
+ }));
158
+ const positions = assets.map((asset) => {
159
+ const stats = morphoStats?.get(asset.morphoVaultAddress?.toLowerCase() ?? "");
160
+ return {
161
+ chain: SURFLIQUID_CHAIN_NAME,
162
+ protocol: "Morpho",
163
+ // The specific MetaMorpho vault (e.g. "Gauntlet USDC Frontier"), read from
164
+ // Morpho — renders as "Morpho <vault>" alongside zyfai's vault detail.
165
+ pool: stats?.name ?? void 0,
166
+ asset: "USDC",
167
+ amount: String(asset.currentValueUSD),
168
+ apy: asset.currentAPY,
169
+ // TVL + liquidity come straight from Morpho (SurfLiquid's API omits them).
170
+ tvl: stats?.tvlUsd,
171
+ liquidity: stats?.liquidityUsd
172
+ };
173
+ });
174
+ const total = assets.reduce(
175
+ (sum, asset) => sum + asset.currentValueUSD,
176
+ 0
177
+ );
178
+ return {
179
+ smartWallet: vault.userVaultAddress ?? void 0,
180
+ totalBalance: String(total),
181
+ totalBalanceAsset: "usdc",
182
+ tokens,
183
+ positions
184
+ };
185
+ }
186
+ function mapAccountApy(vault, days) {
187
+ const breakdown = vault.apyBreakdown;
188
+ const windowed = breakdown ? breakdown[APY_WINDOW_KEY[days]] : void 0;
189
+ const apy = windowed != null ? windowed : breakdown?.currentAPY ?? 0;
190
+ return {
191
+ walletAddress: vault.walletAddress ?? "",
192
+ weightedApyAfterFee: apy,
193
+ apyByChainAndAsset: { [SURFLIQUID_CHAIN_ID]: { USDC: apy } },
194
+ // Parity with Sail: SurfLiquid's core-sdk exposes no daily net-APY series.
195
+ history: []
196
+ };
197
+ }
198
+ function mapEarnings2(vault, vaultAddress) {
199
+ return {
200
+ smartWallet: vaultAddress,
201
+ lifetimeEarnings: vault.earned?.totalEarningsUSD ?? 0,
202
+ tokens: []
203
+ };
204
+ }
205
+ function mapUserProfile2(vault, address) {
206
+ return {
207
+ address,
208
+ smartWallet: vault.userVaultAddress ?? "",
209
+ chains: [SURFLIQUID_CHAIN_ID],
210
+ // SurfLiquid uses cookie-based session auth — no session-key concept.
211
+ hasActiveSessionKey: false,
212
+ protocols: ["SurfLiquid"]
213
+ };
214
+ }
215
+ var APY_WINDOW_KEY;
216
+ var init_surfliquid_mapper = __esm({
217
+ "src/agents/surfliquid/surfliquid.mapper.ts"() {
218
+ "use strict";
219
+ init_surfliquid_constants();
220
+ APY_WINDOW_KEY = {
221
+ "7D": "apy7d",
222
+ "14D": "apy14d",
223
+ "30D": "apy30d"
224
+ };
225
+ }
226
+ });
227
+
228
+ // src/agents/surfliquid/surfliquid.wallet-adapter.ts
229
+ var import_core_sdk, OwneyWalletAdapter;
230
+ var init_surfliquid_wallet_adapter = __esm({
231
+ "src/agents/surfliquid/surfliquid.wallet-adapter.ts"() {
232
+ "use strict";
233
+ import_core_sdk = require("@surf_liquid/core-sdk");
234
+ OwneyWalletAdapter = class extends import_core_sdk.BaseWalletAdapter {
235
+ constructor(eip1193Provider) {
236
+ super();
237
+ this.eip1193Provider = eip1193Provider;
238
+ }
239
+ eip1193Provider;
240
+ name = "owney";
241
+ get installed() {
242
+ return true;
243
+ }
244
+ resolveProvider() {
245
+ return this.eip1193Provider;
246
+ }
247
+ };
248
+ }
249
+ });
250
+
251
+ // src/agents/surfliquid/surfliquid.morpho.ts
252
+ async function fetchMorphoVaultStats(vaultAddress, chainId) {
253
+ try {
254
+ const res = await fetch(MORPHO_API_URL, {
255
+ method: "POST",
256
+ headers: { "Content-Type": "application/json" },
257
+ body: JSON.stringify({
258
+ query: VAULT_STATS_QUERY,
259
+ variables: { address: vaultAddress, chainId }
260
+ })
261
+ });
262
+ if (!res.ok) return null;
263
+ const json = await res.json();
264
+ if (json.errors) return null;
265
+ const vault = json.data?.vaultByAddress;
266
+ const tvlUsd = vault?.state?.totalAssetsUsd;
267
+ const liquidityUsd = vault?.liquidity?.usd;
268
+ if (typeof tvlUsd !== "number" || typeof liquidityUsd !== "number") {
269
+ return null;
270
+ }
271
+ const name = typeof vault?.name === "string" ? vault.name : null;
272
+ return { name, tvlUsd, liquidityUsd };
273
+ } catch (error) {
274
+ console.warn("surfliquid: Morpho vault stats fetch failed", {
275
+ vaultAddress,
276
+ chainId,
277
+ error
278
+ });
279
+ return null;
280
+ }
281
+ }
282
+ async function fetchMorphoStatsByVault(vaultAddresses, chainId) {
283
+ const unique = [...new Set(vaultAddresses.map((a) => a.toLowerCase()))];
284
+ const entries = await Promise.all(
285
+ unique.map(
286
+ async (address) => [address, await fetchMorphoVaultStats(address, chainId)]
287
+ )
288
+ );
289
+ const byVault = /* @__PURE__ */ new Map();
290
+ for (const [address, stats] of entries) {
291
+ if (stats) byVault.set(address, stats);
292
+ }
293
+ return byVault;
294
+ }
295
+ var MORPHO_API_URL, VAULT_STATS_QUERY;
296
+ var init_surfliquid_morpho = __esm({
297
+ "src/agents/surfliquid/surfliquid.morpho.ts"() {
298
+ "use strict";
299
+ MORPHO_API_URL = "https://api.morpho.org/graphql";
300
+ VAULT_STATS_QUERY = `
301
+ query VaultStats($address: String!, $chainId: Int!) {
302
+ vaultByAddress(address: $address, chainId: $chainId) {
303
+ name
304
+ state { totalAssetsUsd }
305
+ liquidity { usd }
306
+ }
307
+ }
308
+ `;
309
+ }
310
+ });
311
+
312
+ // src/agents/surfliquid/surfliquid.agent.ts
313
+ var surfliquid_agent_exports = {};
314
+ __export(surfliquid_agent_exports, {
315
+ SurfLiquidAgent: () => SurfLiquidAgent
316
+ });
317
+ var import_core_sdk2, import_viem5, APY_WINDOW_KEY2, SurfLiquidAgent;
318
+ var init_surfliquid_agent = __esm({
319
+ "src/agents/surfliquid/surfliquid.agent.ts"() {
320
+ "use strict";
321
+ import_core_sdk2 = require("@surf_liquid/core-sdk");
322
+ import_viem5 = require("viem");
323
+ init_errors();
324
+ init_surfliquid_constants();
325
+ init_surfliquid_mapper();
326
+ init_surfliquid_wallet_adapter();
327
+ init_surfliquid_morpho();
328
+ APY_WINDOW_KEY2 = {
329
+ "7D": "apy7d",
330
+ "14D": "apy14d",
331
+ "30D": "apy30d"
332
+ };
333
+ SurfLiquidAgent = class {
334
+ constructor(config) {
335
+ this.config = config;
336
+ }
337
+ config;
338
+ id = "surfliquid";
339
+ supportedChainIds = [SURFLIQUID_CHAIN_ID];
340
+ supportedAssets = SURFLIQUID_SUPPORTED_ASSETS;
341
+ surf = null;
342
+ connectedAddress = null;
343
+ // In-flight connect+auth, shared by concurrent callers to dedupe SIWE.
344
+ connectPromise = null;
345
+ ensureSurf() {
346
+ if (this.surf) return this.surf;
347
+ this.surf = import_core_sdk2.SurfClient.create({
348
+ projectName: this.config.projectName,
349
+ appId: this.config.appId,
350
+ environment: "mainnet",
351
+ chainId: SURFLIQUID_CHAIN_ID,
352
+ apiBaseUrl: this.config.apiBaseUrl,
353
+ rpcUrl: this.config.rpcUrl,
354
+ // Let the SDK auto-approve USDC to the vault before depositing.
355
+ autoApprove: true
356
+ });
357
+ return this.surf;
358
+ }
359
+ /**
360
+ * Connects + authenticates the SurfClient against owney's provider. Skips the
361
+ * SIWE prompt when already authenticated for this wallet (cookie session).
362
+ */
363
+ async connect(state) {
364
+ const surf = this.ensureSurf();
365
+ const sameWallet = this.connectedAddress?.toLowerCase() === state.walletAddress.toLowerCase();
366
+ if (sameWallet && surf.getAuthState().authenticated) {
367
+ return surf;
368
+ }
369
+ if (this.connectPromise) return this.connectPromise;
370
+ this.connectPromise = (async () => {
371
+ surf.registerWalletAdapter(
372
+ "owney",
373
+ new OwneyWalletAdapter(state.provider)
374
+ );
375
+ await surf.connectWallet("owney");
376
+ await surf.authenticate();
377
+ this.connectedAddress = state.walletAddress;
378
+ return surf;
379
+ })();
380
+ try {
381
+ return await this.connectPromise;
382
+ } finally {
383
+ this.connectPromise = null;
384
+ }
385
+ }
386
+ /**
387
+ * Returns the user's vault address, deploying one on first use. SurfLiquid's
388
+ * deposit requires the vault to exist, so this must run before depositing.
389
+ * Existing vault → returned as-is; otherwise `deployVault()` is sent
390
+ * (user-pays) and the freshly deployed address is returned.
391
+ */
392
+ async resolveVaultAddress(surf, owner) {
393
+ const vault = await surf.getVault(owner).catch(() => null);
394
+ if (vault?.exists && vault.userVaultAddress) {
395
+ return vault.userVaultAddress;
396
+ }
397
+ const { vaultAddress } = await surf.deployVault();
398
+ return vaultAddress;
399
+ }
400
+ // --- IAgent: lifecycle ---
401
+ async disconnect() {
402
+ if (this.surf) {
403
+ await this.surf.disconnectWallet();
404
+ }
405
+ this.connectedAddress = null;
406
+ }
407
+ async activateAgent(state) {
408
+ await this.connect(state);
409
+ }
410
+ // --- IAgent: funds (user-pays; asset is always USDC, callback ignored) ---
411
+ async deposit(state, _chainId, amount, _asset, _depositCallback) {
412
+ const surf = await this.connect(state);
413
+ const human = (0, import_viem5.formatUnits)(BigInt(amount), SURFLIQUID_USDC_DECIMALS);
414
+ const smartWallet = await this.resolveVaultAddress(
415
+ surf,
416
+ state.walletAddress
417
+ );
418
+ const tx = await surf.deposit({
419
+ asset: SURFLIQUID_USDC_ADDRESS,
420
+ amount: human
421
+ });
422
+ await tx.wait();
423
+ return { txHash: tx.hash, smartWallet, amount };
424
+ }
425
+ async withdraw(state, _chainId, _token, amount) {
426
+ const surf = await this.connect(state);
427
+ const human = amount != null ? (0, import_viem5.formatUnits)(BigInt(amount), SURFLIQUID_USDC_DECIMALS) : void 0;
428
+ try {
429
+ const tx = await surf.withdraw({
430
+ asset: SURFLIQUID_USDC_ADDRESS,
431
+ amount: human
432
+ });
433
+ await tx.wait();
434
+ return {
435
+ txHash: tx.hash,
436
+ type: amount != null ? "partial" : "full",
437
+ amount: amount ?? "0"
438
+ };
439
+ } catch (error) {
440
+ throw new OwneyError(
441
+ "WITHDRAW_FAILED",
442
+ error instanceof Error ? error.message : "SurfLiquid withdraw failed.",
443
+ { chainId: SURFLIQUID_CHAIN_ID, amount },
444
+ this.id
445
+ );
446
+ }
447
+ }
448
+ // --- IAgent: portfolio reads ---
449
+ async getBalances(state, chainId) {
450
+ const surf = await this.connect(state);
451
+ const vault = await surf.getVault(state.walletAddress);
452
+ const morphoVaultAddresses = (vault.assets ?? []).filter((asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId).map((asset) => asset.morphoVaultAddress).filter((address) => Boolean(address));
453
+ const morphoStats = await fetchMorphoStatsByVault(
454
+ morphoVaultAddresses,
455
+ chainId
456
+ );
457
+ return mapBalances2(vault, chainId, morphoStats);
458
+ }
459
+ async getEarnings(state) {
460
+ const surf = await this.connect(state);
461
+ const vault = await surf.getVault(state.walletAddress);
462
+ return mapEarnings2(vault, vault.userVaultAddress ?? state.walletAddress);
463
+ }
464
+ async getAccountApy(state, _chainId, days, _tokenSymbol) {
465
+ const surf = await this.connect(state);
466
+ const vault = await surf.getVault(state.walletAddress);
467
+ return mapAccountApy(vault, days);
468
+ }
469
+ async getHistory(state, chainId, options) {
470
+ const surf = await this.connect(state);
471
+ const page = options?.cursor ? Number(options.cursor) : 1;
472
+ const limit = options?.limit ?? 10;
473
+ const result = await surf.getAgentMessages(
474
+ state.walletAddress,
475
+ page,
476
+ limit,
477
+ options?.fromDate,
478
+ options?.toDate
479
+ );
480
+ return mapHistory(result, chainId);
481
+ }
482
+ async getUserProfile(state) {
483
+ const surf = await this.connect(state);
484
+ const vault = await surf.getVault(state.walletAddress);
485
+ return mapUserProfile2(vault, state.walletAddress);
486
+ }
487
+ // --- IAgent: discovery (no wallet) ---
488
+ async getAgentApy(days, _options) {
489
+ const surf = this.ensureSurf();
490
+ const assets = await surf.getSupportedAssets(SURFLIQUID_CHAIN_ID);
491
+ const usdc = assets.find((asset) => asset.assetSymbol === "USDC");
492
+ if (!usdc) return { averageApy: 0 };
493
+ const windowed = usdc[APY_WINDOW_KEY2[days]];
494
+ return { averageApy: windowed != null ? windowed : usdc.currentAPY };
495
+ }
496
+ };
497
+ }
498
+ });
499
+
20
500
  // src/index.ts
21
501
  var index_exports = {};
22
502
  __export(index_exports, {
@@ -89,56 +569,8 @@ var SupportedAssets = [
89
569
  }
90
570
  ];
91
571
 
92
- // src/errors.ts
93
- var OwneyError = class extends Error {
94
- code;
95
- details;
96
- agentId;
97
- constructor(code, message, details, agentId) {
98
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
99
- super(`${prefix} ${message}`);
100
- this.name = "OwneyError";
101
- this.code = code;
102
- this.details = details;
103
- this.agentId = agentId;
104
- }
105
- };
106
- var AgentNotFoundError = class extends OwneyError {
107
- constructor(agentId, available) {
108
- super(
109
- "AGENT_NOT_FOUND",
110
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
111
- { agentId, available },
112
- agentId
113
- );
114
- this.name = "AgentNotFoundError";
115
- }
116
- };
117
- var NotConnectedError = class extends OwneyError {
118
- constructor() {
119
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
120
- this.name = "NotConnectedError";
121
- }
122
- };
123
- var AgentChainIncompatibleError = class extends OwneyError {
124
- incompatibleAgents;
125
- connectedChainId;
126
- constructor(incompatibleAgents, connectedChainId) {
127
- const details = incompatibleAgents.map(
128
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
129
- ).join("; ");
130
- super(
131
- "AGENT_CHAIN_INCOMPATIBLE",
132
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
133
- { incompatibleAgents, connectedChainId }
134
- );
135
- this.name = "AgentChainIncompatibleError";
136
- this.incompatibleAgents = incompatibleAgents;
137
- this.connectedChainId = connectedChainId;
138
- }
139
- };
140
-
141
572
  // src/lib/utils.ts
573
+ init_errors();
142
574
  var isValidChainId = (chainId) => {
143
575
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
144
576
  throw new OwneyError(
@@ -212,46 +644,46 @@ function resolveChainId(chain) {
212
644
  function mapBalances(raw, _chainId, smartWallet) {
213
645
  const portfolio = raw.portfolio;
214
646
  const portfolioByChain = portfolio.portfolioByChain ?? {};
647
+ const rawPositions = portfolio.positions ?? [];
648
+ const detailByKey = /* @__PURE__ */ new Map();
649
+ for (const p of rawPositions) {
650
+ const cid = resolveChainId(p.chain ?? "");
651
+ const symbol = (p.token_symbol ?? "").toUpperCase();
652
+ if (Number.isFinite(cid) && symbol) detailByKey.set(`${cid}:${symbol}`, p);
653
+ }
215
654
  let totalBalance = 0;
216
655
  const tokens = [];
656
+ const positions = [];
217
657
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
218
658
  const positionChainId = Number(chainIdKey);
659
+ const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
219
660
  for (const [symbol, { balance, decimals }] of Object.entries(
220
661
  tokensBySymbol ?? {}
221
662
  )) {
222
- const amount = hexToDecimal(balance || "0", decimals);
663
+ const amount = hexToDecimal(balance || "0", tokenDecimals(symbol, decimals));
223
664
  totalBalance += amount;
224
665
  tokens.push({
225
- chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
666
+ chain: chainName,
226
667
  chainId: positionChainId,
227
668
  asset: symbol,
228
669
  amount: String(amount)
229
670
  });
671
+ const detail = detailByKey.get(`${positionChainId}:${symbol.toUpperCase()}`);
672
+ positions.push({
673
+ chain: chainName,
674
+ protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
675
+ protocolId: detail?.protocol_id,
676
+ pool: detail?.pool,
677
+ asset: symbol,
678
+ amount: String(amount),
679
+ apy: detail?.pool_apy,
680
+ tvl: detail?.pool_tvl,
681
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
682
+ // structural read keeps it undefined today and auto-populates later.
683
+ liquidity: detail?.liquidity
684
+ });
230
685
  }
231
686
  }
232
- const positions = (portfolio.positions ?? []).map((p) => ({
233
- chain: p.chain ?? "",
234
- protocol: p.protocol_name ?? p.protocol_id ?? "",
235
- protocolId: p.protocol_id,
236
- pool: p.pool,
237
- asset: p.token_symbol ?? "",
238
- // Position amount is hex-encoded base units (like the token balances
239
- // above). `PositionSlot` doesn't type a `decimals` field, but runtime
240
- // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
241
- // when present, otherwise fall back to the per-symbol lookup so
242
- // non-6-decimal assets like WETH don't render 1e12 too large.
243
- amount: String(
244
- hexToDecimal(
245
- p.amount || "0",
246
- tokenDecimals(p.token_symbol, p.decimals)
247
- )
248
- ),
249
- apy: p.pool_apy,
250
- tvl: p.pool_tvl,
251
- // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
252
- // structural read keeps it undefined today and auto-populates later.
253
- liquidity: p.liquidity
254
- }));
255
687
  return {
256
688
  smartWallet,
257
689
  totalBalance: String(totalBalance),
@@ -528,6 +960,9 @@ function decodeHistoryCursor(token) {
528
960
  return parsed;
529
961
  }
530
962
 
963
+ // src/agents/zyfai/zyfai.agent.ts
964
+ init_errors();
965
+
531
966
  // src/agents/zyfai/zyfai.auth-cache.ts
532
967
  var KEY_PREFIX = "owney.zyfai.session";
533
968
  var storage = () => {
@@ -562,23 +997,23 @@ var readLegacySession = (store, address) => {
562
997
  const legacyKeys = [];
563
998
  try {
564
999
  for (let i = 0; i < store.length; i++) {
565
- const key = store.key(i);
566
- if (key?.startsWith(prefix)) legacyKeys.push(key);
1000
+ const key2 = store.key(i);
1001
+ if (key2?.startsWith(prefix)) legacyKeys.push(key2);
567
1002
  }
568
1003
  } catch {
569
1004
  return null;
570
1005
  }
571
1006
  let adopted = null;
572
- for (const key of legacyKeys) {
1007
+ for (const key2 of legacyKeys) {
573
1008
  if (!adopted) {
574
1009
  try {
575
- const parsed = JSON.parse(store.getItem(key) ?? "");
1010
+ const parsed = JSON.parse(store.getItem(key2) ?? "");
576
1011
  if (isFreshSession(parsed)) adopted = parsed;
577
1012
  } catch {
578
1013
  }
579
1014
  }
580
1015
  try {
581
- store.removeItem(key);
1016
+ store.removeItem(key2);
582
1017
  } catch {
583
1018
  }
584
1019
  }
@@ -586,11 +1021,11 @@ var readLegacySession = (store, address) => {
586
1021
  };
587
1022
  var readSession = (address, _chainId) => {
588
1023
  if (typeof window === "undefined") return null;
589
- const key = buildKey(address);
1024
+ const key2 = buildKey(address);
590
1025
  const store = storage();
591
1026
  let raw = null;
592
1027
  try {
593
- raw = store?.getItem(key) ?? null;
1028
+ raw = store?.getItem(key2) ?? null;
594
1029
  } catch {
595
1030
  raw = null;
596
1031
  }
@@ -600,9 +1035,9 @@ var readSession = (address, _chainId) => {
600
1035
  if (isFreshSession(parsed)) return parsed;
601
1036
  } catch {
602
1037
  }
603
- memorySessions.delete(key);
1038
+ memorySessions.delete(key2);
604
1039
  try {
605
- store?.removeItem(key);
1040
+ store?.removeItem(key2);
606
1041
  } catch {
607
1042
  }
608
1043
  return null;
@@ -612,27 +1047,27 @@ var readSession = (address, _chainId) => {
612
1047
  writeSession(address, _chainId, legacy);
613
1048
  return legacy;
614
1049
  }
615
- const cached = memorySessions.get(key);
1050
+ const cached = memorySessions.get(key2);
616
1051
  if (isFreshSession(cached)) return cached;
617
- if (cached) memorySessions.delete(key);
1052
+ if (cached) memorySessions.delete(key2);
618
1053
  return null;
619
1054
  };
620
1055
  var writeSession = (address, _chainId, session) => {
621
1056
  if (typeof window === "undefined") return;
622
- const key = buildKey(address);
623
- memorySessions.set(key, session);
1057
+ const key2 = buildKey(address);
1058
+ memorySessions.set(key2, session);
624
1059
  const store = storage();
625
1060
  try {
626
- store?.setItem(key, JSON.stringify(session));
1061
+ store?.setItem(key2, JSON.stringify(session));
627
1062
  } catch {
628
1063
  }
629
1064
  };
630
1065
  var clearSession = (address, _chainId) => {
631
- const key = buildKey(address);
632
- memorySessions.delete(key);
1066
+ const key2 = buildKey(address);
1067
+ memorySessions.delete(key2);
633
1068
  const store = storage();
634
1069
  try {
635
- store?.removeItem(key);
1070
+ store?.removeItem(key2);
636
1071
  } catch {
637
1072
  }
638
1073
  readLegacySession(store, address);
@@ -696,6 +1131,99 @@ function splittingNeedsUpdate(current, desired) {
696
1131
  return current.minSplits !== desired.minSplits;
697
1132
  }
698
1133
 
1134
+ // src/agents/zyfai/pool-policy.ts
1135
+ function toZyfaiCustomizations(policy, availableByProtocol, chainsByProtocol) {
1136
+ const selectionByKey = /* @__PURE__ */ new Map();
1137
+ for (const selection of policy.selections) {
1138
+ selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
1139
+ }
1140
+ const customizations = [];
1141
+ for (const [protocolId, chainIds] of Object.entries(chainsByProtocol)) {
1142
+ const available = availableByProtocol.get(protocolId);
1143
+ if (available === void 0) {
1144
+ continue;
1145
+ }
1146
+ const availableSet = new Set(available);
1147
+ for (const chainId of chainIds) {
1148
+ const stored = selectionByKey.get(key(protocolId, chainId));
1149
+ if (stored === void 0) {
1150
+ customizations.push({
1151
+ protocolId,
1152
+ chainId,
1153
+ pools: [],
1154
+ autoselect: true
1155
+ });
1156
+ continue;
1157
+ }
1158
+ const pools = stored.pools.filter((pool) => availableSet.has(pool));
1159
+ customizations.push({
1160
+ protocolId,
1161
+ chainId,
1162
+ pools,
1163
+ autoselect: false
1164
+ });
1165
+ }
1166
+ }
1167
+ return customizations;
1168
+ }
1169
+ function deriveProtocols(policy, universe, chainsByProtocol) {
1170
+ if (policy.selections.length === 0) {
1171
+ return null;
1172
+ }
1173
+ const selectionByKey = /* @__PURE__ */ new Map();
1174
+ for (const selection of policy.selections) {
1175
+ selectionByKey.set(key(selection.protocolId, selection.chainId), selection);
1176
+ }
1177
+ return universe.filter((protocolId) => {
1178
+ const chainIds = chainsByProtocol[protocolId];
1179
+ if (chainIds === void 0 || chainIds.length === 0) {
1180
+ return true;
1181
+ }
1182
+ const fullyDisabled = chainIds.every((chainId) => {
1183
+ const stored = selectionByKey.get(key(protocolId, chainId));
1184
+ return stored !== void 0 && stored.pools.length === 0;
1185
+ });
1186
+ return !fullyDisabled;
1187
+ });
1188
+ }
1189
+ function key(protocolId, chainId) {
1190
+ return `${protocolId}:${chainId}`;
1191
+ }
1192
+ function buildChainsByProtocol(protocols, dashboardChainIds) {
1193
+ const allowed = new Set(dashboardChainIds);
1194
+ const result = {};
1195
+ for (const protocol of protocols) {
1196
+ result[protocol.id] = (protocol.chains ?? []).filter(
1197
+ (chainId) => allowed.has(chainId)
1198
+ );
1199
+ }
1200
+ return result;
1201
+ }
1202
+ function flattenAvailablePools(raw) {
1203
+ const byChain = raw ?? {};
1204
+ const names = [];
1205
+ for (const byToken of Object.values(byChain ?? {})) {
1206
+ for (const entry of Object.values(byToken ?? {})) {
1207
+ for (const pool of entry?.pools ?? []) {
1208
+ if (pool?.name) names.push(pool.name);
1209
+ }
1210
+ }
1211
+ }
1212
+ return names;
1213
+ }
1214
+ function protocolListsEqual(a, b) {
1215
+ const left = [...a ?? []].sort();
1216
+ const right = [...b ?? []].sort();
1217
+ if (left.length !== right.length) return false;
1218
+ return left.every((value, index) => value === right[index]);
1219
+ }
1220
+ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect) {
1221
+ if (!current) return true;
1222
+ if (current.autoSelectProtocols !== desiredAutoSelect) return true;
1223
+ if (desiredProtocols === null) return false;
1224
+ return !protocolListsEqual(current.protocols, desiredProtocols);
1225
+ }
1226
+
699
1227
  // src/lib/debug.ts
700
1228
  var configuredDebug = false;
701
1229
  function setOwneyDebug(enabled) {
@@ -726,6 +1254,7 @@ var WETH_ADDRESS_BY_CHAIN = {
726
1254
  };
727
1255
  var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum, 1: import_chains.mainnet };
728
1256
  var SPLITTING_ASSETS = ["USDC", "WETH"];
1257
+ var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
729
1258
  function isAlreadyActiveSessionKeyError(error) {
730
1259
  const message = error instanceof Error ? error.message : String(error);
731
1260
  return /already has an active session key/i.test(message);
@@ -1110,6 +1639,9 @@ var ZyfaiAgent = class {
1110
1639
  await Promise.all(
1111
1640
  SPLITTING_ASSETS.map((asset) => this.applySplitting(asset, policy))
1112
1641
  );
1642
+ if (policy.poolPolicy) {
1643
+ await this.applyPoolPolicy(policy.poolPolicy);
1644
+ }
1113
1645
  }
1114
1646
  async applySplitting(asset, policy) {
1115
1647
  try {
@@ -1146,6 +1678,117 @@ var ZyfaiAgent = class {
1146
1678
  );
1147
1679
  }
1148
1680
  }
1681
+ /**
1682
+ * Apply the org's protocol/pool policy to this user's Zyfai profile.
1683
+ *
1684
+ * Two Zyfai writes, in a strict order:
1685
+ * 1. `updateUserProfile({ protocols, autoSelectProtocols })` per asset —
1686
+ * the explicit allow-list. Chain-agnostic, mirrors `applySplitting`.
1687
+ * 2. `customizeBatch(...)` once — per (protocol, chain), asset-agnostic
1688
+ * pool narrowing.
1689
+ *
1690
+ * Order matters: constraining a protocol's pools (step 2) while that
1691
+ * protocol is not yet in the allow-list (step 1 not yet applied) lands the
1692
+ * constraint on something the rebalance engine isn't considering.
1693
+ *
1694
+ * Wrapped so a failure anywhere is non-fatal — this runs as a side effect of
1695
+ * connecting, so a Zyfai hiccup must leave the user connected with their
1696
+ * profile unchanged, never fail activation.
1697
+ */
1698
+ async applyPoolPolicy(policy) {
1699
+ try {
1700
+ const { protocols: universe } = await this.sdk.getAvailableProtocols(
1701
+ this.supportedChainIds[0]
1702
+ );
1703
+ const universeIds = universe.map((p) => p.id);
1704
+ const chainsByProtocol = buildChainsByProtocol(
1705
+ universe,
1706
+ POOL_POLICY_CHAIN_IDS
1707
+ );
1708
+ debugLog("zyfai:pools", "protocol universe", {
1709
+ universeIds,
1710
+ chainsByProtocol
1711
+ });
1712
+ let strategy;
1713
+ const desiredProtocols = deriveProtocols(
1714
+ policy,
1715
+ universeIds,
1716
+ chainsByProtocol
1717
+ );
1718
+ const desiredAutoSelect = policy.autoApproveProtocols;
1719
+ for (const asset of SPLITTING_ASSETS) {
1720
+ const current = await this.sdk.getUserDetails(asset);
1721
+ if (strategy === void 0 && (current.strategy === "conservative" || current.strategy === "aggressive")) {
1722
+ strategy = current.strategy;
1723
+ }
1724
+ const needsUpdate = protocolsPolicyNeedsUpdate(
1725
+ current,
1726
+ desiredProtocols,
1727
+ desiredAutoSelect
1728
+ );
1729
+ debugLog("zyfai:pools", `${asset}: comparing protocols`, {
1730
+ current: {
1731
+ protocols: current.protocols,
1732
+ autoSelectProtocols: current.autoSelectProtocols
1733
+ },
1734
+ desired: {
1735
+ protocols: desiredProtocols,
1736
+ autoSelectProtocols: desiredAutoSelect
1737
+ },
1738
+ decision: needsUpdate ? "will write \u2014 profile differs from the org policy" : "skipping \u2014 profile already matches"
1739
+ });
1740
+ if (!needsUpdate) continue;
1741
+ const update = {
1742
+ asset,
1743
+ autoSelectProtocols: desiredAutoSelect
1744
+ };
1745
+ if (desiredProtocols !== null) update.protocols = desiredProtocols;
1746
+ await this.sdk.updateUserProfile(update);
1747
+ debugLog("zyfai:pools", `${asset}: wrote protocols update`, update);
1748
+ }
1749
+ const poolResults = await Promise.all(
1750
+ universe.map(async (protocol) => {
1751
+ try {
1752
+ const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1753
+ return [protocol.id, flattenAvailablePools(raw)];
1754
+ } catch (error) {
1755
+ console.warn(
1756
+ `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
1757
+ error instanceof Error ? error.message : String(error)
1758
+ );
1759
+ return null;
1760
+ }
1761
+ })
1762
+ );
1763
+ const availableByProtocol = /* @__PURE__ */ new Map();
1764
+ for (const result of poolResults) {
1765
+ if (result) availableByProtocol.set(result[0], result[1]);
1766
+ }
1767
+ const customizations = toZyfaiCustomizations(
1768
+ policy,
1769
+ availableByProtocol,
1770
+ chainsByProtocol
1771
+ );
1772
+ debugLog("zyfai:pools", "derived customizeBatch payload", {
1773
+ strategy,
1774
+ availableProtocolCount: availableByProtocol.size,
1775
+ customizations
1776
+ });
1777
+ if (customizations.length === 0) {
1778
+ debugLog("zyfai:pools", "skipping customizeBatch \u2014 nothing to send");
1779
+ return;
1780
+ }
1781
+ await this.sdk.customizeBatch(customizations);
1782
+ debugLog("zyfai:pools", "wrote customizeBatch", {
1783
+ count: customizations.length
1784
+ });
1785
+ } catch (error) {
1786
+ console.warn(
1787
+ "[zyfai] applyPoolPolicy failed (non-fatal):",
1788
+ error instanceof Error ? error.message : String(error)
1789
+ );
1790
+ }
1791
+ }
1149
1792
  async deposit(state, chainId, amount, asset, depositCallback) {
1150
1793
  try {
1151
1794
  await this.connectFull(state, chainId);
@@ -1307,7 +1950,11 @@ var ZyfaiAgent = class {
1307
1950
  }
1308
1951
  };
1309
1952
 
1953
+ // src/client.ts
1954
+ init_errors();
1955
+
1310
1956
  // src/lib/routing-api.ts
1957
+ init_errors();
1311
1958
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1312
1959
  async function fetchOrgAgentConfig(apiKey) {
1313
1960
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
@@ -1372,6 +2019,7 @@ async function fetchAgentKeys(apiKey) {
1372
2019
  }
1373
2020
 
1374
2021
  // src/lib/health-report.ts
2022
+ init_errors();
1375
2023
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1376
2024
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1377
2025
  try {
@@ -1521,9 +2169,12 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1521
2169
  }
1522
2170
 
1523
2171
  // src/client.ts
1524
- var import_viem5 = require("viem");
2172
+ var import_viem6 = require("viem");
1525
2173
  var import_chains2 = require("viem/chains");
1526
2174
 
2175
+ // src/lib/sponsored-deposit.ts
2176
+ init_errors();
2177
+
1527
2178
  // src/lib/transfer-auth.ts
1528
2179
  var import_viem3 = require("viem");
1529
2180
  var ERC20_META_ABI = [
@@ -1561,6 +2212,7 @@ function randomAuthNonce() {
1561
2212
  }
1562
2213
 
1563
2214
  // src/lib/sponsor-client.ts
2215
+ init_errors();
1564
2216
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1565
2217
  async function postSponsorTransferAuth(input) {
1566
2218
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
@@ -1829,6 +2481,7 @@ function makeSponsoredDepositCallback(deps) {
1829
2481
  }
1830
2482
 
1831
2483
  // src/lib/sponsored-weth-deposit.ts
2484
+ init_errors();
1832
2485
  var PERMIT_WINDOW_SECONDS = 15 * 60;
1833
2486
  function makeSponsoredWethCallback(deps) {
1834
2487
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -1937,6 +2590,7 @@ var VIEM_CHAIN2 = {
1937
2590
  42161: import_chains2.arbitrum,
1938
2591
  1: import_chains2.mainnet
1939
2592
  };
2593
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
1940
2594
  var SPONSORED_WETH_BY_CHAIN = {
1941
2595
  8453: "0x4200000000000000000000000000000000000006",
1942
2596
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -1965,6 +2619,7 @@ var OwneySDK = class {
1965
2619
  zyfaiRpcUrls;
1966
2620
  routingApiBaseUrl;
1967
2621
  referralSource;
2622
+ surfliquidApiBaseUrl;
1968
2623
  cachedSponsoredCallback = null;
1969
2624
  cachedWethSponsoredCallback = null;
1970
2625
  initializingAgentsPromise = null;
@@ -1974,6 +2629,7 @@ var OwneySDK = class {
1974
2629
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
1975
2630
  this.routingApiBaseUrl = config.routingApiBaseUrl;
1976
2631
  this.referralSource = config.referralSource;
2632
+ this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
1977
2633
  }
1978
2634
  /**
1979
2635
  * Establish connection state for all subsequent SDK calls.
@@ -2071,14 +2727,14 @@ var OwneySDK = class {
2071
2727
  // Casts work around viem's chain-narrowed Client vs the generic
2072
2728
  // PublicClient/WalletClient param types — structurally identical at
2073
2729
  // runtime, but the two share a name TS treats as unrelated.
2074
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2730
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2075
2731
  chain: VIEM_CHAIN2[cid],
2076
- transport: (0, import_viem5.custom)(provider)
2732
+ transport: (0, import_viem6.custom)(provider)
2077
2733
  }),
2078
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2734
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2079
2735
  account: owner,
2080
2736
  chain: VIEM_CHAIN2[cid],
2081
- transport: (0, import_viem5.custom)(provider)
2737
+ transport: (0, import_viem6.custom)(provider)
2082
2738
  })
2083
2739
  });
2084
2740
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2104,14 +2760,14 @@ var OwneySDK = class {
2104
2760
  // Casts work around viem's chain-narrowed Client vs the generic
2105
2761
  // PublicClient/WalletClient param types — structurally identical at
2106
2762
  // runtime, but the two share a name TS treats as unrelated.
2107
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2763
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2108
2764
  chain: VIEM_CHAIN2[cid],
2109
- transport: (0, import_viem5.custom)(provider)
2765
+ transport: (0, import_viem6.custom)(provider)
2110
2766
  }),
2111
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2767
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2112
2768
  account: owner,
2113
2769
  chain: VIEM_CHAIN2[cid],
2114
- transport: (0, import_viem5.custom)(provider)
2770
+ transport: (0, import_viem6.custom)(provider)
2115
2771
  })
2116
2772
  });
2117
2773
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2181,8 +2837,8 @@ var OwneySDK = class {
2181
2837
  this.initializingAgentsPromise = (async () => {
2182
2838
  const agentKeys = await fetchAgentKeys(this.apiKey);
2183
2839
  this.disabledAgents.clear();
2184
- for (const { key, agent_type, is_enabled } of agentKeys) {
2185
- const agent = this.createAgent(agent_type, key);
2840
+ for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2841
+ const agent = await this.createAgent(agent_type, key2, project_name);
2186
2842
  if (!agent) continue;
2187
2843
  this.agents.set(agent_type, agent);
2188
2844
  if (is_enabled === false) {
@@ -2203,9 +2859,21 @@ var OwneySDK = class {
2203
2859
  this.initializingAgentsPromise = null;
2204
2860
  }
2205
2861
  }
2206
- createAgent(agentId, key) {
2862
+ async createAgent(agentId, key2, projectName) {
2207
2863
  if (agentId === "zyfai") {
2208
- return new ZyfaiAgent(key, this.zyfaiRpcUrls, this.referralSource);
2864
+ return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2865
+ }
2866
+ if (agentId === "surfliquid") {
2867
+ if (!projectName) return null;
2868
+ const { SurfLiquidAgent: SurfLiquidAgent2 } = await Promise.resolve().then(() => (init_surfliquid_agent(), surfliquid_agent_exports));
2869
+ return new SurfLiquidAgent2({
2870
+ projectName,
2871
+ appId: key2,
2872
+ // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
2873
+ // which WebKit (iOS) discards when it is third-party. See
2874
+ // OwneySDKConfig.surfliquidApiBaseUrl.
2875
+ apiBaseUrl: this.surfliquidApiBaseUrl
2876
+ });
2209
2877
  }
2210
2878
  return null;
2211
2879
  }
@@ -2361,18 +3029,30 @@ var OwneySDK = class {
2361
3029
  );
2362
3030
  }
2363
3031
  const agentResults = {};
3032
+ const agentErrors = {};
2364
3033
  for (const { agent, amount: agentAmount } of agentAmounts) {
2365
- agentResults[agent.id] = await this.depositWithFallback(
2366
- agent,
2367
- state,
2368
- chainId,
2369
- agentAmount,
2370
- asset,
2371
- effectiveCallback,
2372
- depositCallback
3034
+ try {
3035
+ agentResults[agent.id] = await this.depositWithFallback(
3036
+ agent,
3037
+ state,
3038
+ chainId,
3039
+ agentAmount,
3040
+ asset,
3041
+ effectiveCallback,
3042
+ depositCallback
3043
+ );
3044
+ } catch (error) {
3045
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
3046
+ }
3047
+ }
3048
+ if (Object.keys(agentResults).length === 0) {
3049
+ throw new OwneyError(
3050
+ "DEPOSIT_ALL_FAILED",
3051
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
3052
+ { agentErrors }
2373
3053
  );
2374
3054
  }
2375
- return { agentResults };
3055
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2376
3056
  }
2377
3057
  /**
2378
3058
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2529,6 +3209,34 @@ var OwneySDK = class {
2529
3209
  }
2530
3210
  return eligible;
2531
3211
  }
3212
+ /**
3213
+ * Agent ids the routing API provisioned for this org that support the given
3214
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3215
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
3216
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
3217
+ * wallet), so the UI can resolve which agent to use before the user connects.
3218
+ *
3219
+ * This is the source of truth for agent availability: an agent appears here
3220
+ * iff the routing API returned its key. No per-app feature flags.
3221
+ */
3222
+ async getEligibleAgentIds(chainId, asset) {
3223
+ try {
3224
+ await this.ensureAgentsInitialized();
3225
+ } catch (error) {
3226
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3227
+ return [];
3228
+ }
3229
+ throw error;
3230
+ }
3231
+ return [...this.agents.values()].filter((agent) => {
3232
+ const chainAssets = agent.supportedAssets.find(
3233
+ (sa) => sa.chainId === chainId
3234
+ );
3235
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3236
+ }).map((agent) => agent.id).sort(
3237
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3238
+ );
3239
+ }
2532
3240
  // --- Fund operations ---
2533
3241
  /**
2534
3242
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2564,9 +3272,17 @@ var OwneySDK = class {
2564
3272
  }
2565
3273
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2566
3274
  if (!amount) {
3275
+ const aggregated2 = await this.getBalances();
3276
+ const funded = projectAgentBalancesForAsset(
3277
+ eligibleAgents,
3278
+ aggregated2.agentBalances,
3279
+ chainId,
3280
+ asset,
3281
+ assetInfo.decimals
3282
+ ).filter((b) => b.balance > 0n);
2567
3283
  const results2 = {};
2568
3284
  const agentErrors2 = {};
2569
- for (const agent of eligibleAgents) {
3285
+ for (const { agent } of funded) {
2570
3286
  try {
2571
3287
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2572
3288
  } catch (err) {
@@ -2583,9 +3299,10 @@ var OwneySDK = class {
2583
3299
  { asset, agentErrors: agentErrors2 }
2584
3300
  );
2585
3301
  }
3302
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2586
3303
  return {
2587
3304
  agentResult: results2,
2588
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3305
+ totalWithdrawn: totalWithdrawn.toString()
2589
3306
  };
2590
3307
  }
2591
3308
  const requested = BigInt(amount);
@@ -2955,10 +3672,10 @@ var OwneySDK = class {
2955
3672
  );
2956
3673
  }
2957
3674
  const provider = this.requireConnectedProvider();
2958
- const wallet = (0, import_viem5.createWalletClient)({
3675
+ const wallet = (0, import_viem6.createWalletClient)({
2959
3676
  account: state.walletAddress,
2960
3677
  chain: VIEM_CHAIN2[chainId],
2961
- transport: (0, import_viem5.custom)(provider)
3678
+ transport: (0, import_viem6.custom)(provider)
2962
3679
  });
2963
3680
  const hash = await wallet.writeContract({
2964
3681
  address: token,
@@ -2968,9 +3685,9 @@ var OwneySDK = class {
2968
3685
  account: state.walletAddress,
2969
3686
  chain: VIEM_CHAIN2[chainId]
2970
3687
  });
2971
- const publicClient = (0, import_viem5.createPublicClient)({
3688
+ const publicClient = (0, import_viem6.createPublicClient)({
2972
3689
  chain: VIEM_CHAIN2[chainId],
2973
- transport: (0, import_viem5.custom)(provider)
3690
+ transport: (0, import_viem6.custom)(provider)
2974
3691
  });
2975
3692
  const receipt = await publicClient.waitForTransactionReceipt({
2976
3693
  hash,
@@ -3078,8 +3795,11 @@ var OwneySDK = class {
3078
3795
  }
3079
3796
  };
3080
3797
 
3798
+ // src/index.ts
3799
+ init_errors();
3800
+
3081
3801
  // src/agents/zyfai/zyfai.siwx.ts
3082
- var import_viem6 = require("viem");
3802
+ var import_viem7 = require("viem");
3083
3803
  var import_siwe = require("siwe");
3084
3804
  var import_sdk2 = require("@zyfai/sdk");
3085
3805
 
@@ -3102,22 +3822,22 @@ var readLegacySiwxSession = (store, address) => {
3102
3822
  const legacyKeys = [];
3103
3823
  try {
3104
3824
  for (let i = 0; i < store.length; i++) {
3105
- const key = store.key(i);
3106
- if (key?.startsWith(prefix)) legacyKeys.push(key);
3825
+ const key2 = store.key(i);
3826
+ if (key2?.startsWith(prefix)) legacyKeys.push(key2);
3107
3827
  }
3108
3828
  } catch {
3109
3829
  return null;
3110
3830
  }
3111
3831
  let adopted = null;
3112
- for (const key of legacyKeys) {
3832
+ for (const key2 of legacyKeys) {
3113
3833
  if (!adopted) {
3114
3834
  try {
3115
- adopted = JSON.parse(store.getItem(key) ?? "");
3835
+ adopted = JSON.parse(store.getItem(key2) ?? "");
3116
3836
  } catch {
3117
3837
  }
3118
3838
  }
3119
3839
  try {
3120
- store.removeItem(key);
3840
+ store.removeItem(key2);
3121
3841
  } catch {
3122
3842
  }
3123
3843
  }
@@ -3125,11 +3845,11 @@ var readLegacySiwxSession = (store, address) => {
3125
3845
  };
3126
3846
  var readSiwxSession = (address, chainId) => {
3127
3847
  if (typeof window === "undefined") return null;
3128
- const key = buildKey2(address);
3848
+ const key2 = buildKey2(address);
3129
3849
  const store = storage2();
3130
3850
  let raw = null;
3131
3851
  try {
3132
- raw = store?.getItem(key) ?? null;
3852
+ raw = store?.getItem(key2) ?? null;
3133
3853
  } catch {
3134
3854
  raw = null;
3135
3855
  }
@@ -3137,9 +3857,9 @@ var readSiwxSession = (address, chainId) => {
3137
3857
  try {
3138
3858
  return JSON.parse(raw);
3139
3859
  } catch {
3140
- memorySiwxSessions.delete(key);
3860
+ memorySiwxSessions.delete(key2);
3141
3861
  try {
3142
- store?.removeItem(key);
3862
+ store?.removeItem(key2);
3143
3863
  } catch {
3144
3864
  }
3145
3865
  return null;
@@ -3150,24 +3870,24 @@ var readSiwxSession = (address, chainId) => {
3150
3870
  writeSiwxSession(address, chainId, legacy);
3151
3871
  return legacy;
3152
3872
  }
3153
- return memorySiwxSessions.get(key) ?? null;
3873
+ return memorySiwxSessions.get(key2) ?? null;
3154
3874
  };
3155
3875
  var writeSiwxSession = (address, _chainId, session) => {
3156
3876
  if (typeof window === "undefined") return;
3157
- const key = buildKey2(address);
3158
- memorySiwxSessions.set(key, session);
3877
+ const key2 = buildKey2(address);
3878
+ memorySiwxSessions.set(key2, session);
3159
3879
  const store = storage2();
3160
3880
  try {
3161
- store?.setItem(key, JSON.stringify(session));
3881
+ store?.setItem(key2, JSON.stringify(session));
3162
3882
  } catch {
3163
3883
  }
3164
3884
  };
3165
3885
  var clearSiwxSession = (address, _chainId) => {
3166
- const key = buildKey2(address);
3167
- memorySiwxSessions.delete(key);
3886
+ const key2 = buildKey2(address);
3887
+ memorySiwxSessions.delete(key2);
3168
3888
  const store = storage2();
3169
3889
  try {
3170
- store?.removeItem(key);
3890
+ store?.removeItem(key2);
3171
3891
  } catch {
3172
3892
  }
3173
3893
  readLegacySiwxSession(store, address);
@@ -3206,7 +3926,7 @@ function buildSIWXConfig(deps) {
3206
3926
  issuedAt,
3207
3927
  toString() {
3208
3928
  return new import_siwe.SiweMessage({
3209
- address: (0, import_viem6.getAddress)(accountAddress),
3929
+ address: (0, import_viem7.getAddress)(accountAddress),
3210
3930
  chainId: numericChainId(chainId),
3211
3931
  domain,
3212
3932
  uri,