@owney/sdk 0.7.8 → 0.7.10-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,54 +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);
253
- for (const [symbol, { balance, balanceWithFee, decimals }] of Object.entries(
254
- tokensBySymbol ?? {}
255
- )) {
256
- const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
870
+ const chainName2 = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
871
+ for (const [
872
+ symbol,
873
+ { balance, balanceWithFee, decimals }
874
+ ] of Object.entries(tokensBySymbol ?? {})) {
875
+ const amount = hexToDecimal(
876
+ balanceWithFee ?? balance ?? "0",
877
+ tokenDecimals(symbol, decimals)
878
+ );
257
879
  totalBalance += amount;
258
880
  tokens.push({
259
- chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
881
+ chain: chainName2,
260
882
  chainId: positionChainId,
261
883
  asset: symbol,
262
884
  amount: String(amount)
263
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
+ });
264
904
  }
265
905
  }
266
- const positions = (portfolio.positions ?? []).map((p) => ({
267
- chain: p.chain ?? "",
268
- protocol: p.protocol_name ?? p.protocol_id ?? "",
269
- protocolId: p.protocol_id,
270
- pool: p.pool,
271
- asset: p.token_symbol ?? "",
272
- // Fee-adjusted underlying amounts are decimal base-unit strings. The
273
- // legacy `amount` fallback is hex. Runtime
274
- // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
275
- // when present, otherwise fall back to the per-symbol lookup.
276
- amount: String(
277
- p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
278
- p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
279
- tokenDecimals(
280
- p.token_symbol,
281
- p.decimals
282
- )
283
- ) : hexToDecimal(
284
- p.amount || "0",
285
- tokenDecimals(
286
- p.token_symbol,
287
- p.decimals
288
- )
289
- )
290
- ),
291
- apy: p.pool_apy,
292
- tvl: p.pool_tvl,
293
- // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
294
- // structural read keeps it undefined today and auto-populates later.
295
- liquidity: p.liquidity
296
- }));
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
+ }
297
941
  const pendingAllocations = mapPendingAllocations(
298
942
  portfolio.staleBalances
299
943
  );
@@ -391,6 +1035,11 @@ function mapEntries(rawEntries, chainId) {
391
1035
  "Top up wallet": "Top up",
392
1036
  "Partial withdraw from protocol": "Withdraw",
393
1037
  "Withdraw from protocol": "Withdraw",
1038
+ // The agent's own leg of a top up: the funds the user sent moving into the
1039
+ // pool that earns the APY. Zyfai emits it as a separate entry seconds after
1040
+ // "Top up wallet" and for slightly less (gas). `Deposit` already exists in
1041
+ // the public HistoryAction union, so no consumer type change is needed.
1042
+ "Deposit to protocol": "Deposit",
394
1043
  Rebalance: "Rebalance"
395
1044
  };
396
1045
  void chainId;
