@owney/sdk 0.6.8-beta.3 → 0.6.9-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.
@@ -0,0 +1,385 @@
1
+ import {
2
+ OwneyError
3
+ } from "./chunk-GMNQBWUB.js";
4
+
5
+ // src/agents/surfliquid/surfliquid.agent.ts
6
+ import { SurfClient } from "@surf_liquid/core-sdk";
7
+ import { formatUnits } from "viem";
8
+
9
+ // src/agents/surfliquid/surfliquid.constants.ts
10
+ var SURFLIQUID_CHAIN_ID = 8453;
11
+ var SURFLIQUID_CHAIN_NAME = "BASE";
12
+ var SURFLIQUID_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
13
+ var SURFLIQUID_USDC_DECIMALS = 6;
14
+ var SURFLIQUID_MIN_DEPOSIT = "0";
15
+ var SURFLIQUID_SUPPORTED_ASSETS = [
16
+ {
17
+ chainId: SURFLIQUID_CHAIN_ID,
18
+ chain: SURFLIQUID_CHAIN_NAME,
19
+ assets: [{ symbol: "USDC", minDepositAmount: SURFLIQUID_MIN_DEPOSIT }]
20
+ }
21
+ ];
22
+ var SURFLIQUID_ACTION_MAP = {
23
+ // INITIAL_DEPOSIT is the user's real first deposit, so surface it like any
24
+ // other funding ("Top up"). The mini-app intentionally hides the "Deposit"
25
+ // action (it marks Zyfai's smart-account-creation event), which would
26
+ // otherwise drop a SurfLiquid user's only history row.
27
+ INITIAL_DEPOSIT: "Top up",
28
+ DEPOSIT: "Top up",
29
+ USER_DEPOSIT: "Top up",
30
+ WITHDRAWAL: "Withdraw",
31
+ USER_WITHDRAWAL: "Withdraw",
32
+ REBALANCE: "Rebalance",
33
+ REBALANCE_COMPLETED: "Rebalance",
34
+ CROSS_CHAIN_REBALANCE: "Rebalance",
35
+ MERKL_CLAIM: "Earned"
36
+ };
37
+
38
+ // src/agents/surfliquid/surfliquid.mapper.ts
39
+ function mapMessage(message) {
40
+ const action = SURFLIQUID_ACTION_MAP[message.transactionType];
41
+ if (!action) return null;
42
+ return {
43
+ agent: "surfliquid",
44
+ action,
45
+ date: message.timestamp,
46
+ oldApy: message.apyBefore != null ? String(message.apyBefore) : null,
47
+ newApy: message.apyAfter != null ? String(message.apyAfter) : null,
48
+ transactions: [
49
+ {
50
+ txHashes: [message.txHash],
51
+ chainId: message.chainId,
52
+ tokenSymbol: message.token ?? void 0,
53
+ amount: message.amount != null ? String(message.amount) : void 0
54
+ }
55
+ ],
56
+ rebalanceLog: []
57
+ };
58
+ }
59
+ function mapHistory(result, _chainId) {
60
+ const data = result.messages.map(mapMessage).filter((entry) => entry !== null);
61
+ const hasMore = result.page < result.pages;
62
+ return {
63
+ data,
64
+ hasMore,
65
+ nextCursor: hasMore ? String(result.page + 1) : void 0
66
+ };
67
+ }
68
+ function mapBalances(vault, chainId, morphoStats) {
69
+ const assets = (vault.assets ?? []).filter(
70
+ (asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId
71
+ );
72
+ const tokens = assets.map((asset) => ({
73
+ chain: SURFLIQUID_CHAIN_NAME,
74
+ chainId: SURFLIQUID_CHAIN_ID,
75
+ asset: "USDC",
76
+ amount: String(asset.currentValueUSD)
77
+ }));
78
+ const positions = assets.map((asset) => {
79
+ const stats = morphoStats?.get(asset.morphoVaultAddress?.toLowerCase() ?? "");
80
+ return {
81
+ chain: SURFLIQUID_CHAIN_NAME,
82
+ protocol: "Morpho",
83
+ // The specific MetaMorpho vault (e.g. "Gauntlet USDC Frontier"), read from
84
+ // Morpho — renders as "Morpho <vault>" alongside zyfai's vault detail.
85
+ pool: stats?.name ?? void 0,
86
+ asset: "USDC",
87
+ amount: String(asset.currentValueUSD),
88
+ apy: asset.currentAPY,
89
+ // TVL + liquidity come straight from Morpho (SurfLiquid's API omits them).
90
+ tvl: stats?.tvlUsd,
91
+ liquidity: stats?.liquidityUsd
92
+ };
93
+ });
94
+ const total = assets.reduce(
95
+ (sum, asset) => sum + asset.currentValueUSD,
96
+ 0
97
+ );
98
+ return {
99
+ smartWallet: vault.userVaultAddress ?? void 0,
100
+ totalBalance: String(total),
101
+ totalBalanceAsset: "usdc",
102
+ tokens,
103
+ positions
104
+ };
105
+ }
106
+ var APY_WINDOW_KEY = {
107
+ "7D": "apy7d",
108
+ "14D": "apy14d",
109
+ "30D": "apy30d"
110
+ };
111
+ function mapAccountApy(vault, days) {
112
+ const breakdown = vault.apyBreakdown;
113
+ const windowed = breakdown ? breakdown[APY_WINDOW_KEY[days]] : void 0;
114
+ const apy = windowed != null ? windowed : breakdown?.currentAPY ?? 0;
115
+ return {
116
+ walletAddress: vault.walletAddress ?? "",
117
+ weightedApyAfterFee: apy,
118
+ apyByChainAndAsset: { [SURFLIQUID_CHAIN_ID]: { USDC: apy } },
119
+ // Parity with Sail: SurfLiquid's core-sdk exposes no daily net-APY series.
120
+ history: []
121
+ };
122
+ }
123
+ function mapEarnings(vault, vaultAddress) {
124
+ return {
125
+ smartWallet: vaultAddress,
126
+ lifetimeEarnings: vault.earned?.totalEarningsUSD ?? 0,
127
+ tokens: []
128
+ };
129
+ }
130
+ function mapUserProfile(vault, address) {
131
+ return {
132
+ address,
133
+ smartWallet: vault.userVaultAddress ?? "",
134
+ chains: [SURFLIQUID_CHAIN_ID],
135
+ // SurfLiquid uses cookie-based session auth — no session-key concept.
136
+ hasActiveSessionKey: false,
137
+ protocols: ["SurfLiquid"]
138
+ };
139
+ }
140
+
141
+ // src/agents/surfliquid/surfliquid.wallet-adapter.ts
142
+ import { BaseWalletAdapter } from "@surf_liquid/core-sdk";
143
+ var OwneyWalletAdapter = class extends BaseWalletAdapter {
144
+ constructor(eip1193Provider) {
145
+ super();
146
+ this.eip1193Provider = eip1193Provider;
147
+ }
148
+ eip1193Provider;
149
+ name = "owney";
150
+ get installed() {
151
+ return true;
152
+ }
153
+ resolveProvider() {
154
+ return this.eip1193Provider;
155
+ }
156
+ };
157
+
158
+ // src/agents/surfliquid/surfliquid.morpho.ts
159
+ var MORPHO_API_URL = "https://api.morpho.org/graphql";
160
+ var VAULT_STATS_QUERY = `
161
+ query VaultStats($address: String!, $chainId: Int!) {
162
+ vaultByAddress(address: $address, chainId: $chainId) {
163
+ name
164
+ state { totalAssetsUsd }
165
+ liquidity { usd }
166
+ }
167
+ }
168
+ `;
169
+ async function fetchMorphoVaultStats(vaultAddress, chainId) {
170
+ try {
171
+ const res = await fetch(MORPHO_API_URL, {
172
+ method: "POST",
173
+ headers: { "Content-Type": "application/json" },
174
+ body: JSON.stringify({
175
+ query: VAULT_STATS_QUERY,
176
+ variables: { address: vaultAddress, chainId }
177
+ })
178
+ });
179
+ if (!res.ok) return null;
180
+ const json = await res.json();
181
+ if (json.errors) return null;
182
+ const vault = json.data?.vaultByAddress;
183
+ const tvlUsd = vault?.state?.totalAssetsUsd;
184
+ const liquidityUsd = vault?.liquidity?.usd;
185
+ if (typeof tvlUsd !== "number" || typeof liquidityUsd !== "number") {
186
+ return null;
187
+ }
188
+ const name = typeof vault?.name === "string" ? vault.name : null;
189
+ return { name, tvlUsd, liquidityUsd };
190
+ } catch (error) {
191
+ console.warn("surfliquid: Morpho vault stats fetch failed", {
192
+ vaultAddress,
193
+ chainId,
194
+ error
195
+ });
196
+ return null;
197
+ }
198
+ }
199
+ async function fetchMorphoStatsByVault(vaultAddresses, chainId) {
200
+ const unique = [...new Set(vaultAddresses.map((a) => a.toLowerCase()))];
201
+ const entries = await Promise.all(
202
+ unique.map(
203
+ async (address) => [address, await fetchMorphoVaultStats(address, chainId)]
204
+ )
205
+ );
206
+ const byVault = /* @__PURE__ */ new Map();
207
+ for (const [address, stats] of entries) {
208
+ if (stats) byVault.set(address, stats);
209
+ }
210
+ return byVault;
211
+ }
212
+
213
+ // src/agents/surfliquid/surfliquid.agent.ts
214
+ var APY_WINDOW_KEY2 = {
215
+ "7D": "apy7d",
216
+ "14D": "apy14d",
217
+ "30D": "apy30d"
218
+ };
219
+ var SurfLiquidAgent = class {
220
+ constructor(config) {
221
+ this.config = config;
222
+ }
223
+ config;
224
+ id = "surfliquid";
225
+ supportedChainIds = [SURFLIQUID_CHAIN_ID];
226
+ supportedAssets = SURFLIQUID_SUPPORTED_ASSETS;
227
+ surf = null;
228
+ connectedAddress = null;
229
+ // In-flight connect+auth, shared by concurrent callers to dedupe SIWE.
230
+ connectPromise = null;
231
+ ensureSurf() {
232
+ if (this.surf) return this.surf;
233
+ this.surf = SurfClient.create({
234
+ projectName: this.config.projectName,
235
+ appId: this.config.appId,
236
+ environment: "mainnet",
237
+ chainId: SURFLIQUID_CHAIN_ID,
238
+ apiBaseUrl: this.config.apiBaseUrl,
239
+ rpcUrl: this.config.rpcUrl,
240
+ // Let the SDK auto-approve USDC to the vault before depositing.
241
+ autoApprove: true
242
+ });
243
+ return this.surf;
244
+ }
245
+ /**
246
+ * Connects + authenticates the SurfClient against owney's provider. Skips the
247
+ * SIWE prompt when already authenticated for this wallet (cookie session).
248
+ */
249
+ async connect(state) {
250
+ const surf = this.ensureSurf();
251
+ const sameWallet = this.connectedAddress?.toLowerCase() === state.walletAddress.toLowerCase();
252
+ if (sameWallet && surf.getAuthState().authenticated) {
253
+ return surf;
254
+ }
255
+ if (this.connectPromise) return this.connectPromise;
256
+ this.connectPromise = (async () => {
257
+ surf.registerWalletAdapter(
258
+ "owney",
259
+ new OwneyWalletAdapter(state.provider)
260
+ );
261
+ await surf.connectWallet("owney");
262
+ await surf.authenticate();
263
+ this.connectedAddress = state.walletAddress;
264
+ return surf;
265
+ })();
266
+ try {
267
+ return await this.connectPromise;
268
+ } finally {
269
+ this.connectPromise = null;
270
+ }
271
+ }
272
+ /**
273
+ * Returns the user's vault address, deploying one on first use. SurfLiquid's
274
+ * deposit requires the vault to exist, so this must run before depositing.
275
+ * Existing vault → returned as-is; otherwise `deployVault()` is sent
276
+ * (user-pays) and the freshly deployed address is returned.
277
+ */
278
+ async resolveVaultAddress(surf, owner) {
279
+ const vault = await surf.getVault(owner).catch(() => null);
280
+ if (vault?.exists && vault.userVaultAddress) {
281
+ return vault.userVaultAddress;
282
+ }
283
+ const { vaultAddress } = await surf.deployVault();
284
+ return vaultAddress;
285
+ }
286
+ // --- IAgent: lifecycle ---
287
+ async disconnect() {
288
+ if (this.surf) {
289
+ await this.surf.disconnectWallet();
290
+ }
291
+ this.connectedAddress = null;
292
+ }
293
+ async activateAgent(state) {
294
+ await this.connect(state);
295
+ }
296
+ // --- IAgent: funds (user-pays; asset is always USDC, callback ignored) ---
297
+ async deposit(state, _chainId, amount, _asset, _depositCallback) {
298
+ const surf = await this.connect(state);
299
+ const human = formatUnits(BigInt(amount), SURFLIQUID_USDC_DECIMALS);
300
+ const smartWallet = await this.resolveVaultAddress(
301
+ surf,
302
+ state.walletAddress
303
+ );
304
+ const tx = await surf.deposit({
305
+ asset: SURFLIQUID_USDC_ADDRESS,
306
+ amount: human
307
+ });
308
+ await tx.wait();
309
+ return { txHash: tx.hash, smartWallet, amount };
310
+ }
311
+ async withdraw(state, _chainId, _token, amount) {
312
+ const surf = await this.connect(state);
313
+ const human = amount != null ? formatUnits(BigInt(amount), SURFLIQUID_USDC_DECIMALS) : void 0;
314
+ try {
315
+ const tx = await surf.withdraw({
316
+ asset: SURFLIQUID_USDC_ADDRESS,
317
+ amount: human
318
+ });
319
+ await tx.wait();
320
+ return {
321
+ txHash: tx.hash,
322
+ type: amount != null ? "partial" : "full",
323
+ amount: amount ?? "0"
324
+ };
325
+ } catch (error) {
326
+ throw new OwneyError(
327
+ "WITHDRAW_FAILED",
328
+ error instanceof Error ? error.message : "SurfLiquid withdraw failed.",
329
+ { chainId: SURFLIQUID_CHAIN_ID, amount },
330
+ this.id
331
+ );
332
+ }
333
+ }
334
+ // --- IAgent: portfolio reads ---
335
+ async getBalances(state, chainId) {
336
+ const surf = await this.connect(state);
337
+ const vault = await surf.getVault(state.walletAddress);
338
+ const morphoVaultAddresses = (vault.assets ?? []).filter((asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId).map((asset) => asset.morphoVaultAddress).filter((address) => Boolean(address));
339
+ const morphoStats = await fetchMorphoStatsByVault(
340
+ morphoVaultAddresses,
341
+ chainId
342
+ );
343
+ return mapBalances(vault, chainId, morphoStats);
344
+ }
345
+ async getEarnings(state) {
346
+ const surf = await this.connect(state);
347
+ const vault = await surf.getVault(state.walletAddress);
348
+ return mapEarnings(vault, vault.userVaultAddress ?? state.walletAddress);
349
+ }
350
+ async getAccountApy(state, _chainId, days, _tokenSymbol) {
351
+ const surf = await this.connect(state);
352
+ const vault = await surf.getVault(state.walletAddress);
353
+ return mapAccountApy(vault, days);
354
+ }
355
+ async getHistory(state, chainId, options) {
356
+ const surf = await this.connect(state);
357
+ const page = options?.cursor ? Number(options.cursor) : 1;
358
+ const limit = options?.limit ?? 10;
359
+ const result = await surf.getAgentMessages(
360
+ state.walletAddress,
361
+ page,
362
+ limit,
363
+ options?.fromDate,
364
+ options?.toDate
365
+ );
366
+ return mapHistory(result, chainId);
367
+ }
368
+ async getUserProfile(state) {
369
+ const surf = await this.connect(state);
370
+ const vault = await surf.getVault(state.walletAddress);
371
+ return mapUserProfile(vault, state.walletAddress);
372
+ }
373
+ // --- IAgent: discovery (no wallet) ---
374
+ async getAgentApy(days, _options) {
375
+ const surf = this.ensureSurf();
376
+ const assets = await surf.getSupportedAssets(SURFLIQUID_CHAIN_ID);
377
+ const usdc = assets.find((asset) => asset.assetSymbol === "USDC");
378
+ if (!usdc) return { averageApy: 0 };
379
+ const windowed = usdc[APY_WINDOW_KEY2[days]];
380
+ return { averageApy: windowed != null ? windowed : usdc.currentAPY };
381
+ }
382
+ };
383
+ export {
384
+ SurfLiquidAgent
385
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.6.8-beta.3",
3
+ "version": "0.6.9-beta.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -23,7 +23,9 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
+ "@surf_liquid/core-sdk": "^0.4.0",
26
27
  "@zyfai/sdk": "^0.2.41",
28
+ "ethers": "^6.17.0",
27
29
  "siwe": "^3.0.0",
28
30
  "viem": "^2.48.1"
29
31
  },