@owney/sdk 0.6.0-beta.0 → 0.6.0-beta.2
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 +20 -67
- package/dist/index.cjs +480 -67
- package/dist/index.d.cts +145 -104
- package/dist/index.d.ts +145 -104
- package/dist/index.js +480 -63
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// src/agents/zyfai/zyfai.agent.ts
|
|
2
2
|
import { ZyfaiSDK } from "@zyfai/sdk";
|
|
3
|
+
import { createPublicClient, http } from "viem";
|
|
4
|
+
import { base, arbitrum } from "viem/chains";
|
|
3
5
|
|
|
4
6
|
// src/types/config.ts
|
|
5
7
|
var P_SUPPORTED_CHAINS = {
|
|
6
8
|
chainIds: [8453, 42161],
|
|
7
9
|
chains: ["BASE", "ARBITRUM"]
|
|
8
10
|
};
|
|
9
|
-
var SUPPORTED_TOKENS = ["USDC", "USDT"];
|
|
11
|
+
var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
|
|
10
12
|
var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
|
|
11
13
|
var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
|
|
12
14
|
var SupportedAssets = [
|
|
@@ -27,6 +29,18 @@ var SupportedAssets = [
|
|
|
27
29
|
symbol: "USDT",
|
|
28
30
|
chainId: 42161,
|
|
29
31
|
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
|
|
30
44
|
}
|
|
31
45
|
];
|
|
32
46
|
|
|
@@ -125,6 +139,18 @@ var CHAIN_ID_TO_NAME = {
|
|
|
125
139
|
8453: "BASE",
|
|
126
140
|
42161: "ARBITRUM"
|
|
127
141
|
};
|
|
142
|
+
var NAME_TO_CHAIN_ID = Object.entries(
|
|
143
|
+
CHAIN_ID_TO_NAME
|
|
144
|
+
).reduce((acc, [id, name]) => {
|
|
145
|
+
acc[name.toLowerCase()] = Number(id);
|
|
146
|
+
return acc;
|
|
147
|
+
}, {});
|
|
148
|
+
function resolveChainId(chain) {
|
|
149
|
+
const asNum = Number(chain);
|
|
150
|
+
if (Number.isFinite(asNum) && asNum > 0)
|
|
151
|
+
return asNum;
|
|
152
|
+
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
153
|
+
}
|
|
128
154
|
function mapBalances(raw, _chainId, smartWallet) {
|
|
129
155
|
const portfolio = raw.portfolio;
|
|
130
156
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
@@ -145,10 +171,27 @@ function mapBalances(raw, _chainId, smartWallet) {
|
|
|
145
171
|
});
|
|
146
172
|
}
|
|
147
173
|
}
|
|
174
|
+
const positions = (portfolio.positions ?? []).map((p) => ({
|
|
175
|
+
chain: p.chain ?? "",
|
|
176
|
+
protocol: p.protocol_name ?? p.protocol_id ?? "",
|
|
177
|
+
protocolId: p.protocol_id,
|
|
178
|
+
pool: p.pool,
|
|
179
|
+
asset: p.token_symbol ?? "",
|
|
180
|
+
// Position amount is hex-encoded base units (like the token balances
|
|
181
|
+
// above). Supported assets (USDC/USDT) are 6-decimal — hexToDecimal's
|
|
182
|
+
// default — so no per-token decimals lookup is needed.
|
|
183
|
+
amount: String(hexToDecimal(p.amount || "0")),
|
|
184
|
+
apy: p.pool_apy,
|
|
185
|
+
tvl: p.pool_tvl,
|
|
186
|
+
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
187
|
+
// structural read keeps it undefined today and auto-populates later.
|
|
188
|
+
liquidity: p.liquidity
|
|
189
|
+
}));
|
|
148
190
|
return {
|
|
149
191
|
smartWallet,
|
|
150
192
|
totalBalance: String(totalBalance),
|
|
151
|
-
tokens
|
|
193
|
+
tokens,
|
|
194
|
+
positions
|
|
152
195
|
};
|
|
153
196
|
}
|
|
154
197
|
function sumTokenValues(tokens) {
|
|
@@ -207,6 +250,14 @@ function mapApyHistory(raw) {
|
|
|
207
250
|
apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain)
|
|
208
251
|
};
|
|
209
252
|
}
|
|
253
|
+
function splitOpportunity(opportunity) {
|
|
254
|
+
const match = opportunity.match(/^(.*?)\s*\(([^)]*)\)\s*$/);
|
|
255
|
+
if (match) {
|
|
256
|
+
const pool = match[2].trim();
|
|
257
|
+
return { protocol: match[1].trim(), pool: pool || void 0 };
|
|
258
|
+
}
|
|
259
|
+
return { protocol: opportunity.trim() };
|
|
260
|
+
}
|
|
210
261
|
function mapEntries(rawEntries, chainId) {
|
|
211
262
|
const actionMap = {
|
|
212
263
|
"Top up wallet": "Top up",
|
|
@@ -226,21 +277,26 @@ function mapEntries(rawEntries, chainId) {
|
|
|
226
277
|
);
|
|
227
278
|
oldApy = Number(rawLog.oldApy);
|
|
228
279
|
newApy = Number(rawLog.newApy);
|
|
280
|
+
const from = splitOpportunity(rawLog.oldOpportunity);
|
|
281
|
+
const to = splitOpportunity(rawLog.newOpportunity);
|
|
229
282
|
rebalanceLog = [
|
|
230
283
|
{
|
|
231
|
-
fromProtocol:
|
|
232
|
-
toProtocol:
|
|
284
|
+
fromProtocol: from.protocol,
|
|
285
|
+
toProtocol: to.protocol,
|
|
286
|
+
fromPool: from.pool,
|
|
287
|
+
toPool: to.pool,
|
|
233
288
|
tokenSymbol: matched?.token_symbol ?? "",
|
|
234
289
|
amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
|
|
235
290
|
status: "success"
|
|
236
291
|
}
|
|
237
292
|
];
|
|
238
293
|
}
|
|
294
|
+
const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
|
|
239
295
|
const rawAmount = entry?.positions?.[0]?.amount;
|
|
240
|
-
const hasAmount = typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
|
|
296
|
+
const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
|
|
241
297
|
return {
|
|
242
298
|
agent: "zyfai",
|
|
243
|
-
action:
|
|
299
|
+
action: mappedAction,
|
|
244
300
|
date: entry.date || "",
|
|
245
301
|
newApy: newApy === null ? null : String(newApy),
|
|
246
302
|
oldApy: oldApy === null ? null : String(oldApy),
|
|
@@ -256,13 +312,6 @@ function mapEntries(rawEntries, chainId) {
|
|
|
256
312
|
};
|
|
257
313
|
});
|
|
258
314
|
}
|
|
259
|
-
function mapAgentStatus(raw) {
|
|
260
|
-
return {
|
|
261
|
-
success: raw.success,
|
|
262
|
-
strategy: raw.strategy,
|
|
263
|
-
protocols: raw.protocols
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
315
|
function mapUserProfile(raw, userAddress) {
|
|
267
316
|
return {
|
|
268
317
|
address: userAddress,
|
|
@@ -298,6 +347,73 @@ function mapApyByStrategy(raw) {
|
|
|
298
347
|
detailedApys: hasDetails ? { apyPerAsset } : void 0
|
|
299
348
|
};
|
|
300
349
|
}
|
|
350
|
+
function computeAllocationApy(positions) {
|
|
351
|
+
let weightedSum = 0;
|
|
352
|
+
let totalValue = 0;
|
|
353
|
+
const buckets = {};
|
|
354
|
+
for (const p of positions) {
|
|
355
|
+
const apy = p.apy;
|
|
356
|
+
const value = Number(p.amount);
|
|
357
|
+
if (typeof apy !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
weightedSum += apy * value;
|
|
361
|
+
totalValue += value;
|
|
362
|
+
const chainId = resolveChainId(p.chain);
|
|
363
|
+
const token = p.asset;
|
|
364
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
365
|
+
continue;
|
|
366
|
+
if (!SUPPORTED_TOKENS.includes(token))
|
|
367
|
+
continue;
|
|
368
|
+
const perAsset = buckets[chainId] ?? {};
|
|
369
|
+
const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
|
|
370
|
+
cell.weightedSum += apy * value;
|
|
371
|
+
cell.value += value;
|
|
372
|
+
perAsset[token] = cell;
|
|
373
|
+
buckets[chainId] = perAsset;
|
|
374
|
+
}
|
|
375
|
+
const apyByChainAndAsset = {};
|
|
376
|
+
for (const [chainKey, perAsset] of Object.entries(buckets)) {
|
|
377
|
+
const chainId = Number(chainKey);
|
|
378
|
+
const out = {};
|
|
379
|
+
for (const [token, cell] of Object.entries(perAsset ?? {})) {
|
|
380
|
+
if (cell && cell.value > 0) {
|
|
381
|
+
out[token] = cell.weightedSum / cell.value;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (Object.keys(out).length > 0)
|
|
385
|
+
apyByChainAndAsset[chainId] = out;
|
|
386
|
+
}
|
|
387
|
+
const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
|
|
388
|
+
return { totalApy, apyByChainAndAsset };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
392
|
+
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
393
|
+
function topicToAddress(topic) {
|
|
394
|
+
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
395
|
+
}
|
|
396
|
+
function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
397
|
+
const wantRecipient = recipient.toLowerCase();
|
|
398
|
+
const wantToken = token.toLowerCase();
|
|
399
|
+
let total = 0n;
|
|
400
|
+
let matched = false;
|
|
401
|
+
for (const log of logs) {
|
|
402
|
+
if (log.address.toLowerCase() !== wantToken)
|
|
403
|
+
continue;
|
|
404
|
+
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
|
|
405
|
+
continue;
|
|
406
|
+
if (topicToAddress(log.topics[2]) !== wantRecipient)
|
|
407
|
+
continue;
|
|
408
|
+
if (!log.data || log.data === "0x")
|
|
409
|
+
continue;
|
|
410
|
+
total += BigInt(log.data);
|
|
411
|
+
matched = true;
|
|
412
|
+
}
|
|
413
|
+
if (!matched)
|
|
414
|
+
return void 0;
|
|
415
|
+
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
416
|
+
}
|
|
301
417
|
|
|
302
418
|
// src/agents/zyfai/zyfai.history-cursor.ts
|
|
303
419
|
var InvalidHistoryCursorError = class extends Error {
|
|
@@ -397,6 +513,11 @@ var DEFAULT_ZYFAI_RPC_URLS = {
|
|
|
397
513
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
398
514
|
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
399
515
|
};
|
|
516
|
+
var WETH_ADDRESS_BY_CHAIN = {
|
|
517
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
518
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
|
519
|
+
};
|
|
520
|
+
var VIEM_CHAIN = { 8453: base, 42161: arbitrum };
|
|
400
521
|
var ZyfaiAgent = class {
|
|
401
522
|
id = "zyfai";
|
|
402
523
|
supportedChainIds = [8453, 42161];
|
|
@@ -404,12 +525,18 @@ var ZyfaiAgent = class {
|
|
|
404
525
|
{
|
|
405
526
|
chainId: 8453,
|
|
406
527
|
chain: "BASE",
|
|
407
|
-
assets: [
|
|
528
|
+
assets: [
|
|
529
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
530
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
531
|
+
]
|
|
408
532
|
},
|
|
409
533
|
{
|
|
410
534
|
chainId: 42161,
|
|
411
535
|
chain: "ARBITRUM",
|
|
412
|
-
assets: [
|
|
536
|
+
assets: [
|
|
537
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
538
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
539
|
+
]
|
|
413
540
|
}
|
|
414
541
|
];
|
|
415
542
|
sdk;
|
|
@@ -417,13 +544,83 @@ var ZyfaiAgent = class {
|
|
|
417
544
|
connectedChainId = null;
|
|
418
545
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
419
546
|
connectAuthPromise = null;
|
|
547
|
+
rpcUrls;
|
|
548
|
+
publicClients = /* @__PURE__ */ new Map();
|
|
549
|
+
// txHash -> resolved withdrawn amount. Receipts are immutable, so a
|
|
550
|
+
// successful decode is cached forever; failures are NOT cached so a transient
|
|
551
|
+
// RPC error retries on the next fetch.
|
|
552
|
+
withdrawAmountCache = /* @__PURE__ */ new Map();
|
|
420
553
|
constructor(apiKey, rpcUrls) {
|
|
554
|
+
this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
|
|
421
555
|
this.sdk = new ZyfaiSDK({
|
|
422
556
|
apiKey,
|
|
423
|
-
rpcUrls: rpcUrls
|
|
557
|
+
rpcUrls: this.rpcUrls
|
|
424
558
|
});
|
|
425
559
|
this.installAuthCache();
|
|
426
560
|
}
|
|
561
|
+
getPublicClient(chainId) {
|
|
562
|
+
const cached = this.publicClients.get(chainId);
|
|
563
|
+
if (cached)
|
|
564
|
+
return cached;
|
|
565
|
+
const client = createPublicClient({
|
|
566
|
+
chain: VIEM_CHAIN[chainId],
|
|
567
|
+
transport: http(this.rpcUrls[chainId])
|
|
568
|
+
});
|
|
569
|
+
this.publicClients.set(chainId, client);
|
|
570
|
+
return client;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Fills in the true withdrawn amount for `Withdraw` entries by decoding the
|
|
574
|
+
* USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
|
|
575
|
+
* payload only reports the *remaining* position for a partial withdraw (see
|
|
576
|
+
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
577
|
+
* balance instead of the amount withdrawn.
|
|
578
|
+
*
|
|
579
|
+
* Mutates `entries` in place. Runs all lookups concurrently and is tolerant
|
|
580
|
+
* of RPC failures: an entry whose amount can't be resolved keeps
|
|
581
|
+
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
582
|
+
* rather than a wrong number.
|
|
583
|
+
*/
|
|
584
|
+
async resolveWithdrawnAmounts(entries, chainId, recipient) {
|
|
585
|
+
const asset = SupportedAssets.find(
|
|
586
|
+
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
587
|
+
);
|
|
588
|
+
if (!asset)
|
|
589
|
+
return;
|
|
590
|
+
const withdrawals = entries.filter(
|
|
591
|
+
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
592
|
+
);
|
|
593
|
+
if (withdrawals.length === 0)
|
|
594
|
+
return;
|
|
595
|
+
const client = this.getPublicClient(chainId);
|
|
596
|
+
await Promise.all(
|
|
597
|
+
withdrawals.map(async (entry) => {
|
|
598
|
+
const tx = entry.transactions[0];
|
|
599
|
+
const txHash = tx.txHashes[0];
|
|
600
|
+
const cached = this.withdrawAmountCache.get(txHash);
|
|
601
|
+
if (cached !== void 0) {
|
|
602
|
+
tx.amount = cached;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
const receipt = await client.getTransactionReceipt({
|
|
607
|
+
hash: txHash
|
|
608
|
+
});
|
|
609
|
+
const amount = extractWithdrawnAmount(
|
|
610
|
+
receipt.logs,
|
|
611
|
+
recipient,
|
|
612
|
+
asset.address,
|
|
613
|
+
asset.decimals
|
|
614
|
+
);
|
|
615
|
+
if (amount !== void 0) {
|
|
616
|
+
this.withdrawAmountCache.set(txHash, amount);
|
|
617
|
+
tx.amount = amount;
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
})
|
|
622
|
+
);
|
|
623
|
+
}
|
|
427
624
|
/**
|
|
428
625
|
* Patches `sdk.authenticateUser` so a fresh page can reuse the JWT from a
|
|
429
626
|
* prior session instead of triggering a new SIWE prompt. Runs inside
|
|
@@ -575,7 +772,7 @@ var ZyfaiAgent = class {
|
|
|
575
772
|
throw error;
|
|
576
773
|
}
|
|
577
774
|
}
|
|
578
|
-
async deposit(state, chainId, amount, depositCallback) {
|
|
775
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
579
776
|
try {
|
|
580
777
|
await this.connectFull(state, chainId);
|
|
581
778
|
const validChainId = isValidChainId(chainId);
|
|
@@ -589,13 +786,15 @@ var ZyfaiAgent = class {
|
|
|
589
786
|
validChainId,
|
|
590
787
|
amount
|
|
591
788
|
);
|
|
592
|
-
|
|
789
|
+
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
790
|
+
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
593
791
|
return { txHash, smartWallet: wallet.address, amount };
|
|
594
792
|
}
|
|
595
793
|
const raw = await this.sdk.depositFunds(
|
|
596
794
|
this.getAddress(),
|
|
597
795
|
validChainId,
|
|
598
|
-
amount
|
|
796
|
+
amount,
|
|
797
|
+
asset
|
|
599
798
|
);
|
|
600
799
|
return mapDeposit(raw);
|
|
601
800
|
} catch (error) {
|
|
@@ -674,23 +873,14 @@ var ZyfaiAgent = class {
|
|
|
674
873
|
toDate,
|
|
675
874
|
rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
|
|
676
875
|
}) : void 0;
|
|
876
|
+
const data = mapEntries(pageData, validChainId);
|
|
877
|
+
await this.resolveWithdrawnAmounts(data, validChainId, state.walletAddress);
|
|
677
878
|
return {
|
|
678
|
-
data
|
|
879
|
+
data,
|
|
679
880
|
hasMore,
|
|
680
881
|
nextCursor
|
|
681
882
|
};
|
|
682
883
|
}
|
|
683
|
-
// --- IAgent: Agent control ---
|
|
684
|
-
async pause(state, chainId) {
|
|
685
|
-
await this.connectAuth(state, chainId);
|
|
686
|
-
const raw = await this.sdk.pauseAgent();
|
|
687
|
-
return mapAgentStatus(raw);
|
|
688
|
-
}
|
|
689
|
-
async resume(state, chainId) {
|
|
690
|
-
await this.connectAuth(state, chainId);
|
|
691
|
-
const raw = await this.sdk.resumeAgent();
|
|
692
|
-
return mapAgentStatus(raw);
|
|
693
|
-
}
|
|
694
884
|
async getUserProfile(state, chainId) {
|
|
695
885
|
await this.connectAuth(state, chainId);
|
|
696
886
|
const validChainId = isValidChainId(chainId);
|
|
@@ -902,6 +1092,134 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
902
1092
|
return out;
|
|
903
1093
|
}
|
|
904
1094
|
|
|
1095
|
+
// src/client.ts
|
|
1096
|
+
import {
|
|
1097
|
+
createPublicClient as createPublicClient2,
|
|
1098
|
+
createWalletClient,
|
|
1099
|
+
custom
|
|
1100
|
+
} from "viem";
|
|
1101
|
+
import { base as base2, arbitrum as arbitrum2 } from "viem/chains";
|
|
1102
|
+
|
|
1103
|
+
// src/lib/transfer-auth.ts
|
|
1104
|
+
import { bytesToHex } from "viem";
|
|
1105
|
+
var ERC20_META_ABI = [
|
|
1106
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
1107
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
1108
|
+
];
|
|
1109
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
1110
|
+
return {
|
|
1111
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
1112
|
+
types: {
|
|
1113
|
+
TransferWithAuthorization: [
|
|
1114
|
+
{ name: "from", type: "address" },
|
|
1115
|
+
{ name: "to", type: "address" },
|
|
1116
|
+
{ name: "value", type: "uint256" },
|
|
1117
|
+
{ name: "validAfter", type: "uint256" },
|
|
1118
|
+
{ name: "validBefore", type: "uint256" },
|
|
1119
|
+
{ name: "nonce", type: "bytes32" }
|
|
1120
|
+
]
|
|
1121
|
+
},
|
|
1122
|
+
primaryType: "TransferWithAuthorization",
|
|
1123
|
+
message: input.message
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
async function readTokenMeta(publicClient, token) {
|
|
1127
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
1128
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
1129
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
1130
|
+
]);
|
|
1131
|
+
return { tokenName, tokenVersion };
|
|
1132
|
+
}
|
|
1133
|
+
function randomAuthNonce() {
|
|
1134
|
+
const bytes = new Uint8Array(32);
|
|
1135
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1136
|
+
return bytesToHex(bytes);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/lib/sponsor-client.ts
|
|
1140
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
1141
|
+
async function postSponsorTransferAuth(input) {
|
|
1142
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1143
|
+
let res;
|
|
1144
|
+
try {
|
|
1145
|
+
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
1146
|
+
method: "POST",
|
|
1147
|
+
headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
|
|
1148
|
+
body: JSON.stringify(input.body)
|
|
1149
|
+
});
|
|
1150
|
+
} catch (networkError) {
|
|
1151
|
+
throw new OwneyError(
|
|
1152
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1153
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1154
|
+
{ cause: String(networkError) }
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
const text = await res.text();
|
|
1158
|
+
let parsed = null;
|
|
1159
|
+
try {
|
|
1160
|
+
parsed = JSON.parse(text);
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
1164
|
+
throw new OwneyError(
|
|
1165
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1166
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1167
|
+
{ statusCode: res.status, responseBody: text.slice(0, 500) }
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
return parsed.data;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/lib/sponsored-deposit.ts
|
|
1174
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
1175
|
+
function makeSponsoredDepositCallback(deps) {
|
|
1176
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
1177
|
+
return async (smartWallet, chainId, amount) => {
|
|
1178
|
+
const cid = chainId;
|
|
1179
|
+
const token = deps.tokenAddressByChain[cid];
|
|
1180
|
+
if (!token) {
|
|
1181
|
+
throw new OwneyError(
|
|
1182
|
+
"CHAIN_UNSUPPORTED",
|
|
1183
|
+
`No sponsored token configured for chain ${chainId}`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
const pub = deps.getPublicClient(cid);
|
|
1187
|
+
const wallet = deps.getWalletClient(cid);
|
|
1188
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
1189
|
+
const validAfter = 0n;
|
|
1190
|
+
const validBefore = BigInt(Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS);
|
|
1191
|
+
const nonce = randomAuthNonce();
|
|
1192
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
1193
|
+
token,
|
|
1194
|
+
chainId: cid,
|
|
1195
|
+
tokenName,
|
|
1196
|
+
tokenVersion,
|
|
1197
|
+
message: { from: deps.ownerAddress, to: smartWallet, value: BigInt(amount), validAfter, validBefore, nonce }
|
|
1198
|
+
});
|
|
1199
|
+
const authSignature = await wallet.signTypedData(
|
|
1200
|
+
{ account: deps.ownerAddress, ...typedData }
|
|
1201
|
+
);
|
|
1202
|
+
const result = await post({
|
|
1203
|
+
baseUrl: deps.baseUrl,
|
|
1204
|
+
apiKey: deps.apiKey,
|
|
1205
|
+
body: {
|
|
1206
|
+
chainId: cid,
|
|
1207
|
+
token,
|
|
1208
|
+
from: deps.ownerAddress,
|
|
1209
|
+
to: smartWallet,
|
|
1210
|
+
value: amount,
|
|
1211
|
+
validAfter: validAfter.toString(),
|
|
1212
|
+
validBefore: validBefore.toString(),
|
|
1213
|
+
nonce,
|
|
1214
|
+
authSignature,
|
|
1215
|
+
tokenName,
|
|
1216
|
+
tokenVersion
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
return result.txHash;
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
905
1223
|
// src/client.ts
|
|
906
1224
|
function encodeMultiAgentCursor(map) {
|
|
907
1225
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
@@ -923,6 +1241,14 @@ function decodeMultiAgentCursor(token) {
|
|
|
923
1241
|
}
|
|
924
1242
|
return parsed;
|
|
925
1243
|
}
|
|
1244
|
+
var SPONSORED_USDC_BY_CHAIN = {
|
|
1245
|
+
8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1246
|
+
42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
|
|
1247
|
+
};
|
|
1248
|
+
var VIEM_CHAIN2 = {
|
|
1249
|
+
8453: base2,
|
|
1250
|
+
42161: arbitrum2
|
|
1251
|
+
};
|
|
926
1252
|
var OwneySDK = class {
|
|
927
1253
|
agents = /* @__PURE__ */ new Map();
|
|
928
1254
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -935,6 +1261,7 @@ var OwneySDK = class {
|
|
|
935
1261
|
state = null;
|
|
936
1262
|
apiKey;
|
|
937
1263
|
zyfaiRpcUrls;
|
|
1264
|
+
cachedSponsoredCallback = null;
|
|
938
1265
|
initializingAgentsPromise = null;
|
|
939
1266
|
constructor(config) {
|
|
940
1267
|
this.apiKey = config.apiKey;
|
|
@@ -958,6 +1285,7 @@ var OwneySDK = class {
|
|
|
958
1285
|
);
|
|
959
1286
|
}
|
|
960
1287
|
this.state = { provider, walletAddress, chainId: null };
|
|
1288
|
+
this.cachedSponsoredCallback = null;
|
|
961
1289
|
}
|
|
962
1290
|
/**
|
|
963
1291
|
* Disconnect from all agents and clear connection state.
|
|
@@ -970,6 +1298,7 @@ var OwneySDK = class {
|
|
|
970
1298
|
this.activeAgents.clear();
|
|
971
1299
|
this.disabledAgents.clear();
|
|
972
1300
|
this.state = null;
|
|
1301
|
+
this.cachedSponsoredCallback = null;
|
|
973
1302
|
}
|
|
974
1303
|
isAgentDisabled(agentId) {
|
|
975
1304
|
return this.disabledAgents.has(agentId);
|
|
@@ -1003,6 +1332,46 @@ var OwneySDK = class {
|
|
|
1003
1332
|
}
|
|
1004
1333
|
return chainId;
|
|
1005
1334
|
}
|
|
1335
|
+
requireConnectedProvider() {
|
|
1336
|
+
if (!this.state?.provider) {
|
|
1337
|
+
throw new OwneyError(
|
|
1338
|
+
"NOT_CONNECTED",
|
|
1339
|
+
"connect(provider) must be called before deposit",
|
|
1340
|
+
{}
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
return this.state.provider;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
|
|
1347
|
+
* used when the caller omits `depositCallback`. Wraps the connected EIP-1193
|
|
1348
|
+
* provider with viem `custom(provider)` to read token meta and sign the
|
|
1349
|
+
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
1350
|
+
*/
|
|
1351
|
+
getDefaultSponsoredCallback() {
|
|
1352
|
+
if (this.cachedSponsoredCallback)
|
|
1353
|
+
return this.cachedSponsoredCallback;
|
|
1354
|
+
const provider = this.requireConnectedProvider();
|
|
1355
|
+
const owner = this.state.walletAddress;
|
|
1356
|
+
this.cachedSponsoredCallback = makeSponsoredDepositCallback({
|
|
1357
|
+
apiKey: this.apiKey,
|
|
1358
|
+
ownerAddress: owner,
|
|
1359
|
+
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
1360
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
1361
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
1362
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
1363
|
+
getPublicClient: (cid) => createPublicClient2({
|
|
1364
|
+
chain: VIEM_CHAIN2[cid],
|
|
1365
|
+
transport: custom(provider)
|
|
1366
|
+
}),
|
|
1367
|
+
getWalletClient: (cid) => createWalletClient({
|
|
1368
|
+
account: owner,
|
|
1369
|
+
chain: VIEM_CHAIN2[cid],
|
|
1370
|
+
transport: custom(provider)
|
|
1371
|
+
})
|
|
1372
|
+
});
|
|
1373
|
+
return this.cachedSponsoredCallback;
|
|
1374
|
+
}
|
|
1006
1375
|
getAgent(agentId) {
|
|
1007
1376
|
const agent = this.agents.get(agentId);
|
|
1008
1377
|
if (!agent) {
|
|
@@ -1156,6 +1525,7 @@ var OwneySDK = class {
|
|
|
1156
1525
|
}
|
|
1157
1526
|
const state = this.requireState();
|
|
1158
1527
|
const chainId = this.requireChainId();
|
|
1528
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? void 0 : this.getDefaultSponsoredCallback());
|
|
1159
1529
|
if (agentId) {
|
|
1160
1530
|
if (this.isAgentDisabled(agentId)) {
|
|
1161
1531
|
throw new OwneyError(
|
|
@@ -1170,7 +1540,13 @@ var OwneySDK = class {
|
|
|
1170
1540
|
return withFailureReporting(
|
|
1171
1541
|
this.apiKey,
|
|
1172
1542
|
agent.id,
|
|
1173
|
-
() => agent.deposit(
|
|
1543
|
+
() => agent.deposit(
|
|
1544
|
+
state,
|
|
1545
|
+
chainId,
|
|
1546
|
+
amount,
|
|
1547
|
+
asset,
|
|
1548
|
+
effectiveCallback
|
|
1549
|
+
)
|
|
1174
1550
|
);
|
|
1175
1551
|
}
|
|
1176
1552
|
const totalAmount = BigInt(amount);
|
|
@@ -1204,7 +1580,13 @@ var OwneySDK = class {
|
|
|
1204
1580
|
agentResults[agent.id] = await withFailureReporting(
|
|
1205
1581
|
this.apiKey,
|
|
1206
1582
|
agent.id,
|
|
1207
|
-
() => agent.deposit(
|
|
1583
|
+
() => agent.deposit(
|
|
1584
|
+
state,
|
|
1585
|
+
chainId,
|
|
1586
|
+
agentAmount,
|
|
1587
|
+
asset,
|
|
1588
|
+
effectiveCallback
|
|
1589
|
+
)
|
|
1208
1590
|
);
|
|
1209
1591
|
}
|
|
1210
1592
|
return { agentResults };
|
|
@@ -1398,10 +1780,7 @@ var OwneySDK = class {
|
|
|
1398
1780
|
disabledBalances,
|
|
1399
1781
|
requested
|
|
1400
1782
|
);
|
|
1401
|
-
const enabledTotal = enabledBalances.reduce(
|
|
1402
|
-
(s, b) => s + b.balance,
|
|
1403
|
-
0n
|
|
1404
|
-
);
|
|
1783
|
+
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
1405
1784
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
1406
1785
|
agent,
|
|
1407
1786
|
balance,
|
|
@@ -1672,27 +2051,6 @@ var OwneySDK = class {
|
|
|
1672
2051
|
nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
|
|
1673
2052
|
};
|
|
1674
2053
|
}
|
|
1675
|
-
// --- Agent control ---
|
|
1676
|
-
/**
|
|
1677
|
-
* Pause an agent's automated operations. The agent will stop rebalancing until resumed.
|
|
1678
|
-
* @param agentId - The agent to pause (e.g. "zyfai")
|
|
1679
|
-
* @returns OwneyAgentStatus with success flag
|
|
1680
|
-
*/
|
|
1681
|
-
async pauseAgent(agentId) {
|
|
1682
|
-
const state = this.requireState();
|
|
1683
|
-
const chainId = this.requireChainId();
|
|
1684
|
-
return this.getAgent(agentId).pause(state, chainId);
|
|
1685
|
-
}
|
|
1686
|
-
/**
|
|
1687
|
-
* Resume an agent's automated operations.
|
|
1688
|
-
* @param agentId - The agent to resume (e.g. "zyfai")
|
|
1689
|
-
* @returns OwneyAgentStatus with success flag
|
|
1690
|
-
*/
|
|
1691
|
-
async resumeAgent(agentId) {
|
|
1692
|
-
const state = this.requireState();
|
|
1693
|
-
const chainId = this.requireChainId();
|
|
1694
|
-
return this.getAgent(agentId).resume(state, chainId);
|
|
1695
|
-
}
|
|
1696
2054
|
/**
|
|
1697
2055
|
* Get the user's profile for a specific agent, or all agents.
|
|
1698
2056
|
* @param agentId - Optional. Agent to query. Omit for all agents.
|
|
@@ -1767,12 +2125,13 @@ var OwneySDK = class {
|
|
|
1767
2125
|
const chainId = this.requireChainId();
|
|
1768
2126
|
const activeAgents = this.getActiveAgents();
|
|
1769
2127
|
const balances = {};
|
|
2128
|
+
const positionsByAgent = {};
|
|
1770
2129
|
let totalBalance = 0;
|
|
1771
2130
|
const entries = [...activeAgents.entries()];
|
|
1772
2131
|
const balanceResults = await Promise.allSettled(
|
|
1773
2132
|
entries.map(async ([id, agent]) => {
|
|
1774
2133
|
const b = await agent.getBalances(state, chainId);
|
|
1775
|
-
return [id, Number(b.totalBalance)];
|
|
2134
|
+
return [id, Number(b.totalBalance), b.positions ?? []];
|
|
1776
2135
|
})
|
|
1777
2136
|
);
|
|
1778
2137
|
let successCount = 0;
|
|
@@ -1781,8 +2140,9 @@ var OwneySDK = class {
|
|
|
1781
2140
|
const settledResult = balanceResults[i];
|
|
1782
2141
|
const [agentId] = entries[i];
|
|
1783
2142
|
if (settledResult.status === "fulfilled") {
|
|
1784
|
-
const [id, bal] = settledResult.value;
|
|
2143
|
+
const [id, bal, agentPositions] = settledResult.value;
|
|
1785
2144
|
balances[id] = bal;
|
|
2145
|
+
positionsByAgent[id] = agentPositions;
|
|
1786
2146
|
totalBalance += bal;
|
|
1787
2147
|
successCount += 1;
|
|
1788
2148
|
} else {
|
|
@@ -1816,7 +2176,64 @@ var OwneySDK = class {
|
|
|
1816
2176
|
}
|
|
1817
2177
|
}
|
|
1818
2178
|
return {
|
|
1819
|
-
agentAllocation: allocation
|
|
2179
|
+
agentAllocation: allocation,
|
|
2180
|
+
agentPositions: positionsByAgent
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Allocation-weighted APY from the current portfolio: each pool's APY
|
|
2185
|
+
* weighted by the user's position value in it. More accurate per-user than
|
|
2186
|
+
* the historical 30D APY. (ROUT-228)
|
|
2187
|
+
* @param options.agentId - Optional. Scope to one agent. Omit to aggregate
|
|
2188
|
+
* positions across all active agents.
|
|
2189
|
+
*/
|
|
2190
|
+
async getAllocationApy({
|
|
2191
|
+
agentId
|
|
2192
|
+
} = {}) {
|
|
2193
|
+
const state = this.requireState();
|
|
2194
|
+
const chainId = this.requireChainId();
|
|
2195
|
+
const activeAgents = agentId ? /* @__PURE__ */ new Map([[agentId, this.getAgent(agentId)]]) : this.getActiveAgents();
|
|
2196
|
+
const entries = [...activeAgents.entries()];
|
|
2197
|
+
const balanceResults = await Promise.allSettled(
|
|
2198
|
+
entries.map(async ([id, agent]) => {
|
|
2199
|
+
const b = await agent.getBalances(state, chainId);
|
|
2200
|
+
return [id, b.positions ?? []];
|
|
2201
|
+
})
|
|
2202
|
+
);
|
|
2203
|
+
const agentApy = {};
|
|
2204
|
+
const allPositions = [];
|
|
2205
|
+
let successCount = 0;
|
|
2206
|
+
const agentErrors = {};
|
|
2207
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
2208
|
+
const settled = balanceResults[i];
|
|
2209
|
+
const [aid] = entries[i];
|
|
2210
|
+
if (settled.status === "fulfilled") {
|
|
2211
|
+
const [id, positions] = settled.value;
|
|
2212
|
+
const per = computeAllocationApy(positions);
|
|
2213
|
+
agentApy[id] = {
|
|
2214
|
+
totalApy: per.totalApy,
|
|
2215
|
+
apyByChainAndAsset: per.apyByChainAndAsset
|
|
2216
|
+
};
|
|
2217
|
+
allPositions.push(...positions);
|
|
2218
|
+
successCount += 1;
|
|
2219
|
+
} else {
|
|
2220
|
+
const reason = settled.reason;
|
|
2221
|
+
agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
|
|
2222
|
+
console.error(`getAllocationApy agent "${aid}" failed:`, reason);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (successCount === 0) {
|
|
2226
|
+
throw new OwneyError(
|
|
2227
|
+
"ALLOCATION_ALL_FAILED",
|
|
2228
|
+
"Failed to fetch allocation APY: all agents failed.",
|
|
2229
|
+
{ agentErrors }
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
const overall = computeAllocationApy(allPositions);
|
|
2233
|
+
return {
|
|
2234
|
+
totalApy: overall.totalApy,
|
|
2235
|
+
apyByChainAndAsset: overall.apyByChainAndAsset,
|
|
2236
|
+
agentApy
|
|
1820
2237
|
};
|
|
1821
2238
|
}
|
|
1822
2239
|
};
|
|
@@ -1980,9 +2397,9 @@ function buildSIWXConfig(deps) {
|
|
|
1980
2397
|
}
|
|
1981
2398
|
function createOwneySIWX(config) {
|
|
1982
2399
|
const zyfai = new ZyfaiSDK2({ apiKey: config.apiKey });
|
|
1983
|
-
const
|
|
2400
|
+
const http2 = zyfai.httpClient;
|
|
1984
2401
|
return buildSIWXConfig({
|
|
1985
|
-
post: (url, data) =>
|
|
2402
|
+
post: (url, data) => http2.post(url, data),
|
|
1986
2403
|
referralSource: config.referralSource
|
|
1987
2404
|
});
|
|
1988
2405
|
}
|