@owney/sdk 0.6.5-beta.3 → 0.6.6-beta.1
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/chunk-GMNQBWUB.js +55 -0
- package/dist/index.cjs +755 -699
- package/dist/index.d.cts +82 -144
- package/dist/index.d.ts +82 -144
- package/dist/index.js +265 -698
- package/dist/surfliquid.agent-EPFISJPR.js +390 -0
- package/package.json +4 -5
package/dist/index.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentChainIncompatibleError,
|
|
3
|
+
AgentNotFoundError,
|
|
4
|
+
NotConnectedError,
|
|
5
|
+
OwneyError
|
|
6
|
+
} from "./chunk-GMNQBWUB.js";
|
|
7
|
+
|
|
1
8
|
// src/agents/zyfai/zyfai.agent.ts
|
|
2
9
|
import { ZyfaiSDK } from "@zyfai/sdk";
|
|
3
10
|
import { createPublicClient, http } from "viem";
|
|
4
|
-
import { base, arbitrum
|
|
11
|
+
import { base, arbitrum } from "viem/chains";
|
|
5
12
|
|
|
6
13
|
// src/types/config.ts
|
|
7
14
|
var P_SUPPORTED_CHAINS = {
|
|
8
|
-
chainIds: [8453, 42161
|
|
9
|
-
chains: ["BASE", "ARBITRUM"
|
|
15
|
+
chainIds: [8453, 42161],
|
|
16
|
+
chains: ["BASE", "ARBITRUM"]
|
|
10
17
|
};
|
|
11
|
-
var SUPPORTED_TOKENS = ["USDC", "USDT"
|
|
18
|
+
var SUPPORTED_TOKENS = ["USDC", "USDT"];
|
|
12
19
|
var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
|
|
13
20
|
var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
|
|
14
21
|
var SupportedAssets = [
|
|
@@ -29,82 +36,9 @@ var SupportedAssets = [
|
|
|
29
36
|
symbol: "USDT",
|
|
30
37
|
chainId: 42161,
|
|
31
38
|
decimals: 6
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
address: `0x4200000000000000000000000000000000000006`,
|
|
35
|
-
symbol: "WETH",
|
|
36
|
-
chainId: 8453,
|
|
37
|
-
decimals: 18
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`,
|
|
41
|
-
symbol: "WETH",
|
|
42
|
-
chainId: 42161,
|
|
43
|
-
decimals: 18
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
address: `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`,
|
|
47
|
-
symbol: "USDC",
|
|
48
|
-
chainId: 1,
|
|
49
|
-
decimals: 6
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
address: `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`,
|
|
53
|
-
symbol: "WETH",
|
|
54
|
-
chainId: 1,
|
|
55
|
-
decimals: 18
|
|
56
39
|
}
|
|
57
40
|
];
|
|
58
41
|
|
|
59
|
-
// src/errors.ts
|
|
60
|
-
var OwneyError = class extends Error {
|
|
61
|
-
code;
|
|
62
|
-
details;
|
|
63
|
-
agentId;
|
|
64
|
-
constructor(code, message, details, agentId) {
|
|
65
|
-
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
66
|
-
super(`${prefix} ${message}`);
|
|
67
|
-
this.name = "OwneyError";
|
|
68
|
-
this.code = code;
|
|
69
|
-
this.details = details;
|
|
70
|
-
this.agentId = agentId;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
var AgentNotFoundError = class extends OwneyError {
|
|
74
|
-
constructor(agentId, available) {
|
|
75
|
-
super(
|
|
76
|
-
"AGENT_NOT_FOUND",
|
|
77
|
-
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
78
|
-
{ agentId, available },
|
|
79
|
-
agentId
|
|
80
|
-
);
|
|
81
|
-
this.name = "AgentNotFoundError";
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
var NotConnectedError = class extends OwneyError {
|
|
85
|
-
constructor() {
|
|
86
|
-
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
87
|
-
this.name = "NotConnectedError";
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
var AgentChainIncompatibleError = class extends OwneyError {
|
|
91
|
-
incompatibleAgents;
|
|
92
|
-
connectedChainId;
|
|
93
|
-
constructor(incompatibleAgents, connectedChainId) {
|
|
94
|
-
const details = incompatibleAgents.map(
|
|
95
|
-
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
96
|
-
).join("; ");
|
|
97
|
-
super(
|
|
98
|
-
"AGENT_CHAIN_INCOMPATIBLE",
|
|
99
|
-
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
100
|
-
{ incompatibleAgents, connectedChainId }
|
|
101
|
-
);
|
|
102
|
-
this.name = "AgentChainIncompatibleError";
|
|
103
|
-
this.incompatibleAgents = incompatibleAgents;
|
|
104
|
-
this.connectedChainId = connectedChainId;
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
42
|
// src/lib/utils.ts
|
|
109
43
|
var isValidChainId = (chainId) => {
|
|
110
44
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
|
@@ -119,7 +53,8 @@ var isValidChainId = (chainId) => {
|
|
|
119
53
|
function hexToDecimal(hex, decimals = 6) {
|
|
120
54
|
const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
|
|
121
55
|
const parsed = BigInt(normalized);
|
|
122
|
-
if (decimals <= 0)
|
|
56
|
+
if (decimals <= 0)
|
|
57
|
+
return Number(parsed);
|
|
123
58
|
const divisor = BigInt(10 ** decimals);
|
|
124
59
|
const whole = parsed / divisor;
|
|
125
60
|
const remainder = parsed % divisor;
|
|
@@ -132,20 +67,6 @@ var DayFilterMapping = {
|
|
|
132
67
|
};
|
|
133
68
|
|
|
134
69
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
135
|
-
var DECIMALS_BY_SYMBOL = {
|
|
136
|
-
usdc: 6,
|
|
137
|
-
usdt: 6,
|
|
138
|
-
weth: 18,
|
|
139
|
-
eth: 18,
|
|
140
|
-
cbeth: 18,
|
|
141
|
-
wbtc: 8,
|
|
142
|
-
cbbtc: 8
|
|
143
|
-
};
|
|
144
|
-
function tokenDecimals(symbol, explicit) {
|
|
145
|
-
if (typeof explicit === "number" && Number.isFinite(explicit))
|
|
146
|
-
return explicit;
|
|
147
|
-
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
148
|
-
}
|
|
149
70
|
function mapDeposit(raw) {
|
|
150
71
|
return {
|
|
151
72
|
txHash: raw.txHash,
|
|
@@ -162,8 +83,7 @@ function mapWithdraw(raw) {
|
|
|
162
83
|
}
|
|
163
84
|
var CHAIN_ID_TO_NAME = {
|
|
164
85
|
8453: "BASE",
|
|
165
|
-
42161: "ARBITRUM"
|
|
166
|
-
1: "ETHEREUM"
|
|
86
|
+
42161: "ARBITRUM"
|
|
167
87
|
};
|
|
168
88
|
var NAME_TO_CHAIN_ID = Object.entries(
|
|
169
89
|
CHAIN_ID_TO_NAME
|
|
@@ -173,52 +93,54 @@ var NAME_TO_CHAIN_ID = Object.entries(
|
|
|
173
93
|
}, {});
|
|
174
94
|
function resolveChainId(chain) {
|
|
175
95
|
const asNum = Number(chain);
|
|
176
|
-
if (Number.isFinite(asNum) && asNum > 0)
|
|
96
|
+
if (Number.isFinite(asNum) && asNum > 0)
|
|
97
|
+
return asNum;
|
|
177
98
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
178
99
|
}
|
|
179
100
|
function mapBalances(raw, _chainId, smartWallet) {
|
|
180
101
|
const portfolio = raw.portfolio;
|
|
181
102
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
103
|
+
const rawPositions = portfolio.positions ?? [];
|
|
104
|
+
const detailByKey = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const p of rawPositions) {
|
|
106
|
+
const cid = resolveChainId(p.chain ?? "");
|
|
107
|
+
const symbol = (p.token_symbol ?? "").toUpperCase();
|
|
108
|
+
if (Number.isFinite(cid) && symbol)
|
|
109
|
+
detailByKey.set(`${cid}:${symbol}`, p);
|
|
110
|
+
}
|
|
182
111
|
let totalBalance = 0;
|
|
183
112
|
const tokens = [];
|
|
113
|
+
const positions = [];
|
|
184
114
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
185
115
|
const positionChainId = Number(chainIdKey);
|
|
116
|
+
const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
|
|
186
117
|
for (const [symbol, { balance, decimals }] of Object.entries(
|
|
187
118
|
tokensBySymbol ?? {}
|
|
188
119
|
)) {
|
|
189
120
|
const amount = hexToDecimal(balance || "0", decimals);
|
|
190
121
|
totalBalance += amount;
|
|
191
122
|
tokens.push({
|
|
192
|
-
chain:
|
|
123
|
+
chain: chainName,
|
|
193
124
|
chainId: positionChainId,
|
|
194
125
|
asset: symbol,
|
|
195
126
|
amount: String(amount)
|
|
196
127
|
});
|
|
128
|
+
const detail = detailByKey.get(`${positionChainId}:${symbol.toUpperCase()}`);
|
|
129
|
+
positions.push({
|
|
130
|
+
chain: chainName,
|
|
131
|
+
protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
|
|
132
|
+
protocolId: detail?.protocol_id,
|
|
133
|
+
pool: detail?.pool,
|
|
134
|
+
asset: symbol,
|
|
135
|
+
amount: String(amount),
|
|
136
|
+
apy: detail?.pool_apy,
|
|
137
|
+
tvl: detail?.pool_tvl,
|
|
138
|
+
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
139
|
+
// structural read keeps it undefined today and auto-populates later.
|
|
140
|
+
liquidity: detail?.liquidity
|
|
141
|
+
});
|
|
197
142
|
}
|
|
198
143
|
}
|
|
199
|
-
const positions = (portfolio.positions ?? []).map((p) => ({
|
|
200
|
-
chain: p.chain ?? "",
|
|
201
|
-
protocol: p.protocol_name ?? p.protocol_id ?? "",
|
|
202
|
-
protocolId: p.protocol_id,
|
|
203
|
-
pool: p.pool,
|
|
204
|
-
asset: p.token_symbol ?? "",
|
|
205
|
-
// Position amount is hex-encoded base units (like the token balances
|
|
206
|
-
// above). `PositionSlot` doesn't type a `decimals` field, but runtime
|
|
207
|
-
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
208
|
-
// when present, otherwise fall back to the per-symbol lookup so
|
|
209
|
-
// non-6-decimal assets like WETH don't render 1e12 too large.
|
|
210
|
-
amount: String(
|
|
211
|
-
hexToDecimal(
|
|
212
|
-
p.amount || "0",
|
|
213
|
-
tokenDecimals(p.token_symbol, p.decimals)
|
|
214
|
-
)
|
|
215
|
-
),
|
|
216
|
-
apy: p.pool_apy,
|
|
217
|
-
tvl: p.pool_tvl,
|
|
218
|
-
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
219
|
-
// structural read keeps it undefined today and auto-populates later.
|
|
220
|
-
liquidity: p.liquidity
|
|
221
|
-
}));
|
|
222
144
|
return {
|
|
223
145
|
smartWallet,
|
|
224
146
|
totalBalance: String(totalBalance),
|
|
@@ -257,39 +179,41 @@ function mapEarnings(raw, smartWallet) {
|
|
|
257
179
|
};
|
|
258
180
|
}
|
|
259
181
|
function mapWeightedApyByChain(raw) {
|
|
260
|
-
if (!raw)
|
|
182
|
+
if (!raw)
|
|
183
|
+
return void 0;
|
|
261
184
|
const out = {};
|
|
262
185
|
for (const [chainKey, tokenApy] of Object.entries(raw)) {
|
|
263
186
|
const chainId = Number(chainKey);
|
|
264
|
-
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
187
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
188
|
+
continue;
|
|
265
189
|
const perAsset = {};
|
|
266
190
|
for (const [symbol, apy] of Object.entries(tokenApy)) {
|
|
267
191
|
const token = symbol;
|
|
268
|
-
if (!SUPPORTED_TOKENS.includes(token))
|
|
192
|
+
if (!SUPPORTED_TOKENS.includes(token))
|
|
193
|
+
continue;
|
|
269
194
|
perAsset[token] = apy;
|
|
270
195
|
}
|
|
271
|
-
if (Object.keys(perAsset).length > 0)
|
|
196
|
+
if (Object.keys(perAsset).length > 0)
|
|
197
|
+
out[chainId] = perAsset;
|
|
272
198
|
}
|
|
273
199
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
274
200
|
}
|
|
275
|
-
function rawPoolApyForChain(entry, chainId
|
|
201
|
+
function rawPoolApyForChain(entry, chainId) {
|
|
276
202
|
let weightedSum = 0;
|
|
277
203
|
let totalBalance = 0;
|
|
278
204
|
for (const p of entry.positions ?? []) {
|
|
279
|
-
if (p.chainId !== chainId)
|
|
280
|
-
|
|
205
|
+
if (p.chainId !== chainId)
|
|
206
|
+
continue;
|
|
281
207
|
const balance = p.balance ?? 0;
|
|
282
|
-
if (balance <= 0 || typeof p.apy !== "number")
|
|
208
|
+
if (balance <= 0 || typeof p.apy !== "number")
|
|
209
|
+
continue;
|
|
283
210
|
weightedSum += p.apy * balance;
|
|
284
211
|
totalBalance += balance;
|
|
285
212
|
}
|
|
286
213
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
287
214
|
}
|
|
288
|
-
function mapApyHistory(raw, chainId
|
|
289
|
-
const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
|
|
290
|
-
date,
|
|
291
|
-
apy: rawPoolApyForChain(entry, chainId, tokenSymbol)
|
|
292
|
-
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
215
|
+
function mapApyHistory(raw, chainId) {
|
|
216
|
+
const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({ date, apy: rawPoolApyForChain(entry, chainId) })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
293
217
|
return {
|
|
294
218
|
walletAddress: raw.walletAddress,
|
|
295
219
|
weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
|
|
@@ -312,7 +236,6 @@ function mapEntries(rawEntries, chainId) {
|
|
|
312
236
|
"Withdraw from protocol": "Withdraw",
|
|
313
237
|
Rebalance: "Rebalance"
|
|
314
238
|
};
|
|
315
|
-
void chainId;
|
|
316
239
|
return rawEntries.map((entry) => {
|
|
317
240
|
const rawLog = entry.rebalanceLog;
|
|
318
241
|
const positions = entry.positions ?? [];
|
|
@@ -334,15 +257,7 @@ function mapEntries(rawEntries, chainId) {
|
|
|
334
257
|
fromPool: from.pool,
|
|
335
258
|
toPool: to.pool,
|
|
336
259
|
tokenSymbol: matched?.token_symbol ?? "",
|
|
337
|
-
amount: matched ? String(
|
|
338
|
-
hexToDecimal(
|
|
339
|
-
matched.amount || "0x",
|
|
340
|
-
tokenDecimals(
|
|
341
|
-
matched.token_symbol,
|
|
342
|
-
matched.decimals
|
|
343
|
-
)
|
|
344
|
-
)
|
|
345
|
-
) : "",
|
|
260
|
+
amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
|
|
346
261
|
status: "success"
|
|
347
262
|
}
|
|
348
263
|
];
|
|
@@ -361,12 +276,7 @@ function mapEntries(rawEntries, chainId) {
|
|
|
361
276
|
txHashes: entry.transactionHash ? [entry.transactionHash] : [],
|
|
362
277
|
chainId: entry.chainId,
|
|
363
278
|
tokenSymbol: entry?.positions?.[0]?.token_symbol,
|
|
364
|
-
amount: hasAmount ? String(
|
|
365
|
-
hexToDecimal(
|
|
366
|
-
rawAmount,
|
|
367
|
-
tokenDecimals(entry?.positions?.[0]?.token_symbol)
|
|
368
|
-
)
|
|
369
|
-
) : void 0
|
|
279
|
+
amount: hasAmount ? String(hexToDecimal(rawAmount)) : void 0
|
|
370
280
|
}
|
|
371
281
|
],
|
|
372
282
|
rebalanceLog
|
|
@@ -391,7 +301,8 @@ function mapApyByStrategy(raw) {
|
|
|
391
301
|
const supported = SupportedAssets.find(
|
|
392
302
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
393
303
|
);
|
|
394
|
-
if (!supported)
|
|
304
|
+
if (!supported)
|
|
305
|
+
continue;
|
|
395
306
|
const chainKey = supported.chainId;
|
|
396
307
|
const symbolKey = supported.symbol;
|
|
397
308
|
const bucket = apyPerAsset[chainKey] ?? {};
|
|
@@ -421,8 +332,10 @@ function computeAllocationApy(positions) {
|
|
|
421
332
|
totalValue += value;
|
|
422
333
|
const chainId = resolveChainId(p.chain);
|
|
423
334
|
const token = p.asset;
|
|
424
|
-
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
425
|
-
|
|
335
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
336
|
+
continue;
|
|
337
|
+
if (!SUPPORTED_TOKENS.includes(token))
|
|
338
|
+
continue;
|
|
426
339
|
const perAsset = buckets[chainId] ?? {};
|
|
427
340
|
const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
|
|
428
341
|
cell.weightedSum += apy * value;
|
|
@@ -439,7 +352,8 @@ function computeAllocationApy(positions) {
|
|
|
439
352
|
out[token] = cell.weightedSum / cell.value;
|
|
440
353
|
}
|
|
441
354
|
}
|
|
442
|
-
if (Object.keys(out).length > 0)
|
|
355
|
+
if (Object.keys(out).length > 0)
|
|
356
|
+
apyByChainAndAsset[chainId] = out;
|
|
443
357
|
}
|
|
444
358
|
const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
|
|
445
359
|
return { totalApy, apyByChainAndAsset };
|
|
@@ -456,14 +370,19 @@ function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
|
456
370
|
let total = 0n;
|
|
457
371
|
let matched = false;
|
|
458
372
|
for (const log of logs) {
|
|
459
|
-
if (log.address.toLowerCase() !== wantToken)
|
|
460
|
-
|
|
461
|
-
if (
|
|
462
|
-
|
|
373
|
+
if (log.address.toLowerCase() !== wantToken)
|
|
374
|
+
continue;
|
|
375
|
+
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
|
|
376
|
+
continue;
|
|
377
|
+
if (topicToAddress(log.topics[2]) !== wantRecipient)
|
|
378
|
+
continue;
|
|
379
|
+
if (!log.data || log.data === "0x")
|
|
380
|
+
continue;
|
|
463
381
|
total += BigInt(log.data);
|
|
464
382
|
matched = true;
|
|
465
383
|
}
|
|
466
|
-
if (!matched)
|
|
384
|
+
if (!matched)
|
|
385
|
+
return void 0;
|
|
467
386
|
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
468
387
|
}
|
|
469
388
|
|
|
@@ -479,7 +398,8 @@ function encodeHistoryCursor(payload) {
|
|
|
479
398
|
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
480
399
|
}
|
|
481
400
|
function decodeHistoryCursor(token) {
|
|
482
|
-
if (!token)
|
|
401
|
+
if (!token)
|
|
402
|
+
throw new InvalidHistoryCursorError("Cursor is empty.");
|
|
483
403
|
let parsed;
|
|
484
404
|
try {
|
|
485
405
|
const json = Buffer.from(token, "base64").toString("utf8");
|
|
@@ -496,7 +416,8 @@ function decodeHistoryCursor(token) {
|
|
|
496
416
|
// src/agents/zyfai/zyfai.auth-cache.ts
|
|
497
417
|
var KEY_PREFIX = "owney.zyfai.session";
|
|
498
418
|
var storage = () => {
|
|
499
|
-
if (typeof window === "undefined")
|
|
419
|
+
if (typeof window === "undefined")
|
|
420
|
+
return null;
|
|
500
421
|
try {
|
|
501
422
|
return window.localStorage;
|
|
502
423
|
} catch {
|
|
@@ -506,13 +427,15 @@ var storage = () => {
|
|
|
506
427
|
var buildKey = (address, chainId) => `${KEY_PREFIX}:${address.toLowerCase()}:${chainId}`;
|
|
507
428
|
var isJwtExpired = (token) => {
|
|
508
429
|
const parts = token.split(".");
|
|
509
|
-
if (parts.length !== 3)
|
|
430
|
+
if (parts.length !== 3)
|
|
431
|
+
return false;
|
|
510
432
|
try {
|
|
511
433
|
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
512
434
|
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
513
435
|
const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
514
436
|
const claims = JSON.parse(decoded);
|
|
515
|
-
if (typeof claims.exp !== "number")
|
|
437
|
+
if (typeof claims.exp !== "number")
|
|
438
|
+
return false;
|
|
516
439
|
return Date.now() / 1e3 >= claims.exp - 30;
|
|
517
440
|
} catch {
|
|
518
441
|
return false;
|
|
@@ -520,9 +443,11 @@ var isJwtExpired = (token) => {
|
|
|
520
443
|
};
|
|
521
444
|
var readSession = (address, chainId) => {
|
|
522
445
|
const store = storage();
|
|
523
|
-
if (!store)
|
|
446
|
+
if (!store)
|
|
447
|
+
return null;
|
|
524
448
|
const raw = store.getItem(buildKey(address, chainId));
|
|
525
|
-
if (!raw)
|
|
449
|
+
if (!raw)
|
|
450
|
+
return null;
|
|
526
451
|
try {
|
|
527
452
|
const parsed = JSON.parse(raw);
|
|
528
453
|
if (!parsed.token || isJwtExpired(parsed.token)) {
|
|
@@ -537,7 +462,8 @@ var readSession = (address, chainId) => {
|
|
|
537
462
|
};
|
|
538
463
|
var writeSession = (address, chainId, session) => {
|
|
539
464
|
const store = storage();
|
|
540
|
-
if (!store)
|
|
465
|
+
if (!store)
|
|
466
|
+
return;
|
|
541
467
|
try {
|
|
542
468
|
store.setItem(buildKey(address, chainId), JSON.stringify(session));
|
|
543
469
|
} catch {
|
|
@@ -545,67 +471,33 @@ var writeSession = (address, chainId, session) => {
|
|
|
545
471
|
};
|
|
546
472
|
var clearSession = (address, chainId) => {
|
|
547
473
|
const store = storage();
|
|
548
|
-
if (!store)
|
|
474
|
+
if (!store)
|
|
475
|
+
return;
|
|
549
476
|
try {
|
|
550
477
|
store.removeItem(buildKey(address, chainId));
|
|
551
478
|
} catch {
|
|
552
479
|
}
|
|
553
480
|
};
|
|
554
481
|
|
|
555
|
-
// src/agents/zyfai/autoSelect.ts
|
|
556
|
-
function buildAutoSelectUpdate(current, asset) {
|
|
557
|
-
if (current.autoSelectProtocols === true) return null;
|
|
558
|
-
return {
|
|
559
|
-
asset,
|
|
560
|
-
autoSelectProtocols: true,
|
|
561
|
-
...current.chains ? { chains: current.chains } : {}
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
|
|
565
482
|
// src/agents/zyfai/zyfai.agent.ts
|
|
566
483
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
567
484
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
568
|
-
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
569
|
-
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
570
|
-
};
|
|
571
|
-
var WETH_ADDRESS_BY_CHAIN = {
|
|
572
|
-
8453: "0x4200000000000000000000000000000000000006",
|
|
573
|
-
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
574
|
-
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
485
|
+
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
575
486
|
};
|
|
576
|
-
var VIEM_CHAIN = { 8453: base, 42161: arbitrum
|
|
487
|
+
var VIEM_CHAIN = { 8453: base, 42161: arbitrum };
|
|
577
488
|
var ZyfaiAgent = class {
|
|
578
489
|
id = "zyfai";
|
|
579
|
-
supportedChainIds = [8453, 42161
|
|
490
|
+
supportedChainIds = [8453, 42161];
|
|
580
491
|
supportedAssets = [
|
|
581
492
|
{
|
|
582
493
|
chainId: 8453,
|
|
583
494
|
chain: "BASE",
|
|
584
|
-
assets: [
|
|
585
|
-
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
586
|
-
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
587
|
-
]
|
|
495
|
+
assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
|
|
588
496
|
},
|
|
589
497
|
{
|
|
590
498
|
chainId: 42161,
|
|
591
499
|
chain: "ARBITRUM",
|
|
592
|
-
assets: [
|
|
593
|
-
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
594
|
-
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
595
|
-
]
|
|
596
|
-
},
|
|
597
|
-
{
|
|
598
|
-
chainId: 1,
|
|
599
|
-
chain: "ETHEREUM",
|
|
600
|
-
// Zyfai enforces per-asset minimum portfolios on Ethereum mainnet, keyed
|
|
601
|
-
// by MIN_PORTFOLIO_BALANCE[1] in @zyfai/sdk (mainnet gas only pays off on
|
|
602
|
-
// large positions). USDC: 10,000 (10_000 * 1e6). WETH: 5 (5 * 1e18).
|
|
603
|
-
// NOTE: the @zyfai/sdk README claims WETH has no minimum — that's stale;
|
|
604
|
-
// the shipped code enforces 5 WETH, so match it or deposits reject on-chain.
|
|
605
|
-
assets: [
|
|
606
|
-
{ symbol: "USDC", minDepositAmount: "10000000000" },
|
|
607
|
-
{ symbol: "WETH", minDepositAmount: "5000000000000000000" }
|
|
608
|
-
]
|
|
500
|
+
assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
|
|
609
501
|
}
|
|
610
502
|
];
|
|
611
503
|
sdk;
|
|
@@ -629,7 +521,8 @@ var ZyfaiAgent = class {
|
|
|
629
521
|
}
|
|
630
522
|
getPublicClient(chainId) {
|
|
631
523
|
const cached = this.publicClients.get(chainId);
|
|
632
|
-
if (cached)
|
|
524
|
+
if (cached)
|
|
525
|
+
return cached;
|
|
633
526
|
const client = createPublicClient({
|
|
634
527
|
chain: VIEM_CHAIN[chainId],
|
|
635
528
|
transport: http(this.rpcUrls[chainId])
|
|
@@ -639,43 +532,33 @@ var ZyfaiAgent = class {
|
|
|
639
532
|
}
|
|
640
533
|
/**
|
|
641
534
|
* Fills in the true withdrawn amount for `Withdraw` entries by decoding the
|
|
642
|
-
*
|
|
535
|
+
* USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
|
|
643
536
|
* payload only reports the *remaining* position for a partial withdraw (see
|
|
644
537
|
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
645
538
|
* balance instead of the amount withdrawn.
|
|
646
539
|
*
|
|
647
|
-
* The withdrawn asset varies per entry (USDC or WETH), so it's resolved
|
|
648
|
-
* from `tx.tokenSymbol` (set by `mapEntries` from the entry's own
|
|
649
|
-
* position) rather than assumed to always be USDC. Zyfai sometimes
|
|
650
|
-
* denominates WETH as lowercase "eth" — that's normalized to WETH.
|
|
651
|
-
* Entries with no symbol (older payload shapes) fall back to USDC to
|
|
652
|
-
* preserve prior behavior.
|
|
653
|
-
*
|
|
654
540
|
* Mutates `entries` in place. Runs all lookups concurrently and is tolerant
|
|
655
541
|
* of RPC failures: an entry whose amount can't be resolved keeps
|
|
656
542
|
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
657
543
|
* rather than a wrong number.
|
|
658
544
|
*/
|
|
659
545
|
async resolveWithdrawnAmounts(entries, chainId, recipient) {
|
|
546
|
+
const asset = SupportedAssets.find(
|
|
547
|
+
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
548
|
+
);
|
|
549
|
+
if (!asset)
|
|
550
|
+
return;
|
|
660
551
|
const withdrawals = entries.filter(
|
|
661
552
|
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
662
553
|
);
|
|
663
|
-
if (withdrawals.length === 0)
|
|
554
|
+
if (withdrawals.length === 0)
|
|
555
|
+
return;
|
|
664
556
|
const client = this.getPublicClient(chainId);
|
|
665
557
|
await Promise.all(
|
|
666
558
|
withdrawals.map(async (entry) => {
|
|
667
559
|
const tx = entry.transactions[0];
|
|
668
560
|
const txHash = tx.txHashes[0];
|
|
669
|
-
const
|
|
670
|
-
const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
|
|
671
|
-
const asset = SupportedAssets.find(
|
|
672
|
-
(a) => a.chainId === chainId && a.symbol === normalizedSymbol
|
|
673
|
-
) ?? SupportedAssets.find(
|
|
674
|
-
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
675
|
-
);
|
|
676
|
-
if (!asset) return;
|
|
677
|
-
const cacheKey = `${txHash}:${asset.address}`;
|
|
678
|
-
const cached = this.withdrawAmountCache.get(cacheKey);
|
|
561
|
+
const cached = this.withdrawAmountCache.get(txHash);
|
|
679
562
|
if (cached !== void 0) {
|
|
680
563
|
tx.amount = cached;
|
|
681
564
|
return;
|
|
@@ -691,7 +574,7 @@ var ZyfaiAgent = class {
|
|
|
691
574
|
asset.decimals
|
|
692
575
|
);
|
|
693
576
|
if (amount !== void 0) {
|
|
694
|
-
this.withdrawAmountCache.set(
|
|
577
|
+
this.withdrawAmountCache.set(txHash, amount);
|
|
695
578
|
tx.amount = amount;
|
|
696
579
|
}
|
|
697
580
|
} catch {
|
|
@@ -710,11 +593,13 @@ var ZyfaiAgent = class {
|
|
|
710
593
|
* for some apps and the patch only matters in the browser anyway.
|
|
711
594
|
*/
|
|
712
595
|
installAuthCache() {
|
|
713
|
-
if (typeof window === "undefined")
|
|
596
|
+
if (typeof window === "undefined")
|
|
597
|
+
return;
|
|
714
598
|
const internals = this.sdk;
|
|
715
599
|
const original = internals.authenticateUser.bind(internals);
|
|
716
600
|
internals.authenticateUser = async () => {
|
|
717
|
-
if (internals.authenticatedUserId !== null)
|
|
601
|
+
if (internals.authenticatedUserId !== null)
|
|
602
|
+
return;
|
|
718
603
|
const address = internals.walletClient?.account?.address;
|
|
719
604
|
const chainId = internals.currentChainId;
|
|
720
605
|
if (address && chainId) {
|
|
@@ -789,10 +674,12 @@ var ZyfaiAgent = class {
|
|
|
789
674
|
return await this.sdk.connectAccount(provider, chainId);
|
|
790
675
|
} catch (error) {
|
|
791
676
|
const message = error instanceof Error ? error.message : String(error);
|
|
792
|
-
if (!message.toLowerCase().includes("unauthorized"))
|
|
677
|
+
if (!message.toLowerCase().includes("unauthorized"))
|
|
678
|
+
throw error;
|
|
793
679
|
const internals = this.sdk;
|
|
794
680
|
const address = internals.walletClient?.account?.address;
|
|
795
|
-
if (address)
|
|
681
|
+
if (address)
|
|
682
|
+
clearSession(address, chainId);
|
|
796
683
|
return await this.sdk.connectAccount(provider, chainId);
|
|
797
684
|
}
|
|
798
685
|
}
|
|
@@ -805,22 +692,6 @@ var ZyfaiAgent = class {
|
|
|
805
692
|
this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
|
|
806
693
|
if (!wallet.isDeployed) {
|
|
807
694
|
await this.sdk.deploySafe(address, validChainId, "aggressive", true);
|
|
808
|
-
try {
|
|
809
|
-
if (validChainId === 42161) {
|
|
810
|
-
await this.sdk.updateUserProfile({
|
|
811
|
-
asset: "WETH",
|
|
812
|
-
chains: [validChainId],
|
|
813
|
-
protocols: ["c24a33fa-4a87-4376-b756-93bd3957d3ea"],
|
|
814
|
-
// AAVE protocol ID
|
|
815
|
-
autoSelectProtocols: false
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
} catch (err) {
|
|
819
|
-
console.warn(
|
|
820
|
-
"Failed to configure AAVE protocol after Safe deployment:",
|
|
821
|
-
err instanceof Error ? err.message : String(err)
|
|
822
|
-
);
|
|
823
|
-
}
|
|
824
695
|
}
|
|
825
696
|
const user = await this.sdk.getUserDetails();
|
|
826
697
|
if (!user.hasActiveSessionKey) {
|
|
@@ -862,7 +733,7 @@ var ZyfaiAgent = class {
|
|
|
862
733
|
throw error;
|
|
863
734
|
}
|
|
864
735
|
}
|
|
865
|
-
async deposit(state, chainId, amount,
|
|
736
|
+
async deposit(state, chainId, amount, depositCallback) {
|
|
866
737
|
try {
|
|
867
738
|
await this.connectFull(state, chainId);
|
|
868
739
|
const validChainId = isValidChainId(chainId);
|
|
@@ -876,15 +747,13 @@ var ZyfaiAgent = class {
|
|
|
876
747
|
validChainId,
|
|
877
748
|
amount
|
|
878
749
|
);
|
|
879
|
-
|
|
880
|
-
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
750
|
+
await this.sdk.logDeposit(validChainId, txHash, amount);
|
|
881
751
|
return { txHash, smartWallet: wallet.address, amount };
|
|
882
752
|
}
|
|
883
753
|
const raw = await this.sdk.depositFunds(
|
|
884
754
|
this.getAddress(),
|
|
885
755
|
validChainId,
|
|
886
|
-
amount
|
|
887
|
-
asset
|
|
756
|
+
amount
|
|
888
757
|
);
|
|
889
758
|
return mapDeposit(raw);
|
|
890
759
|
} catch (error) {
|
|
@@ -922,10 +791,10 @@ var ZyfaiAgent = class {
|
|
|
922
791
|
const raw = await this.sdk.getOnchainEarnings(smartWallet);
|
|
923
792
|
return mapEarnings(raw, smartWallet);
|
|
924
793
|
}
|
|
925
|
-
async getAccountApy(state, chainId, days
|
|
794
|
+
async getAccountApy(state, chainId, days) {
|
|
926
795
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
927
796
|
const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
928
|
-
return mapApyHistory(raw, chainId
|
|
797
|
+
return mapApyHistory(raw, chainId);
|
|
929
798
|
}
|
|
930
799
|
async getHistory(state, chainId, options) {
|
|
931
800
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -980,20 +849,6 @@ var ZyfaiAgent = class {
|
|
|
980
849
|
}
|
|
981
850
|
return mapUserProfile(raw, this.connectedAddress);
|
|
982
851
|
}
|
|
983
|
-
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
984
|
-
await this.connectAuth(state, chainId);
|
|
985
|
-
const current = await this.sdk.getUserDetails(asset);
|
|
986
|
-
const update = buildAutoSelectUpdate(
|
|
987
|
-
{
|
|
988
|
-
autoSelectProtocols: current.autoSelectProtocols,
|
|
989
|
-
chains: current.chains
|
|
990
|
-
},
|
|
991
|
-
asset
|
|
992
|
-
);
|
|
993
|
-
if (!update) return false;
|
|
994
|
-
await this.sdk.updateUserProfile(update);
|
|
995
|
-
return true;
|
|
996
|
-
}
|
|
997
852
|
// --- IAgent: Discovery (no wallet required) ---
|
|
998
853
|
async getAgentApy(days, options) {
|
|
999
854
|
const raw = await this.sdk.getAPYPerStrategy(
|
|
@@ -1079,7 +934,8 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
1079
934
|
const tokenBalance = agentBalance?.tokens.find(
|
|
1080
935
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
1081
936
|
);
|
|
1082
|
-
if (!tokenBalance)
|
|
937
|
+
if (!tokenBalance)
|
|
938
|
+
return { agent, balance: 0n };
|
|
1083
939
|
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
1084
940
|
});
|
|
1085
941
|
}
|
|
@@ -1096,9 +952,11 @@ function planProportionalShares(balances, requested, totalAvailable) {
|
|
|
1096
952
|
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
1097
953
|
});
|
|
1098
954
|
for (const p of byHeadroom) {
|
|
1099
|
-
if (remainder === 0n)
|
|
955
|
+
if (remainder === 0n)
|
|
956
|
+
break;
|
|
1100
957
|
const headroom = p.balance - p.planned;
|
|
1101
|
-
if (headroom <= 0n)
|
|
958
|
+
if (headroom <= 0n)
|
|
959
|
+
continue;
|
|
1102
960
|
const take = headroom < remainder ? headroom : remainder;
|
|
1103
961
|
p.planned += take;
|
|
1104
962
|
remainder -= take;
|
|
@@ -1127,7 +985,8 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
|
1127
985
|
(s, c) => s + (c.balance - c.planned),
|
|
1128
986
|
0n
|
|
1129
987
|
);
|
|
1130
|
-
if (totalHeadroom === 0n)
|
|
988
|
+
if (totalHeadroom === 0n)
|
|
989
|
+
return;
|
|
1131
990
|
let distributed = 0n;
|
|
1132
991
|
for (const c of candidates) {
|
|
1133
992
|
const headroom = c.balance - c.planned;
|
|
@@ -1138,9 +997,11 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
|
1138
997
|
}
|
|
1139
998
|
let leftover = amount - distributed;
|
|
1140
999
|
for (const c of candidates) {
|
|
1141
|
-
if (leftover === 0n)
|
|
1000
|
+
if (leftover === 0n)
|
|
1001
|
+
break;
|
|
1142
1002
|
const headroom = c.balance - c.planned;
|
|
1143
|
-
if (headroom <= 0n)
|
|
1003
|
+
if (headroom <= 0n)
|
|
1004
|
+
continue;
|
|
1144
1005
|
const take = headroom < leftover ? headroom : leftover;
|
|
1145
1006
|
c.planned += take;
|
|
1146
1007
|
leftover -= take;
|
|
@@ -1157,13 +1018,16 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
1157
1018
|
for (const id of Object.keys(agentApys)) {
|
|
1158
1019
|
const cells = agentApys[id].apyByChainAndAsset;
|
|
1159
1020
|
const balance = agentBalances[id] ?? 0;
|
|
1160
|
-
if (!cells || balance <= 0)
|
|
1021
|
+
if (!cells || balance <= 0)
|
|
1022
|
+
continue;
|
|
1161
1023
|
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
1162
|
-
if (!perAsset)
|
|
1024
|
+
if (!perAsset)
|
|
1025
|
+
continue;
|
|
1163
1026
|
const chainId = Number(chainKey);
|
|
1164
1027
|
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
1165
1028
|
const apy = Number(apyValue ?? 0);
|
|
1166
|
-
if (apy === 0)
|
|
1029
|
+
if (apy === 0)
|
|
1030
|
+
continue;
|
|
1167
1031
|
sums[chainId] ??= {};
|
|
1168
1032
|
weights[chainId] ??= {};
|
|
1169
1033
|
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
@@ -1177,7 +1041,8 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
1177
1041
|
const perAssetOut = {};
|
|
1178
1042
|
for (const asset of Object.keys(sums[chainId])) {
|
|
1179
1043
|
const w = weights[chainId][asset];
|
|
1180
|
-
if (w > 0)
|
|
1044
|
+
if (w > 0)
|
|
1045
|
+
perAssetOut[asset] = sums[chainId][asset] / w;
|
|
1181
1046
|
}
|
|
1182
1047
|
if (Object.keys(perAssetOut).length > 0) {
|
|
1183
1048
|
out[chainId] = perAssetOut;
|
|
@@ -1192,7 +1057,7 @@ import {
|
|
|
1192
1057
|
createWalletClient,
|
|
1193
1058
|
custom
|
|
1194
1059
|
} from "viem";
|
|
1195
|
-
import { base as base2, arbitrum as arbitrum2
|
|
1060
|
+
import { base as base2, arbitrum as arbitrum2 } from "viem/chains";
|
|
1196
1061
|
|
|
1197
1062
|
// src/lib/transfer-auth.ts
|
|
1198
1063
|
import { bytesToHex } from "viem";
|
|
@@ -1238,10 +1103,7 @@ async function postSponsorTransferAuth(input) {
|
|
|
1238
1103
|
try {
|
|
1239
1104
|
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
1240
1105
|
method: "POST",
|
|
1241
|
-
headers: {
|
|
1242
|
-
"content-type": "application/json",
|
|
1243
|
-
"x-owney-api-key": input.apiKey
|
|
1244
|
-
},
|
|
1106
|
+
headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
|
|
1245
1107
|
body: JSON.stringify(input.body)
|
|
1246
1108
|
});
|
|
1247
1109
|
} catch (networkError) {
|
|
@@ -1261,91 +1123,11 @@ async function postSponsorTransferAuth(input) {
|
|
|
1261
1123
|
throw new OwneyError(
|
|
1262
1124
|
"SPONSOR_REQUEST_FAILED",
|
|
1263
1125
|
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1264
|
-
{
|
|
1265
|
-
statusCode: res.status,
|
|
1266
|
-
responseBody: text.slice(0, 500),
|
|
1267
|
-
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
1268
|
-
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
1269
|
-
safeToFallback: res.status === 503
|
|
1270
|
-
}
|
|
1126
|
+
{ statusCode: res.status, responseBody: text.slice(0, 500) }
|
|
1271
1127
|
);
|
|
1272
1128
|
}
|
|
1273
1129
|
return parsed.data;
|
|
1274
1130
|
}
|
|
1275
|
-
async function postSponsorPermit2Transfer(input) {
|
|
1276
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1277
|
-
let res;
|
|
1278
|
-
try {
|
|
1279
|
-
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
1280
|
-
method: "POST",
|
|
1281
|
-
headers: {
|
|
1282
|
-
"content-type": "application/json",
|
|
1283
|
-
"x-owney-api-key": input.apiKey
|
|
1284
|
-
},
|
|
1285
|
-
body: JSON.stringify(input.body)
|
|
1286
|
-
});
|
|
1287
|
-
} catch (networkError) {
|
|
1288
|
-
throw new OwneyError(
|
|
1289
|
-
"SPONSOR_REQUEST_FAILED",
|
|
1290
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1291
|
-
{ cause: String(networkError), safeToFallback: false }
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
const text = await res.text();
|
|
1295
|
-
let parsed = null;
|
|
1296
|
-
try {
|
|
1297
|
-
parsed = JSON.parse(text);
|
|
1298
|
-
} catch {
|
|
1299
|
-
}
|
|
1300
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
1301
|
-
throw new OwneyError(
|
|
1302
|
-
"SPONSOR_REQUEST_FAILED",
|
|
1303
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1304
|
-
{
|
|
1305
|
-
statusCode: res.status,
|
|
1306
|
-
responseBody: text.slice(0, 500),
|
|
1307
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
1308
|
-
}
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
return parsed.data;
|
|
1312
|
-
}
|
|
1313
|
-
async function getSponsorRelayerAddress(input) {
|
|
1314
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1315
|
-
let res;
|
|
1316
|
-
try {
|
|
1317
|
-
res = await fetch(
|
|
1318
|
-
`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
1319
|
-
{
|
|
1320
|
-
headers: { "x-owney-api-key": input.apiKey }
|
|
1321
|
-
}
|
|
1322
|
-
);
|
|
1323
|
-
} catch (networkError) {
|
|
1324
|
-
throw new OwneyError(
|
|
1325
|
-
"SPONSOR_REQUEST_FAILED",
|
|
1326
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1327
|
-
{ cause: String(networkError), safeToFallback: true }
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
const text = await res.text();
|
|
1331
|
-
let parsed = null;
|
|
1332
|
-
try {
|
|
1333
|
-
parsed = JSON.parse(text);
|
|
1334
|
-
} catch {
|
|
1335
|
-
}
|
|
1336
|
-
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
1337
|
-
throw new OwneyError(
|
|
1338
|
-
"SPONSOR_REQUEST_FAILED",
|
|
1339
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1340
|
-
{
|
|
1341
|
-
statusCode: res.status,
|
|
1342
|
-
responseBody: text.slice(0, 500),
|
|
1343
|
-
safeToFallback: true
|
|
1344
|
-
}
|
|
1345
|
-
);
|
|
1346
|
-
}
|
|
1347
|
-
return parsed.data.relayer;
|
|
1348
|
-
}
|
|
1349
1131
|
|
|
1350
1132
|
// src/lib/sponsored-deposit.ts
|
|
1351
1133
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
@@ -1397,114 +1179,6 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
1397
1179
|
};
|
|
1398
1180
|
}
|
|
1399
1181
|
|
|
1400
|
-
// src/lib/permit2.ts
|
|
1401
|
-
import { bytesToHex as bytesToHex2 } from "viem";
|
|
1402
|
-
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
1403
|
-
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
1404
|
-
var ERC20_ALLOWANCE_ABI = [
|
|
1405
|
-
{
|
|
1406
|
-
type: "function",
|
|
1407
|
-
name: "allowance",
|
|
1408
|
-
stateMutability: "view",
|
|
1409
|
-
inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }],
|
|
1410
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
1411
|
-
},
|
|
1412
|
-
{
|
|
1413
|
-
type: "function",
|
|
1414
|
-
name: "approve",
|
|
1415
|
-
stateMutability: "nonpayable",
|
|
1416
|
-
inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
|
|
1417
|
-
outputs: [{ name: "", type: "bool" }]
|
|
1418
|
-
}
|
|
1419
|
-
];
|
|
1420
|
-
function buildPermitTransferFromTypedData(input) {
|
|
1421
|
-
return {
|
|
1422
|
-
domain: { name: "Permit2", chainId: input.chainId, verifyingContract: PERMIT2_ADDRESS },
|
|
1423
|
-
types: {
|
|
1424
|
-
PermitTransferFrom: [
|
|
1425
|
-
{ name: "permitted", type: "TokenPermissions" },
|
|
1426
|
-
{ name: "spender", type: "address" },
|
|
1427
|
-
{ name: "nonce", type: "uint256" },
|
|
1428
|
-
{ name: "deadline", type: "uint256" }
|
|
1429
|
-
],
|
|
1430
|
-
TokenPermissions: [
|
|
1431
|
-
{ name: "token", type: "address" },
|
|
1432
|
-
{ name: "amount", type: "uint256" }
|
|
1433
|
-
]
|
|
1434
|
-
},
|
|
1435
|
-
primaryType: "PermitTransferFrom",
|
|
1436
|
-
message: input.message
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
function randomPermit2Nonce() {
|
|
1440
|
-
const bytes = new Uint8Array(32);
|
|
1441
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
1442
|
-
return BigInt(bytesToHex2(bytes));
|
|
1443
|
-
}
|
|
1444
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
1445
|
-
return publicClient.readContract({
|
|
1446
|
-
address: token,
|
|
1447
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
1448
|
-
functionName: "allowance",
|
|
1449
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
// src/lib/sponsored-weth-deposit.ts
|
|
1454
|
-
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
1455
|
-
function makeSponsoredWethCallback(deps) {
|
|
1456
|
-
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
1457
|
-
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
1458
|
-
return async (smartWallet, chainId, amount) => {
|
|
1459
|
-
const cid = chainId;
|
|
1460
|
-
const token = deps.tokenAddressByChain[cid];
|
|
1461
|
-
if (!token) {
|
|
1462
|
-
throw new OwneyError("CHAIN_UNSUPPORTED", `No sponsored WETH configured for chain ${chainId}`);
|
|
1463
|
-
}
|
|
1464
|
-
const amountWei = BigInt(amount);
|
|
1465
|
-
const pub = deps.getPublicClient(cid);
|
|
1466
|
-
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
1467
|
-
if (allowance < amountWei) {
|
|
1468
|
-
throw new OwneyError(
|
|
1469
|
-
"PERMIT2_APPROVAL_REQUIRED",
|
|
1470
|
-
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
1471
|
-
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
1472
|
-
);
|
|
1473
|
-
}
|
|
1474
|
-
const relayer = await get({ baseUrl: deps.baseUrl, apiKey: deps.apiKey, chainId: cid });
|
|
1475
|
-
const nonce = randomPermit2Nonce();
|
|
1476
|
-
const deadline = BigInt(Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS);
|
|
1477
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
1478
|
-
chainId: cid,
|
|
1479
|
-
message: {
|
|
1480
|
-
permitted: { token, amount: amountWei },
|
|
1481
|
-
spender: relayer,
|
|
1482
|
-
nonce,
|
|
1483
|
-
deadline
|
|
1484
|
-
}
|
|
1485
|
-
});
|
|
1486
|
-
const wallet = deps.getWalletClient(cid);
|
|
1487
|
-
const signature = await wallet.signTypedData(
|
|
1488
|
-
{ account: deps.ownerAddress, ...typedData }
|
|
1489
|
-
);
|
|
1490
|
-
const result = await post({
|
|
1491
|
-
baseUrl: deps.baseUrl,
|
|
1492
|
-
apiKey: deps.apiKey,
|
|
1493
|
-
body: {
|
|
1494
|
-
chainId: cid,
|
|
1495
|
-
token,
|
|
1496
|
-
from: deps.ownerAddress,
|
|
1497
|
-
to: smartWallet,
|
|
1498
|
-
amount,
|
|
1499
|
-
nonce: nonce.toString(),
|
|
1500
|
-
deadline: deadline.toString(),
|
|
1501
|
-
signature
|
|
1502
|
-
}
|
|
1503
|
-
});
|
|
1504
|
-
return result.txHash;
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
1182
|
// src/client.ts
|
|
1509
1183
|
function encodeMultiAgentCursor(map) {
|
|
1510
1184
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
@@ -1528,22 +1202,13 @@ function decodeMultiAgentCursor(token) {
|
|
|
1528
1202
|
}
|
|
1529
1203
|
var SPONSORED_USDC_BY_CHAIN = {
|
|
1530
1204
|
8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1531
|
-
42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
|
|
1532
|
-
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
1205
|
+
42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
|
|
1533
1206
|
};
|
|
1534
1207
|
var VIEM_CHAIN2 = {
|
|
1535
1208
|
8453: base2,
|
|
1536
|
-
42161: arbitrum2
|
|
1537
|
-
1: mainnet2
|
|
1538
|
-
};
|
|
1539
|
-
var SPONSORED_WETH_BY_CHAIN = {
|
|
1540
|
-
8453: "0x4200000000000000000000000000000000000006",
|
|
1541
|
-
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
1542
|
-
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
1209
|
+
42161: arbitrum2
|
|
1543
1210
|
};
|
|
1544
|
-
|
|
1545
|
-
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
1546
|
-
}
|
|
1211
|
+
var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai", "sail"];
|
|
1547
1212
|
var OwneySDK = class {
|
|
1548
1213
|
agents = /* @__PURE__ */ new Map();
|
|
1549
1214
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -1556,14 +1221,11 @@ var OwneySDK = class {
|
|
|
1556
1221
|
state = null;
|
|
1557
1222
|
apiKey;
|
|
1558
1223
|
zyfaiRpcUrls;
|
|
1559
|
-
routingApiBaseUrl;
|
|
1560
1224
|
cachedSponsoredCallback = null;
|
|
1561
|
-
cachedWethSponsoredCallback = null;
|
|
1562
1225
|
initializingAgentsPromise = null;
|
|
1563
1226
|
constructor(config) {
|
|
1564
1227
|
this.apiKey = config.apiKey;
|
|
1565
1228
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
1566
|
-
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
1567
1229
|
}
|
|
1568
1230
|
/**
|
|
1569
1231
|
* Establish connection state for all subsequent SDK calls.
|
|
@@ -1584,7 +1246,6 @@ var OwneySDK = class {
|
|
|
1584
1246
|
}
|
|
1585
1247
|
this.state = { provider, walletAddress, chainId: null };
|
|
1586
1248
|
this.cachedSponsoredCallback = null;
|
|
1587
|
-
this.cachedWethSponsoredCallback = null;
|
|
1588
1249
|
}
|
|
1589
1250
|
/**
|
|
1590
1251
|
* Disconnect from all agents and clear connection state.
|
|
@@ -1598,7 +1259,6 @@ var OwneySDK = class {
|
|
|
1598
1259
|
this.disabledAgents.clear();
|
|
1599
1260
|
this.state = null;
|
|
1600
1261
|
this.cachedSponsoredCallback = null;
|
|
1601
|
-
this.cachedWethSponsoredCallback = null;
|
|
1602
1262
|
}
|
|
1603
1263
|
isAgentDisabled(agentId) {
|
|
1604
1264
|
return this.disabledAgents.has(agentId);
|
|
@@ -1618,7 +1278,8 @@ var OwneySDK = class {
|
|
|
1618
1278
|
return this.state?.chainId ?? null;
|
|
1619
1279
|
}
|
|
1620
1280
|
requireState() {
|
|
1621
|
-
if (!this.state)
|
|
1281
|
+
if (!this.state)
|
|
1282
|
+
throw new NotConnectedError();
|
|
1622
1283
|
return this.state;
|
|
1623
1284
|
}
|
|
1624
1285
|
requireChainId() {
|
|
@@ -1648,12 +1309,12 @@ var OwneySDK = class {
|
|
|
1648
1309
|
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
1649
1310
|
*/
|
|
1650
1311
|
getDefaultSponsoredCallback() {
|
|
1651
|
-
if (this.cachedSponsoredCallback)
|
|
1312
|
+
if (this.cachedSponsoredCallback)
|
|
1313
|
+
return this.cachedSponsoredCallback;
|
|
1652
1314
|
const provider = this.requireConnectedProvider();
|
|
1653
1315
|
const owner = this.state.walletAddress;
|
|
1654
1316
|
this.cachedSponsoredCallback = makeSponsoredDepositCallback({
|
|
1655
1317
|
apiKey: this.apiKey,
|
|
1656
|
-
baseUrl: this.routingApiBaseUrl,
|
|
1657
1318
|
ownerAddress: owner,
|
|
1658
1319
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
1659
1320
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
@@ -1671,37 +1332,6 @@ var OwneySDK = class {
|
|
|
1671
1332
|
});
|
|
1672
1333
|
return this.cachedSponsoredCallback;
|
|
1673
1334
|
}
|
|
1674
|
-
/**
|
|
1675
|
-
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
1676
|
-
* callback used when the caller omits `depositCallback` for a WETH
|
|
1677
|
-
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
1678
|
-
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
1679
|
-
*/
|
|
1680
|
-
getDefaultWethSponsoredCallback() {
|
|
1681
|
-
if (this.cachedWethSponsoredCallback)
|
|
1682
|
-
return this.cachedWethSponsoredCallback;
|
|
1683
|
-
const provider = this.requireConnectedProvider();
|
|
1684
|
-
const owner = this.state.walletAddress;
|
|
1685
|
-
this.cachedWethSponsoredCallback = makeSponsoredWethCallback({
|
|
1686
|
-
apiKey: this.apiKey,
|
|
1687
|
-
baseUrl: this.routingApiBaseUrl,
|
|
1688
|
-
ownerAddress: owner,
|
|
1689
|
-
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
1690
|
-
// Casts work around viem's chain-narrowed Client vs the generic
|
|
1691
|
-
// PublicClient/WalletClient param types — structurally identical at
|
|
1692
|
-
// runtime, but the two share a name TS treats as unrelated.
|
|
1693
|
-
getPublicClient: (cid) => createPublicClient2({
|
|
1694
|
-
chain: VIEM_CHAIN2[cid],
|
|
1695
|
-
transport: custom(provider)
|
|
1696
|
-
}),
|
|
1697
|
-
getWalletClient: (cid) => createWalletClient({
|
|
1698
|
-
account: owner,
|
|
1699
|
-
chain: VIEM_CHAIN2[cid],
|
|
1700
|
-
transport: custom(provider)
|
|
1701
|
-
})
|
|
1702
|
-
});
|
|
1703
|
-
return this.cachedWethSponsoredCallback;
|
|
1704
|
-
}
|
|
1705
1335
|
getAgent(agentId) {
|
|
1706
1336
|
const agent = this.agents.get(agentId);
|
|
1707
1337
|
if (!agent) {
|
|
@@ -1713,19 +1343,22 @@ var OwneySDK = class {
|
|
|
1713
1343
|
const active = /* @__PURE__ */ new Map();
|
|
1714
1344
|
for (const id of this.activeAgents) {
|
|
1715
1345
|
const agent = this.agents.get(id);
|
|
1716
|
-
if (agent)
|
|
1346
|
+
if (agent)
|
|
1347
|
+
active.set(id, agent);
|
|
1717
1348
|
}
|
|
1718
1349
|
return active;
|
|
1719
1350
|
}
|
|
1720
1351
|
async ensureAgentsInitialized() {
|
|
1721
|
-
if (this.agents.size > 0)
|
|
1352
|
+
if (this.agents.size > 0)
|
|
1353
|
+
return;
|
|
1722
1354
|
if (!this.initializingAgentsPromise) {
|
|
1723
1355
|
this.initializingAgentsPromise = (async () => {
|
|
1724
1356
|
const agentKeys = await fetchAgentKeys(this.apiKey);
|
|
1725
1357
|
this.disabledAgents.clear();
|
|
1726
|
-
for (const { key, agent_type, is_enabled } of agentKeys) {
|
|
1727
|
-
const agent = this.createAgent(agent_type, key);
|
|
1728
|
-
if (!agent)
|
|
1358
|
+
for (const { key, agent_type, project_name, is_enabled } of agentKeys) {
|
|
1359
|
+
const agent = await this.createAgent(agent_type, key, project_name);
|
|
1360
|
+
if (!agent)
|
|
1361
|
+
continue;
|
|
1729
1362
|
this.agents.set(agent_type, agent);
|
|
1730
1363
|
if (is_enabled === false) {
|
|
1731
1364
|
this.disabledAgents.add(agent_type);
|
|
@@ -1745,10 +1378,16 @@ var OwneySDK = class {
|
|
|
1745
1378
|
this.initializingAgentsPromise = null;
|
|
1746
1379
|
}
|
|
1747
1380
|
}
|
|
1748
|
-
createAgent(agentId, key) {
|
|
1381
|
+
async createAgent(agentId, key, projectName) {
|
|
1749
1382
|
if (agentId === "zyfai") {
|
|
1750
1383
|
return new ZyfaiAgent(key, this.zyfaiRpcUrls);
|
|
1751
1384
|
}
|
|
1385
|
+
if (agentId === "surfliquid") {
|
|
1386
|
+
if (!projectName)
|
|
1387
|
+
return null;
|
|
1388
|
+
const { SurfLiquidAgent } = await import("./surfliquid.agent-EPFISJPR.js");
|
|
1389
|
+
return new SurfLiquidAgent({ projectName, appId: key });
|
|
1390
|
+
}
|
|
1752
1391
|
return null;
|
|
1753
1392
|
}
|
|
1754
1393
|
// --- Account lifecycle ---
|
|
@@ -1852,7 +1491,7 @@ var OwneySDK = class {
|
|
|
1852
1491
|
}
|
|
1853
1492
|
const state = this.requireState();
|
|
1854
1493
|
const chainId = this.requireChainId();
|
|
1855
|
-
const effectiveCallback = depositCallback ??
|
|
1494
|
+
const effectiveCallback = depositCallback ?? this.getDefaultSponsoredCallback();
|
|
1856
1495
|
if (agentId) {
|
|
1857
1496
|
if (this.isAgentDisabled(agentId)) {
|
|
1858
1497
|
throw new OwneyError(
|
|
@@ -1864,14 +1503,10 @@ var OwneySDK = class {
|
|
|
1864
1503
|
const agent = this.getAgent(agentId);
|
|
1865
1504
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1866
1505
|
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
1867
|
-
return
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
chainId,
|
|
1871
|
-
amount,
|
|
1872
|
-
asset,
|
|
1873
|
-
effectiveCallback,
|
|
1874
|
-
depositCallback
|
|
1506
|
+
return withFailureReporting(
|
|
1507
|
+
this.apiKey,
|
|
1508
|
+
agent.id,
|
|
1509
|
+
() => agent.deposit(state, chainId, amount, effectiveCallback)
|
|
1875
1510
|
);
|
|
1876
1511
|
}
|
|
1877
1512
|
const totalAmount = BigInt(amount);
|
|
@@ -1901,77 +1536,26 @@ var OwneySDK = class {
|
|
|
1901
1536
|
);
|
|
1902
1537
|
}
|
|
1903
1538
|
const agentResults = {};
|
|
1539
|
+
const agentErrors = {};
|
|
1904
1540
|
for (const { agent, amount: agentAmount } of agentAmounts) {
|
|
1905
|
-
agentResults[agent.id] = await this.depositWithFallback(
|
|
1906
|
-
agent,
|
|
1907
|
-
state,
|
|
1908
|
-
chainId,
|
|
1909
|
-
agentAmount,
|
|
1910
|
-
asset,
|
|
1911
|
-
effectiveCallback,
|
|
1912
|
-
depositCallback
|
|
1913
|
-
);
|
|
1914
|
-
}
|
|
1915
|
-
return { agentResults };
|
|
1916
|
-
}
|
|
1917
|
-
/**
|
|
1918
|
-
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
1919
|
-
* two independent auto-recovery mechanisms:
|
|
1920
|
-
*
|
|
1921
|
-
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
1922
|
-
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
1923
|
-
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
1924
|
-
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
1925
|
-
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
1926
|
-
* per call so a wallet/agent that keeps reporting the allowance as
|
|
1927
|
-
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
1928
|
-
* the user rejects the wallet prompt), that error propagates as-is —
|
|
1929
|
-
* the user said no to a transaction, so we must not turn around and ask
|
|
1930
|
-
* them to pay for a different one via the fallback below.
|
|
1931
|
-
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
1932
|
-
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
1933
|
-
* and retry once with `undefined`, falling through to a user-paid
|
|
1934
|
-
* native deposit. This still applies after a successful Permit2
|
|
1935
|
-
* approval retry (the retried sponsored attempt can itself hit a safe
|
|
1936
|
-
* sponsor failure).
|
|
1937
|
-
*
|
|
1938
|
-
* Any other error — an app-supplied callback's own failure in particular —
|
|
1939
|
-
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
1940
|
-
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
1941
|
-
*/
|
|
1942
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
1943
|
-
const attempt = (callback) => withFailureReporting(
|
|
1944
|
-
this.apiKey,
|
|
1945
|
-
agent.id,
|
|
1946
|
-
() => agent.deposit(
|
|
1947
|
-
state,
|
|
1948
|
-
chainId,
|
|
1949
|
-
amount,
|
|
1950
|
-
asset,
|
|
1951
|
-
callback
|
|
1952
|
-
)
|
|
1953
|
-
);
|
|
1954
|
-
let approvalAttempted = false;
|
|
1955
|
-
for (; ; ) {
|
|
1956
1541
|
try {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
console.warn(
|
|
1962
|
-
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
1963
|
-
);
|
|
1964
|
-
await this.approvePermit2();
|
|
1965
|
-
continue;
|
|
1966
|
-
}
|
|
1967
|
-
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
1968
|
-
console.warn(
|
|
1969
|
-
`[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
|
|
1970
|
-
error
|
|
1542
|
+
agentResults[agent.id] = await withFailureReporting(
|
|
1543
|
+
this.apiKey,
|
|
1544
|
+
agent.id,
|
|
1545
|
+
() => agent.deposit(state, chainId, agentAmount, effectiveCallback)
|
|
1971
1546
|
);
|
|
1972
|
-
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
|
|
1973
1549
|
}
|
|
1974
1550
|
}
|
|
1551
|
+
if (Object.keys(agentResults).length === 0) {
|
|
1552
|
+
throw new OwneyError(
|
|
1553
|
+
"DEPOSIT_ALL_FAILED",
|
|
1554
|
+
`All ${agentAmounts.length} agent deposit(s) failed.`,
|
|
1555
|
+
{ agentErrors }
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
|
|
1975
1559
|
}
|
|
1976
1560
|
getMinDepositAmount(agent, chainId, asset) {
|
|
1977
1561
|
const chainAssets = agent.supportedAssets.find(
|
|
@@ -1981,7 +1565,8 @@ var OwneySDK = class {
|
|
|
1981
1565
|
return BigInt(assetEntry?.minDepositAmount ?? "0");
|
|
1982
1566
|
}
|
|
1983
1567
|
splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
|
|
1984
|
-
if (agents.length === 0)
|
|
1568
|
+
if (agents.length === 0)
|
|
1569
|
+
return [];
|
|
1985
1570
|
const perAgent = totalAmount / BigInt(agents.length);
|
|
1986
1571
|
const remainder = totalAmount % BigInt(agents.length);
|
|
1987
1572
|
const splits = agents.map((agent, i) => ({
|
|
@@ -2005,8 +1590,10 @@ var OwneySDK = class {
|
|
|
2005
1590
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
2006
1591
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
2007
1592
|
const parsedAmount = BigInt(amount);
|
|
2008
|
-
if (parsedAmount >= minDepositAmount)
|
|
2009
|
-
|
|
1593
|
+
if (parsedAmount >= minDepositAmount)
|
|
1594
|
+
return;
|
|
1595
|
+
if (await this.hasExistingBalance(agent, state, chainId, asset))
|
|
1596
|
+
return;
|
|
2010
1597
|
throw new OwneyError(
|
|
2011
1598
|
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
2012
1599
|
`Amount "${amount}" is below the required minimum deposit "${minDepositAmount.toString()}" for agent "${agent.id}" on chain ${chainId} for asset "${asset}".`,
|
|
@@ -2069,6 +1656,34 @@ var OwneySDK = class {
|
|
|
2069
1656
|
}
|
|
2070
1657
|
return eligible;
|
|
2071
1658
|
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Agent ids the routing API provisioned for this org that support the given
|
|
1661
|
+
* chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
|
|
1662
|
+
* surfliquid first). Returns `[]` when the org has no compatible agent — never
|
|
1663
|
+
* throws on an empty org. Loads agent keys on first call (apiKey only, no
|
|
1664
|
+
* wallet), so the UI can resolve which agent to use before the user connects.
|
|
1665
|
+
*
|
|
1666
|
+
* This is the source of truth for agent availability: an agent appears here
|
|
1667
|
+
* iff the routing API returned its key. No per-app feature flags.
|
|
1668
|
+
*/
|
|
1669
|
+
async getEligibleAgentIds(chainId, asset) {
|
|
1670
|
+
try {
|
|
1671
|
+
await this.ensureAgentsInitialized();
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
|
|
1674
|
+
return [];
|
|
1675
|
+
}
|
|
1676
|
+
throw error;
|
|
1677
|
+
}
|
|
1678
|
+
return [...this.agents.values()].filter((agent) => {
|
|
1679
|
+
const chainAssets = agent.supportedAssets.find(
|
|
1680
|
+
(sa) => sa.chainId === chainId
|
|
1681
|
+
);
|
|
1682
|
+
return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
|
|
1683
|
+
}).map((agent) => agent.id).sort(
|
|
1684
|
+
(a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
2072
1687
|
// --- Fund operations ---
|
|
2073
1688
|
/**
|
|
2074
1689
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -2104,9 +1719,17 @@ var OwneySDK = class {
|
|
|
2104
1719
|
}
|
|
2105
1720
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
2106
1721
|
if (!amount) {
|
|
1722
|
+
const aggregated2 = await this.getBalances();
|
|
1723
|
+
const funded = projectAgentBalancesForAsset(
|
|
1724
|
+
eligibleAgents,
|
|
1725
|
+
aggregated2.agentBalances,
|
|
1726
|
+
chainId,
|
|
1727
|
+
asset,
|
|
1728
|
+
assetInfo.decimals
|
|
1729
|
+
).filter((b) => b.balance > 0n);
|
|
2107
1730
|
const results2 = {};
|
|
2108
1731
|
const agentErrors2 = {};
|
|
2109
|
-
for (const agent of
|
|
1732
|
+
for (const { agent } of funded) {
|
|
2110
1733
|
try {
|
|
2111
1734
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
2112
1735
|
} catch (err) {
|
|
@@ -2123,9 +1746,10 @@ var OwneySDK = class {
|
|
|
2123
1746
|
{ asset, agentErrors: agentErrors2 }
|
|
2124
1747
|
);
|
|
2125
1748
|
}
|
|
1749
|
+
const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
|
|
2126
1750
|
return {
|
|
2127
1751
|
agentResult: results2,
|
|
2128
|
-
totalWithdrawn:
|
|
1752
|
+
totalWithdrawn: totalWithdrawn.toString()
|
|
2129
1753
|
};
|
|
2130
1754
|
}
|
|
2131
1755
|
const requested = BigInt(amount);
|
|
@@ -2170,7 +1794,8 @@ var OwneySDK = class {
|
|
|
2170
1794
|
const agentErrors = {};
|
|
2171
1795
|
for (let i = 0; i < plans.length; i++) {
|
|
2172
1796
|
const p = plans[i];
|
|
2173
|
-
if (p.planned === 0n)
|
|
1797
|
+
if (p.planned === 0n)
|
|
1798
|
+
continue;
|
|
2174
1799
|
try {
|
|
2175
1800
|
results[p.agent.id] = await p.agent.withdraw(
|
|
2176
1801
|
state,
|
|
@@ -2304,30 +1929,19 @@ var OwneySDK = class {
|
|
|
2304
1929
|
*/
|
|
2305
1930
|
async getAccountApy({
|
|
2306
1931
|
agentId,
|
|
2307
|
-
days
|
|
2308
|
-
tokenSymbol
|
|
1932
|
+
days
|
|
2309
1933
|
}) {
|
|
2310
1934
|
const state = this.requireState();
|
|
2311
1935
|
const chainId = this.requireChainId();
|
|
2312
1936
|
if (agentId) {
|
|
2313
|
-
return this.getAgent(agentId).getAccountApy(
|
|
2314
|
-
state,
|
|
2315
|
-
chainId,
|
|
2316
|
-
days,
|
|
2317
|
-
tokenSymbol
|
|
2318
|
-
);
|
|
1937
|
+
return this.getAgent(agentId).getAccountApy(state, chainId, days);
|
|
2319
1938
|
}
|
|
2320
1939
|
const activeAgents = this.getActiveAgents();
|
|
2321
1940
|
const entries = [...activeAgents.entries()];
|
|
2322
1941
|
const [apyResults, balanceResults] = await Promise.all([
|
|
2323
1942
|
Promise.all(
|
|
2324
1943
|
entries.map(async ([id, agent]) => {
|
|
2325
|
-
const apy = await agent.getAccountApy(
|
|
2326
|
-
state,
|
|
2327
|
-
chainId,
|
|
2328
|
-
days,
|
|
2329
|
-
tokenSymbol
|
|
2330
|
-
);
|
|
1944
|
+
const apy = await agent.getAccountApy(state, chainId, days);
|
|
2331
1945
|
return [id, apy];
|
|
2332
1946
|
})
|
|
2333
1947
|
),
|
|
@@ -2350,13 +1964,15 @@ var OwneySDK = class {
|
|
|
2350
1964
|
let relevantBalance = 0;
|
|
2351
1965
|
for (const id of Object.keys(results)) {
|
|
2352
1966
|
const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
|
|
2353
|
-
if (agentApy === 0)
|
|
1967
|
+
if (agentApy === 0)
|
|
1968
|
+
continue;
|
|
2354
1969
|
relevantBalance += balances[id];
|
|
2355
1970
|
}
|
|
2356
1971
|
if (relevantBalance > 0) {
|
|
2357
1972
|
for (const id of Object.keys(results)) {
|
|
2358
1973
|
const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
|
|
2359
|
-
if (agentApy === 0)
|
|
1974
|
+
if (agentApy === 0)
|
|
1975
|
+
continue;
|
|
2360
1976
|
const weight = balances[id] / relevantBalance;
|
|
2361
1977
|
totalApy += agentApy * weight;
|
|
2362
1978
|
}
|
|
@@ -2411,7 +2027,8 @@ var OwneySDK = class {
|
|
|
2411
2027
|
const nextCursorMap = {};
|
|
2412
2028
|
let hasMore = false;
|
|
2413
2029
|
for (const { agentId: id, page } of perAgent) {
|
|
2414
|
-
if (!page)
|
|
2030
|
+
if (!page)
|
|
2031
|
+
continue;
|
|
2415
2032
|
merged.push(...page.data);
|
|
2416
2033
|
if (page.hasMore && page.nextCursor) {
|
|
2417
2034
|
nextCursorMap[id] = page.nextCursor;
|
|
@@ -2420,12 +2037,16 @@ var OwneySDK = class {
|
|
|
2420
2037
|
}
|
|
2421
2038
|
merged.sort((a, b) => {
|
|
2422
2039
|
const dateDiff = new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
2423
|
-
if (dateDiff !== 0)
|
|
2424
|
-
|
|
2040
|
+
if (dateDiff !== 0)
|
|
2041
|
+
return dateDiff;
|
|
2042
|
+
if (a.agent !== b.agent)
|
|
2043
|
+
return a.agent < b.agent ? -1 : 1;
|
|
2425
2044
|
const aTx = a.transactions?.[0]?.txHashes?.[0] ?? "";
|
|
2426
2045
|
const bTx = b.transactions?.[0]?.txHashes?.[0] ?? "";
|
|
2427
|
-
if (aTx !== bTx)
|
|
2428
|
-
|
|
2046
|
+
if (aTx !== bTx)
|
|
2047
|
+
return aTx < bTx ? -1 : 1;
|
|
2048
|
+
if (a.action !== b.action)
|
|
2049
|
+
return a.action < b.action ? -1 : 1;
|
|
2429
2050
|
return 0;
|
|
2430
2051
|
});
|
|
2431
2052
|
return {
|
|
@@ -2460,67 +2081,6 @@ var OwneySDK = class {
|
|
|
2460
2081
|
agentUserProfile: results
|
|
2461
2082
|
};
|
|
2462
2083
|
}
|
|
2463
|
-
/**
|
|
2464
|
-
* Ensure the given asset uses backend protocol auto-selection for the active
|
|
2465
|
-
* chain's account. No-op (returns false) for agents that don't support it.
|
|
2466
|
-
* @param asset - "USDC" or "WETH".
|
|
2467
|
-
* @param agentId - Agent to target. Defaults to "zyfai".
|
|
2468
|
-
* @returns whether a profile write occurred.
|
|
2469
|
-
*/
|
|
2470
|
-
async ensureAutoSelectProtocols(asset, agentId = "zyfai") {
|
|
2471
|
-
const state = this.requireState();
|
|
2472
|
-
const chainId = this.requireChainId();
|
|
2473
|
-
const agent = this.getAgent(agentId);
|
|
2474
|
-
if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
|
|
2475
|
-
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
2476
|
-
}
|
|
2477
|
-
/**
|
|
2478
|
-
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
2479
|
-
* the active chain. Required once per wallet per chain before gasless WETH
|
|
2480
|
-
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
2481
|
-
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
2482
|
-
* will see the new allowance; throws if the transaction reverted.
|
|
2483
|
-
* @returns the approval transaction hash.
|
|
2484
|
-
*/
|
|
2485
|
-
async approvePermit2(asset = "WETH") {
|
|
2486
|
-
void asset;
|
|
2487
|
-
const state = this.requireState();
|
|
2488
|
-
const chainId = this.requireChainId();
|
|
2489
|
-
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
2490
|
-
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
2491
|
-
if (!token) {
|
|
2492
|
-
throw new OwneyError(
|
|
2493
|
-
"CHAIN_UNSUPPORTED",
|
|
2494
|
-
`No sponsored WETH on chain ${chainId}`
|
|
2495
|
-
);
|
|
2496
|
-
}
|
|
2497
|
-
const provider = this.requireConnectedProvider();
|
|
2498
|
-
const wallet = createWalletClient({
|
|
2499
|
-
account: state.walletAddress,
|
|
2500
|
-
chain: VIEM_CHAIN2[chainId],
|
|
2501
|
-
transport: custom(provider)
|
|
2502
|
-
});
|
|
2503
|
-
const hash = await wallet.writeContract({
|
|
2504
|
-
address: token,
|
|
2505
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2506
|
-
functionName: "approve",
|
|
2507
|
-
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
2508
|
-
account: state.walletAddress,
|
|
2509
|
-
chain: VIEM_CHAIN2[chainId]
|
|
2510
|
-
});
|
|
2511
|
-
const publicClient = createPublicClient2({
|
|
2512
|
-
chain: VIEM_CHAIN2[chainId],
|
|
2513
|
-
transport: custom(provider)
|
|
2514
|
-
});
|
|
2515
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
2516
|
-
hash,
|
|
2517
|
-
confirmations: 1
|
|
2518
|
-
});
|
|
2519
|
-
if (receipt.status !== "success") {
|
|
2520
|
-
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
2521
|
-
}
|
|
2522
|
-
return hash;
|
|
2523
|
-
}
|
|
2524
2084
|
// --- Discovery (no wallet required) ---
|
|
2525
2085
|
/**
|
|
2526
2086
|
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|
|
@@ -2626,7 +2186,8 @@ import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
|
2626
2186
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
2627
2187
|
var KEY_PREFIX2 = "owney.siwx.session";
|
|
2628
2188
|
var storage2 = () => {
|
|
2629
|
-
if (typeof window === "undefined")
|
|
2189
|
+
if (typeof window === "undefined")
|
|
2190
|
+
return null;
|
|
2630
2191
|
try {
|
|
2631
2192
|
return window.localStorage;
|
|
2632
2193
|
} catch {
|
|
@@ -2636,9 +2197,11 @@ var storage2 = () => {
|
|
|
2636
2197
|
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2637
2198
|
var readSiwxSession = (address, chainId) => {
|
|
2638
2199
|
const store = storage2();
|
|
2639
|
-
if (!store)
|
|
2200
|
+
if (!store)
|
|
2201
|
+
return null;
|
|
2640
2202
|
const raw = store.getItem(buildKey2(address, chainId));
|
|
2641
|
-
if (!raw)
|
|
2203
|
+
if (!raw)
|
|
2204
|
+
return null;
|
|
2642
2205
|
try {
|
|
2643
2206
|
return JSON.parse(raw);
|
|
2644
2207
|
} catch {
|
|
@@ -2648,7 +2211,8 @@ var readSiwxSession = (address, chainId) => {
|
|
|
2648
2211
|
};
|
|
2649
2212
|
var writeSiwxSession = (address, chainId, session) => {
|
|
2650
2213
|
const store = storage2();
|
|
2651
|
-
if (!store)
|
|
2214
|
+
if (!store)
|
|
2215
|
+
return;
|
|
2652
2216
|
try {
|
|
2653
2217
|
store.setItem(buildKey2(address, chainId), JSON.stringify(session));
|
|
2654
2218
|
} catch {
|
|
@@ -2656,7 +2220,8 @@ var writeSiwxSession = (address, chainId, session) => {
|
|
|
2656
2220
|
};
|
|
2657
2221
|
var clearSiwxSession = (address, chainId) => {
|
|
2658
2222
|
const store = storage2();
|
|
2659
|
-
if (!store)
|
|
2223
|
+
if (!store)
|
|
2224
|
+
return;
|
|
2660
2225
|
try {
|
|
2661
2226
|
store.removeItem(buildKey2(address, chainId));
|
|
2662
2227
|
} catch {
|
|
@@ -2719,10 +2284,12 @@ function buildSIWXConfig(deps) {
|
|
|
2719
2284
|
const jwt = readSession(address, id);
|
|
2720
2285
|
const stored = readSiwxSession(address, id);
|
|
2721
2286
|
if (!jwt) {
|
|
2722
|
-
if (stored)
|
|
2287
|
+
if (stored)
|
|
2288
|
+
clearSiwxSession(address, id);
|
|
2723
2289
|
return [];
|
|
2724
2290
|
}
|
|
2725
|
-
if (!stored)
|
|
2291
|
+
if (!stored)
|
|
2292
|
+
return [];
|
|
2726
2293
|
const session = {
|
|
2727
2294
|
// `stored.data.chainId` is a plain string in the cache (kept AppKit-free);
|
|
2728
2295
|
// it always holds a valid CaipNetworkId here, so narrow at the boundary.
|