@owney/sdk 0.6.9-beta.5 → 0.6.9-beta.7

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,620 @@ 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/lib/permit2.ts
84
+ function buildPermitTransferFromTypedData(input) {
85
+ return {
86
+ domain: {
87
+ name: "Permit2",
88
+ chainId: input.chainId,
89
+ verifyingContract: PERMIT2_ADDRESS
90
+ },
91
+ types: {
92
+ PermitTransferFrom: [
93
+ { name: "permitted", type: "TokenPermissions" },
94
+ { name: "spender", type: "address" },
95
+ { name: "nonce", type: "uint256" },
96
+ { name: "deadline", type: "uint256" }
97
+ ],
98
+ TokenPermissions: [
99
+ { name: "token", type: "address" },
100
+ { name: "amount", type: "uint256" }
101
+ ]
102
+ },
103
+ primaryType: "PermitTransferFrom",
104
+ message: input.message
105
+ };
106
+ }
107
+ function randomPermit2Nonce() {
108
+ const bytes = new Uint8Array(32);
109
+ globalThis.crypto.getRandomValues(bytes);
110
+ return BigInt((0, import_viem4.bytesToHex)(bytes));
111
+ }
112
+ async function readPermit2Allowance(publicClient, token, owner) {
113
+ return publicClient.readContract({
114
+ address: token,
115
+ abi: ERC20_ALLOWANCE_ABI,
116
+ functionName: "allowance",
117
+ args: [owner, PERMIT2_ADDRESS]
118
+ });
119
+ }
120
+ async function readErc20Balance(publicClient, token, owner) {
121
+ return publicClient.readContract({
122
+ address: token,
123
+ abi: ERC20_ALLOWANCE_ABI,
124
+ functionName: "balanceOf",
125
+ args: [owner]
126
+ });
127
+ }
128
+ var import_viem4, PERMIT2_ADDRESS, MAX_UINT256, ERC20_ALLOWANCE_ABI;
129
+ var init_permit2 = __esm({
130
+ "src/lib/permit2.ts"() {
131
+ "use strict";
132
+ import_viem4 = require("viem");
133
+ PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
134
+ MAX_UINT256 = 2n ** 256n - 1n;
135
+ ERC20_ALLOWANCE_ABI = [
136
+ {
137
+ type: "function",
138
+ name: "allowance",
139
+ stateMutability: "view",
140
+ inputs: [
141
+ { name: "owner", type: "address" },
142
+ { name: "spender", type: "address" }
143
+ ],
144
+ outputs: [{ name: "", type: "uint256" }]
145
+ },
146
+ {
147
+ type: "function",
148
+ name: "approve",
149
+ stateMutability: "nonpayable",
150
+ inputs: [
151
+ { name: "spender", type: "address" },
152
+ { name: "amount", type: "uint256" }
153
+ ],
154
+ outputs: [{ name: "", type: "bool" }]
155
+ },
156
+ {
157
+ type: "function",
158
+ name: "balanceOf",
159
+ stateMutability: "view",
160
+ inputs: [{ name: "account", type: "address" }],
161
+ outputs: [{ name: "", type: "uint256" }]
162
+ }
163
+ ];
164
+ }
165
+ });
166
+
167
+ // src/agents/surfliquid/surfliquid.constants.ts
168
+ var SURFLIQUID_CHAIN_ID, SURFLIQUID_CHAIN_NAME, SURFLIQUID_USDC_ADDRESS, SURFLIQUID_USDC_DECIMALS, SURFLIQUID_MIN_DEPOSIT, SURFLIQUID_APPROVAL_TARGET, SURFLIQUID_SUPPORTED_ASSETS, SURFLIQUID_ACTION_MAP;
169
+ var init_surfliquid_constants = __esm({
170
+ "src/agents/surfliquid/surfliquid.constants.ts"() {
171
+ "use strict";
172
+ SURFLIQUID_CHAIN_ID = 8453;
173
+ SURFLIQUID_CHAIN_NAME = "BASE";
174
+ SURFLIQUID_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
175
+ SURFLIQUID_USDC_DECIMALS = 6;
176
+ SURFLIQUID_MIN_DEPOSIT = "0";
177
+ SURFLIQUID_APPROVAL_TARGET = 100000000000n;
178
+ SURFLIQUID_SUPPORTED_ASSETS = [
179
+ {
180
+ chainId: SURFLIQUID_CHAIN_ID,
181
+ chain: SURFLIQUID_CHAIN_NAME,
182
+ assets: [{ symbol: "USDC", minDepositAmount: SURFLIQUID_MIN_DEPOSIT }]
183
+ }
184
+ ];
185
+ SURFLIQUID_ACTION_MAP = {
186
+ // INITIAL_DEPOSIT is the user's real first deposit, so surface it like any
187
+ // other funding ("Top up"). The mini-app intentionally hides the "Deposit"
188
+ // action (it marks Zyfai's smart-account-creation event), which would
189
+ // otherwise drop a SurfLiquid user's only history row.
190
+ INITIAL_DEPOSIT: "Top up",
191
+ DEPOSIT: "Top up",
192
+ USER_DEPOSIT: "Top up",
193
+ WITHDRAWAL: "Withdraw",
194
+ USER_WITHDRAWAL: "Withdraw",
195
+ REBALANCE: "Rebalance",
196
+ REBALANCE_COMPLETED: "Rebalance",
197
+ CROSS_CHAIN_REBALANCE: "Rebalance",
198
+ MERKL_CLAIM: "Earned"
199
+ };
200
+ }
201
+ });
202
+
203
+ // src/agents/surfliquid/surfliquid.mapper.ts
204
+ function mapMessage(message) {
205
+ const action = SURFLIQUID_ACTION_MAP[message.transactionType];
206
+ if (!action) return null;
207
+ return {
208
+ agent: "surfliquid",
209
+ action,
210
+ date: message.timestamp,
211
+ oldApy: message.apyBefore != null ? String(message.apyBefore) : null,
212
+ newApy: message.apyAfter != null ? String(message.apyAfter) : null,
213
+ transactions: [
214
+ {
215
+ txHashes: [message.txHash],
216
+ chainId: message.chainId,
217
+ tokenSymbol: message.token ?? void 0,
218
+ amount: message.amount != null ? String(message.amount) : void 0
219
+ }
220
+ ],
221
+ rebalanceLog: []
222
+ };
223
+ }
224
+ function mapHistory(result, _chainId) {
225
+ const data = result.messages.map(mapMessage).filter((entry) => entry !== null);
226
+ const hasMore = result.page < result.pages;
227
+ return {
228
+ data,
229
+ hasMore,
230
+ nextCursor: hasMore ? String(result.page + 1) : void 0
231
+ };
232
+ }
233
+ function mapBalances2(vault, chainId, morphoStats) {
234
+ const assets = (vault.assets ?? []).filter(
235
+ (asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId
236
+ );
237
+ const tokens = assets.map((asset) => ({
238
+ chain: SURFLIQUID_CHAIN_NAME,
239
+ chainId: SURFLIQUID_CHAIN_ID,
240
+ asset: "USDC",
241
+ amount: String(asset.currentValueUSD)
242
+ }));
243
+ const positions = assets.map((asset) => {
244
+ const stats = morphoStats?.get(asset.morphoVaultAddress?.toLowerCase() ?? "");
245
+ return {
246
+ chain: SURFLIQUID_CHAIN_NAME,
247
+ protocol: "Morpho",
248
+ // The specific MetaMorpho vault (e.g. "Gauntlet USDC Frontier"), read from
249
+ // Morpho — renders as "Morpho <vault>" alongside zyfai's vault detail.
250
+ pool: stats?.name ?? void 0,
251
+ asset: "USDC",
252
+ amount: String(asset.currentValueUSD),
253
+ apy: asset.currentAPY,
254
+ // TVL + liquidity come straight from Morpho (SurfLiquid's API omits them).
255
+ tvl: stats?.tvlUsd,
256
+ liquidity: stats?.liquidityUsd
257
+ };
258
+ });
259
+ const total = assets.reduce(
260
+ (sum, asset) => sum + asset.currentValueUSD,
261
+ 0
262
+ );
263
+ return {
264
+ smartWallet: vault.userVaultAddress ?? void 0,
265
+ totalBalance: String(total),
266
+ totalBalanceAsset: "usdc",
267
+ tokens,
268
+ positions
269
+ };
270
+ }
271
+ function mapAccountApy(vault, days) {
272
+ const breakdown = vault.apyBreakdown;
273
+ const windowed = breakdown ? breakdown[APY_WINDOW_KEY[days]] : void 0;
274
+ const apy = windowed != null ? windowed : breakdown?.currentAPY ?? 0;
275
+ return {
276
+ walletAddress: vault.walletAddress ?? "",
277
+ weightedApyAfterFee: apy,
278
+ apyByChainAndAsset: { [SURFLIQUID_CHAIN_ID]: { USDC: apy } },
279
+ // Parity with Sail: SurfLiquid's core-sdk exposes no daily net-APY series.
280
+ history: []
281
+ };
282
+ }
283
+ function mapEarnings2(vault, vaultAddress) {
284
+ return {
285
+ smartWallet: vaultAddress,
286
+ lifetimeEarnings: vault.earned?.totalEarningsUSD ?? 0,
287
+ tokens: []
288
+ };
289
+ }
290
+ function mapUserProfile2(vault, address) {
291
+ return {
292
+ address,
293
+ smartWallet: vault.userVaultAddress ?? "",
294
+ chains: [SURFLIQUID_CHAIN_ID],
295
+ // SurfLiquid uses cookie-based session auth — no session-key concept.
296
+ hasActiveSessionKey: false,
297
+ protocols: ["SurfLiquid"]
298
+ };
299
+ }
300
+ var APY_WINDOW_KEY;
301
+ var init_surfliquid_mapper = __esm({
302
+ "src/agents/surfliquid/surfliquid.mapper.ts"() {
303
+ "use strict";
304
+ init_surfliquid_constants();
305
+ APY_WINDOW_KEY = {
306
+ "7D": "apy7d",
307
+ "14D": "apy14d",
308
+ "30D": "apy30d"
309
+ };
310
+ }
311
+ });
312
+
313
+ // src/agents/surfliquid/surfliquid.wallet-adapter.ts
314
+ var import_core_sdk, OwneyWalletAdapter;
315
+ var init_surfliquid_wallet_adapter = __esm({
316
+ "src/agents/surfliquid/surfliquid.wallet-adapter.ts"() {
317
+ "use strict";
318
+ import_core_sdk = require("@surf_liquid/core-sdk");
319
+ OwneyWalletAdapter = class extends import_core_sdk.BaseWalletAdapter {
320
+ constructor(eip1193Provider) {
321
+ super();
322
+ this.eip1193Provider = eip1193Provider;
323
+ }
324
+ eip1193Provider;
325
+ name = "owney";
326
+ get installed() {
327
+ return true;
328
+ }
329
+ resolveProvider() {
330
+ return this.eip1193Provider;
331
+ }
332
+ };
333
+ }
334
+ });
335
+
336
+ // src/agents/surfliquid/surfliquid.morpho.ts
337
+ async function fetchMorphoVaultStats(vaultAddress, chainId) {
338
+ try {
339
+ const res = await fetch(MORPHO_API_URL, {
340
+ method: "POST",
341
+ headers: { "Content-Type": "application/json" },
342
+ body: JSON.stringify({
343
+ query: VAULT_STATS_QUERY,
344
+ variables: { address: vaultAddress, chainId }
345
+ })
346
+ });
347
+ if (!res.ok) return null;
348
+ const json = await res.json();
349
+ if (json.errors) return null;
350
+ const vault = json.data?.vaultByAddress;
351
+ const tvlUsd = vault?.state?.totalAssetsUsd;
352
+ const liquidityUsd = vault?.liquidity?.usd;
353
+ if (typeof tvlUsd !== "number" || typeof liquidityUsd !== "number") {
354
+ return null;
355
+ }
356
+ const name = typeof vault?.name === "string" ? vault.name : null;
357
+ return { name, tvlUsd, liquidityUsd };
358
+ } catch (error) {
359
+ console.warn("surfliquid: Morpho vault stats fetch failed", {
360
+ vaultAddress,
361
+ chainId,
362
+ error
363
+ });
364
+ return null;
365
+ }
366
+ }
367
+ async function fetchMorphoStatsByVault(vaultAddresses, chainId) {
368
+ const unique = [...new Set(vaultAddresses.map((a) => a.toLowerCase()))];
369
+ const entries = await Promise.all(
370
+ unique.map(
371
+ async (address) => [address, await fetchMorphoVaultStats(address, chainId)]
372
+ )
373
+ );
374
+ const byVault = /* @__PURE__ */ new Map();
375
+ for (const [address, stats] of entries) {
376
+ if (stats) byVault.set(address, stats);
377
+ }
378
+ return byVault;
379
+ }
380
+ var MORPHO_API_URL, VAULT_STATS_QUERY;
381
+ var init_surfliquid_morpho = __esm({
382
+ "src/agents/surfliquid/surfliquid.morpho.ts"() {
383
+ "use strict";
384
+ MORPHO_API_URL = "https://api.morpho.org/graphql";
385
+ VAULT_STATS_QUERY = `
386
+ query VaultStats($address: String!, $chainId: Int!) {
387
+ vaultByAddress(address: $address, chainId: $chainId) {
388
+ name
389
+ state { totalAssetsUsd }
390
+ liquidity { usd }
391
+ }
392
+ }
393
+ `;
394
+ }
395
+ });
396
+
397
+ // src/agents/surfliquid/surfliquid.agent.ts
398
+ var surfliquid_agent_exports = {};
399
+ __export(surfliquid_agent_exports, {
400
+ SurfLiquidAgent: () => SurfLiquidAgent
401
+ });
402
+ var import_core_sdk2, import_viem5, import_chains2, APY_WINDOW_KEY2, SurfLiquidAgent;
403
+ var init_surfliquid_agent = __esm({
404
+ "src/agents/surfliquid/surfliquid.agent.ts"() {
405
+ "use strict";
406
+ import_core_sdk2 = require("@surf_liquid/core-sdk");
407
+ import_viem5 = require("viem");
408
+ import_chains2 = require("viem/chains");
409
+ init_errors();
410
+ init_surfliquid_constants();
411
+ init_permit2();
412
+ init_surfliquid_mapper();
413
+ init_surfliquid_wallet_adapter();
414
+ init_surfliquid_morpho();
415
+ APY_WINDOW_KEY2 = {
416
+ "7D": "apy7d",
417
+ "14D": "apy14d",
418
+ "30D": "apy30d"
419
+ };
420
+ SurfLiquidAgent = class {
421
+ constructor(config) {
422
+ this.config = config;
423
+ }
424
+ config;
425
+ id = "surfliquid";
426
+ supportedChainIds = [SURFLIQUID_CHAIN_ID];
427
+ supportedAssets = SURFLIQUID_SUPPORTED_ASSETS;
428
+ surf = null;
429
+ connectedAddress = null;
430
+ // In-flight connect+auth, shared by concurrent callers to dedupe SIWE.
431
+ connectPromise = null;
432
+ ensureSurf() {
433
+ if (this.surf) return this.surf;
434
+ this.surf = import_core_sdk2.SurfClient.create({
435
+ projectName: this.config.projectName,
436
+ appId: this.config.appId,
437
+ environment: "mainnet",
438
+ chainId: SURFLIQUID_CHAIN_ID,
439
+ apiBaseUrl: this.config.apiBaseUrl,
440
+ rpcUrl: this.config.rpcUrl,
441
+ // Let the SDK auto-approve USDC to the vault before depositing.
442
+ autoApprove: true
443
+ });
444
+ return this.surf;
445
+ }
446
+ /**
447
+ * Connects + authenticates the SurfClient against owney's provider. Skips the
448
+ * SIWE prompt when already authenticated for this wallet (cookie session).
449
+ */
450
+ async connect(state) {
451
+ const surf = this.ensureSurf();
452
+ const sameWallet = this.connectedAddress?.toLowerCase() === state.walletAddress.toLowerCase();
453
+ if (sameWallet && surf.getAuthState().authenticated) {
454
+ return surf;
455
+ }
456
+ if (this.connectPromise) return this.connectPromise;
457
+ this.connectPromise = (async () => {
458
+ surf.registerWalletAdapter(
459
+ "owney",
460
+ new OwneyWalletAdapter(state.provider)
461
+ );
462
+ await surf.connectWallet("owney");
463
+ await surf.authenticate();
464
+ this.connectedAddress = state.walletAddress;
465
+ return surf;
466
+ })();
467
+ try {
468
+ return await this.connectPromise;
469
+ } finally {
470
+ this.connectPromise = null;
471
+ }
472
+ }
473
+ /**
474
+ * Returns the user's vault address, deploying one on first use. SurfLiquid's
475
+ * deposit requires the vault to exist, so this must run before depositing.
476
+ * Existing vault → returned as-is; otherwise `deployVault()` is sent
477
+ * (user-pays) and the freshly deployed address is returned.
478
+ */
479
+ async resolveVaultAddress(surf, owner) {
480
+ const vault = await surf.getVault(owner).catch(() => null);
481
+ if (vault?.exists && vault.userVaultAddress) {
482
+ return vault.userVaultAddress;
483
+ }
484
+ const { vaultAddress } = await surf.deployVault();
485
+ return vaultAddress;
486
+ }
487
+ /**
488
+ * Grants the user's vault a standing USDC allowance so SurfLiquid's
489
+ * `autoApprove` has nothing to do — see {@link SURFLIQUID_APPROVAL_TARGET}
490
+ * for why one prompt per deposit is worth removing.
491
+ *
492
+ * Fails OPEN on a read error: if the allowance can't be checked we simply
493
+ * return and let SurfLiquid's own per-deposit approve run, because a flaky
494
+ * RPC must never block a deposit. A failed or rejected APPROVE does
495
+ * propagate — the user declined a transaction, and letting SurfLiquid
496
+ * immediately ask again for a smaller one is exactly the double prompt this
497
+ * removes.
498
+ */
499
+ async ensureVaultAllowance(state, vaultAddress, amount) {
500
+ const spender = vaultAddress;
501
+ const transport = (0, import_viem5.custom)(state.provider);
502
+ const publicClient = (0, import_viem5.createPublicClient)({ chain: import_chains2.base, transport });
503
+ let allowance;
504
+ try {
505
+ allowance = await publicClient.readContract({
506
+ address: SURFLIQUID_USDC_ADDRESS,
507
+ abi: ERC20_ALLOWANCE_ABI,
508
+ functionName: "allowance",
509
+ args: [state.walletAddress, spender]
510
+ });
511
+ } catch (error) {
512
+ console.warn(
513
+ "[owney-sdk] SurfLiquid allowance pre-check failed; deferring to its per-deposit approve:",
514
+ error instanceof Error ? error.message : String(error)
515
+ );
516
+ return;
517
+ }
518
+ if (allowance >= amount) return;
519
+ const target = amount > SURFLIQUID_APPROVAL_TARGET ? amount : SURFLIQUID_APPROVAL_TARGET;
520
+ const wallet = (0, import_viem5.createWalletClient)({
521
+ account: state.walletAddress,
522
+ chain: import_chains2.base,
523
+ transport
524
+ });
525
+ const hash = await wallet.writeContract({
526
+ address: SURFLIQUID_USDC_ADDRESS,
527
+ abi: ERC20_ALLOWANCE_ABI,
528
+ functionName: "approve",
529
+ args: [spender, target],
530
+ account: state.walletAddress,
531
+ chain: import_chains2.base
532
+ });
533
+ const receipt = await publicClient.waitForTransactionReceipt({
534
+ hash,
535
+ confirmations: 1
536
+ });
537
+ if (receipt.status !== "success") {
538
+ throw new Error(`SurfLiquid USDC approval reverted (tx ${hash})`);
539
+ }
540
+ }
541
+ // --- IAgent: lifecycle ---
542
+ async disconnect() {
543
+ if (this.surf) {
544
+ await this.surf.disconnectWallet();
545
+ }
546
+ this.connectedAddress = null;
547
+ }
548
+ async activateAgent(state) {
549
+ await this.connect(state);
550
+ }
551
+ // --- IAgent: funds (user-pays; asset is always USDC, callback ignored) ---
552
+ async deposit(state, _chainId, amount, _asset, _depositCallback) {
553
+ const surf = await this.connect(state);
554
+ const human = (0, import_viem5.formatUnits)(BigInt(amount), SURFLIQUID_USDC_DECIMALS);
555
+ const smartWallet = await this.resolveVaultAddress(
556
+ surf,
557
+ state.walletAddress
558
+ );
559
+ await this.ensureVaultAllowance(state, smartWallet, BigInt(amount));
560
+ const tx = await surf.deposit({
561
+ asset: SURFLIQUID_USDC_ADDRESS,
562
+ amount: human
563
+ });
564
+ await tx.wait();
565
+ return { txHash: tx.hash, smartWallet, amount };
566
+ }
567
+ async withdraw(state, _chainId, _token, amount) {
568
+ const surf = await this.connect(state);
569
+ const human = amount != null ? (0, import_viem5.formatUnits)(BigInt(amount), SURFLIQUID_USDC_DECIMALS) : void 0;
570
+ try {
571
+ const tx = await surf.withdraw({
572
+ asset: SURFLIQUID_USDC_ADDRESS,
573
+ amount: human
574
+ });
575
+ await tx.wait();
576
+ return {
577
+ txHash: tx.hash,
578
+ type: amount != null ? "partial" : "full",
579
+ amount: amount ?? "0"
580
+ };
581
+ } catch (error) {
582
+ throw new OwneyError(
583
+ "WITHDRAW_FAILED",
584
+ error instanceof Error ? error.message : "SurfLiquid withdraw failed.",
585
+ { chainId: SURFLIQUID_CHAIN_ID, amount },
586
+ this.id
587
+ );
588
+ }
589
+ }
590
+ // --- IAgent: portfolio reads ---
591
+ async getBalances(state, chainId) {
592
+ const surf = await this.connect(state);
593
+ const vault = await surf.getVault(state.walletAddress);
594
+ const morphoVaultAddresses = (vault.assets ?? []).filter((asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId).map((asset) => asset.morphoVaultAddress).filter((address) => Boolean(address));
595
+ const morphoStats = await fetchMorphoStatsByVault(
596
+ morphoVaultAddresses,
597
+ chainId
598
+ );
599
+ return mapBalances2(vault, chainId, morphoStats);
600
+ }
601
+ async getEarnings(state) {
602
+ const surf = await this.connect(state);
603
+ const vault = await surf.getVault(state.walletAddress);
604
+ return mapEarnings2(vault, vault.userVaultAddress ?? state.walletAddress);
605
+ }
606
+ async getAccountApy(state, _chainId, days, _tokenSymbol) {
607
+ const surf = await this.connect(state);
608
+ const vault = await surf.getVault(state.walletAddress);
609
+ return mapAccountApy(vault, days);
610
+ }
611
+ async getHistory(state, chainId, options) {
612
+ const surf = await this.connect(state);
613
+ const page = options?.cursor ? Number(options.cursor) : 1;
614
+ const limit = options?.limit ?? 10;
615
+ const result = await surf.getAgentMessages(
616
+ state.walletAddress,
617
+ page,
618
+ limit,
619
+ options?.fromDate,
620
+ options?.toDate
621
+ );
622
+ return mapHistory(result, chainId);
623
+ }
624
+ async getUserProfile(state) {
625
+ const surf = await this.connect(state);
626
+ const vault = await surf.getVault(state.walletAddress);
627
+ return mapUserProfile2(vault, state.walletAddress);
628
+ }
629
+ // --- IAgent: discovery (no wallet) ---
630
+ async getAgentApy(days, _options) {
631
+ const surf = this.ensureSurf();
632
+ const assets = await surf.getSupportedAssets(SURFLIQUID_CHAIN_ID);
633
+ const usdc = assets.find((asset) => asset.assetSymbol === "USDC");
634
+ if (!usdc) return { averageApy: 0 };
635
+ const windowed = usdc[APY_WINDOW_KEY2[days]];
636
+ return { averageApy: windowed != null ? windowed : usdc.currentAPY };
637
+ }
638
+ };
639
+ }
640
+ });
641
+
20
642
  // src/index.ts
21
643
  var index_exports = {};
22
644
  __export(index_exports, {
@@ -89,56 +711,8 @@ var SupportedAssets = [
89
711
  }
90
712
  ];
91
713
 
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
714
  // src/lib/utils.ts
715
+ init_errors();
142
716
  var isValidChainId = (chainId) => {
143
717
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
144
718
  throw new OwneyError(
@@ -212,46 +786,46 @@ function resolveChainId(chain) {
212
786
  function mapBalances(raw, _chainId, smartWallet) {
213
787
  const portfolio = raw.portfolio;
214
788
  const portfolioByChain = portfolio.portfolioByChain ?? {};
789
+ const rawPositions = portfolio.positions ?? [];
790
+ const detailByKey = /* @__PURE__ */ new Map();
791
+ for (const p of rawPositions) {
792
+ const cid = resolveChainId(p.chain ?? "");
793
+ const symbol = (p.token_symbol ?? "").toUpperCase();
794
+ if (Number.isFinite(cid) && symbol) detailByKey.set(`${cid}:${symbol}`, p);
795
+ }
215
796
  let totalBalance = 0;
216
797
  const tokens = [];
798
+ const positions = [];
217
799
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
218
800
  const positionChainId = Number(chainIdKey);
801
+ const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
219
802
  for (const [symbol, { balance, decimals }] of Object.entries(
220
803
  tokensBySymbol ?? {}
221
804
  )) {
222
- const amount = hexToDecimal(balance || "0", decimals);
805
+ const amount = hexToDecimal(balance || "0", tokenDecimals(symbol, decimals));
223
806
  totalBalance += amount;
224
807
  tokens.push({
225
- chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
808
+ chain: chainName,
226
809
  chainId: positionChainId,
227
810
  asset: symbol,
228
811
  amount: String(amount)
229
812
  });
813
+ const detail = detailByKey.get(`${positionChainId}:${symbol.toUpperCase()}`);
814
+ positions.push({
815
+ chain: chainName,
816
+ protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
817
+ protocolId: detail?.protocol_id,
818
+ pool: detail?.pool,
819
+ asset: symbol,
820
+ amount: String(amount),
821
+ apy: detail?.pool_apy,
822
+ tvl: detail?.pool_tvl,
823
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
824
+ // structural read keeps it undefined today and auto-populates later.
825
+ liquidity: detail?.liquidity
826
+ });
230
827
  }
231
828
  }
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
829
  return {
256
830
  smartWallet,
257
831
  totalBalance: String(totalBalance),
@@ -528,6 +1102,9 @@ function decodeHistoryCursor(token) {
528
1102
  return parsed;
529
1103
  }
530
1104
 
1105
+ // src/agents/zyfai/zyfai.agent.ts
1106
+ init_errors();
1107
+
531
1108
  // src/agents/zyfai/zyfai.auth-cache.ts
532
1109
  var KEY_PREFIX = "owney.zyfai.session";
533
1110
  var storage = () => {
@@ -1515,7 +2092,11 @@ var ZyfaiAgent = class {
1515
2092
  }
1516
2093
  };
1517
2094
 
2095
+ // src/client.ts
2096
+ init_errors();
2097
+
1518
2098
  // src/lib/routing-api.ts
2099
+ init_errors();
1519
2100
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1520
2101
  async function fetchOrgAgentConfig(apiKey) {
1521
2102
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
@@ -1580,6 +2161,7 @@ async function fetchAgentKeys(apiKey) {
1580
2161
  }
1581
2162
 
1582
2163
  // src/lib/health-report.ts
2164
+ init_errors();
1583
2165
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1584
2166
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1585
2167
  try {
@@ -1729,8 +2311,11 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1729
2311
  }
1730
2312
 
1731
2313
  // src/client.ts
1732
- var import_viem5 = require("viem");
1733
- var import_chains2 = require("viem/chains");
2314
+ var import_viem6 = require("viem");
2315
+ var import_chains3 = require("viem/chains");
2316
+
2317
+ // src/lib/sponsored-deposit.ts
2318
+ init_errors();
1734
2319
 
1735
2320
  // src/lib/transfer-auth.ts
1736
2321
  var import_viem3 = require("viem");
@@ -1769,12 +2354,13 @@ function randomAuthNonce() {
1769
2354
  }
1770
2355
 
1771
2356
  // src/lib/sponsor-client.ts
2357
+ init_errors();
1772
2358
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1773
2359
  async function postSponsorTransferAuth(input) {
1774
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2360
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1775
2361
  let res;
1776
2362
  try {
1777
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
2363
+ res = await fetch(`${base4}/api/v1/sponsor/erc20-transfer-auth`, {
1778
2364
  method: "POST",
1779
2365
  headers: {
1780
2366
  "content-type": "application/json",
@@ -1811,10 +2397,10 @@ async function postSponsorTransferAuth(input) {
1811
2397
  return parsed.data;
1812
2398
  }
1813
2399
  async function postSponsorPermit2Transfer(input) {
1814
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2400
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1815
2401
  let res;
1816
2402
  try {
1817
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2403
+ res = await fetch(`${base4}/api/v1/sponsor/permit2-transfer`, {
1818
2404
  method: "POST",
1819
2405
  headers: {
1820
2406
  "content-type": "application/json",
@@ -1849,11 +2435,11 @@ async function postSponsorPermit2Transfer(input) {
1849
2435
  return parsed.data;
1850
2436
  }
1851
2437
  async function getSponsorRelayerAddress(input) {
1852
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2438
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1853
2439
  let res;
1854
2440
  try {
1855
2441
  res = await fetch(
1856
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2442
+ `${base4}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
1857
2443
  {
1858
2444
  headers: { "x-owney-api-key": input.apiKey }
1859
2445
  }
@@ -1885,85 +2471,8 @@ async function getSponsorRelayerAddress(input) {
1885
2471
  return parsed.data.relayer;
1886
2472
  }
1887
2473
 
1888
- // src/lib/permit2.ts
1889
- var import_viem4 = require("viem");
1890
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
1891
- var MAX_UINT256 = 2n ** 256n - 1n;
1892
- var ERC20_ALLOWANCE_ABI = [
1893
- {
1894
- type: "function",
1895
- name: "allowance",
1896
- stateMutability: "view",
1897
- inputs: [
1898
- { name: "owner", type: "address" },
1899
- { name: "spender", type: "address" }
1900
- ],
1901
- outputs: [{ name: "", type: "uint256" }]
1902
- },
1903
- {
1904
- type: "function",
1905
- name: "approve",
1906
- stateMutability: "nonpayable",
1907
- inputs: [
1908
- { name: "spender", type: "address" },
1909
- { name: "amount", type: "uint256" }
1910
- ],
1911
- outputs: [{ name: "", type: "bool" }]
1912
- },
1913
- {
1914
- type: "function",
1915
- name: "balanceOf",
1916
- stateMutability: "view",
1917
- inputs: [{ name: "account", type: "address" }],
1918
- outputs: [{ name: "", type: "uint256" }]
1919
- }
1920
- ];
1921
- function buildPermitTransferFromTypedData(input) {
1922
- return {
1923
- domain: {
1924
- name: "Permit2",
1925
- chainId: input.chainId,
1926
- verifyingContract: PERMIT2_ADDRESS
1927
- },
1928
- types: {
1929
- PermitTransferFrom: [
1930
- { name: "permitted", type: "TokenPermissions" },
1931
- { name: "spender", type: "address" },
1932
- { name: "nonce", type: "uint256" },
1933
- { name: "deadline", type: "uint256" }
1934
- ],
1935
- TokenPermissions: [
1936
- { name: "token", type: "address" },
1937
- { name: "amount", type: "uint256" }
1938
- ]
1939
- },
1940
- primaryType: "PermitTransferFrom",
1941
- message: input.message
1942
- };
1943
- }
1944
- function randomPermit2Nonce() {
1945
- const bytes = new Uint8Array(32);
1946
- globalThis.crypto.getRandomValues(bytes);
1947
- return BigInt((0, import_viem4.bytesToHex)(bytes));
1948
- }
1949
- async function readPermit2Allowance(publicClient, token, owner) {
1950
- return publicClient.readContract({
1951
- address: token,
1952
- abi: ERC20_ALLOWANCE_ABI,
1953
- functionName: "allowance",
1954
- args: [owner, PERMIT2_ADDRESS]
1955
- });
1956
- }
1957
- async function readErc20Balance(publicClient, token, owner) {
1958
- return publicClient.readContract({
1959
- address: token,
1960
- abi: ERC20_ALLOWANCE_ABI,
1961
- functionName: "balanceOf",
1962
- args: [owner]
1963
- });
1964
- }
1965
-
1966
2474
  // src/lib/sponsored-deposit.ts
2475
+ init_permit2();
1967
2476
  var AUTH_WINDOW_SECONDS = 15 * 60;
1968
2477
  function makeSponsoredDepositCallback(deps) {
1969
2478
  const post = deps.httpPost ?? postSponsorTransferAuth;
@@ -2037,6 +2546,8 @@ function makeSponsoredDepositCallback(deps) {
2037
2546
  }
2038
2547
 
2039
2548
  // src/lib/sponsored-weth-deposit.ts
2549
+ init_errors();
2550
+ init_permit2();
2040
2551
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2041
2552
  function makeSponsoredWethCallback(deps) {
2042
2553
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -2115,6 +2626,7 @@ function makeSponsoredWethCallback(deps) {
2115
2626
  }
2116
2627
 
2117
2628
  // src/client.ts
2629
+ init_permit2();
2118
2630
  function encodeMultiAgentCursor(map) {
2119
2631
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
2120
2632
  }
@@ -2141,10 +2653,11 @@ var SPONSORED_USDC_BY_CHAIN = {
2141
2653
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2142
2654
  };
2143
2655
  var VIEM_CHAIN2 = {
2144
- 8453: import_chains2.base,
2145
- 42161: import_chains2.arbitrum,
2146
- 1: import_chains2.mainnet
2656
+ 8453: import_chains3.base,
2657
+ 42161: import_chains3.arbitrum,
2658
+ 1: import_chains3.mainnet
2147
2659
  };
2660
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2148
2661
  var SPONSORED_WETH_BY_CHAIN = {
2149
2662
  8453: "0x4200000000000000000000000000000000000006",
2150
2663
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2173,6 +2686,7 @@ var OwneySDK = class {
2173
2686
  zyfaiRpcUrls;
2174
2687
  routingApiBaseUrl;
2175
2688
  referralSource;
2689
+ surfliquidApiBaseUrl;
2176
2690
  cachedSponsoredCallback = null;
2177
2691
  cachedWethSponsoredCallback = null;
2178
2692
  initializingAgentsPromise = null;
@@ -2182,6 +2696,7 @@ var OwneySDK = class {
2182
2696
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2183
2697
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2184
2698
  this.referralSource = config.referralSource;
2699
+ this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2185
2700
  }
2186
2701
  /**
2187
2702
  * Establish connection state for all subsequent SDK calls.
@@ -2279,14 +2794,14 @@ var OwneySDK = class {
2279
2794
  // Casts work around viem's chain-narrowed Client vs the generic
2280
2795
  // PublicClient/WalletClient param types — structurally identical at
2281
2796
  // runtime, but the two share a name TS treats as unrelated.
2282
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2797
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2283
2798
  chain: VIEM_CHAIN2[cid],
2284
- transport: (0, import_viem5.custom)(provider)
2799
+ transport: (0, import_viem6.custom)(provider)
2285
2800
  }),
2286
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2801
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2287
2802
  account: owner,
2288
2803
  chain: VIEM_CHAIN2[cid],
2289
- transport: (0, import_viem5.custom)(provider)
2804
+ transport: (0, import_viem6.custom)(provider)
2290
2805
  })
2291
2806
  });
2292
2807
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2312,14 +2827,14 @@ var OwneySDK = class {
2312
2827
  // Casts work around viem's chain-narrowed Client vs the generic
2313
2828
  // PublicClient/WalletClient param types — structurally identical at
2314
2829
  // runtime, but the two share a name TS treats as unrelated.
2315
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2830
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2316
2831
  chain: VIEM_CHAIN2[cid],
2317
- transport: (0, import_viem5.custom)(provider)
2832
+ transport: (0, import_viem6.custom)(provider)
2318
2833
  }),
2319
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2834
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2320
2835
  account: owner,
2321
2836
  chain: VIEM_CHAIN2[cid],
2322
- transport: (0, import_viem5.custom)(provider)
2837
+ transport: (0, import_viem6.custom)(provider)
2323
2838
  })
2324
2839
  });
2325
2840
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2389,8 +2904,8 @@ var OwneySDK = class {
2389
2904
  this.initializingAgentsPromise = (async () => {
2390
2905
  const agentKeys = await fetchAgentKeys(this.apiKey);
2391
2906
  this.disabledAgents.clear();
2392
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
2393
- const agent = this.createAgent(agent_type, key2);
2907
+ for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2908
+ const agent = await this.createAgent(agent_type, key2, project_name);
2394
2909
  if (!agent) continue;
2395
2910
  this.agents.set(agent_type, agent);
2396
2911
  if (is_enabled === false) {
@@ -2411,10 +2926,22 @@ var OwneySDK = class {
2411
2926
  this.initializingAgentsPromise = null;
2412
2927
  }
2413
2928
  }
2414
- createAgent(agentId, key2) {
2929
+ async createAgent(agentId, key2, projectName) {
2415
2930
  if (agentId === "zyfai") {
2416
2931
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2417
2932
  }
2933
+ if (agentId === "surfliquid") {
2934
+ if (!projectName) return null;
2935
+ const { SurfLiquidAgent: SurfLiquidAgent2 } = await Promise.resolve().then(() => (init_surfliquid_agent(), surfliquid_agent_exports));
2936
+ return new SurfLiquidAgent2({
2937
+ projectName,
2938
+ appId: key2,
2939
+ // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
2940
+ // which WebKit (iOS) discards when it is third-party. See
2941
+ // OwneySDKConfig.surfliquidApiBaseUrl.
2942
+ apiBaseUrl: this.surfliquidApiBaseUrl
2943
+ });
2944
+ }
2418
2945
  return null;
2419
2946
  }
2420
2947
  // --- Account lifecycle ---
@@ -2569,18 +3096,30 @@ var OwneySDK = class {
2569
3096
  );
2570
3097
  }
2571
3098
  const agentResults = {};
3099
+ const agentErrors = {};
2572
3100
  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
3101
+ try {
3102
+ agentResults[agent.id] = await this.depositWithFallback(
3103
+ agent,
3104
+ state,
3105
+ chainId,
3106
+ agentAmount,
3107
+ asset,
3108
+ effectiveCallback,
3109
+ depositCallback
3110
+ );
3111
+ } catch (error) {
3112
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
3113
+ }
3114
+ }
3115
+ if (Object.keys(agentResults).length === 0) {
3116
+ throw new OwneyError(
3117
+ "DEPOSIT_ALL_FAILED",
3118
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
3119
+ { agentErrors }
2581
3120
  );
2582
3121
  }
2583
- return { agentResults };
3122
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
2584
3123
  }
2585
3124
  /**
2586
3125
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -2737,6 +3276,34 @@ var OwneySDK = class {
2737
3276
  }
2738
3277
  return eligible;
2739
3278
  }
3279
+ /**
3280
+ * Agent ids the routing API provisioned for this org that support the given
3281
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3282
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
3283
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
3284
+ * wallet), so the UI can resolve which agent to use before the user connects.
3285
+ *
3286
+ * This is the source of truth for agent availability: an agent appears here
3287
+ * iff the routing API returned its key. No per-app feature flags.
3288
+ */
3289
+ async getEligibleAgentIds(chainId, asset) {
3290
+ try {
3291
+ await this.ensureAgentsInitialized();
3292
+ } catch (error) {
3293
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3294
+ return [];
3295
+ }
3296
+ throw error;
3297
+ }
3298
+ return [...this.agents.values()].filter((agent) => {
3299
+ const chainAssets = agent.supportedAssets.find(
3300
+ (sa) => sa.chainId === chainId
3301
+ );
3302
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3303
+ }).map((agent) => agent.id).sort(
3304
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3305
+ );
3306
+ }
2740
3307
  // --- Fund operations ---
2741
3308
  /**
2742
3309
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -2772,9 +3339,17 @@ var OwneySDK = class {
2772
3339
  }
2773
3340
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
2774
3341
  if (!amount) {
3342
+ const aggregated2 = await this.getBalances();
3343
+ const funded = projectAgentBalancesForAsset(
3344
+ eligibleAgents,
3345
+ aggregated2.agentBalances,
3346
+ chainId,
3347
+ asset,
3348
+ assetInfo.decimals
3349
+ ).filter((b) => b.balance > 0n);
2775
3350
  const results2 = {};
2776
3351
  const agentErrors2 = {};
2777
- for (const agent of eligibleAgents) {
3352
+ for (const { agent } of funded) {
2778
3353
  try {
2779
3354
  results2[agent.id] = await agent.withdraw(state, chainId, token);
2780
3355
  } catch (err) {
@@ -2791,9 +3366,10 @@ var OwneySDK = class {
2791
3366
  { asset, agentErrors: agentErrors2 }
2792
3367
  );
2793
3368
  }
3369
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
2794
3370
  return {
2795
3371
  agentResult: results2,
2796
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3372
+ totalWithdrawn: totalWithdrawn.toString()
2797
3373
  };
2798
3374
  }
2799
3375
  const requested = BigInt(amount);
@@ -3163,10 +3739,10 @@ var OwneySDK = class {
3163
3739
  );
3164
3740
  }
3165
3741
  const provider = this.requireConnectedProvider();
3166
- const wallet = (0, import_viem5.createWalletClient)({
3742
+ const wallet = (0, import_viem6.createWalletClient)({
3167
3743
  account: state.walletAddress,
3168
3744
  chain: VIEM_CHAIN2[chainId],
3169
- transport: (0, import_viem5.custom)(provider)
3745
+ transport: (0, import_viem6.custom)(provider)
3170
3746
  });
3171
3747
  const hash = await wallet.writeContract({
3172
3748
  address: token,
@@ -3176,9 +3752,9 @@ var OwneySDK = class {
3176
3752
  account: state.walletAddress,
3177
3753
  chain: VIEM_CHAIN2[chainId]
3178
3754
  });
3179
- const publicClient = (0, import_viem5.createPublicClient)({
3755
+ const publicClient = (0, import_viem6.createPublicClient)({
3180
3756
  chain: VIEM_CHAIN2[chainId],
3181
- transport: (0, import_viem5.custom)(provider)
3757
+ transport: (0, import_viem6.custom)(provider)
3182
3758
  });
3183
3759
  const receipt = await publicClient.waitForTransactionReceipt({
3184
3760
  hash,
@@ -3286,8 +3862,11 @@ var OwneySDK = class {
3286
3862
  }
3287
3863
  };
3288
3864
 
3865
+ // src/index.ts
3866
+ init_errors();
3867
+
3289
3868
  // src/agents/zyfai/zyfai.siwx.ts
3290
- var import_viem6 = require("viem");
3869
+ var import_viem7 = require("viem");
3291
3870
  var import_siwe = require("siwe");
3292
3871
  var import_sdk2 = require("@zyfai/sdk");
3293
3872
 
@@ -3414,7 +3993,7 @@ function buildSIWXConfig(deps) {
3414
3993
  issuedAt,
3415
3994
  toString() {
3416
3995
  return new import_siwe.SiweMessage({
3417
- address: (0, import_viem6.getAddress)(accountAddress),
3996
+ address: (0, import_viem7.getAddress)(accountAddress),
3418
3997
  chainId: numericChainId(chainId),
3419
3998
  domain,
3420
3999
  uri,