@owney/sdk 0.6.6-beta.7 → 0.6.7-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,6 +3,14 @@ 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 });
@@ -17,6 +25,478 @@ var __copyProps = (to, from, except, desc) => {
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, {
@@ -88,56 +568,8 @@ var SupportedAssets = [
88
568
  }
89
569
  ];
90
570
 
91
- // src/errors.ts
92
- var OwneyError = class extends Error {
93
- code;
94
- details;
95
- agentId;
96
- constructor(code, message, details, agentId) {
97
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
98
- super(`${prefix} ${message}`);
99
- this.name = "OwneyError";
100
- this.code = code;
101
- this.details = details;
102
- this.agentId = agentId;
103
- }
104
- };
105
- var AgentNotFoundError = class extends OwneyError {
106
- constructor(agentId, available) {
107
- super(
108
- "AGENT_NOT_FOUND",
109
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
110
- { agentId, available },
111
- agentId
112
- );
113
- this.name = "AgentNotFoundError";
114
- }
115
- };
116
- var NotConnectedError = class extends OwneyError {
117
- constructor() {
118
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
119
- this.name = "NotConnectedError";
120
- }
121
- };
122
- var AgentChainIncompatibleError = class extends OwneyError {
123
- incompatibleAgents;
124
- connectedChainId;
125
- constructor(incompatibleAgents, connectedChainId) {
126
- const details = incompatibleAgents.map(
127
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
128
- ).join("; ");
129
- super(
130
- "AGENT_CHAIN_INCOMPATIBLE",
131
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
132
- { incompatibleAgents, connectedChainId }
133
- );
134
- this.name = "AgentChainIncompatibleError";
135
- this.incompatibleAgents = incompatibleAgents;
136
- this.connectedChainId = connectedChainId;
137
- }
138
- };
139
-
140
571
  // src/lib/utils.ts
572
+ init_errors();
141
573
  var isValidChainId = (chainId) => {
142
574
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
143
575
  throw new OwneyError(
@@ -211,46 +643,46 @@ function resolveChainId(chain) {
211
643
  function mapBalances(raw, _chainId, smartWallet) {
212
644
  const portfolio = raw.portfolio;
213
645
  const portfolioByChain = portfolio.portfolioByChain ?? {};
646
+ const rawPositions = portfolio.positions ?? [];
647
+ const detailByKey = /* @__PURE__ */ new Map();
648
+ for (const p of rawPositions) {
649
+ const cid = resolveChainId(p.chain ?? "");
650
+ const symbol = (p.token_symbol ?? "").toUpperCase();
651
+ if (Number.isFinite(cid) && symbol) detailByKey.set(`${cid}:${symbol}`, p);
652
+ }
214
653
  let totalBalance = 0;
215
654
  const tokens = [];
655
+ const positions = [];
216
656
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
217
657
  const positionChainId = Number(chainIdKey);
658
+ const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
218
659
  for (const [symbol, { balance, decimals }] of Object.entries(
219
660
  tokensBySymbol ?? {}
220
661
  )) {
221
- const amount = hexToDecimal(balance || "0", decimals);
662
+ const amount = hexToDecimal(balance || "0", tokenDecimals(symbol, decimals));
222
663
  totalBalance += amount;
223
664
  tokens.push({
224
- chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
665
+ chain: chainName,
225
666
  chainId: positionChainId,
226
667
  asset: symbol,
227
668
  amount: String(amount)
228
669
  });
670
+ const detail = detailByKey.get(`${positionChainId}:${symbol.toUpperCase()}`);
671
+ positions.push({
672
+ chain: chainName,
673
+ protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
674
+ protocolId: detail?.protocol_id,
675
+ pool: detail?.pool,
676
+ asset: symbol,
677
+ amount: String(amount),
678
+ apy: detail?.pool_apy,
679
+ tvl: detail?.pool_tvl,
680
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
681
+ // structural read keeps it undefined today and auto-populates later.
682
+ liquidity: detail?.liquidity
683
+ });
229
684
  }
230
685
  }
231
- const positions = (portfolio.positions ?? []).map((p) => ({
232
- chain: p.chain ?? "",
233
- protocol: p.protocol_name ?? p.protocol_id ?? "",
234
- protocolId: p.protocol_id,
235
- pool: p.pool,
236
- asset: p.token_symbol ?? "",
237
- // Position amount is hex-encoded base units (like the token balances
238
- // above). `PositionSlot` doesn't type a `decimals` field, but runtime
239
- // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
240
- // when present, otherwise fall back to the per-symbol lookup so
241
- // non-6-decimal assets like WETH don't render 1e12 too large.
242
- amount: String(
243
- hexToDecimal(
244
- p.amount || "0",
245
- tokenDecimals(p.token_symbol, p.decimals)
246
- )
247
- ),
248
- apy: p.pool_apy,
249
- tvl: p.pool_tvl,
250
- // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
251
- // structural read keeps it undefined today and auto-populates later.
252
- liquidity: p.liquidity
253
- }));
254
686
  return {
255
687
  smartWallet,
256
688
  totalBalance: String(totalBalance),
@@ -525,6 +957,9 @@ function decodeHistoryCursor(token) {
525
957
  return parsed;
526
958
  }
527
959
 
960
+ // src/agents/zyfai/zyfai.agent.ts
961
+ init_errors();
962
+
528
963
  // src/agents/zyfai/zyfai.auth-cache.ts
529
964
  var KEY_PREFIX = "owney.zyfai.session";
530
965
  var storage = () => {
@@ -1096,7 +1531,11 @@ var ZyfaiAgent = class {
1096
1531
  }
1097
1532
  };
1098
1533
 
1534
+ // src/client.ts
1535
+ init_errors();
1536
+
1099
1537
  // src/lib/routing-api.ts
1538
+ init_errors();
1100
1539
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1101
1540
  async function fetchAgentKeys(apiKey) {
1102
1541
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
@@ -1127,6 +1566,7 @@ async function fetchAgentKeys(apiKey) {
1127
1566
  }
1128
1567
 
1129
1568
  // src/lib/health-report.ts
1569
+ init_errors();
1130
1570
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1131
1571
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1132
1572
  try {
@@ -1276,9 +1716,12 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1276
1716
  }
1277
1717
 
1278
1718
  // src/client.ts
1279
- var import_viem5 = require("viem");
1719
+ var import_viem6 = require("viem");
1280
1720
  var import_chains2 = require("viem/chains");
1281
1721
 
1722
+ // src/lib/sponsored-deposit.ts
1723
+ init_errors();
1724
+
1282
1725
  // src/lib/transfer-auth.ts
1283
1726
  var import_viem3 = require("viem");
1284
1727
  var ERC20_META_ABI = [
@@ -1316,6 +1759,7 @@ function randomAuthNonce() {
1316
1759
  }
1317
1760
 
1318
1761
  // src/lib/sponsor-client.ts
1762
+ init_errors();
1319
1763
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1320
1764
  async function postSponsorTransferAuth(input) {
1321
1765
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
@@ -1482,6 +1926,9 @@ function makeSponsoredDepositCallback(deps) {
1482
1926
  };
1483
1927
  }
1484
1928
 
1929
+ // src/lib/sponsored-weth-deposit.ts
1930
+ init_errors();
1931
+
1485
1932
  // src/lib/permit2.ts
1486
1933
  var import_viem4 = require("viem");
1487
1934
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
@@ -1621,6 +2068,7 @@ var VIEM_CHAIN2 = {
1621
2068
  42161: import_chains2.arbitrum,
1622
2069
  1: import_chains2.mainnet
1623
2070
  };
2071
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
1624
2072
  var SPONSORED_WETH_BY_CHAIN = {
1625
2073
  8453: "0x4200000000000000000000000000000000000006",
1626
2074
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -1744,14 +2192,14 @@ var OwneySDK = class {
1744
2192
  // Casts work around viem's chain-narrowed Client vs the generic
1745
2193
  // PublicClient/WalletClient param types — structurally identical at
1746
2194
  // runtime, but the two share a name TS treats as unrelated.
1747
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2195
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
1748
2196
  chain: VIEM_CHAIN2[cid],
1749
- transport: (0, import_viem5.custom)(provider)
2197
+ transport: (0, import_viem6.custom)(provider)
1750
2198
  }),
1751
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2199
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
1752
2200
  account: owner,
1753
2201
  chain: VIEM_CHAIN2[cid],
1754
- transport: (0, import_viem5.custom)(provider)
2202
+ transport: (0, import_viem6.custom)(provider)
1755
2203
  })
1756
2204
  });
1757
2205
  return this.cachedSponsoredCallback;
@@ -1775,14 +2223,14 @@ var OwneySDK = class {
1775
2223
  // Casts work around viem's chain-narrowed Client vs the generic
1776
2224
  // PublicClient/WalletClient param types — structurally identical at
1777
2225
  // runtime, but the two share a name TS treats as unrelated.
1778
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2226
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
1779
2227
  chain: VIEM_CHAIN2[cid],
1780
- transport: (0, import_viem5.custom)(provider)
2228
+ transport: (0, import_viem6.custom)(provider)
1781
2229
  }),
1782
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2230
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
1783
2231
  account: owner,
1784
2232
  chain: VIEM_CHAIN2[cid],
1785
- transport: (0, import_viem5.custom)(provider)
2233
+ transport: (0, import_viem6.custom)(provider)
1786
2234
  })
