@owney/sdk 0.7.11 → 0.7.12-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,6 +3,14 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res, err) => function __init() {
7
+ if (err) throw err[0];
8
+ try {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ } catch (e) {
11
+ throw err = [e], e;
12
+ }
13
+ };
6
14
  var __export = (target, all) => {
7
15
  for (var name in all)
8
16
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,6 +25,645 @@ 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 queryMorpho(query, 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,
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
+ return json.data ?? null;
351
+ } catch (error) {
352
+ console.warn("surfliquid: Morpho vault stats fetch failed", {
353
+ vaultAddress,
354
+ chainId,
355
+ error
356
+ });
357
+ return null;
358
+ }
359
+ }
360
+ function toStats(name, tvlUsd, liquidityUsd) {
361
+ if (typeof tvlUsd !== "number" || typeof liquidityUsd !== "number") {
362
+ return null;
363
+ }
364
+ return {
365
+ name: typeof name === "string" ? name : null,
366
+ tvlUsd,
367
+ liquidityUsd
368
+ };
369
+ }
370
+ async function fetchMorphoVaultStats(vaultAddress, chainId) {
371
+ const v1 = await queryMorpho(VAULT_STATS_QUERY, vaultAddress, chainId);
372
+ const vault = v1?.vaultByAddress;
373
+ const v1Stats = toStats(
374
+ vault?.name,
375
+ vault?.state?.totalAssetsUsd,
376
+ vault?.liquidity?.usd
377
+ );
378
+ if (v1Stats) return v1Stats;
379
+ const v2 = await queryMorpho(VAULT_V2_STATS_QUERY, vaultAddress, chainId);
380
+ const vaultV2 = v2?.vaultV2ByAddress;
381
+ return toStats(vaultV2?.name, vaultV2?.totalAssetsUsd, vaultV2?.liquidityUsd);
382
+ }
383
+ async function fetchMorphoStatsByVault(vaultAddresses, chainId) {
384
+ const unique = [...new Set(vaultAddresses.map((a) => a.toLowerCase()))];
385
+ const entries = await Promise.all(
386
+ unique.map(
387
+ async (address) => [address, await fetchMorphoVaultStats(address, chainId)]
388
+ )
389
+ );
390
+ const byVault = /* @__PURE__ */ new Map();
391
+ for (const [address, stats] of entries) {
392
+ if (stats) byVault.set(address, stats);
393
+ }
394
+ return byVault;
395
+ }
396
+ var MORPHO_API_URL, VAULT_STATS_QUERY, VAULT_V2_STATS_QUERY;
397
+ var init_surfliquid_morpho = __esm({
398
+ "src/agents/surfliquid/surfliquid.morpho.ts"() {
399
+ "use strict";
400
+ MORPHO_API_URL = "https://api.morpho.org/graphql";
401
+ VAULT_STATS_QUERY = `
402
+ query VaultStats($address: String!, $chainId: Int!) {
403
+ vaultByAddress(address: $address, chainId: $chainId) {
404
+ name
405
+ state { totalAssetsUsd }
406
+ liquidity { usd }
407
+ }
408
+ }
409
+ `;
410
+ VAULT_V2_STATS_QUERY = `
411
+ query VaultV2Stats($address: String!, $chainId: Int!) {
412
+ vaultV2ByAddress(address: $address, chainId: $chainId) {
413
+ name
414
+ totalAssetsUsd
415
+ liquidityUsd
416
+ }
417
+ }
418
+ `;
419
+ }
420
+ });
421
+
422
+ // src/agents/surfliquid/surfliquid.agent.ts
423
+ var surfliquid_agent_exports = {};
424
+ __export(surfliquid_agent_exports, {
425
+ SurfLiquidAgent: () => SurfLiquidAgent
426
+ });
427
+ var import_core_sdk2, import_viem6, import_chains2, APY_WINDOW_KEY2, SurfLiquidAgent;
428
+ var init_surfliquid_agent = __esm({
429
+ "src/agents/surfliquid/surfliquid.agent.ts"() {
430
+ "use strict";
431
+ import_core_sdk2 = require("@surf_liquid/core-sdk");
432
+ import_viem6 = require("viem");
433
+ import_chains2 = require("viem/chains");
434
+ init_errors();
435
+ init_surfliquid_constants();
436
+ init_permit2();
437
+ init_surfliquid_mapper();
438
+ init_surfliquid_wallet_adapter();
439
+ init_surfliquid_morpho();
440
+ APY_WINDOW_KEY2 = {
441
+ "7D": "apy7d",
442
+ "14D": "apy14d",
443
+ "30D": "apy30d"
444
+ };
445
+ SurfLiquidAgent = class {
446
+ constructor(config) {
447
+ this.config = config;
448
+ }
449
+ config;
450
+ id = "surfliquid";
451
+ supportedChainIds = [SURFLIQUID_CHAIN_ID];
452
+ supportedAssets = SURFLIQUID_SUPPORTED_ASSETS;
453
+ surf = null;
454
+ connectedAddress = null;
455
+ // In-flight connect+auth, shared by concurrent callers to dedupe SIWE.
456
+ connectPromise = null;
457
+ ensureSurf() {
458
+ if (this.surf) return this.surf;
459
+ this.surf = import_core_sdk2.SurfClient.create({
460
+ projectName: this.config.projectName,
461
+ appId: this.config.appId,
462
+ environment: "mainnet",
463
+ chainId: SURFLIQUID_CHAIN_ID,
464
+ apiBaseUrl: this.config.apiBaseUrl,
465
+ rpcUrl: this.config.rpcUrl,
466
+ // Let the SDK auto-approve USDC to the vault before depositing.
467
+ autoApprove: true
468
+ });
469
+ return this.surf;
470
+ }
471
+ /**
472
+ * Connects + authenticates the SurfClient against owney's provider. Skips the
473
+ * SIWE prompt when already authenticated for this wallet (cookie session).
474
+ */
475
+ async connect(state) {
476
+ const surf = this.ensureSurf();
477
+ const sameWallet = this.connectedAddress?.toLowerCase() === state.walletAddress.toLowerCase();
478
+ if (sameWallet && surf.getAuthState().authenticated) {
479
+ return surf;
480
+ }
481
+ if (this.connectPromise) return this.connectPromise;
482
+ this.connectPromise = (async () => {
483
+ surf.registerWalletAdapter(
484
+ "owney",
485
+ new OwneyWalletAdapter(state.provider)
486
+ );
487
+ await surf.connectWallet("owney");
488
+ await surf.authenticate();
489
+ this.connectedAddress = state.walletAddress;
490
+ return surf;
491
+ })();
492
+ try {
493
+ return await this.connectPromise;
494
+ } finally {
495
+ this.connectPromise = null;
496
+ }
497
+ }
498
+ /**
499
+ * Returns the user's vault address, deploying one on first use. SurfLiquid's
500
+ * deposit requires the vault to exist, so this must run before depositing.
501
+ * Existing vault → returned as-is; otherwise `deployVault()` is sent
502
+ * (user-pays) and the freshly deployed address is returned.
503
+ */
504
+ async resolveVaultAddress(surf, owner) {
505
+ const vault = await surf.getVault(owner).catch(() => null);
506
+ if (vault?.exists && vault.userVaultAddress) {
507
+ return vault.userVaultAddress;
508
+ }
509
+ const { vaultAddress } = await surf.deployVault();
510
+ return vaultAddress;
511
+ }
512
+ /**
513
+ * Grants the user's vault a standing USDC allowance so SurfLiquid's
514
+ * `autoApprove` has nothing to do — see {@link SURFLIQUID_APPROVAL_TARGET}
515
+ * for why one prompt per deposit is worth removing.
516
+ *
517
+ * Fails OPEN on a read error: if the allowance can't be checked we simply
518
+ * return and let SurfLiquid's own per-deposit approve run, because a flaky
519
+ * RPC must never block a deposit. A failed or rejected APPROVE does
520
+ * propagate — the user declined a transaction, and letting SurfLiquid
521
+ * immediately ask again for a smaller one is exactly the double prompt this
522
+ * removes.
523
+ */
524
+ async ensureVaultAllowance(state, vaultAddress, amount) {
525
+ const spender = vaultAddress;
526
+ const transport = (0, import_viem6.custom)(state.provider);
527
+ const publicClient = (0, import_viem6.createPublicClient)({ chain: import_chains2.base, transport });
528
+ let allowance;
529
+ try {
530
+ allowance = await publicClient.readContract({
531
+ address: SURFLIQUID_USDC_ADDRESS,
532
+ abi: ERC20_ALLOWANCE_ABI,
533
+ functionName: "allowance",
534
+ args: [state.walletAddress, spender]
535
+ });
536
+ } catch (error) {
537
+ console.warn(
538
+ "[owney-sdk] SurfLiquid allowance pre-check failed; deferring to its per-deposit approve:",
539
+ error instanceof Error ? error.message : String(error)
540
+ );
541
+ return;
542
+ }
543
+ if (allowance >= amount) return;
544
+ const target = amount > SURFLIQUID_APPROVAL_TARGET ? amount : SURFLIQUID_APPROVAL_TARGET;
545
+ const wallet = (0, import_viem6.createWalletClient)({
546
+ account: state.walletAddress,
547
+ chain: import_chains2.base,
548
+ transport
549
+ });
550
+ const hash = await wallet.writeContract({
551
+ address: SURFLIQUID_USDC_ADDRESS,
552
+ abi: ERC20_ALLOWANCE_ABI,
553
+ functionName: "approve",
554
+ args: [spender, target],
555
+ account: state.walletAddress,
556
+ chain: import_chains2.base
557
+ });
558
+ const receipt = await publicClient.waitForTransactionReceipt({
559
+ hash,
560
+ confirmations: 1
561
+ });
562
+ if (receipt.status !== "success") {
563
+ throw new Error(`SurfLiquid USDC approval reverted (tx ${hash})`);
564
+ }
565
+ }
566
+ // --- IAgent: lifecycle ---
567
+ async disconnect() {
568
+ if (this.surf) {
569
+ await this.surf.disconnectWallet();
570
+ }
571
+ this.connectedAddress = null;
572
+ }
573
+ async activateAgent(state) {
574
+ await this.connect(state);
575
+ }
576
+ // --- IAgent: funds (user-pays; asset is always USDC, callback ignored) ---
577
+ async deposit(state, _chainId, amount, _asset, _depositCallback) {
578
+ const surf = await this.connect(state);
579
+ const human = (0, import_viem6.formatUnits)(BigInt(amount), SURFLIQUID_USDC_DECIMALS);
580
+ const smartWallet = await this.resolveVaultAddress(
581
+ surf,
582
+ state.walletAddress
583
+ );
584
+ await this.ensureVaultAllowance(state, smartWallet, BigInt(amount));
585
+ const tx = await surf.deposit({
586
+ asset: SURFLIQUID_USDC_ADDRESS,
587
+ amount: human
588
+ });
589
+ await tx.wait();
590
+ return { txHash: tx.hash, smartWallet, amount };
591
+ }
592
+ async withdraw(state, _chainId, _token, amount) {
593
+ const surf = await this.connect(state);
594
+ const human = amount != null ? (0, import_viem6.formatUnits)(BigInt(amount), SURFLIQUID_USDC_DECIMALS) : void 0;
595
+ try {
596
+ const tx = await surf.withdraw({
597
+ asset: SURFLIQUID_USDC_ADDRESS,
598
+ amount: human
599
+ });
600
+ await tx.wait();
601
+ return {
602
+ txHash: tx.hash,
603
+ type: amount != null ? "partial" : "full",
604
+ amount: amount ?? "0"
605
+ };
606
+ } catch (error) {
607
+ throw new OwneyError(
608
+ "WITHDRAW_FAILED",
609
+ error instanceof Error ? error.message : "SurfLiquid withdraw failed.",
610
+ { chainId: SURFLIQUID_CHAIN_ID, amount },
611
+ this.id
612
+ );
613
+ }
614
+ }
615
+ // --- IAgent: portfolio reads ---
616
+ async getBalances(state, chainId) {
617
+ const surf = await this.connect(state);
618
+ const vault = await surf.getVault(state.walletAddress);
619
+ const morphoVaultAddresses = (vault.assets ?? []).filter((asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId).map((asset) => asset.morphoVaultAddress).filter((address) => Boolean(address));
620
+ const morphoStats = await fetchMorphoStatsByVault(
621
+ morphoVaultAddresses,
622
+ chainId
623
+ );
624
+ return mapBalances2(vault, chainId, morphoStats);
625
+ }
626
+ async getEarnings(state) {
627
+ const surf = await this.connect(state);
628
+ const vault = await surf.getVault(state.walletAddress);
629
+ return mapEarnings2(vault, vault.userVaultAddress ?? state.walletAddress);
630
+ }
631
+ async getAccountApy(state, _chainId, days, _tokenSymbol) {
632
+ const surf = await this.connect(state);
633
+ const vault = await surf.getVault(state.walletAddress);
634
+ return mapAccountApy(vault, days);
635
+ }
636
+ async getHistory(state, chainId, options) {
637
+ const surf = await this.connect(state);
638
+ const page = options?.cursor ? Number(options.cursor) : 1;
639
+ const limit = options?.limit ?? 10;
640
+ const result = await surf.getAgentMessages(
641
+ state.walletAddress,
642
+ page,
643
+ limit,
644
+ options?.fromDate,
645
+ options?.toDate
646
+ );
647
+ return mapHistory(result, chainId);
648
+ }
649
+ async getUserProfile(state) {
650
+ const surf = await this.connect(state);
651
+ const vault = await surf.getVault(state.walletAddress);
652
+ return mapUserProfile2(vault, state.walletAddress);
653
+ }
654
+ // --- IAgent: discovery (no wallet) ---
655
+ async getAgentApy(days, _options) {
656
+ const surf = this.ensureSurf();
657
+ const assets = await surf.getSupportedAssets(SURFLIQUID_CHAIN_ID);
658
+ const usdc = assets.find((asset) => asset.assetSymbol === "USDC");
659
+ if (!usdc) return { averageApy: 0 };
660
+ const windowed = usdc[APY_WINDOW_KEY2[days]];
661
+ return { averageApy: windowed != null ? windowed : usdc.currentAPY };
662
+ }
663
+ };
664
+ }
665
+ });
666
+
20
667
  // src/index.ts
21
668
  var index_exports = {};
22
669
  __export(index_exports, {
@@ -89,56 +736,8 @@ var SupportedAssets = [
89
736
  }
90
737
  ];
91
738
 
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
739
  // src/lib/utils.ts
740
+ init_errors();
142
741
  var isValidChainId = (chainId) => {
143
742
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
144
743
  throw new OwneyError(
@@ -149,6 +748,12 @@ var isValidChainId = (chainId) => {
149
748
  }
150
749
  return chainId;
151
750
  };
751
+ function isSameAsset(tokenSymbol, asset) {
752
+ const token = tokenSymbol.toLowerCase();
753
+ const target = asset.toLowerCase();
754
+ if (token === target) return true;
755
+ return target === "weth" && token === "eth" || target === "eth" && token === "weth";
756
+ }
152
757
  function hexToDecimal(hex, decimals = 6) {
153
758
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
154
759
  const parsed = BigInt(normalized);
@@ -182,6 +787,10 @@ var DECIMALS_BY_SYMBOL = {
182
787
  wbtc: 8,
183
788
  cbbtc: 8
184
789
  };
790
+ function canonicalSymbol(symbol) {
791
+ const upper = symbol.toUpperCase();
792
+ return upper === "ETH" ? "WETH" : upper;
793
+ }
185
794
  function tokenDecimals(symbol, explicit) {
186
795
  if (typeof explicit === "number" && Number.isFinite(explicit))
187
796
  return explicit;
@@ -246,55 +855,89 @@ function mapPendingAllocations(raw) {
246
855
  function mapBalances(raw, _chainId, smartWallet) {
247
856
  const portfolio = raw.portfolio;
248
857
  const portfolioByChain = portfolio.portfolioByChain ?? {};
858
+ const rawPositions = portfolio.positions ?? [];
859
+ const detailByKey = /* @__PURE__ */ new Map();
860
+ for (const p of rawPositions) {
861
+ const cid = resolveChainId(p.chain ?? "");
862
+ const symbol = canonicalSymbol(p.token_symbol ?? "");
863
+ if (Number.isFinite(cid) && symbol) detailByKey.set(`${cid}:${symbol}`, p);
864
+ }
249
865
  let totalBalance = 0;
250
866
  const tokens = [];
867
+ const positions = [];
251
868
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
252
869
  const positionChainId = Number(chainIdKey);
870
+ const chainName2 = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
253
871
  for (const [
254
872
  symbol,
255
873
  { balance, balanceWithFee, decimals }
256
874
  ] of Object.entries(tokensBySymbol ?? {})) {
257
- const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
875
+ const amount = hexToDecimal(
876
+ balanceWithFee ?? balance ?? "0",
877
+ tokenDecimals(symbol, decimals)
878
+ );
258
879
  totalBalance += amount;
259
880
  tokens.push({
260
- chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
881
+ chain: chainName2,
261
882
  chainId: positionChainId,
262
883
  asset: symbol,
263
884
  amount: String(amount)
264
885
  });
886
+ const detail = detailByKey.get(
887
+ `${positionChainId}:${canonicalSymbol(symbol)}`
888
+ );
889
+ positions.push({
890
+ chain: chainName2,
891
+ protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
892
+ protocolId: detail?.protocol_id,
893
+ pool: detail?.pool,
894
+ // Report the token the way the rest of owney spells it ("WETH", as the
895
+ // pre-portfolioByChain mapper did), not Zyfai's internal "eth".
896
+ asset: canonicalSymbol(symbol),
897
+ amount: String(amount),
898
+ apy: detail?.pool_apy,
899
+ tvl: detail?.pool_tvl,
900
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
901
+ // structural read keeps it undefined today and auto-populates later.
902
+ liquidity: detail?.liquidity
903
+ });
265
904
  }
266
905
  }
267
- const positions = (portfolio.positions ?? []).map((p) => ({
268
- chain: p.chain ?? "",
269
- protocol: p.protocol_name ?? p.protocol_id ?? "",
270
- protocolId: p.protocol_id,
271
- pool: p.pool,
272
- asset: p.token_symbol ?? "",
273
- // Fee-adjusted underlying amounts are decimal base-unit strings. The
274
- // legacy `amount` fallback is hex. Runtime
275
- // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
276
- // when present, otherwise fall back to the per-symbol lookup.
277
- amount: String(
278
- p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
279
- p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
280
- tokenDecimals(
281
- p.token_symbol,
282
- p.decimals
283
- )
284
- ) : hexToDecimal(
285
- p.amount || "0",
286
- tokenDecimals(
287
- p.token_symbol,
288
- p.decimals
289
- )
290
- )
291
- ),
292
- apy: p.pool_apy,
293
- tvl: p.pool_tvl,
294
- // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
295
- // structural read keeps it undefined today and auto-populates later.
296
- liquidity: p.liquidity
297
- }));
906
+ if (positions.length === 0) {
907
+ positions.push(
908
+ ...(portfolio.positions ?? []).map((p) => ({
909
+ chain: p.chain ?? "",
910
+ protocol: p.protocol_name ?? p.protocol_id ?? "",
911
+ protocolId: p.protocol_id,
912
+ pool: p.pool,
913
+ asset: p.token_symbol ?? "",
914
+ // Fee-adjusted underlying amounts are decimal base-unit strings; the
915
+ // legacy `amount` fallback is hex. Runtime getBalances payloads often
916
+ // carry `decimals` (e.g. 18 for WETH) — prefer it when present,
917
+ // otherwise fall back to the per-symbol lookup.
918
+ amount: String(
919
+ p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
920
+ p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
921
+ tokenDecimals(
922
+ p.token_symbol,
923
+ p.decimals
924
+ )
925
+ ) : hexToDecimal(
926
+ p.amount || "0",
927
+ tokenDecimals(
928
+ p.token_symbol,
929
+ p.decimals
930
+ )
931
+ )
932
+ ),
933
+ apy: p.pool_apy,
934
+ tvl: p.pool_tvl,
935
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
936
+ // structural read keeps it undefined today and auto-populates later.
937
+ liquidity: p.liquidity
938
+ }))
939
+ );
940
+ }
298
941
  const pendingAllocations = mapPendingAllocations(
299
942
  portfolio.staleBalances
300
943
  );
@@ -589,6 +1232,9 @@ function decodeHistoryCursor(token) {
589
1232
  return parsed;
590
1233
  }
591
1234
 
1235
+ // src/agents/zyfai/zyfai.agent.ts
1236
+ init_errors();
1237
+
592
1238
  // src/agents/zyfai/zyfai.auth-cache.ts
593
1239
  var KEY_PREFIX = "owney.zyfai.session";
594
1240
  var storage = () => {
@@ -1377,6 +2023,37 @@ var ZyfaiAgent = class _ZyfaiAgent {
1377
2023
  );
1378
2024
  }
1379
2025
  }
2026
+ /**
2027
+ * Records the deposit with Zyfai, retrying transient failures.
2028
+ *
2029
+ * This runs AFTER the transfer has already landed on-chain, so it must never
2030
+ * fail the deposit — the funds moved. But it is also the ONLY source of the
2031
+ * "Top up wallet" entry the history is built from: Zyfai auto-deploys the
2032
+ * balance it detects either way, so when this call is lost the user's deposit
2033
+ * never appears in Activity (an earlier withdrawal stays the newest row) and
2034
+ * nothing ever backfills it. Retry, then log loudly enough to be recoverable.
2035
+ */
2036
+ async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
2037
+ const ATTEMPTS = 3;
2038
+ const RETRY_DELAY_MS = 1e3;
2039
+ for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
2040
+ try {
2041
+ await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
2042
+ return;
2043
+ } catch (logError) {
2044
+ if (attempt === ATTEMPTS) {
2045
+ console.error(
2046
+ "[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
2047
+ { txHash, chainId, amount, tokenAddress, error: logError }
2048
+ );
2049
+ return;
2050
+ }
2051
+ await new Promise(
2052
+ (resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
2053
+ );
2054
+ }
2055
+ }
2056
+ }
1380
2057
  /**
1381
2058
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1382
2059
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1680,14 +2357,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
1680
2357
  );
1681
2358
  const txHash = await depositCallback(smartWallet, validChainId, amount);
1682
2359
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1683
- try {
1684
- await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
1685
- } catch (logError) {
1686
- console.warn(
1687
- "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
1688
- logError
1689
- );
1690
- }
2360
+ await this.logDepositWithRetry(
2361
+ validChainId,
2362
+ txHash,
2363
+ amount,
2364
+ tokenAddress
2365
+ );
1691
2366
  return { txHash, smartWallet, amount };
1692
2367
  }
1693
2368
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
@@ -1868,7 +2543,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1868
2543
  }
1869
2544
  };
1870
2545
 
2546
+ // src/client.ts
2547
+ init_errors();
2548
+
1871
2549
  // src/lib/routing-api.ts
2550
+ init_errors();
1872
2551
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1873
2552
  async function fetchOrgAgentConfig(apiKey) {
1874
2553
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
@@ -1933,6 +2612,7 @@ async function fetchAgentKeys(apiKey) {
1933
2612
  }
1934
2613
 
1935
2614
  // src/lib/health-report.ts
2615
+ init_errors();
1936
2616
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1937
2617
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1938
2618
  try {
@@ -1968,11 +2648,10 @@ async function withFailureReporting(apiKey, agentType, fn) {
1968
2648
  // src/lib/helpers/withdraw-helper.ts
1969
2649
  var import_viem2 = require("viem");
1970
2650
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1971
- const target = asset.toUpperCase();
1972
2651
  return agents.map((agent) => {
1973
2652
  const agentBalance = aggregated[agent.id];
1974
2653
  const tokenBalance = agentBalance?.tokens.find(
1975
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2654
+ (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
1976
2655
  );
1977
2656
  if (!tokenBalance) return { agent, balance: 0n };
1978
2657
  return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
@@ -2082,8 +2761,11 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2082
2761
  }
2083
2762
 
2084
2763
  // src/client.ts
2085
- var import_viem6 = require("viem");
2086
- var import_chains2 = require("viem/chains");
2764
+ var import_viem7 = require("viem");
2765
+ var import_chains3 = require("viem/chains");
2766
+
2767
+ // src/lib/sponsored-deposit.ts
2768
+ init_errors();
2087
2769
 
2088
2770
  // src/lib/transfer-auth.ts
2089
2771
  var import_viem3 = require("viem");
@@ -2122,12 +2804,13 @@ function randomAuthNonce() {
2122
2804
  }
2123
2805
 
2124
2806
  // src/lib/sponsor-client.ts
2807
+ init_errors();
2125
2808
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2126
2809
  async function postSponsorTransferAuth(input) {
2127
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2810
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2128
2811
  let res;
2129
2812
  try {
2130
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
2813
+ res = await fetch(`${base4}/api/v1/sponsor/erc20-transfer-auth`, {
2131
2814
  method: "POST",
2132
2815
  headers: {
2133
2816
  "content-type": "application/json",
@@ -2164,10 +2847,10 @@ async function postSponsorTransferAuth(input) {
2164
2847
  return parsed.data;
2165
2848
  }
2166
2849
  async function postSponsorPermit2Transfer(input) {
2167
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2850
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2168
2851
  let res;
2169
2852
  try {
2170
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2853
+ res = await fetch(`${base4}/api/v1/sponsor/permit2-transfer`, {
2171
2854
  method: "POST",
2172
2855
  headers: {
2173
2856
  "content-type": "application/json",
@@ -2202,11 +2885,11 @@ async function postSponsorPermit2Transfer(input) {
2202
2885
  return parsed.data;
2203
2886
  }
2204
2887
  async function getSponsorRelayerAddress(input) {
2205
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2888
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2206
2889
  let res;
2207
2890
  try {
2208
2891
  res = await fetch(
2209
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2892
+ `${base4}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2210
2893
  {
2211
2894
  headers: { "x-owney-api-key": input.apiKey }
2212
2895
  }
@@ -2238,85 +2921,11 @@ async function getSponsorRelayerAddress(input) {
2238
2921
  return parsed.data.relayer;
2239
2922
  }
2240
2923
 
2241
- // src/lib/permit2.ts
2242
- var import_viem4 = require("viem");
2243
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2244
- var MAX_UINT256 = 2n ** 256n - 1n;
2245
- var ERC20_ALLOWANCE_ABI = [
2246
- {
2247
- type: "function",
2248
- name: "allowance",
2249
- stateMutability: "view",
2250
- inputs: [
2251
- { name: "owner", type: "address" },
2252
- { name: "spender", type: "address" }
2253
- ],
2254
- outputs: [{ name: "", type: "uint256" }]
2255
- },
2256
- {
2257
- type: "function",
2258
- name: "approve",
2259
- stateMutability: "nonpayable",
2260
- inputs: [
2261
- { name: "spender", type: "address" },
2262
- { name: "amount", type: "uint256" }
2263
- ],
2264
- outputs: [{ name: "", type: "bool" }]
2265
- },
2266
- {
2267
- type: "function",
2268
- name: "balanceOf",
2269
- stateMutability: "view",
2270
- inputs: [{ name: "account", type: "address" }],
2271
- outputs: [{ name: "", type: "uint256" }]
2272
- }
2273
- ];
2274
- function buildPermitTransferFromTypedData(input) {
2275
- return {
2276
- domain: {
2277
- name: "Permit2",
2278
- chainId: input.chainId,
2279
- verifyingContract: PERMIT2_ADDRESS
2280
- },
2281
- types: {
2282
- PermitTransferFrom: [
2283
- { name: "permitted", type: "TokenPermissions" },
2284
- { name: "spender", type: "address" },
2285
- { name: "nonce", type: "uint256" },
2286
- { name: "deadline", type: "uint256" }
2287
- ],
2288
- TokenPermissions: [
2289
- { name: "token", type: "address" },
2290
- { name: "amount", type: "uint256" }
2291
- ]
2292
- },
2293
- primaryType: "PermitTransferFrom",
2294
- message: input.message
2295
- };
2296
- }
2297
- function randomPermit2Nonce() {
2298
- const bytes = new Uint8Array(32);
2299
- globalThis.crypto.getRandomValues(bytes);
2300
- return BigInt((0, import_viem4.bytesToHex)(bytes));
2301
- }
2302
- async function readPermit2Allowance(publicClient, token, owner) {
2303
- return publicClient.readContract({
2304
- address: token,
2305
- abi: ERC20_ALLOWANCE_ABI,
2306
- functionName: "allowance",
2307
- args: [owner, PERMIT2_ADDRESS]
2308
- });
2309
- }
2310
- async function readErc20Balance(publicClient, token, owner) {
2311
- return publicClient.readContract({
2312
- address: token,
2313
- abi: ERC20_ALLOWANCE_ABI,
2314
- functionName: "balanceOf",
2315
- args: [owner]
2316
- });
2317
- }
2924
+ // src/lib/sponsored-deposit.ts
2925
+ init_permit2();
2318
2926
 
2319
2927
  // src/lib/chain-guard.ts
2928
+ init_errors();
2320
2929
  var CHAIN_NAMES = {
2321
2930
  1: "Ethereum",
2322
2931
  8453: "Base",
@@ -2430,6 +3039,8 @@ function makeSponsoredDepositCallback(deps) {
2430
3039
  }
2431
3040
 
2432
3041
  // src/lib/sponsored-weth-deposit.ts
3042
+ init_errors();
3043
+ init_permit2();
2433
3044
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2434
3045
  function makeSponsoredWethCallback(deps) {
2435
3046
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -2514,6 +3125,7 @@ function makeSponsoredWethCallback(deps) {
2514
3125
 
2515
3126
  // src/lib/sponsored-calls-deposit.ts
2516
3127
  var import_viem5 = require("viem");
3128
+ init_errors();
2517
3129
  var DEFAULT_POLL_INTERVAL_MS = 1500;
2518
3130
  var DEFAULT_MAX_POLLS = 30;
2519
3131
  async function paymasterSupported(provider, owner, chainId) {
@@ -2604,6 +3216,7 @@ function makeSponsoredCallsCallback(deps) {
2604
3216
  }
2605
3217
 
2606
3218
  // src/client.ts
3219
+ init_permit2();
2607
3220
  function encodeMultiAgentCursor(map) {
2608
3221
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
2609
3222
  }
@@ -2630,10 +3243,11 @@ var SPONSORED_USDC_BY_CHAIN = {
2630
3243
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2631
3244
  };
2632
3245
  var VIEM_CHAIN2 = {
2633
- 8453: import_chains2.base,
2634
- 42161: import_chains2.arbitrum,
2635
- 1: import_chains2.mainnet
3246
+ 8453: import_chains3.base,
3247
+ 42161: import_chains3.arbitrum,
3248
+ 1: import_chains3.mainnet
2636
3249
  };
3250
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2637
3251
  var SPONSORED_WETH_BY_CHAIN = {
2638
3252
  8453: "0x4200000000000000000000000000000000000006",
2639
3253
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2662,6 +3276,7 @@ var OwneySDK = class {
2662
3276
  zyfaiRpcUrls;
2663
3277
  routingApiBaseUrl;
2664
3278
  referralSource;
3279
+ surfliquidApiBaseUrl;
2665
3280
  cachedSponsoredCallback = null;
2666
3281
  cachedWethSponsoredCallback = null;
2667
3282
  paymasterServiceUrl;
@@ -2674,6 +3289,7 @@ var OwneySDK = class {
2674
3289
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2675
3290
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2676
3291
  this.referralSource = config.referralSource;
3292
+ this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2677
3293
  }
2678
3294
  /**
2679
3295
  * Establish connection state for all subsequent SDK calls.
@@ -2773,14 +3389,14 @@ var OwneySDK = class {
2773
3389
  // Casts work around viem's chain-narrowed Client vs the generic
2774
3390
  // PublicClient/WalletClient param types — structurally identical at
2775
3391
  // runtime, but the two share a name TS treats as unrelated.
2776
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
3392
+ getPublicClient: (cid) => (0, import_viem7.createPublicClient)({
2777
3393
  chain: VIEM_CHAIN2[cid],
2778
- transport: (0, import_viem6.custom)(provider)
3394
+ transport: (0, import_viem7.custom)(provider)
2779
3395
  }),
2780
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
3396
+ getWalletClient: (cid) => (0, import_viem7.createWalletClient)({
2781
3397
  account: owner,
2782
3398
  chain: VIEM_CHAIN2[cid],
2783
- transport: (0, import_viem6.custom)(provider)
3399
+ transport: (0, import_viem7.custom)(provider)
2784
3400
  })
2785
3401
  });
2786
3402
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2826,14 +3442,14 @@ var OwneySDK = class {
2826
3442
  // Casts work around viem's chain-narrowed Client vs the generic
2827
3443
  // PublicClient/WalletClient param types — structurally identical at
2828
3444
  // runtime, but the two share a name TS treats as unrelated.
2829
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
3445
+ getPublicClient: (cid) => (0, import_viem7.createPublicClient)({
2830
3446
  chain: VIEM_CHAIN2[cid],
2831
- transport: (0, import_viem6.custom)(provider)
3447
+ transport: (0, import_viem7.custom)(provider)
2832
3448
  }),
2833
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
3449
+ getWalletClient: (cid) => (0, import_viem7.createWalletClient)({
2834
3450
  account: owner,
2835
3451
  chain: VIEM_CHAIN2[cid],
2836
- transport: (0, import_viem6.custom)(provider)
3452
+ transport: (0, import_viem7.custom)(provider)
2837
3453
  })
2838
3454
  });
2839
3455
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2903,8 +3519,8 @@ var OwneySDK = class {
2903
3519
  this.initializingAgentsPromise = (async () => {
2904
3520
  const agentKeys = await fetchAgentKeys(this.apiKey);
2905
3521
  this.disabledAgents.clear();
2906
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
2907
- const agent = this.createAgent(agent_type, key2);
3522
+ for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
3523
+ const agent = await this.createAgent(agent_type, key2, project_name);
2908
3524
  if (!agent) continue;
2909
3525
  this.agents.set(agent_type, agent);
2910
3526
  if (is_enabled === false) {
@@ -2925,10 +3541,22 @@ var OwneySDK = class {
2925
3541
  this.initializingAgentsPromise = null;
2926
3542
  }
2927
3543
  }
2928
- createAgent(agentId, key2) {
3544
+ async createAgent(agentId, key2, projectName) {
2929
3545
  if (agentId === "zyfai") {
2930
3546
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2931
3547
  }
3548
+ if (agentId === "surfliquid") {
3549
+ if (!projectName) return null;
3550
+ const { SurfLiquidAgent: SurfLiquidAgent2 } = await Promise.resolve().then(() => (init_surfliquid_agent(), surfliquid_agent_exports));
3551
+ return new SurfLiquidAgent2({
3552
+ projectName,
3553
+ appId: key2,
3554
+ // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
3555
+ // which WebKit (iOS) discards when it is third-party. See
3556
+ // OwneySDKConfig.surfliquidApiBaseUrl.
3557
+ apiBaseUrl: this.surfliquidApiBaseUrl
3558
+ });
3559
+ }
2932
3560
  return null;
2933
3561
  }
2934
3562
  // --- Account lifecycle ---
@@ -3083,18 +3711,30 @@ var OwneySDK = class {
3083
3711
  );
3084
3712
  }
3085
3713
  const agentResults = {};
3714
+ const agentErrors = {};
3086
3715
  for (const { agent, amount: agentAmount } of agentAmounts) {
3087
- agentResults[agent.id] = await this.depositWithFallback(
3088
- agent,
3089
- state,
3090
- chainId,
3091
- agentAmount,
3092
- asset,
3093
- effectiveCallback,
3094
- depositCallback
3716
+ try {
3717
+ agentResults[agent.id] = await this.depositWithFallback(
3718
+ agent,
3719
+ state,
3720
+ chainId,
3721
+ agentAmount,
3722
+ asset,
3723
+ effectiveCallback,
3724
+ depositCallback
3725
+ );
3726
+ } catch (error) {
3727
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
3728
+ }
3729
+ }
3730
+ if (Object.keys(agentResults).length === 0) {
3731
+ throw new OwneyError(
3732
+ "DEPOSIT_ALL_FAILED",
3733
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
3734
+ { agentErrors }
3095
3735
  );
3096
3736
  }
3097
- return { agentResults };
3737
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
3098
3738
  }
3099
3739
  /**
3100
3740
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -3210,9 +3850,8 @@ var OwneySDK = class {
3210
3850
  async hasExistingBalance(agent, state, chainId, asset) {
3211
3851
  try {
3212
3852
  const balance = await agent.getBalances(state, chainId);
3213
- const target = asset.toLowerCase();
3214
3853
  const token = balance.tokens.find(
3215
- (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3854
+ (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
3216
3855
  );
3217
3856
  return !!token && Number(token.amount) > 0;
3218
3857
  } catch {
@@ -3251,6 +3890,34 @@ var OwneySDK = class {
3251
3890
  }
3252
3891
  return eligible;
3253
3892
  }
3893
+ /**
3894
+ * Agent ids the routing API provisioned for this org that support the given
3895
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3896
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
3897
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
3898
+ * wallet), so the UI can resolve which agent to use before the user connects.
3899
+ *
3900
+ * This is the source of truth for agent availability: an agent appears here
3901
+ * iff the routing API returned its key. No per-app feature flags.
3902
+ */
3903
+ async getEligibleAgentIds(chainId, asset) {
3904
+ try {
3905
+ await this.ensureAgentsInitialized();
3906
+ } catch (error) {
3907
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3908
+ return [];
3909
+ }
3910
+ throw error;
3911
+ }
3912
+ return [...this.agents.values()].filter((agent) => {
3913
+ const chainAssets = agent.supportedAssets.find(
3914
+ (sa) => sa.chainId === chainId
3915
+ );
3916
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3917
+ }).map((agent) => agent.id).sort(
3918
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3919
+ );
3920
+ }
3254
3921
  // --- Fund operations ---
3255
3922
  /**
3256
3923
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -3286,9 +3953,17 @@ var OwneySDK = class {
3286
3953
  }
3287
3954
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
3288
3955
  if (!amount) {
3956
+ const aggregated2 = await this.getBalances();
3957
+ const funded = projectAgentBalancesForAsset(
3958
+ eligibleAgents,
3959
+ aggregated2.agentBalances,
3960
+ chainId,
3961
+ asset,
3962
+ assetInfo.decimals
3963
+ ).filter((b) => b.balance > 0n);
3289
3964
  const results2 = {};
3290
3965
  const agentErrors2 = {};
3291
- for (const agent of eligibleAgents) {
3966
+ for (const { agent } of funded) {
3292
3967
  try {
3293
3968
  results2[agent.id] = await agent.withdraw(state, chainId, token);
3294
3969
  } catch (err) {
@@ -3305,9 +3980,10 @@ var OwneySDK = class {
3305
3980
  { asset, agentErrors: agentErrors2 }
3306
3981
  );
3307
3982
  }
3983
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
3308
3984
  return {
3309
3985
  agentResult: results2,
3310
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3986
+ totalWithdrawn: totalWithdrawn.toString()
3311
3987
  };
3312
3988
  }
3313
3989
  const requested = BigInt(amount);
@@ -3699,10 +4375,10 @@ var OwneySDK = class {
3699
4375
  );
3700
4376
  }
3701
4377
  const provider = this.requireConnectedProvider();
3702
- const wallet = (0, import_viem6.createWalletClient)({
4378
+ const wallet = (0, import_viem7.createWalletClient)({
3703
4379
  account: state.walletAddress,
3704
4380
  chain: VIEM_CHAIN2[chainId],
3705
- transport: (0, import_viem6.custom)(provider)
4381
+ transport: (0, import_viem7.custom)(provider)
3706
4382
  });
3707
4383
  const hash = await wallet.writeContract({
3708
4384
  address: token,
@@ -3712,9 +4388,9 @@ var OwneySDK = class {
3712
4388
  account: state.walletAddress,
3713
4389
  chain: VIEM_CHAIN2[chainId]
3714
4390
  });
3715
- const publicClient = (0, import_viem6.createPublicClient)({
4391
+ const publicClient = (0, import_viem7.createPublicClient)({
3716
4392
  chain: VIEM_CHAIN2[chainId],
3717
- transport: (0, import_viem6.custom)(provider)
4393
+ transport: (0, import_viem7.custom)(provider)
3718
4394
  });
3719
4395
  const receipt = await publicClient.waitForTransactionReceipt({
3720
4396
  hash,
@@ -3822,8 +4498,11 @@ var OwneySDK = class {
3822
4498
  }
3823
4499
  };
3824
4500
 
4501
+ // src/index.ts
4502
+ init_errors();
4503
+
3825
4504
  // src/agents/zyfai/zyfai.siwx.ts
3826
- var import_viem7 = require("viem");
4505
+ var import_viem8 = require("viem");
3827
4506
  var import_siwe = require("siwe");
3828
4507
  var import_sdk2 = require("@zyfai/sdk");
3829
4508
 
@@ -3950,7 +4629,7 @@ function buildSIWXConfig(deps) {
3950
4629
  issuedAt,
3951
4630
  toString() {
3952
4631
  return new import_siwe.SiweMessage({
3953
- address: (0, import_viem7.getAddress)(accountAddress),
4632
+ address: (0, import_viem8.getAddress)(accountAddress),
3954
4633
  chainId: numericChainId(chainId),
3955
4634
  domain,
3956
4635
  uri,