@owney/sdk 0.6.9-beta.5 → 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,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, {
@@ -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 = () => {
@@ -1515,7 +1950,11 @@ var ZyfaiAgent = class {
1515
1950
  }
1516
1951
  };
1517
1952
 
1953
+ // src/client.ts
1954
+ init_errors();
1955
+
1518
1956
  // src/lib/routing-api.ts
1957
+ init_errors();
1519
1958
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1520
1959
  async function fetchOrgAgentConfig(apiKey) {
1521
1960
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
@@ -1580,6 +2019,7 @@ async function fetchAgentKeys(apiKey) {
1580
2019
  }
1581
2020
 
1582
2021
  // src/lib/health-report.ts
2022
+ init_errors();
1583
2023
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1584
2024
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1585
2025
  try {
@@ -1729,9 +2169,12 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1729
2169
  }
1730
2170
 
1731
2171
  // src/client.ts
1732
- var import_viem5 = require("viem");
2172
+ var import_viem6 = require("viem");
1733
2173
  var import_chains2 = require("viem/chains");
1734
2174
 
2175
+ // src/lib/sponsored-deposit.ts
2176
+ init_errors();
2177
+
1735
2178
  // src/lib/transfer-auth.ts
1736
2179
  var import_viem3 = require("viem");
1737
2180
  var ERC20_META_ABI = [
@@ -1769,6 +2212,7 @@ function randomAuthNonce() {
1769
2212
  }
1770
2213
 
1771
2214
  // src/lib/sponsor-client.ts
2215
+ init_errors();
1772
2216
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1773
2217
  async function postSponsorTransferAuth(input) {
1774
2218
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
@@ -2037,6 +2481,7 @@ function makeSponsoredDepositCallback(deps) {
2037
2481
  }
2038
2482
 
2039
2483
  // src/lib/sponsored-weth-deposit.ts
2484
+ init_errors();
2040
2485
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2041
2486
  function makeSponsoredWethCallback(deps) {
2042
2487
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -2145,6 +2590,7 @@ var VIEM_CHAIN2 = {
2145
2590
  42161: import_chains2.arbitrum,
2146
2591
  1: import_chains2.mainnet
2147
2592
  };
2593
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2148
2594
  var SPONSORED_WETH_BY_CHAIN = {
2149
2595
  8453: "0x4200000000000000000000000000000000000006",
2150
2596
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2173,6 +2619,7 @@ var OwneySDK = class {
2173
2619
  zyfaiRpcUrls;
2174
2620
  routingApiBaseUrl;
2175
2621
  referralSource;
2622
+ surfliquidApiBaseUrl;
2176
2623
  cachedSponsoredCallback = null;
2177
2624
  cachedWethSponsoredCallback = null;
2178
2625
  initializingAgentsPromise = null;
@@ -2182,6 +2629,7 @@ var OwneySDK = class {
2182
2629
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2183
2630
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2184
2631
  this.referralSource = config.referralSource;
2632
+ this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2185
2633
  }
2186
2634
  /**
2187
2635
  * Establish connection state for all subsequent SDK calls.
@@ -2279,14 +2727,14 @@ var OwneySDK = class {
2279
2727
  // Casts work around viem's chain-narrowed Client vs the generic
2280
2728
  // PublicClient/WalletClient param types — structurally identical at
2281
2729
  // runtime, but the two share a name TS treats as unrelated.
2282
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2730
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2283
2731
  chain: VIEM_CHAIN2[cid],
2284
- transport: (0, import_viem5.custom)(provider)
2732
+ transport: (0, import_viem6.custom)(provider)
2285
2733
  }),
2286
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2734
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2287
2735
  account: owner,
2288
2736
  chain: VIEM_CHAIN2[cid],
2289
- transport: (0, import_viem5.custom)(provider)
2737
+ transport: (0, import_viem6.custom)(provider)
2290
2738
  })
2291
2739
  });
2292
2740
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2312,14 +2760,14 @@ var OwneySDK = class {
2312
2760
  // Casts work around viem's chain-narrowed Client vs the generic
2313
2761
  // PublicClient/WalletClient param types — structurally identical at
2314
2762
  // runtime, but the two share a name TS treats as unrelated.
2315
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2763
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2316
2764
  chain: VIEM_CHAIN2[cid],
2317
- transport: (0, import_viem5.custom)(provider)
2765
+ transport: (0, import_viem6.custom)(provider)
2318
2766
  }),
2319
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2767
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2320
2768
  account: owner,
2321
2769
  chain: VIEM_CHAIN2[cid],
2322
- transport: (0, import_viem5.custom)(provider)
2770
+ transport: (0, import_viem6.custom)(provider)
2323
2771
  })
2324
2772
  });
2325
2773
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2389,8 +2837,8 @@ var OwneySDK = class {
2389
2837
  this.initializingAgentsPromise = (async () => {
2390
2838
  const agentKeys = await fetchAgentKeys(this.apiKey);
2391
2839
  this.disabledAgents.clear();
2392
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
2393
- const agent = this.createAgent(agent_type, key2);
2840
+ for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2841
+ const agent = await this.createAgent(agent_type, key2, project_name);
2394
2842
  if (!agent) continue;
2395
2843
  this.agents.set(agent_type, agent);
2396
2844
  if (is_enabled === false) {
@@ -2411,10 +2859,22 @@ var OwneySDK = class {
2411
2859
  this.initializingAgentsPromise = null;
2412
2860
  }
2413
2861
  }
2414
- createAgent(agentId, key2) {
2862
+ async createAgent(agentId, key2, projectName) {
2415
2863
  if (agentId === "zyfai") {
2416
2864
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2417
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
+ });
2877
+ }
2418
2878
  return null;
2419
2879
  }
2420
2880
  // --- Account lifecycle ---
@@ -2569,18 +3029,30 @@ var OwneySDK = class {
2569
3029
  );
2570
3030
  }
2571
3031
  const agentResults = {};
3032
+ const agentErrors = {};
2572
3033
  for (const { agent, amount: agentAmount } of agentAmounts) {
2573
- agentResults[agent.id] = await this.depositWithFallback(
2574
- agent,
2575
- state,
2576
- chainId,
2577
- agentAmount,
2578
- asset,
2579
- effectiveCallback,
2580
- 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 }
2581
3053
  );
2582
3054
  }
2583
- return { agentResults };
3055
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2584
3056
  }
2585
3057
  /**
2586
3058
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2737,6 +3209,34 @@ var OwneySDK = class {
2737
3209
  }
2738
3210
  return eligible;
2739
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
+ }
2740
3240
  // --- Fund operations ---
2741
3241
  /**
2742
3242
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2772,9 +3272,17 @@ var OwneySDK = class {
2772
3272
  }
2773
3273
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2774
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);
2775
3283
  const results2 = {};
2776
3284
  const agentErrors2 = {};
2777
- for (const agent of eligibleAgents) {
3285
+ for (const { agent } of funded) {
2778
3286
  try {
2779
3287
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2780
3288
  } catch (err) {
@@ -2791,9 +3299,10 @@ var OwneySDK = class {
2791
3299
  { asset, agentErrors: agentErrors2 }
2792
3300
  );
2793
3301
  }
3302
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2794
3303
  return {
2795
3304
  agentResult: results2,
2796
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3305
+ totalWithdrawn: totalWithdrawn.toString()
2797
3306
  };
2798
3307
  }
2799
3308
  const requested = BigInt(amount);
@@ -3163,10 +3672,10 @@ var OwneySDK = class {
3163
3672
  );
3164
3673
  }
3165
3674
  const provider = this.requireConnectedProvider();
3166
- const wallet = (0, import_viem5.createWalletClient)({
3675
+ const wallet = (0, import_viem6.createWalletClient)({
3167
3676
  account: state.walletAddress,
3168
3677
  chain: VIEM_CHAIN2[chainId],
3169
- transport: (0, import_viem5.custom)(provider)
3678
+ transport: (0, import_viem6.custom)(provider)
3170
3679
  });
3171
3680
  const hash = await wallet.writeContract({
3172
3681
  address: token,
@@ -3176,9 +3685,9 @@ var OwneySDK = class {
3176
3685
  account: state.walletAddress,
3177
3686
  chain: VIEM_CHAIN2[chainId]
3178
3687
  });
3179
- const publicClient = (0, import_viem5.createPublicClient)({
3688
+ const publicClient = (0, import_viem6.createPublicClient)({
3180
3689
  chain: VIEM_CHAIN2[chainId],
3181
- transport: (0, import_viem5.custom)(provider)
3690
+ transport: (0, import_viem6.custom)(provider)
3182
3691
  });
3183
3692
  const receipt = await publicClient.waitForTransactionReceipt({
3184
3693
  hash,
@@ -3286,8 +3795,11 @@ var OwneySDK = class {
3286
3795
  }
3287
3796
  };
3288
3797
 
3798
+ // src/index.ts
3799
+ init_errors();
3800
+
3289
3801
  // src/agents/zyfai/zyfai.siwx.ts
3290
- var import_viem6 = require("viem");
3802
+ var import_viem7 = require("viem");
3291
3803
  var import_siwe = require("siwe");
3292
3804
  var import_sdk2 = require("@zyfai/sdk");
3293
3805
 
@@ -3414,7 +3926,7 @@ function buildSIWXConfig(deps) {
3414
3926
  issuedAt,
3415
3927
  toString() {
3416
3928
  return new import_siwe.SiweMessage({
3417
- address: (0, import_viem6.getAddress)(accountAddress),
3929
+ address: (0, import_viem7.getAddress)(accountAddress),
3418
3930
  chainId: numericChainId(chainId),
3419
3931
  domain,
3420
3932
  uri,