1787
2235
  });
1788
2236
  return this.cachedWethSponsoredCallback;
@@ -1808,8 +2256,8 @@ var OwneySDK = class {
1808
2256
  this.initializingAgentsPromise = (async () => {
1809
2257
  const agentKeys = await fetchAgentKeys(this.apiKey);
1810
2258
  this.disabledAgents.clear();
1811
- for (const { key, agent_type, is_enabled } of agentKeys) {
1812
- const agent = this.createAgent(agent_type, key);
2259
+ for (const { key, agent_type, project_name, is_enabled } of agentKeys) {
2260
+ const agent = await this.createAgent(agent_type, key, project_name);
1813
2261
  if (!agent) continue;
1814
2262
  this.agents.set(agent_type, agent);
1815
2263
  if (is_enabled === false) {
@@ -1830,10 +2278,15 @@ var OwneySDK = class {
1830
2278
  this.initializingAgentsPromise = null;
1831
2279
  }
1832
2280
  }
1833
- createAgent(agentId, key) {
2281
+ async createAgent(agentId, key, projectName) {
1834
2282
  if (agentId === "zyfai") {
1835
2283
  return new ZyfaiAgent(key, this.zyfaiRpcUrls);
1836
2284
  }
2285
+ if (agentId === "surfliquid") {
2286
+ if (!projectName) return null;
2287
+ const { SurfLiquidAgent: SurfLiquidAgent2 } = await Promise.resolve().then(() => (init_surfliquid_agent(), surfliquid_agent_exports));
2288
+ return new SurfLiquidAgent2({ projectName, appId: key });
2289
+ }
1837
2290
  return null;
1838
2291
  }
1839
2292
  // --- Account lifecycle ---
@@ -1986,18 +2439,30 @@ var OwneySDK = class {
1986
2439
  );
1987
2440
  }
1988
2441
  const agentResults = {};
2442
+ const agentErrors = {};
1989
2443
  for (const { agent, amount: agentAmount } of agentAmounts) {
1990
- agentResults[agent.id] = await this.depositWithFallback(
1991
- agent,
1992
- state,
1993
- chainId,
1994
- agentAmount,
1995
- asset,
1996
- effectiveCallback,
1997
- depositCallback
2444
+ try {
2445
+ agentResults[agent.id] = await this.depositWithFallback(
2446
+ agent,
2447
+ state,
2448
+ chainId,
2449
+ agentAmount,
2450
+ asset,
2451
+ effectiveCallback,
2452
+ depositCallback
2453
+ );
2454
+ } catch (error) {
2455
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
2456
+ }
2457
+ }
2458
+ if (Object.keys(agentResults).length === 0) {
2459
+ throw new OwneyError(
2460
+ "DEPOSIT_ALL_FAILED",
2461
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
2462
+ { agentErrors }
1998
2463
  );
1999
2464
  }
2000
- return { agentResults };
2465
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2001
2466
  }
2002
2467
  /**
2003
2468
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2154,6 +2619,34 @@ var OwneySDK = class {
2154
2619
  }
2155
2620
  return eligible;
2156
2621
  }
2622
+ /**
2623
+ * Agent ids the routing API provisioned for this org that support the given
2624
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
2625
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
2626
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
2627
+ * wallet), so the UI can resolve which agent to use before the user connects.
2628
+ *
2629
+ * This is the source of truth for agent availability: an agent appears here
2630
+ * iff the routing API returned its key. No per-app feature flags.
2631
+ */
2632
+ async getEligibleAgentIds(chainId, asset) {
2633
+ try {
2634
+ await this.ensureAgentsInitialized();
2635
+ } catch (error) {
2636
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
2637
+ return [];
2638
+ }
2639
+ throw error;
2640
+ }
2641
+ return [...this.agents.values()].filter((agent) => {
2642
+ const chainAssets = agent.supportedAssets.find(
2643
+ (sa) => sa.chainId === chainId
2644
+ );
2645
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
2646
+ }).map((agent) => agent.id).sort(
2647
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
2648
+ );
2649
+ }
2157
2650
  // --- Fund operations ---
2158
2651
  /**
2159
2652
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2189,9 +2682,17 @@ var OwneySDK = class {
2189
2682
  }
2190
2683
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2191
2684
  if (!amount) {
2685
+ const aggregated2 = await this.getBalances();
2686
+ const funded = projectAgentBalancesForAsset(
2687
+ eligibleAgents,
2688
+ aggregated2.agentBalances,
2689
+ chainId,
2690
+ asset,
2691
+ assetInfo.decimals
2692
+ ).filter((b) => b.balance > 0n);
2192
2693
  const results2 = {};
2193
2694
  const agentErrors2 = {};
2194
- for (const agent of eligibleAgents) {
2695
+ for (const { agent } of funded) {
2195
2696
  try {
2196
2697
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2197
2698
  } catch (err) {
@@ -2208,9 +2709,10 @@ var OwneySDK = class {
2208
2709
  { asset, agentErrors: agentErrors2 }
2209
2710
  );
2210
2711
  }
2712
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2211
2713
  return {
2212
2714
  agentResult: results2,
2213
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
2715
+ totalWithdrawn: totalWithdrawn.toString()
2214
2716
  };
2215
2717
  }
2216
2718
  const requested = BigInt(amount);
@@ -2580,10 +3082,10 @@ var OwneySDK = class {
2580
3082
  );
2581
3083
  }
2582
3084
  const provider = this.requireConnectedProvider();
2583
- const wallet = (0, import_viem5.createWalletClient)({
3085
+ const wallet = (0, import_viem6.createWalletClient)({
2584
3086
  account: state.walletAddress,
2585
3087
  chain: VIEM_CHAIN2[chainId],
2586
- transport: (0, import_viem5.custom)(provider)
3088
+ transport: (0, import_viem6.custom)(provider)
2587
3089
  });
2588
3090
  const hash = await wallet.writeContract({
2589
3091
  address: token,
@@ -2593,9 +3095,9 @@ var OwneySDK = class {
2593
3095
  account: state.walletAddress,
2594
3096
  chain: VIEM_CHAIN2[chainId]
2595
3097
  });
2596
- const publicClient = (0, import_viem5.createPublicClient)({
3098
+ const publicClient = (0, import_viem6.createPublicClient)({
2597
3099
  chain: VIEM_CHAIN2[chainId],
2598
- transport: (0, import_viem5.custom)(provider)
3100
+ transport: (0, import_viem6.custom)(provider)
2599
3101
  });
2600
3102
  const receipt = await publicClient.waitForTransactionReceipt({
2601
3103
  hash,
@@ -2703,8 +3205,11 @@ var OwneySDK = class {
2703
3205
  }
2704
3206
  };
2705
3207
 
3208
+ // src/index.ts
3209
+ init_errors();
3210
+
2706
3211
  // src/agents/zyfai/zyfai.siwx.ts
2707
- var import_viem6 = require("viem");
3212
+ var import_viem7 = require("viem");
2708
3213
  var import_siwe = require("siwe");
2709
3214
  var import_sdk2 = require("@zyfai/sdk");
2710
3215
 
@@ -2797,7 +3302,7 @@ function buildSIWXConfig(deps) {
2797
3302
  issuedAt,
2798
3303
  toString() {
2799
3304
  return new import_siwe.SiweMessage({
2800
- address: (0, import_viem6.getAddress)(accountAddress),
3305
+ address: (0, import_viem7.getAddress)(accountAddress),
2801
3306
  chainId: numericChainId(chainId),
2802
3307
  domain,
2803
3308
  uri,