@owney/sdk 0.6.8 → 0.6.9-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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 = () => {
@@ -1187,7 +1622,11 @@ var ZyfaiAgent = class {
1187
1622
  }
1188
1623
  };
1189
1624
 
1625
+ // src/client.ts
1626
+ init_errors();
1627
+
1190
1628
  // src/lib/routing-api.ts
1629
+ init_errors();
1191
1630
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1192
1631
  async function fetchAgentKeys(apiKey) {
1193
1632
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
@@ -1218,6 +1657,7 @@ async function fetchAgentKeys(apiKey) {
1218
1657
  }
1219
1658
 
1220
1659
  // src/lib/health-report.ts
1660
+ init_errors();
1221
1661
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1222
1662
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1223
1663
  try {
@@ -1367,9 +1807,12 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1367
1807
  }
1368
1808
 
1369
1809
  // src/client.ts
1370
- var import_viem5 = require("viem");
1810
+ var import_viem6 = require("viem");
1371
1811
  var import_chains2 = require("viem/chains");
1372
1812
 
1813
+ // src/lib/sponsored-deposit.ts
1814
+ init_errors();
1815
+
1373
1816
  // src/lib/transfer-auth.ts
1374
1817
  var import_viem3 = require("viem");
1375
1818
  var ERC20_META_ABI = [
@@ -1407,6 +1850,7 @@ function randomAuthNonce() {
1407
1850
  }
1408
1851
 
1409
1852
  // src/lib/sponsor-client.ts
1853
+ init_errors();
1410
1854
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1411
1855
  async function postSponsorTransferAuth(input) {
1412
1856
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
@@ -1675,6 +2119,7 @@ function makeSponsoredDepositCallback(deps) {
1675
2119
  }
1676
2120
 
1677
2121
  // src/lib/sponsored-weth-deposit.ts
2122
+ init_errors();
1678
2123
  var PERMIT_WINDOW_SECONDS = 15 * 60;
1679
2124
  function makeSponsoredWethCallback(deps) {
1680
2125
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -1783,6 +2228,7 @@ var VIEM_CHAIN2 = {
1783
2228
  42161: import_chains2.arbitrum,
1784
2229
  1: import_chains2.mainnet
1785
2230
  };
2231
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
1786
2232
  var SPONSORED_WETH_BY_CHAIN = {
1787
2233
  8453: "0x4200000000000000000000000000000000000006",
1788
2234
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -1910,14 +2356,14 @@ var OwneySDK = class {
1910
2356
  // Casts work around viem's chain-narrowed Client vs the generic
1911
2357
  // PublicClient/WalletClient param types — structurally identical at
1912
2358
  // runtime, but the two share a name TS treats as unrelated.
1913
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2359
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
1914
2360
  chain: VIEM_CHAIN2[cid],
1915
- transport: (0, import_viem5.custom)(provider)
2361
+ transport: (0, import_viem6.custom)(provider)
1916
2362
  }),
1917
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2363
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
1918
2364
  account: owner,
1919
2365
  chain: VIEM_CHAIN2[cid],
1920
- transport: (0, import_viem5.custom)(provider)
2366
+ transport: (0, import_viem6.custom)(provider)
1921
2367
  })
1922
2368
  });
1923
2369
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -1943,14 +2389,14 @@ var OwneySDK = class {
1943
2389
  // Casts work around viem's chain-narrowed Client vs the generic
1944
2390
  // PublicClient/WalletClient param types — structurally identical at
1945
2391
  // runtime, but the two share a name TS treats as unrelated.
1946
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2392
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
1947
2393
  chain: VIEM_CHAIN2[cid],
1948
- transport: (0, import_viem5.custom)(provider)
2394
+ transport: (0, import_viem6.custom)(provider)
1949
2395
  }),
1950
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2396
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
1951
2397
  account: owner,
1952
2398
  chain: VIEM_CHAIN2[cid],
