@owney/sdk 0.6.9-beta.0 → 0.6.9-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,14 +3,6 @@ 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
- };
14
6
  var __export = (target, all) => {
15
7
  for (var name in all)
16
8
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -25,478 +17,6 @@ var __copyProps = (to, from, except, desc) => {
25
17
  };
26
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
19
 
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
-
500
20
  // src/index.ts
501
21
  var index_exports = {};
502
22
  __export(index_exports, {
@@ -568,8 +88,56 @@ var SupportedAssets = [
568
88
  }
569
89
  ];
570
90
 
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
+
571
140
  // src/lib/utils.ts
572
- init_errors();
573
141
  var isValidChainId = (chainId) => {
574
142
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
575
143
  throw new OwneyError(
@@ -643,46 +211,46 @@ function resolveChainId(chain) {
643
211
  function mapBalances(raw, _chainId, smartWallet) {
644
212
  const portfolio = raw.portfolio;
645
213
  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
- }
653
214
  let totalBalance = 0;
654
215
  const tokens = [];
655
- const positions = [];
656
216
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
657
217
  const positionChainId = Number(chainIdKey);
658
- const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
659
218
  for (const [symbol, { balance, decimals }] of Object.entries(
660
219
  tokensBySymbol ?? {}
661
220
  )) {
662
- const amount = hexToDecimal(balance || "0", tokenDecimals(symbol, decimals));
221
+ const amount = hexToDecimal(balance || "0", decimals);
663
222
  totalBalance += amount;
664
223
  tokens.push({
665
- chain: chainName,
224
+ chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
666
225
  chainId: positionChainId,
667
226
  asset: symbol,
668
227
  amount: String(amount)
669
228
  });
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
- });
684
229
  }
685
230
  }
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
+ }));
686
254
  return {
687
255
  smartWallet,
688
256
  totalBalance: String(totalBalance),
@@ -957,9 +525,6 @@ function decodeHistoryCursor(token) {
957
525
  return parsed;
958
526
  }
959
527
 
960
- // src/agents/zyfai/zyfai.agent.ts
961
- init_errors();
962
-
963
528
  // src/agents/zyfai/zyfai.auth-cache.ts
964
529
  var KEY_PREFIX = "owney.zyfai.session";
965
530
  var storage = () => {
@@ -1095,6 +660,39 @@ function buildProtocolMergeUpdate(available, current, asset) {
1095
660
  };
1096
661
  }
1097
662
 
663
+ // src/agents/zyfai/splitting.ts
664
+ var MAX_MIN_SPLITS = 4;
665
+ var MIN_FORCE_SPLITS = 2;
666
+ var AUTOMATIC_MIN_SPLITS = 1;
667
+ function toZyfaiSplitting(policy) {
668
+ switch (policy.splittingMode) {
669
+ case "none":
670
+ return { splitting: false };
671
+ case "automatic":
672
+ return { splitting: true, minSplits: AUTOMATIC_MIN_SPLITS };
673
+ case "force":
674
+ return {
675
+ splitting: true,
676
+ minSplits: clampForceSplits(policy.minSplits)
677
+ };
678
+ }
679
+ }
680
+ function clampForceSplits(minSplits) {
681
+ if (typeof minSplits !== "number" || !Number.isFinite(minSplits)) {
682
+ return MIN_FORCE_SPLITS;
683
+ }
684
+ const rounded = Math.round(minSplits);
685
+ if (rounded < MIN_FORCE_SPLITS) return MIN_FORCE_SPLITS;
686
+ if (rounded > MAX_MIN_SPLITS) return MAX_MIN_SPLITS;
687
+ return rounded;
688
+ }
689
+ function splittingNeedsUpdate(current, desired) {
690
+ if (!current) return true;
691
+ if (current.splitting !== desired.splitting) return true;
692
+ if (desired.minSplits === void 0) return false;
693
+ return current.minSplits !== desired.minSplits;
694
+ }
695
+
1098
696
  // src/agents/zyfai/zyfai.agent.ts
1099
697
  var DEFAULT_ZYFAI_RPC_URLS = {
1100
698
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
@@ -1107,6 +705,7 @@ var WETH_ADDRESS_BY_CHAIN = {
1107
705
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
1108
706
  };
1109
707
  var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum, 1: import_chains.mainnet };
708
+ var SPLITTING_ASSETS = ["USDC", "WETH"];
1110
709
  function isAlreadyActiveSessionKeyError(error) {
1111
710
  const message = error instanceof Error ? error.message : String(error);
1112
711
  return /already has an active session key/i.test(message);
@@ -1471,6 +1070,35 @@ var ZyfaiAgent = class {
1471
1070
  throw error;
1472
1071
  }
1473
1072
  }
1073
+ /**
1074
+ * Apply the org's capital splitting policy to this user's Zyfai profile.
1075
+ *
1076
+ * Per ASSET, not per chain: Zyfai stores splitting under
1077
+ * `assetTypeSettings[asset]`, so one call would only configure USDC and leave a
1078
+ * WETH holder's real setting untouched.
1079
+ *
1080
+ * Non-fatal throughout. This is a side effect of connecting, so a Zyfai hiccup
1081
+ * must leave the user connected with their profile unchanged, never fail
1082
+ * activation.
1083
+ */
1084
+ async applyOrgPolicy(_state, _chainId, policy) {
1085
+ await Promise.all(
1086
+ SPLITTING_ASSETS.map((asset) => this.applySplitting(asset, policy))
1087
+ );
1088
+ }
1089
+ async applySplitting(asset, policy) {
1090
+ try {
1091
+ const desired = toZyfaiSplitting(policy);
1092
+ const current = await this.sdk.getUserDetails(asset);
1093
+ if (!splittingNeedsUpdate(current, desired)) return;
1094
+ await this.sdk.updateUserProfile({ asset, ...desired });
1095
+ } catch (error) {
1096
+ console.warn(
1097
+ `[zyfai] applyOrgPolicy(${asset}) failed (non-fatal):`,
1098
+ error instanceof Error ? error.message : String(error)
1099
+ );
1100
+ }
1101
+ }
1474
1102
  async deposit(state, chainId, amount, asset, depositCallback) {
1475
1103
  try {
1476
1104
  await this.connectFull(state, chainId);
@@ -1622,12 +1250,36 @@ var ZyfaiAgent = class {
1622
1250
  }
1623
1251
  };
1624
1252
 
1625
- // src/client.ts
1626
- init_errors();
1627
-
1628
1253
  // src/lib/routing-api.ts
1629
- init_errors();
1630
1254
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1255
+ async function fetchOrgAgentConfig(apiKey) {
1256
+ const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
1257
+ try {
1258
+ const res = await fetch(url, {
1259
+ method: "GET",
1260
+ headers: {
1261
+ "Content-Type": "application/json",
1262
+ "x-owney-api-key": `${apiKey}`
1263
+ }
1264
+ });
1265
+ if (!res.ok) {
1266
+ if (res.status !== 404) {
1267
+ console.warn(
1268
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
1269
+ );
1270
+ }
1271
+ return null;
1272
+ }
1273
+ const json = await res.json();
1274
+ return json.success ? json.data ?? null : null;
1275
+ } catch (error) {
1276
+ console.warn(
1277
+ "[owney-sdk] Could not read org agent config (non-fatal):",
1278
+ error instanceof Error ? error.message : String(error)
1279
+ );
1280
+ return null;
1281
+ }
1282
+ }
1631
1283
  async function fetchAgentKeys(apiKey) {
1632
1284
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
1633
1285
  const res = await fetch(url, {
@@ -1657,7 +1309,6 @@ async function fetchAgentKeys(apiKey) {
1657
1309
  }
1658
1310
 
1659
1311
  // src/lib/health-report.ts
1660
- init_errors();
1661
1312
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1662
1313
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1663
1314
  try {
@@ -1807,12 +1458,9 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1807
1458
  }
1808
1459
 
1809
1460
  // src/client.ts
1810
- var import_viem6 = require("viem");
1461
+ var import_viem5 = require("viem");
1811
1462
  var import_chains2 = require("viem/chains");
1812
1463
 
1813
- // src/lib/sponsored-deposit.ts
1814
- init_errors();
1815
-
1816
1464
  // src/lib/transfer-auth.ts
1817
1465
  var import_viem3 = require("viem");
1818
1466
  var ERC20_META_ABI = [
@@ -1850,7 +1498,6 @@ function randomAuthNonce() {
1850
1498
  }
1851
1499
 
1852
1500
  // src/lib/sponsor-client.ts
1853
- init_errors();
1854
1501
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1855
1502
  async function postSponsorTransferAuth(input) {
1856
1503
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
@@ -2119,7 +1766,6 @@ function makeSponsoredDepositCallback(deps) {
2119
1766
  }
2120
1767
 
2121
1768
  // src/lib/sponsored-weth-deposit.ts
2122
- init_errors();
2123
1769
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2124
1770
  function makeSponsoredWethCallback(deps) {
2125
1771
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -2228,7 +1874,6 @@ var VIEM_CHAIN2 = {
2228
1874
  42161: import_chains2.arbitrum,
2229
1875
  1: import_chains2.mainnet
2230
1876
  };
2231
- var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2232
1877
  var SPONSORED_WETH_BY_CHAIN = {
2233
1878
  8453: "0x4200000000000000000000000000000000000006",
2234
1879
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2248,6 +1893,12 @@ var OwneySDK = class {
2248
1893
  disabledAgents = /* @__PURE__ */ new Set();
2249
1894
  state = null;
2250
1895
  apiKey;
1896
+ // The org's agent policy, fetched once per client. Applied to each agent on
1897
+ // activation (ROUT-229). `undefined` means "not fetched yet", `null` means
1898
+ // "fetched, and the partner has no policy" — which is a real answer meaning
1899
+ // leave every user's agent profile alone.
1900
+ orgAgentConfig;
1901
+ orgAgentConfigPromise = null;
2251
1902
  zyfaiRpcUrls;
2252
1903
  routingApiBaseUrl;
2253
1904
  referralSource;
@@ -2356,14 +2007,14 @@ var OwneySDK = class {
2356
2007
  // Casts work around viem's chain-narrowed Client vs the generic
2357
2008
  // PublicClient/WalletClient param types — structurally identical at
2358
2009
  // runtime, but the two share a name TS treats as unrelated.
2359
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2010
+ getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2360
2011
  chain: VIEM_CHAIN2[cid],
2361
- transport: (0, import_viem6.custom)(provider)
2012
+ transport: (0, import_viem5.custom)(provider)
2362
2013
  }),
2363
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2014
+ getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2364
2015
  account: owner,
2365
2016
  chain: VIEM_CHAIN2[cid],
2366
- transport: (0, import_viem6.custom)(provider)
2017
+ transport: (0, import_viem5.custom)(provider)
2367
2018
  })
2368
2019
  });
2369
2020
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2389,14 +2040,14 @@ var OwneySDK = class {
2389
2040
  // Casts work around viem's chain-narrowed Client vs the generic
2390
2041
  // PublicClient/WalletClient param types — structurally identical at
2391
2042
  // runtime, but the two share a name TS treats as unrelated.
2392
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2043
+ getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2393
2044
  chain: VIEM_CHAIN2[cid],
2394
- transport: (0, import_viem6.custom)(provider)
2045
+ transport: (0, import_viem5.custom)(provider)
2395
2046
  }),
2396
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2047
+ getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2397
2048
  account: owner,
2398
2049
  chain: VIEM_CHAIN2[cid],
2399
- transport: (0, import_viem6.custom)(provider)
2050
+ transport: (0, import_viem5.custom)(provider)
2400
2051
  })
2401
2052
  });
