@owney/sdk 0.7.16-beta.5 → 0.7.16-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/chunk-5LU2SHO7.js +50 -0
- package/dist/chunk-DBRXNDSB.js +87 -0
- package/dist/chunk-GMNQBWUB.js +55 -0
- package/dist/chunk-QA7JUSDB.js +92 -0
- package/dist/chunk-V2LNMTE6.js +222 -0
- package/dist/chunk-VHQ4VXY7.js +39 -0
- package/dist/index.cjs +3374 -4015
- package/dist/index.d.cts +38 -146
- package/dist/index.d.ts +38 -146
- package/dist/index.js +663 -2554
- package/dist/routing-api-KAILDWMD.js +11 -0
- package/dist/surfliquid.agent-5GY7A6HY.js +532 -0
- package/dist/surfliquid.smart-account-ZTZV7HUD.js +125 -0
- package/dist/surfliquid.sponsorship-BFAL2O3B.js +14 -0
- package/package.json +5 -2
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERC20_ALLOWANCE_ABI
|
|
3
|
+
} from "./chunk-DBRXNDSB.js";
|
|
4
|
+
import {
|
|
5
|
+
OwneyError
|
|
6
|
+
} from "./chunk-GMNQBWUB.js";
|
|
7
|
+
import {
|
|
8
|
+
SURFLIQUID_ACTION_MAP,
|
|
9
|
+
SURFLIQUID_APPROVAL_TARGET,
|
|
10
|
+
SURFLIQUID_CHAIN_ID,
|
|
11
|
+
SURFLIQUID_CHAIN_NAME,
|
|
12
|
+
SURFLIQUID_SUPPORTED_ASSETS,
|
|
13
|
+
SURFLIQUID_USDC_ADDRESS,
|
|
14
|
+
SURFLIQUID_USDC_DECIMALS
|
|
15
|
+
} from "./chunk-VHQ4VXY7.js";
|
|
16
|
+
|
|
17
|
+
// src/agents/surfliquid/surfliquid.agent.ts
|
|
18
|
+
import { SurfClient } from "@surf_liquid/core-sdk";
|
|
19
|
+
import {
|
|
20
|
+
createPublicClient,
|
|
21
|
+
createWalletClient,
|
|
22
|
+
custom,
|
|
23
|
+
formatUnits
|
|
24
|
+
} from "viem";
|
|
25
|
+
import { base } from "viem/chains";
|
|
26
|
+
|
|
27
|
+
// src/agents/surfliquid/surfliquid.mapper.ts
|
|
28
|
+
function mapMessage(message) {
|
|
29
|
+
const action = SURFLIQUID_ACTION_MAP[message.transactionType];
|
|
30
|
+
if (!action) return null;
|
|
31
|
+
return {
|
|
32
|
+
agent: "surfliquid",
|
|
33
|
+
action,
|
|
34
|
+
date: message.timestamp,
|
|
35
|
+
oldApy: message.apyBefore != null ? String(message.apyBefore) : null,
|
|
36
|
+
newApy: message.apyAfter != null ? String(message.apyAfter) : null,
|
|
37
|
+
transactions: [
|
|
38
|
+
{
|
|
39
|
+
txHashes: [message.txHash],
|
|
40
|
+
chainId: message.chainId,
|
|
41
|
+
tokenSymbol: message.token ?? void 0,
|
|
42
|
+
amount: message.amount != null ? String(message.amount) : void 0
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
rebalanceLog: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function mapHistory(result, _chainId) {
|
|
49
|
+
const data = result.messages.map(mapMessage).filter((entry) => entry !== null);
|
|
50
|
+
const hasMore = result.page < result.pages;
|
|
51
|
+
return {
|
|
52
|
+
data,
|
|
53
|
+
hasMore,
|
|
54
|
+
nextCursor: hasMore ? String(result.page + 1) : void 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function mapBalances(vault, chainId, morphoStats) {
|
|
58
|
+
const assets = (vault.assets ?? []).filter(
|
|
59
|
+
(asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId
|
|
60
|
+
);
|
|
61
|
+
const tokens = assets.map((asset) => ({
|
|
62
|
+
chain: SURFLIQUID_CHAIN_NAME,
|
|
63
|
+
chainId: SURFLIQUID_CHAIN_ID,
|
|
64
|
+
asset: "USDC",
|
|
65
|
+
amount: String(asset.currentValueUSD)
|
|
66
|
+
}));
|
|
67
|
+
const positions = assets.map((asset) => {
|
|
68
|
+
const stats = morphoStats?.get(asset.morphoVaultAddress?.toLowerCase() ?? "");
|
|
69
|
+
return {
|
|
70
|
+
chain: SURFLIQUID_CHAIN_NAME,
|
|
71
|
+
protocol: "Morpho",
|
|
72
|
+
// The specific MetaMorpho vault (e.g. "Gauntlet USDC Frontier"), read from
|
|
73
|
+
// Morpho — renders as "Morpho <vault>" alongside zyfai's vault detail.
|
|
74
|
+
pool: stats?.name ?? void 0,
|
|
75
|
+
asset: "USDC",
|
|
76
|
+
amount: String(asset.currentValueUSD),
|
|
77
|
+
apy: asset.currentAPY,
|
|
78
|
+
// TVL + liquidity come straight from Morpho (SurfLiquid's API omits them).
|
|
79
|
+
tvl: stats?.tvlUsd,
|
|
80
|
+
liquidity: stats?.liquidityUsd
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
const total = assets.reduce(
|
|
84
|
+
(sum, asset) => sum + asset.currentValueUSD,
|
|
85
|
+
0
|
|
86
|
+
);
|
|
87
|
+
return {
|
|
88
|
+
smartWallet: vault.userVaultAddress ?? void 0,
|
|
89
|
+
totalBalance: String(total),
|
|
90
|
+
totalBalanceAsset: "usdc",
|
|
91
|
+
tokens,
|
|
92
|
+
positions
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
var APY_WINDOW_KEY = {
|
|
96
|
+
"7D": "apy7d",
|
|
97
|
+
"14D": "apy14d",
|
|
98
|
+
"30D": "apy30d"
|
|
99
|
+
};
|
|
100
|
+
function mapAccountApy(vault, days) {
|
|
101
|
+
const breakdown = vault.apyBreakdown;
|
|
102
|
+
const windowed = breakdown ? breakdown[APY_WINDOW_KEY[days]] : void 0;
|
|
103
|
+
const apy = windowed != null ? windowed : breakdown?.currentAPY ?? 0;
|
|
104
|
+
return {
|
|
105
|
+
walletAddress: vault.walletAddress ?? "",
|
|
106
|
+
weightedApyAfterFee: apy,
|
|
107
|
+
apyByChainAndAsset: { [SURFLIQUID_CHAIN_ID]: { USDC: apy } },
|
|
108
|
+
// Parity with Sail: SurfLiquid's core-sdk exposes no daily net-APY series.
|
|
109
|
+
history: []
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function mapEarnings(vault, vaultAddress) {
|
|
113
|
+
return {
|
|
114
|
+
smartWallet: vaultAddress,
|
|
115
|
+
lifetimeEarnings: vault.earned?.totalEarningsUSD ?? 0,
|
|
116
|
+
tokens: []
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function mapUserProfile(vault, address) {
|
|
120
|
+
return {
|
|
121
|
+
address,
|
|
122
|
+
smartWallet: vault.userVaultAddress ?? "",
|
|
123
|
+
chains: [SURFLIQUID_CHAIN_ID],
|
|
124
|
+
// SurfLiquid uses cookie-based session auth — no session-key concept.
|
|
125
|
+
hasActiveSessionKey: false,
|
|
126
|
+
protocols: ["SurfLiquid"]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/agents/surfliquid/surfliquid.wallet-adapter.ts
|
|
131
|
+
import { BaseWalletAdapter } from "@surf_liquid/core-sdk";
|
|
132
|
+
var OwneyWalletAdapter = class extends BaseWalletAdapter {
|
|
133
|
+
constructor(eip1193Provider) {
|
|
134
|
+
super();
|
|
135
|
+
this.eip1193Provider = eip1193Provider;
|
|
136
|
+
}
|
|
137
|
+
eip1193Provider;
|
|
138
|
+
name = "owney";
|
|
139
|
+
get installed() {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
resolveProvider() {
|
|
143
|
+
return this.eip1193Provider;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/agents/surfliquid/surfliquid.morpho.ts
|
|
148
|
+
var MORPHO_API_URL = "https://api.morpho.org/graphql";
|
|
149
|
+
var VAULT_STATS_QUERY = `
|
|
150
|
+
query VaultStats($address: String!, $chainId: Int!) {
|
|
151
|
+
vaultByAddress(address: $address, chainId: $chainId) {
|
|
152
|
+
name
|
|
153
|
+
state { totalAssetsUsd }
|
|
154
|
+
liquidity { usd }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
var VAULT_V2_STATS_QUERY = `
|
|
159
|
+
query VaultV2Stats($address: String!, $chainId: Int!) {
|
|
160
|
+
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
161
|
+
name
|
|
162
|
+
totalAssetsUsd
|
|
163
|
+
liquidityUsd
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
async function queryMorpho(query, vaultAddress, chainId) {
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch(MORPHO_API_URL, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "Content-Type": "application/json" },
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
query,
|
|
174
|
+
variables: { address: vaultAddress, chainId }
|
|
175
|
+
})
|
|
176
|
+
});
|
|
177
|
+
if (!res.ok) return null;
|
|
178
|
+
const json = await res.json();
|
|
179
|
+
if (json.errors) return null;
|
|
180
|
+
return json.data ?? null;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn("surfliquid: Morpho vault stats fetch failed", {
|
|
183
|
+
vaultAddress,
|
|
184
|
+
chainId,
|
|
185
|
+
error
|
|
186
|
+
});
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function toStats(name, tvlUsd, liquidityUsd) {
|
|
191
|
+
if (typeof tvlUsd !== "number" || typeof liquidityUsd !== "number") {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
name: typeof name === "string" ? name : null,
|
|
196
|
+
tvlUsd,
|
|
197
|
+
liquidityUsd
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function fetchMorphoVaultStats(vaultAddress, chainId) {
|
|
201
|
+
const v1 = await queryMorpho(VAULT_STATS_QUERY, vaultAddress, chainId);
|
|
202
|
+
const vault = v1?.vaultByAddress;
|
|
203
|
+
const v1Stats = toStats(
|
|
204
|
+
vault?.name,
|
|
205
|
+
vault?.state?.totalAssetsUsd,
|
|
206
|
+
vault?.liquidity?.usd
|
|
207
|
+
);
|
|
208
|
+
if (v1Stats) return v1Stats;
|
|
209
|
+
const v2 = await queryMorpho(VAULT_V2_STATS_QUERY, vaultAddress, chainId);
|
|
210
|
+
const vaultV2 = v2?.vaultV2ByAddress;
|
|
211
|
+
return toStats(vaultV2?.name, vaultV2?.totalAssetsUsd, vaultV2?.liquidityUsd);
|
|
212
|
+
}
|
|
213
|
+
async function fetchMorphoStatsByVault(vaultAddresses, chainId) {
|
|
214
|
+
const unique = [...new Set(vaultAddresses.map((a) => a.toLowerCase()))];
|
|
215
|
+
const entries = await Promise.all(
|
|
216
|
+
unique.map(
|
|
217
|
+
async (address) => [address, await fetchMorphoVaultStats(address, chainId)]
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
const byVault = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const [address, stats] of entries) {
|
|
222
|
+
if (stats) byVault.set(address, stats);
|
|
223
|
+
}
|
|
224
|
+
return byVault;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/agents/surfliquid/surfliquid.agent.ts
|
|
228
|
+
var APY_WINDOW_KEY2 = {
|
|
229
|
+
"7D": "apy7d",
|
|
230
|
+
"14D": "apy14d",
|
|
231
|
+
"30D": "apy30d"
|
|
232
|
+
};
|
|
233
|
+
var SurfLiquidAgent = class {
|
|
234
|
+
constructor(config) {
|
|
235
|
+
this.config = config;
|
|
236
|
+
}
|
|
237
|
+
config;
|
|
238
|
+
id = "surfliquid";
|
|
239
|
+
supportedChainIds = [SURFLIQUID_CHAIN_ID];
|
|
240
|
+
supportedAssets = SURFLIQUID_SUPPORTED_ASSETS;
|
|
241
|
+
surf = null;
|
|
242
|
+
connectedAddress = null;
|
|
243
|
+
// In-flight connect+auth, shared by concurrent callers to dedupe SIWE.
|
|
244
|
+
connectPromise = null;
|
|
245
|
+
ensureSurf() {
|
|
246
|
+
if (this.surf) return this.surf;
|
|
247
|
+
this.surf = SurfClient.create({
|
|
248
|
+
projectName: this.config.projectName,
|
|
249
|
+
appId: this.config.appId,
|
|
250
|
+
environment: "mainnet",
|
|
251
|
+
chainId: SURFLIQUID_CHAIN_ID,
|
|
252
|
+
apiBaseUrl: this.config.apiBaseUrl,
|
|
253
|
+
rpcUrl: this.config.rpcUrl,
|
|
254
|
+
// Let the SDK auto-approve USDC to the vault before depositing.
|
|
255
|
+
autoApprove: true
|
|
256
|
+
});
|
|
257
|
+
return this.surf;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Connects + authenticates the SurfClient against owney's provider. Skips the
|
|
261
|
+
* SIWE prompt when already authenticated for this wallet (cookie session).
|
|
262
|
+
*/
|
|
263
|
+
async connect(state) {
|
|
264
|
+
const surf = this.ensureSurf();
|
|
265
|
+
const sameWallet = this.connectedAddress?.toLowerCase() === state.walletAddress.toLowerCase();
|
|
266
|
+
if (sameWallet && surf.getAuthState().authenticated) {
|
|
267
|
+
return surf;
|
|
268
|
+
}
|
|
269
|
+
if (this.connectPromise) return this.connectPromise;
|
|
270
|
+
this.connectPromise = (async () => {
|
|
271
|
+
surf.registerWalletAdapter(
|
|
272
|
+
"owney",
|
|
273
|
+
new OwneyWalletAdapter(state.provider)
|
|
274
|
+
);
|
|
275
|
+
await surf.connectWallet("owney");
|
|
276
|
+
await surf.authenticate();
|
|
277
|
+
this.connectedAddress = state.walletAddress;
|
|
278
|
+
return surf;
|
|
279
|
+
})();
|
|
280
|
+
try {
|
|
281
|
+
return await this.connectPromise;
|
|
282
|
+
} finally {
|
|
283
|
+
this.connectPromise = null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Returns the user's vault address, deploying one on first use. SurfLiquid's
|
|
288
|
+
* deposit requires the vault to exist, so this must run before depositing.
|
|
289
|
+
* Existing vault → returned as-is; otherwise `deployVault()` is sent
|
|
290
|
+
* (user-pays) and the freshly deployed address is returned.
|
|
291
|
+
*/
|
|
292
|
+
async resolveVaultAddress(surf, owner) {
|
|
293
|
+
const vault = await surf.getVault(owner).catch(() => null);
|
|
294
|
+
if (vault?.exists && vault.userVaultAddress) {
|
|
295
|
+
return vault.userVaultAddress;
|
|
296
|
+
}
|
|
297
|
+
const { vaultAddress } = await surf.deployVault();
|
|
298
|
+
return vaultAddress;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Grants the user's vault a standing USDC allowance so SurfLiquid's
|
|
302
|
+
* `autoApprove` has nothing to do — see {@link SURFLIQUID_APPROVAL_TARGET}
|
|
303
|
+
* for why one prompt per deposit is worth removing.
|
|
304
|
+
*
|
|
305
|
+
* Fails OPEN on a read error: if the allowance can't be checked we simply
|
|
306
|
+
* return and let SurfLiquid's own per-deposit approve run, because a flaky
|
|
307
|
+
* RPC must never block a deposit. A failed or rejected APPROVE does
|
|
308
|
+
* propagate — the user declined a transaction, and letting SurfLiquid
|
|
309
|
+
* immediately ask again for a smaller one is exactly the double prompt this
|
|
310
|
+
* removes.
|
|
311
|
+
*/
|
|
312
|
+
async ensureVaultAllowance(state, vaultAddress, amount) {
|
|
313
|
+
const spender = vaultAddress;
|
|
314
|
+
const transport = custom(state.provider);
|
|
315
|
+
const publicClient = createPublicClient({ chain: base, transport });
|
|
316
|
+
let allowance;
|
|
317
|
+
try {
|
|
318
|
+
allowance = await publicClient.readContract({
|
|
319
|
+
address: SURFLIQUID_USDC_ADDRESS,
|
|
320
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
321
|
+
functionName: "allowance",
|
|
322
|
+
args: [state.walletAddress, spender]
|
|
323
|
+
});
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.warn(
|
|
326
|
+
"[owney-sdk] SurfLiquid allowance pre-check failed; deferring to its per-deposit approve:",
|
|
327
|
+
error instanceof Error ? error.message : String(error)
|
|
328
|
+
);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (allowance >= amount) return;
|
|
332
|
+
const target = amount > SURFLIQUID_APPROVAL_TARGET ? amount : SURFLIQUID_APPROVAL_TARGET;
|
|
333
|
+
const wallet = createWalletClient({
|
|
334
|
+
account: state.walletAddress,
|
|
335
|
+
chain: base,
|
|
336
|
+
transport
|
|
337
|
+
});
|
|
338
|
+
const hash = await wallet.writeContract({
|
|
339
|
+
address: SURFLIQUID_USDC_ADDRESS,
|
|
340
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
341
|
+
functionName: "approve",
|
|
342
|
+
args: [spender, target],
|
|
343
|
+
account: state.walletAddress,
|
|
344
|
+
chain: base
|
|
345
|
+
});
|
|
346
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
347
|
+
hash,
|
|
348
|
+
confirmations: 1
|
|
349
|
+
});
|
|
350
|
+
if (receipt.status !== "success") {
|
|
351
|
+
throw new Error(`SurfLiquid USDC approval reverted (tx ${hash})`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// --- IAgent: lifecycle ---
|
|
355
|
+
async disconnect() {
|
|
356
|
+
if (this.surf) {
|
|
357
|
+
await this.surf.disconnectWallet();
|
|
358
|
+
}
|
|
359
|
+
this.connectedAddress = null;
|
|
360
|
+
}
|
|
361
|
+
async activateAgent(state) {
|
|
362
|
+
await this.connect(state);
|
|
363
|
+
}
|
|
364
|
+
/** Their exported client, so `prepare`/`confirm` ride the SIWE cookie this session already holds. */
|
|
365
|
+
async vaultApi() {
|
|
366
|
+
const { HttpClient, VaultApiService } = await import("@surf_liquid/core-sdk");
|
|
367
|
+
return new VaultApiService(
|
|
368
|
+
new HttpClient({
|
|
369
|
+
baseUrl: this.config.apiBaseUrl ?? "https://api.surfliquid.com",
|
|
370
|
+
projectName: this.config.projectName,
|
|
371
|
+
projectId: this.config.projectName,
|
|
372
|
+
appId: this.config.appId ?? ""
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
async sponsoredWallet(state) {
|
|
377
|
+
if (!this.config.apiKey) return null;
|
|
378
|
+
const { createSponsoredWallet } = await import("./surfliquid.smart-account-ZTZV7HUD.js");
|
|
379
|
+
const { ROUTING_API_BASE_URL } = await import("./routing-api-KAILDWMD.js");
|
|
380
|
+
return createSponsoredWallet({
|
|
381
|
+
provider: state.provider,
|
|
382
|
+
ownerAddress: state.walletAddress,
|
|
383
|
+
// Apps rarely set this; without the default, sponsorship silently never runs in production.
|
|
384
|
+
routingApiBaseUrl: this.config.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
|
|
385
|
+
apiKey: this.config.apiKey,
|
|
386
|
+
rpcUrl: this.config.rpcUrl
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/** Null means nothing happened on-chain, so the caller is free to retry as user-pays. */
|
|
390
|
+
async trySponsoredDeposit(state, amount) {
|
|
391
|
+
const wallet = await this.sponsoredWallet(state).catch(() => null);
|
|
392
|
+
if (!wallet) return null;
|
|
393
|
+
const { depositSponsored, isSafeToRetry } = await import("./surfliquid.sponsorship-BFAL2O3B.js");
|
|
394
|
+
const { createSponsoredChain } = await import("./surfliquid.smart-account-ZTZV7HUD.js");
|
|
395
|
+
try {
|
|
396
|
+
const { txHash, vault } = await depositSponsored({
|
|
397
|
+
amount,
|
|
398
|
+
api: await this.vaultApi(),
|
|
399
|
+
chain: createSponsoredChain(this.config.rpcUrl),
|
|
400
|
+
wallet
|
|
401
|
+
});
|
|
402
|
+
return { txHash, smartWallet: vault };
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (!isSafeToRetry(error)) throw error;
|
|
405
|
+
console.warn(
|
|
406
|
+
"[owney-sdk] SurfLiquid sponsored deposit unavailable; falling back to user-pays:",
|
|
407
|
+
error instanceof Error ? error.message : String(error)
|
|
408
|
+
);
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async trySponsoredWithdraw(state, amount) {
|
|
413
|
+
const wallet = await this.sponsoredWallet(state).catch(() => null);
|
|
414
|
+
if (!wallet) return null;
|
|
415
|
+
const { withdrawSponsored, isSafeToRetry } = await import("./surfliquid.sponsorship-BFAL2O3B.js");
|
|
416
|
+
const { createSponsoredChain } = await import("./surfliquid.smart-account-ZTZV7HUD.js");
|
|
417
|
+
try {
|
|
418
|
+
const result = await withdrawSponsored({
|
|
419
|
+
amount: amount != null ? BigInt(amount) : void 0,
|
|
420
|
+
api: await this.vaultApi(),
|
|
421
|
+
chain: createSponsoredChain(this.config.rpcUrl),
|
|
422
|
+
wallet
|
|
423
|
+
});
|
|
424
|
+
return {
|
|
425
|
+
txHash: result.txHash,
|
|
426
|
+
type: amount != null ? "partial" : "full",
|
|
427
|
+
amount: result.amount
|
|
428
|
+
};
|
|
429
|
+
} catch (error) {
|
|
430
|
+
if (!isSafeToRetry(error)) throw error;
|
|
431
|
+
console.warn(
|
|
432
|
+
"[owney-sdk] SurfLiquid sponsored withdraw unavailable; falling back to user-pays:",
|
|
433
|
+
error instanceof Error ? error.message : String(error)
|
|
434
|
+
);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// --- IAgent: funds (gasless when sponsorship is reachable, else user-pays) ---
|
|
439
|
+
async deposit(state, _chainId, amount, _asset, _depositCallback) {
|
|
440
|
+
const surf = await this.connect(state);
|
|
441
|
+
const sponsored = await this.trySponsoredDeposit(state, BigInt(amount));
|
|
442
|
+
if (sponsored) return { ...sponsored, amount };
|
|
443
|
+
const human = formatUnits(BigInt(amount), SURFLIQUID_USDC_DECIMALS);
|
|
444
|
+
const smartWallet = await this.resolveVaultAddress(
|
|
445
|
+
surf,
|
|
446
|
+
state.walletAddress
|
|
447
|
+
);
|
|
448
|
+
await this.ensureVaultAllowance(state, smartWallet, BigInt(amount));
|
|
449
|
+
const tx = await surf.deposit({
|
|
450
|
+
asset: SURFLIQUID_USDC_ADDRESS,
|
|
451
|
+
amount: human
|
|
452
|
+
});
|
|
453
|
+
await tx.wait();
|
|
454
|
+
return { txHash: tx.hash, smartWallet, amount };
|
|
455
|
+
}
|
|
456
|
+
async withdraw(state, _chainId, _token, amount) {
|
|
457
|
+
const surf = await this.connect(state);
|
|
458
|
+
const sponsored = await this.trySponsoredWithdraw(state, amount);
|
|
459
|
+
if (sponsored) return sponsored;
|
|
460
|
+
const human = amount != null ? formatUnits(BigInt(amount), SURFLIQUID_USDC_DECIMALS) : void 0;
|
|
461
|
+
try {
|
|
462
|
+
const tx = await surf.withdraw({
|
|
463
|
+
asset: SURFLIQUID_USDC_ADDRESS,
|
|
464
|
+
amount: human
|
|
465
|
+
});
|
|
466
|
+
await tx.wait();
|
|
467
|
+
return {
|
|
468
|
+
txHash: tx.hash,
|
|
469
|
+
type: amount != null ? "partial" : "full",
|
|
470
|
+
amount: amount ?? "0"
|
|
471
|
+
};
|
|
472
|
+
} catch (error) {
|
|
473
|
+
throw new OwneyError(
|
|
474
|
+
"WITHDRAW_FAILED",
|
|
475
|
+
error instanceof Error ? error.message : "SurfLiquid withdraw failed.",
|
|
476
|
+
{ chainId: SURFLIQUID_CHAIN_ID, amount },
|
|
477
|
+
this.id
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// --- IAgent: portfolio reads ---
|
|
482
|
+
async getBalances(state, chainId) {
|
|
483
|
+
const surf = await this.connect(state);
|
|
484
|
+
const vault = await surf.getVault(state.walletAddress);
|
|
485
|
+
const morphoVaultAddresses = (vault.assets ?? []).filter((asset) => asset.assetSymbol === "USDC" && asset.chainId === chainId).map((asset) => asset.morphoVaultAddress).filter((address) => Boolean(address));
|
|
486
|
+
const morphoStats = await fetchMorphoStatsByVault(
|
|
487
|
+
morphoVaultAddresses,
|
|
488
|
+
chainId
|
|
489
|
+
);
|
|
490
|
+
return mapBalances(vault, chainId, morphoStats);
|
|
491
|
+
}
|
|
492
|
+
async getEarnings(state) {
|
|
493
|
+
const surf = await this.connect(state);
|
|
494
|
+
const vault = await surf.getVault(state.walletAddress);
|
|
495
|
+
return mapEarnings(vault, vault.userVaultAddress ?? state.walletAddress);
|
|
496
|
+
}
|
|
497
|
+
async getAccountApy(state, _chainId, days, _tokenSymbol) {
|
|
498
|
+
const surf = await this.connect(state);
|
|
499
|
+
const vault = await surf.getVault(state.walletAddress);
|
|
500
|
+
return mapAccountApy(vault, days);
|
|
501
|
+
}
|
|
502
|
+
async getHistory(state, chainId, options) {
|
|
503
|
+
const surf = await this.connect(state);
|
|
504
|
+
const page = options?.cursor ? Number(options.cursor) : 1;
|
|
505
|
+
const limit = options?.limit ?? 10;
|
|
506
|
+
const result = await surf.getAgentMessages(
|
|
507
|
+
state.walletAddress,
|
|
508
|
+
page,
|
|
509
|
+
limit,
|
|
510
|
+
options?.fromDate,
|
|
511
|
+
options?.toDate
|
|
512
|
+
);
|
|
513
|
+
return mapHistory(result, chainId);
|
|
514
|
+
}
|
|
515
|
+
async getUserProfile(state) {
|
|
516
|
+
const surf = await this.connect(state);
|
|
517
|
+
const vault = await surf.getVault(state.walletAddress);
|
|
518
|
+
return mapUserProfile(vault, state.walletAddress);
|
|
519
|
+
}
|
|
520
|
+
// --- IAgent: discovery (no wallet) ---
|
|
521
|
+
async getAgentApy(days, _options) {
|
|
522
|
+
const surf = this.ensureSurf();
|
|
523
|
+
const assets = await surf.getSupportedAssets(SURFLIQUID_CHAIN_ID);
|
|
524
|
+
const usdc = assets.find((asset) => asset.assetSymbol === "USDC");
|
|
525
|
+
if (!usdc) return { averageApy: 0 };
|
|
526
|
+
const windowed = usdc[APY_WINDOW_KEY2[days]];
|
|
527
|
+
return { averageApy: windowed != null ? windowed : usdc.currentAPY };
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
export {
|
|
531
|
+
SurfLiquidAgent
|
|
532
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SURFLIQUID_FACTORY_ABI,
|
|
3
|
+
SURFLIQUID_FACTORY_ADDRESS,
|
|
4
|
+
SURFLIQUID_VAULT_ABI,
|
|
5
|
+
SubmittedError,
|
|
6
|
+
USDC_ABI
|
|
7
|
+
} from "./chunk-V2LNMTE6.js";
|
|
8
|
+
import {
|
|
9
|
+
readTokenMeta
|
|
10
|
+
} from "./chunk-5LU2SHO7.js";
|
|
11
|
+
import {
|
|
12
|
+
SURFLIQUID_CHAIN_ID,
|
|
13
|
+
SURFLIQUID_USDC_ADDRESS
|
|
14
|
+
} from "./chunk-VHQ4VXY7.js";
|
|
15
|
+
|
|
16
|
+
// src/agents/surfliquid/surfliquid.smart-account.ts
|
|
17
|
+
import { createSmartAccountClient } from "permissionless";
|
|
18
|
+
import { toSimpleSmartAccount } from "permissionless/accounts";
|
|
19
|
+
import { createPimlicoClient } from "permissionless/clients/pimlico";
|
|
20
|
+
import {
|
|
21
|
+
createPublicClient,
|
|
22
|
+
createWalletClient,
|
|
23
|
+
custom,
|
|
24
|
+
http
|
|
25
|
+
} from "viem";
|
|
26
|
+
import { entryPoint08Address } from "viem/account-abstraction";
|
|
27
|
+
import { base } from "viem/chains";
|
|
28
|
+
var sponsorProxyUrl = (routingApiBaseUrl) => `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/sponsor/pimlico-rpc/${SURFLIQUID_CHAIN_ID}`;
|
|
29
|
+
function publicClientFor(rpcUrl) {
|
|
30
|
+
return createPublicClient({ chain: base, transport: http(rpcUrl) });
|
|
31
|
+
}
|
|
32
|
+
function createSponsoredChain(rpcUrl) {
|
|
33
|
+
const client = publicClientFor(rpcUrl);
|
|
34
|
+
return {
|
|
35
|
+
computeVaultAddress: (owner, salt) => client.readContract({
|
|
36
|
+
address: SURFLIQUID_FACTORY_ADDRESS,
|
|
37
|
+
abi: SURFLIQUID_FACTORY_ABI,
|
|
38
|
+
functionName: "computeVaultAddress",
|
|
39
|
+
args: [owner, salt]
|
|
40
|
+
}),
|
|
41
|
+
isDeployed: async (address) => {
|
|
42
|
+
const code = await client.getCode({ address });
|
|
43
|
+
return Boolean(code && code !== "0x");
|
|
44
|
+
},
|
|
45
|
+
readVaultOwner: (vault) => client.readContract({ address: vault, abi: SURFLIQUID_VAULT_ABI, functionName: "owner" }),
|
|
46
|
+
hasInitialDeposit: (vault, asset) => client.readContract({
|
|
47
|
+
address: vault,
|
|
48
|
+
abi: SURFLIQUID_VAULT_ABI,
|
|
49
|
+
functionName: "assetHasInitialDeposit",
|
|
50
|
+
args: [asset]
|
|
51
|
+
}),
|
|
52
|
+
usdcBalanceOf: (address) => client.readContract({
|
|
53
|
+
address: SURFLIQUID_USDC_ADDRESS,
|
|
54
|
+
abi: USDC_ABI,
|
|
55
|
+
functionName: "balanceOf",
|
|
56
|
+
args: [address]
|
|
57
|
+
}),
|
|
58
|
+
// Cast: viem's OP-stack tx union does not match the generic PublicClient
|
|
59
|
+
// the helper is typed against, though every method it uses is present.
|
|
60
|
+
readTokenMeta: () => readTokenMeta(client, SURFLIQUID_USDC_ADDRESS)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function pinAccount(provider, address) {
|
|
64
|
+
return {
|
|
65
|
+
...provider,
|
|
66
|
+
request: (args) => args.method === "eth_accounts" || args.method === "eth_requestAccounts" ? Promise.resolve([address]) : provider.request(args)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function createSponsoredWallet(input) {
|
|
70
|
+
const entryPoint = { address: entryPoint08Address, version: "0.8" };
|
|
71
|
+
const account = await toSimpleSmartAccount({
|
|
72
|
+
client: publicClientFor(input.rpcUrl),
|
|
73
|
+
owner: pinAccount(input.provider, input.ownerAddress),
|
|
74
|
+
entryPoint
|
|
75
|
+
});
|
|
76
|
+
const bundlerTransport = http(sponsorProxyUrl(input.routingApiBaseUrl), {
|
|
77
|
+
fetchOptions: { headers: { "x-owney-api-key": input.apiKey } }
|
|
78
|
+
});
|
|
79
|
+
const pimlico = createPimlicoClient({ transport: bundlerTransport, entryPoint });
|
|
80
|
+
const smartAccountClient = createSmartAccountClient({
|
|
81
|
+
account,
|
|
82
|
+
chain: base,
|
|
83
|
+
bundlerTransport,
|
|
84
|
+
paymaster: pimlico,
|
|
85
|
+
userOperation: {
|
|
86
|
+
estimateFeesPerGas: async () => (await pimlico.getUserOperationGasPrice()).fast
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const walletClient = createWalletClient({
|
|
90
|
+
account: input.ownerAddress,
|
|
91
|
+
chain: base,
|
|
92
|
+
transport: custom(input.provider)
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
smartAccountAddress: account.address,
|
|
96
|
+
ownerAddress: input.ownerAddress,
|
|
97
|
+
// The EOA signs, not the smart account: USDC verifies ECDSA from the token holder.
|
|
98
|
+
signTransferAuthorization: (typedData) => walletClient.signTypedData({
|
|
99
|
+
account: input.ownerAddress,
|
|
100
|
+
domain: typedData.domain,
|
|
101
|
+
types: typedData.types,
|
|
102
|
+
primaryType: typedData.primaryType,
|
|
103
|
+
message: typedData.message
|
|
104
|
+
}),
|
|
105
|
+
sendCalls: async (calls) => {
|
|
106
|
+
const userOpHash = await smartAccountClient.sendUserOperation({
|
|
107
|
+
calls: calls.map((c) => ({ to: c.to, data: c.data, value: 0n }))
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash: userOpHash });
|
|
111
|
+
return receipt.receipt.transactionHash;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new SubmittedError(
|
|
114
|
+
`user operation ${userOpHash} was submitted but its receipt never arrived`,
|
|
115
|
+
userOpHash
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
createSponsoredChain,
|
|
123
|
+
createSponsoredWallet,
|
|
124
|
+
pinAccount
|
|
125
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SubmittedError,
|
|
3
|
+
depositSponsored,
|
|
4
|
+
isSafeToRetry,
|
|
5
|
+
withdrawSponsored
|
|
6
|
+
} from "./chunk-V2LNMTE6.js";
|
|
7
|
+
import "./chunk-5LU2SHO7.js";
|
|
8
|
+
import "./chunk-VHQ4VXY7.js";
|
|
9
|
+
export {
|
|
10
|
+
SubmittedError,
|
|
11
|
+
depositSponsored,
|
|
12
|
+
isSafeToRetry,
|
|
13
|
+
withdrawSponsored
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owney/sdk",
|
|
3
|
-
"version": "0.7.16-beta.
|
|
3
|
+
"version": "0.7.16-beta.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"test:watch": "vitest"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@
|
|
26
|
+
"@surf_liquid/core-sdk": "^0.4.0",
|
|
27
|
+
"@zyfai/sdk": "0.2.52",
|
|
28
|
+
"ethers": "^6.17.0",
|
|
29
|
+
"permissionless": "^0.4.0",
|
|
27
30
|
"siwe": "^3.0.0",
|
|
28
31
|
"viem": "^2.48.1"
|
|
29
32
|
},
|