1953
- transport: (0, import_viem5.custom)(provider)
2399
+ transport: (0, import_viem6.custom)(provider)
1954
2400
  })
1955
2401
  });
1956
2402
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -1977,8 +2423,8 @@ var OwneySDK = class {
1977
2423
  this.initializingAgentsPromise = (async () => {
1978
2424
  const agentKeys = await fetchAgentKeys(this.apiKey);
1979
2425
  this.disabledAgents.clear();
1980
- for (const { key, agent_type, is_enabled } of agentKeys) {
1981
- const agent = this.createAgent(agent_type, key);
2426
+ for (const { key, agent_type, project_name, is_enabled } of agentKeys) {
2427
+ const agent = await this.createAgent(agent_type, key, project_name);
1982
2428
  if (!agent) continue;
1983
2429
  this.agents.set(agent_type, agent);
1984
2430
  if (is_enabled === false) {
@@ -1999,10 +2445,15 @@ var OwneySDK = class {
1999
2445
  this.initializingAgentsPromise = null;
2000
2446
  }
2001
2447
  }
2002
- createAgent(agentId, key) {
2448
+ async createAgent(agentId, key, projectName) {
2003
2449
  if (agentId === "zyfai") {
2004
2450
  return new ZyfaiAgent(key, this.zyfaiRpcUrls, this.referralSource);
2005
2451
  }
2452
+ if (agentId === "surfliquid") {
2453
+ if (!projectName) return null;
2454
+ const { SurfLiquidAgent: SurfLiquidAgent2 } = await Promise.resolve().then(() => (init_surfliquid_agent(), surfliquid_agent_exports));
2455
+ return new SurfLiquidAgent2({ projectName, appId: key });
2456
+ }
2006
2457
  return null;
2007
2458
  }
2008
2459
  // --- Account lifecycle ---
@@ -2155,18 +2606,30 @@ var OwneySDK = class {
2155
2606
  );
2156
2607
  }
2157
2608
  const agentResults = {};
2609
+ const agentErrors = {};
2158
2610
  for (const { agent, amount: agentAmount } of agentAmounts) {
2159
- agentResults[agent.id] = await this.depositWithFallback(
2160
- agent,
2161
- state,
2162
- chainId,
2163
- agentAmount,
2164
- asset,
2165
- effectiveCallback,
2166
- depositCallback
2611
+ try {
2612
+ agentResults[agent.id] = await this.depositWithFallback(
2613
+ agent,
2614
+ state,
2615
+ chainId,
2616
+ agentAmount,
2617
+ asset,
2618
+ effectiveCallback,
2619
+ depositCallback
2620
+ );
2621
+ } catch (error) {
2622
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
2623
+ }
2624
+ }
2625
+ if (Object.keys(agentResults).length === 0) {
2626
+ throw new OwneyError(
2627
+ "DEPOSIT_ALL_FAILED",
2628
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
2629
+ { agentErrors }
2167
2630
  );
2168
2631
  }
2169
- return { agentResults };
2632
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2170
2633
  }
2171
2634
  /**
2172
2635
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2323,6 +2786,34 @@ var OwneySDK = class {
2323
2786
  }
2324
2787
  return eligible;
2325
2788
  }
2789
+ /**
2790
+ * Agent ids the routing API provisioned for this org that support the given
2791
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
2792
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
2793
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
2794
+ * wallet), so the UI can resolve which agent to use before the user connects.
2795
+ *
2796
+ * This is the source of truth for agent availability: an agent appears here
2797
+ * iff the routing API returned its key. No per-app feature flags.
2798
+ */
2799
+ async getEligibleAgentIds(chainId, asset) {
2800
+ try {
2801
+ await this.ensureAgentsInitialized();
2802
+ } catch (error) {
2803
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
2804
+ return [];
2805
+ }
2806
+ throw error;
2807
+ }
2808
+ return [...this.agents.values()].filter((agent) => {
2809
+ const chainAssets = agent.supportedAssets.find(
2810
+ (sa) => sa.chainId === chainId
2811
+ );
2812
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
2813
+ }).map((agent) => agent.id).sort(
2814
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
2815
+ );
2816
+ }
2326
2817
  // --- Fund operations ---
2327
2818
  /**
2328
2819
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2358,9 +2849,17 @@ var OwneySDK = class {
2358
2849
  }
2359
2850
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2360
2851
  if (!amount) {
2852
+ const aggregated2 = await this.getBalances();
2853
+ const funded = projectAgentBalancesForAsset(
2854
+ eligibleAgents,
2855
+ aggregated2.agentBalances,
2856
+ chainId,
2857
+ asset,
2858
+ assetInfo.decimals
2859
+ ).filter((b) => b.balance > 0n);
2361
2860
  const results2 = {};
2362
2861
  const agentErrors2 = {};
2363
- for (const agent of eligibleAgents) {
2862
+ for (const { agent } of funded) {
2364
2863
  try {
2365
2864
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2366
2865
  } catch (err) {
@@ -2377,9 +2876,10 @@ var OwneySDK = class {
2377
2876
  { asset, agentErrors: agentErrors2 }
2378
2877
  );
2379
2878
  }
2879
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2380
2880
  return {
2381
2881
  agentResult: results2,
2382
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
2882
+ totalWithdrawn: totalWithdrawn.toString()
2383
2883
  };
2384
2884
  }
2385
2885
  const requested = BigInt(amount);
@@ -2749,10 +3249,10 @@ var OwneySDK = class {
2749
3249
  );
2750
3250
  }
2751
3251
  const provider = this.requireConnectedProvider();
2752
- const wallet = (0, import_viem5.createWalletClient)({
3252
+ const wallet = (0, import_viem6.createWalletClient)({
2753
3253
  account: state.walletAddress,
2754
3254
  chain: VIEM_CHAIN2[chainId],
2755
- transport: (0, import_viem5.custom)(provider)
3255
+ transport: (0, import_viem6.custom)(provider)
2756
3256
  });
2757
3257
  const hash = await wallet.writeContract({
2758
3258
  address: token,
@@ -2762,9 +3262,9 @@ var OwneySDK = class {
2762
3262
  account: state.walletAddress,
2763
3263
  chain: VIEM_CHAIN2[chainId]
2764
3264
  });
2765
- const publicClient = (0, import_viem5.createPublicClient)({
3265
+ const publicClient = (0, import_viem6.createPublicClient)({
2766
3266
  chain: VIEM_CHAIN2[chainId],
2767
- transport: (0, import_viem5.custom)(provider)
3267
+ transport: (0, import_viem6.custom)(provider)
2768
3268
  });
2769
3269
  const receipt = await publicClient.waitForTransactionReceipt({
2770
3270
  hash,
@@ -2872,8 +3372,11 @@ var OwneySDK = class {
2872
3372
  }
2873
3373
  };
2874
3374
 
3375
+ // src/index.ts
3376
+ init_errors();
3377
+
2875
3378
  // src/agents/zyfai/zyfai.siwx.ts
2876
- var import_viem6 = require("viem");
3379
+ var import_viem7 = require("viem");
2877
3380
  var import_siwe = require("siwe");
2878
3381
  var import_sdk2 = require("@zyfai/sdk");
2879
3382
 
@@ -3000,7 +3503,7 @@ function buildSIWXConfig(deps) {
3000
3503
  issuedAt,
3001
3504
  toString() {
3002
3505
  return new import_siwe.SiweMessage({
3003
- address: (0, import_viem6.getAddress)(accountAddress),
3506
+ address: (0, import_viem7.getAddress)(accountAddress),
3004
3507
  chainId: numericChainId(chainId),
3005
3508
  domain,
3006
3509
  uri,