@@ -431,12 +1080,19 @@ function mapEntries(rawEntries, chainId) {
431
1080
  const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
432
1081
  const rawAmount = entry?.positions?.[0]?.amount;
433
1082
  const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
1083
+ const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
1084
+ protocol: p.protocol_name,
1085
+ pool: p.pool ?? void 0,
1086
+ tokenSymbol: p.token_symbol ?? void 0,
1087
+ amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0
1088
+ }));
434
1089
  return {
435
1090
  agent: "zyfai",
436
1091
  action: mappedAction,
437
1092
  date: entry.date || "",
438
1093
  newApy: newApy === null ? null : String(newApy),
439
1094
  oldApy: oldApy === null ? null : String(oldApy),
1095
+ ...mappedPositions.length ? { positions: mappedPositions } : {},
440
1096
  transactions: [
441
1097
  {
442
1098
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
@@ -576,6 +1232,9 @@ function decodeHistoryCursor(token) {
576
1232
  return parsed;
577
1233
  }
578
1234
 
1235
+ // src/agents/zyfai/zyfai.agent.ts
1236
+ init_errors();
1237
+
579
1238
  // src/agents/zyfai/zyfai.auth-cache.ts
580
1239
  var KEY_PREFIX = "owney.zyfai.session";
581
1240
  var storage = () => {
@@ -1364,6 +2023,37 @@ var ZyfaiAgent = class {
1364
2023
  );
1365
2024
  }
1366
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
+ }
1367
2057
  /**
1368
2058
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1369
2059
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1667,14 +2357,12 @@ var ZyfaiAgent = class {
1667
2357
  );
1668
2358
  const txHash = await depositCallback(smartWallet, validChainId, amount);
1669
2359
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1670
- try {
1671
- await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
1672
- } catch (logError) {
1673
- console.warn(
1674
- "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
1675
- logError
1676
- );
1677
- }
2360
+ await this.logDepositWithRetry(
2361
+ validChainId,
2362
+ txHash,
2363
+ amount,
2364
+ tokenAddress
2365
+ );
1678
2366
  return { txHash, smartWallet, amount };
1679
2367
  }
1680
2368
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
@@ -1761,7 +2449,7 @@ var ZyfaiAgent = class {
1761
2449
  toDate
1762
2450
  });
1763
2451
  raw.data.forEach((entry, idx) => {
1764
- if (entry.chainId === validChainId && entry.action !== "Deposit to protocol") {
2452
+ if (entry.chainId === validChainId) {
1765
2453
  matched.push({ entry, rawIdx: offset + idx });
1766
2454
  }
1767
2455
  });
@@ -1829,7 +2517,11 @@ var ZyfaiAgent = class {
1829
2517
  }
1830
2518
  };
1831
2519
 
2520
+ // src/client.ts
2521
+ init_errors();
2522
+
1832
2523
  // src/lib/routing-api.ts
2524
+ init_errors();
1833
2525
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1834
2526
  async function fetchOrgAgentConfig(apiKey) {
1835
2527
  const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
@@ -1894,6 +2586,7 @@ async function fetchAgentKeys(apiKey) {
1894
2586
  }
1895
2587
 
1896
2588
  // src/lib/health-report.ts
2589
+ init_errors();
1897
2590
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1898
2591
  async function reportAgentFailure(apiKey, agentType, errorCode) {
1899
2592
  try {
@@ -1929,11 +2622,10 @@ async function withFailureReporting(apiKey, agentType, fn) {
1929
2622
  // src/lib/helpers/withdraw-helper.ts
1930
2623
  var import_viem2 = require("viem");
1931
2624
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1932
- const target = asset.toUpperCase();
1933
2625
  return agents.map((agent) => {
1934
2626
  const agentBalance = aggregated[agent.id];
1935
2627
  const tokenBalance = agentBalance?.tokens.find(
1936
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2628
+ (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
1937
2629
  );
1938
2630
  if (!tokenBalance) return { agent, balance: 0n };
1939
2631
  return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
@@ -2043,8 +2735,11 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2043
2735
  }
2044
2736
 
2045
2737
  // src/client.ts
2046
- var import_viem6 = require("viem");
2047
- var import_chains2 = require("viem/chains");
2738
+ var import_viem7 = require("viem");
2739
+ var import_chains3 = require("viem/chains");
2740
+
2741
+ // src/lib/sponsored-deposit.ts
2742
+ init_errors();
2048
2743
 
2049
2744
  // src/lib/transfer-auth.ts
2050
2745
  var import_viem3 = require("viem");
@@ -2083,12 +2778,13 @@ function randomAuthNonce() {
2083
2778
  }
2084
2779
 
2085
2780
  // src/lib/sponsor-client.ts
2781
+ init_errors();
2086
2782
  var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2087
2783
  async function postSponsorTransferAuth(input) {
2088
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2784
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2089
2785
  let res;
2090
2786
  try {
2091
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
2787
+ res = await fetch(`${base4}/api/v1/sponsor/erc20-transfer-auth`, {
2092
2788
  method: "POST",
2093
2789
  headers: {
2094
2790
  "content-type": "application/json",
@@ -2125,10 +2821,10 @@ async function postSponsorTransferAuth(input) {
2125
2821
  return parsed.data;
2126
2822
  }
2127
2823
  async function postSponsorPermit2Transfer(input) {
2128
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2824
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2129
2825
  let res;
2130
2826
  try {
2131
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2827
+ res = await fetch(`${base4}/api/v1/sponsor/permit2-transfer`, {
2132
2828
  method: "POST",
2133
2829
  headers: {
2134
2830
  "content-type": "application/json",
@@ -2163,11 +2859,11 @@ async function postSponsorPermit2Transfer(input) {
2163
2859
  return parsed.data;
2164
2860
  }
2165
2861
  async function getSponsorRelayerAddress(input) {
2166
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2862
+ const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2167
2863
  let res;
2168
2864
  try {
2169
2865
  res = await fetch(
2170
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2866
+ `${base4}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2171
2867
  {
2172
2868
  headers: { "x-owney-api-key": input.apiKey }
2173
2869
  }
@@ -2199,85 +2895,11 @@ async function getSponsorRelayerAddress(input) {
2199
2895
  return parsed.data.relayer;
2200
2896
  }
2201
2897
 
2202
- // src/lib/permit2.ts
2203
- var import_viem4 = require("viem");
2204
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2205
- var MAX_UINT256 = 2n ** 256n - 1n;
2206
- var ERC20_ALLOWANCE_ABI = [
2207
- {
2208
- type: "function",
2209
- name: "allowance",
2210
- stateMutability: "view",
2211
- inputs: [
2212
- { name: "owner", type: "address" },
2213
- { name: "spender", type: "address" }
2214
- ],
2215
- outputs: [{ name: "", type: "uint256" }]
2216
- },
2217
- {
2218
- type: "function",
2219
- name: "approve",
2220
- stateMutability: "nonpayable",
2221
- inputs: [
2222
- { name: "spender", type: "address" },
2223
- { name: "amount", type: "uint256" }
2224
- ],
2225
- outputs: [{ name: "", type: "bool" }]
2226
- },
2227
- {
2228
- type: "function",
2229
- name: "balanceOf",
2230
- stateMutability: "view",
2231
- inputs: [{ name: "account", type: "address" }],
2232
- outputs: [{ name: "", type: "uint256" }]
2233
- }
2234
- ];
2235
- function buildPermitTransferFromTypedData(input) {
2236
- return {
2237
- domain: {
2238
- name: "Permit2",
2239
- chainId: input.chainId,
2240
- verifyingContract: PERMIT2_ADDRESS
2241
- },
2242
- types: {
2243
- PermitTransferFrom: [
2244
- { name: "permitted", type: "TokenPermissions" },
2245
- { name: "spender", type: "address" },
2246
- { name: "nonce", type: "uint256" },
2247
- { name: "deadline", type: "uint256" }
2248
- ],
2249
- TokenPermissions: [
2250
- { name: "token", type: "address" },
2251
- { name: "amount", type: "uint256" }
2252
- ]
2253
- },
2254
- primaryType: "PermitTransferFrom",
2255
- message: input.message
2256
- };
2257
- }
2258
- function randomPermit2Nonce() {
2259
- const bytes = new Uint8Array(32);
2260
- globalThis.crypto.getRandomValues(bytes);
2261
- return BigInt((0, import_viem4.bytesToHex)(bytes));
2262
- }
2263
- async function readPermit2Allowance(publicClient, token, owner) {
2264
- return publicClient.readContract({
2265
- address: token,
2266
- abi: ERC20_ALLOWANCE_ABI,
2267
- functionName: "allowance",
2268
- args: [owner, PERMIT2_ADDRESS]
2269
- });
2270
- }
2271
- async function readErc20Balance(publicClient, token, owner) {
2272
- return publicClient.readContract({
2273
- address: token,
2274
- abi: ERC20_ALLOWANCE_ABI,
2275
- functionName: "balanceOf",
2276
- args: [owner]
2277
- });
2278
- }
2898
+ // src/lib/sponsored-deposit.ts
2899
+ init_permit2();
2279
2900
 
2280
2901
  // src/lib/chain-guard.ts
2902
+ init_errors();
2281
2903
  var CHAIN_NAMES = {
2282
2904
  1: "Ethereum",
2283
2905
  8453: "Base",
@@ -2391,6 +3013,8 @@ function makeSponsoredDepositCallback(deps) {
2391
3013
  }
2392
3014
 
2393
3015
  // src/lib/sponsored-weth-deposit.ts
3016
+ init_errors();
3017
+ init_permit2();
2394
3018
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2395
3019
  function makeSponsoredWethCallback(deps) {
2396
3020
  const get = deps.httpGet ?? getSponsorRelayerAddress;
@@ -2475,6 +3099,7 @@ function makeSponsoredWethCallback(deps) {
2475
3099
 
2476
3100
  // src/lib/sponsored-calls-deposit.ts
2477
3101
  var import_viem5 = require("viem");
3102
+ init_errors();
2478
3103
  var DEFAULT_POLL_INTERVAL_MS = 1500;
2479
3104
  var DEFAULT_MAX_POLLS = 30;
2480
3105
  async function paymasterSupported(provider, owner, chainId) {
@@ -2565,6 +3190,7 @@ function makeSponsoredCallsCallback(deps) {
2565
3190
  }
2566
3191
 
2567
3192
  // src/client.ts
3193
+ init_permit2();
2568
3194
  function encodeMultiAgentCursor(map) {
2569
3195
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
2570
3196
  }
@@ -2591,10 +3217,11 @@ var SPONSORED_USDC_BY_CHAIN = {
2591
3217
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2592
3218
  };
2593
3219
  var VIEM_CHAIN2 = {
2594
- 8453: import_chains2.base,
2595
- 42161: import_chains2.arbitrum,
2596
- 1: import_chains2.mainnet
3220
+ 8453: import_chains3.base,
3221
+ 42161: import_chains3.arbitrum,
3222
+ 1: import_chains3.mainnet
2597
3223
  };
3224
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
2598
3225
  var SPONSORED_WETH_BY_CHAIN = {
2599
3226
  8453: "0x4200000000000000000000000000000000000006",
2600
3227
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2623,6 +3250,7 @@ var OwneySDK = class {
2623
3250
  zyfaiRpcUrls;
2624
3251
  routingApiBaseUrl;
2625
3252
  referralSource;
3253
+ surfliquidApiBaseUrl;
2626
3254
  cachedSponsoredCallback = null;
2627
3255
  cachedWethSponsoredCallback = null;
2628
3256
  paymasterServiceUrl;
@@ -2635,6 +3263,7 @@ var OwneySDK = class {
2635
3263
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2636
3264
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2637
3265
  this.referralSource = config.referralSource;
3266
+ this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2638
3267
  }
2639
3268
  /**
2640
3269
  * Establish connection state for all subsequent SDK calls.
@@ -2734,14 +3363,14 @@ var OwneySDK = class {
2734
3363
  // Casts work around viem's chain-narrowed Client vs the generic
2735
3364
  // PublicClient/WalletClient param types — structurally identical at
2736
3365
  // runtime, but the two share a name TS treats as unrelated.
2737
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
3366
+ getPublicClient: (cid) => (0, import_viem7.createPublicClient)({
2738
3367
  chain: VIEM_CHAIN2[cid],
2739
- transport: (0, import_viem6.custom)(provider)
3368
+ transport: (0, import_viem7.custom)(provider)
2740
3369
  }),
2741
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
3370
+ getWalletClient: (cid) => (0, import_viem7.createWalletClient)({
2742
3371
  account: owner,
2743
3372
  chain: VIEM_CHAIN2[cid],
2744
- transport: (0, import_viem6.custom)(provider)
3373
+ transport: (0, import_viem7.custom)(provider)
2745
3374
  })
2746
3375
  });
2747
3376
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2787,14 +3416,14 @@ var OwneySDK = class {
2787
3416
  // Casts work around viem's chain-narrowed Client vs the generic
2788
3417
  // PublicClient/WalletClient param types — structurally identical at
2789
3418
  // runtime, but the two share a name TS treats as unrelated.
2790
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
3419
+ getPublicClient: (cid) => (0, import_viem7.createPublicClient)({
2791
3420
  chain: VIEM_CHAIN2[cid],
2792
- transport: (0, import_viem6.custom)(provider)
3421
+ transport: (0, import_viem7.custom)(provider)
2793
3422
  }),
2794
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
3423
+ getWalletClient: (cid) => (0, import_viem7.createWalletClient)({
2795
3424
  account: owner,
2796
3425
  chain: VIEM_CHAIN2[cid],
2797
- transport: (0, import_viem6.custom)(provider)
3426
+ transport: (0, import_viem7.custom)(provider)
2798
3427
  })
2799
3428
  });
2800
3429
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2864,8 +3493,8 @@ var OwneySDK = class {
2864
3493
  this.initializingAgentsPromise = (async () => {
2865
3494
  const agentKeys = await fetchAgentKeys(this.apiKey);
2866
3495
  this.disabledAgents.clear();
2867
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
2868
- const agent = this.createAgent(agent_type, key2);
3496
+ for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
3497
+ const agent = await this.createAgent(agent_type, key2, project_name);
2869
3498
  if (!agent) continue;
2870
3499
  this.agents.set(agent_type, agent);
2871
3500
  if (is_enabled === false) {
@@ -2886,10 +3515,22 @@ var OwneySDK = class {
2886
3515
  this.initializingAgentsPromise = null;
2887
3516
  }
2888
3517
  }
2889
- createAgent(agentId, key2) {
3518
+ async createAgent(agentId, key2, projectName) {
2890
3519
  if (agentId === "zyfai") {
2891
3520
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2892
3521
  }
3522
+ if (agentId === "surfliquid") {
3523
+ if (!projectName) return null;
3524
+ const { SurfLiquidAgent: SurfLiquidAgent2 } = await Promise.resolve().then(() => (init_surfliquid_agent(), surfliquid_agent_exports));
3525
+ return new SurfLiquidAgent2({
3526
+ projectName,
3527
+ appId: key2,
3528
+ // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
3529
+ // which WebKit (iOS) discards when it is third-party. See
3530
+ // OwneySDKConfig.surfliquidApiBaseUrl.
3531
+ apiBaseUrl: this.surfliquidApiBaseUrl
3532
+ });
3533
+ }
2893
3534
  return null;
2894
3535
  }
2895
3536
  // --- Account lifecycle ---
@@ -3044,18 +3685,30 @@ var OwneySDK = class {
3044
3685
  );
3045
3686
  }
3046
3687
  const agentResults = {};
3688
+ const agentErrors = {};
3047
3689
  for (const { agent, amount: agentAmount } of agentAmounts) {
3048
- agentResults[agent.id] = await this.depositWithFallback(
3049
- agent,
3050
- state,
3051
- chainId,
3052
- agentAmount,
3053
- asset,
3054
- effectiveCallback,
3055
- depositCallback
3690
+ try {
3691
+ agentResults[agent.id] = await this.depositWithFallback(
3692
+ agent,
3693
+ state,
3694
+ chainId,
3695
+ agentAmount,
3696
+ asset,
3697
+ effectiveCallback,
3698
+ depositCallback
3699
+ );
3700
+ } catch (error) {
3701
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
3702
+ }
3703
+ }
3704
+ if (Object.keys(agentResults).length === 0) {
3705
+ throw new OwneyError(
3706
+ "DEPOSIT_ALL_FAILED",
3707
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
3708
+ { agentErrors }
3056
3709
  );
3057
3710
  }
3058
- return { agentResults };
3711
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
3059
3712
  }
3060
3713
  /**
3061
3714
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -3171,9 +3824,8 @@ var OwneySDK = class {
3171
3824
  async hasExistingBalance(agent, state, chainId, asset) {
3172
3825
  try {
3173
3826
  const balance = await agent.getBalances(state, chainId);
3174
- const target = asset.toLowerCase();
3175
3827
  const token = balance.tokens.find(
3176
- (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3828
+ (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
3177
3829
  );
3178
3830
  return !!token && Number(token.amount) > 0;
3179
3831
  } catch {
@@ -3212,6 +3864,34 @@ var OwneySDK = class {
3212
3864
  }
3213
3865
  return eligible;
3214
3866
  }
3867
+ /**
3868
+ * Agent ids the routing API provisioned for this org that support the given
3869
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3870
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
3871
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
3872
+ * wallet), so the UI can resolve which agent to use before the user connects.
3873
+ *
3874
+ * This is the source of truth for agent availability: an agent appears here
3875
+ * iff the routing API returned its key. No per-app feature flags.
3876
+ */
3877
+ async getEligibleAgentIds(chainId, asset) {
3878
+ try {
3879
+ await this.ensureAgentsInitialized();
3880
+ } catch (error) {
3881
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3882
+ return [];
3883
+ }
3884
+ throw error;
3885
+ }
3886
+ return [...this.agents.values()].filter((agent) => {
3887
+ const chainAssets = agent.supportedAssets.find(
3888
+ (sa) => sa.chainId === chainId
3889
+ );
3890
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3891
+ }).map((agent) => agent.id).sort(
3892
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3893
+ );
3894
+ }
3215
3895
  // --- Fund operations ---
3216
3896
  /**
3217
3897
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -3247,9 +3927,17 @@ var OwneySDK = class {
3247
3927
  }
3248
3928
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
3249
3929
  if (!amount) {
3930
+ const aggregated2 = await this.getBalances();
3931
+ const funded = projectAgentBalancesForAsset(
3932
+ eligibleAgents,
3933
+ aggregated2.agentBalances,
3934
+ chainId,
3935
+ asset,
3936
+ assetInfo.decimals
3937
+ ).filter((b) => b.balance > 0n);
3250
3938
  const results2 = {};
3251
3939
  const agentErrors2 = {};
3252
- for (const agent of eligibleAgents) {
3940
+ for (const { agent } of funded) {
3253
3941
  try {
3254
3942
  results2[agent.id] = await agent.withdraw(state, chainId, token);
3255
3943
  } catch (err) {
@@ -3266,9 +3954,10 @@ var OwneySDK = class {
3266
3954
  { asset, agentErrors: agentErrors2 }
3267
3955
  );
3268
3956
  }
3957
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
3269
3958
  return {
3270
3959
  agentResult: results2,
3271
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3960
+ totalWithdrawn: totalWithdrawn.toString()
3272
3961
  };
3273
3962
  }
3274
3963
  const requested = BigInt(amount);
@@ -3660,10 +4349,10 @@ var OwneySDK = class {
3660
4349
  );
3661
4350
  }
3662
4351
  const provider = this.requireConnectedProvider();
3663
- const wallet = (0, import_viem6.createWalletClient)({
4352
+ const wallet = (0, import_viem7.createWalletClient)({
3664
4353
  account: state.walletAddress,
3665
4354
  chain: VIEM_CHAIN2[chainId],
3666
- transport: (0, import_viem6.custom)(provider)
4355
+ transport: (0, import_viem7.custom)(provider)
3667
4356
  });
3668
4357
  const hash = await wallet.writeContract({
3669
4358
  address: token,
@@ -3673,9 +4362,9 @@ var OwneySDK = class {
3673
4362
  account: state.walletAddress,
3674
4363
  chain: VIEM_CHAIN2[chainId]
3675
4364
  });
3676
- const publicClient = (0, import_viem6.createPublicClient)({
4365
+ const publicClient = (0, import_viem7.createPublicClient)({
3677
4366
  chain: VIEM_CHAIN2[chainId],
3678
- transport: (0, import_viem6.custom)(provider)
4367
+ transport: (0, import_viem7.custom)(provider)
3679
4368
  });
3680
4369
  const receipt = await publicClient.waitForTransactionReceipt({
3681
4370
  hash,
@@ -3783,8 +4472,11 @@ var OwneySDK = class {
3783
4472
  }
3784
4473
  };
3785
4474
 
4475
+ // src/index.ts
4476
+ init_errors();
4477
+
3786
4478
  // src/agents/zyfai/zyfai.siwx.ts
3787
- var import_viem7 = require("viem");
4479
+ var import_viem8 = require("viem");
3788
4480
  var import_siwe = require("siwe");
3789
4481
  var import_sdk2 = require("@zyfai/sdk");
3790
4482
 
@@ -3911,7 +4603,7 @@ function buildSIWXConfig(deps) {
3911
4603
  issuedAt,
3912
4604
  toString() {
3913
4605
  return new import_siwe.SiweMessage({
3914
- address: (0, import_viem7.getAddress)(accountAddress),
4606
+ address: (0, import_viem8.getAddress)(accountAddress),
3915
4607
  chainId: numericChainId(chainId),
3916
4608
  domain,
3917
4609
  uri,