2402
2053
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2417,14 +2068,51 @@ var OwneySDK = class {
2417
2068
  }
2418
2069
  return active;
2419
2070
  }
2071
+ /**
2072
+ * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
2073
+ * throws — it degrades to null — so this cannot break activation.
2074
+ */
2075
+ async getOrgAgentConfig() {
2076
+ if (this.orgAgentConfig !== void 0) return this.orgAgentConfig;
2077
+ if (!this.orgAgentConfigPromise) {
2078
+ this.orgAgentConfigPromise = fetchOrgAgentConfig(this.apiKey).then(
2079
+ (config) => {
2080
+ this.orgAgentConfig = config;
2081
+ return config;
2082
+ }
2083
+ );
2084
+ }
2085
+ return this.orgAgentConfigPromise;
2086
+ }
2087
+ /**
2088
+ * Apply the org policy to one agent, after it has activated.
2089
+ *
2090
+ * Sequenced after activation rather than alongside it because applying the
2091
+ * policy needs the authenticated session activation establishes. Every failure
2092
+ * mode is swallowed: a partner's splitting preference is not worth failing a
2093
+ * user's connect over.
2094
+ */
2095
+ async applyOrgPolicyTo(agent, state, chainId) {
2096
+ if (!agent.applyOrgPolicy) return;
2097
+ try {
2098
+ const policy = await this.getOrgAgentConfig();
2099
+ if (!policy) return;
2100
+ await agent.applyOrgPolicy(state, chainId, policy);
2101
+ } catch (error) {
2102
+ console.warn(
2103
+ `[owney-sdk] applyOrgPolicy(${agent.id}) failed (non-fatal):`,
2104
+ error instanceof Error ? error.message : String(error)
2105
+ );
2106
+ }
2107
+ }
2420
2108
  async ensureAgentsInitialized() {
2421
2109
  if (this.agents.size > 0) return;
2422
2110
  if (!this.initializingAgentsPromise) {
2423
2111
  this.initializingAgentsPromise = (async () => {
2424
2112
  const agentKeys = await fetchAgentKeys(this.apiKey);
2425
2113
  this.disabledAgents.clear();
2426
- for (const { key, agent_type, project_name, is_enabled } of agentKeys) {
2427
- const agent = await this.createAgent(agent_type, key, project_name);
2114
+ for (const { key, agent_type, is_enabled } of agentKeys) {
2115
+ const agent = this.createAgent(agent_type, key);
2428
2116
  if (!agent) continue;
2429
2117
  this.agents.set(agent_type, agent);
2430
2118
  if (is_enabled === false) {
@@ -2445,15 +2133,10 @@ var OwneySDK = class {
2445
2133
  this.initializingAgentsPromise = null;
2446
2134
  }
2447
2135
  }
2448
- async createAgent(agentId, key, projectName) {
2136
+ createAgent(agentId, key) {
2449
2137
  if (agentId === "zyfai") {
2450
2138
  return new ZyfaiAgent(key, this.zyfaiRpcUrls, this.referralSource);
2451
2139
  }
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
- }
2457
2140
  return null;
2458
2141
  }
2459
2142
  // --- Account lifecycle ---
@@ -2506,6 +2189,7 @@ var OwneySDK = class {
2506
2189
  Promise.all(
2507
2190
  agents.map(async (agent) => {
2508
2191
  await agent.activateAgent(state, chainId);
2192
+ await this.applyOrgPolicyTo(agent, state, chainId);
2509
2193
  })
2510
2194
  ).catch((error) => {
2511
2195
  console.error("activateAgent background init failed:", error);
@@ -2533,6 +2217,7 @@ var OwneySDK = class {
2533
2217
  Promise.all(
2534
2218
  enabledCompatible.map(async (agent) => {
2535
2219
  await agent.activateAgent(state, chainId);
2220
+ await this.applyOrgPolicyTo(agent, state, chainId);
2536
2221
  })
2537
2222
  ).catch((error) => {
2538
2223
  console.error("activateAgent background init failed:", error);
@@ -2606,30 +2291,18 @@ var OwneySDK = class {
2606
2291
  );
2607
2292
  }
2608
2293
  const agentResults = {};
2609
- const agentErrors = {};
2610
2294
  for (const { agent, amount: agentAmount } of agentAmounts) {
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 }
2295
+ agentResults[agent.id] = await this.depositWithFallback(
2296
+ agent,
2297
+ state,
2298
+ chainId,
2299
+ agentAmount,
2300
+ asset,
2301
+ effectiveCallback,
2302
+ depositCallback
2630
2303
  );
2631
2304
  }
2632
- return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2305
+ return { agentResults };
2633
2306
  }
2634
2307
  /**
2635
2308
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2786,34 +2459,6 @@ var OwneySDK = class {
2786
2459
  }
2787
2460
  return eligible;
2788
2461
  }
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
- }
2817
2462
  // --- Fund operations ---
2818
2463
  /**
2819
2464
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2849,17 +2494,9 @@ var OwneySDK = class {
2849
2494
  }
2850
2495
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2851
2496
  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);
2860
2497
  const results2 = {};
2861
2498
  const agentErrors2 = {};
2862
- for (const { agent } of funded) {
2499
+ for (const agent of eligibleAgents) {
2863
2500
  try {
2864
2501
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2865
2502
  } catch (err) {
@@ -2876,10 +2513,9 @@ var OwneySDK = class {
2876
2513
  { asset, agentErrors: agentErrors2 }
2877
2514
  );
2878
2515
  }
2879
- const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2880
2516
  return {
2881
2517
  agentResult: results2,
2882
- totalWithdrawn: totalWithdrawn.toString()
2518
+ totalWithdrawn: sumWithdrawnAmount(results2).toString()
2883
2519
  };
2884
2520
  }
2885
2521
  const requested = BigInt(amount);
@@ -3249,10 +2885,10 @@ var OwneySDK = class {
3249
2885
  );
3250
2886
  }
3251
2887
  const provider = this.requireConnectedProvider();
3252
- const wallet = (0, import_viem6.createWalletClient)({
2888
+ const wallet = (0, import_viem5.createWalletClient)({
3253
2889
  account: state.walletAddress,
3254
2890
  chain: VIEM_CHAIN2[chainId],
3255
- transport: (0, import_viem6.custom)(provider)
2891
+ transport: (0, import_viem5.custom)(provider)
3256
2892
  });
3257
2893
  const hash = await wallet.writeContract({
3258
2894
  address: token,
@@ -3262,9 +2898,9 @@ var OwneySDK = class {
3262
2898
  account: state.walletAddress,
3263
2899
  chain: VIEM_CHAIN2[chainId]
3264
2900
  });
3265
- const publicClient = (0, import_viem6.createPublicClient)({
2901
+ const publicClient = (0, import_viem5.createPublicClient)({
3266
2902
  chain: VIEM_CHAIN2[chainId],
3267
- transport: (0, import_viem6.custom)(provider)
2903
+ transport: (0, import_viem5.custom)(provider)
3268
2904
  });
3269
2905
  const receipt = await publicClient.waitForTransactionReceipt({
3270
2906
  hash,
@@ -3372,11 +3008,8 @@ var OwneySDK = class {
3372
3008
  }
3373
3009
  };
3374
3010
 
3375
- // src/index.ts
3376
- init_errors();
3377
-
3378
3011
  // src/agents/zyfai/zyfai.siwx.ts
3379
- var import_viem7 = require("viem");
3012
+ var import_viem6 = require("viem");
3380
3013
  var import_siwe = require("siwe");
3381
3014
  var import_sdk2 = require("@zyfai/sdk");
3382
3015
 
@@ -3503,7 +3136,7 @@ function buildSIWXConfig(deps) {
3503
3136
  issuedAt,
3504
3137
  toString() {
3505
3138
  return new import_siwe.SiweMessage({
3506
- address: (0, import_viem7.getAddress)(accountAddress),
3139
+ address: (0, import_viem6.getAddress)(accountAddress),
3507
3140
  chainId: numericChainId(chainId),
3508
3141
  domain,
3509
3142
  uri,