@augustdigital/sdk 9.8.0 → 9.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/lib/adapters/evm/index.d.ts +23 -5
- package/lib/adapters/evm/index.js +25 -4
- package/lib/core/analytics/method-taxonomy.js +2 -0
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/attribution.d.ts +37 -5
- package/lib/core/attribution.js +39 -3
- package/lib/core/base.class.d.ts +6 -0
- package/lib/core/constants/swap-router.d.ts +21 -4
- package/lib/core/constants/swap-router.js +22 -5
- package/lib/core/helpers/swap-router.d.ts +66 -5
- package/lib/core/helpers/swap-router.js +158 -7
- package/lib/main.d.ts +21 -3
- package/lib/main.js +20 -3
- package/lib/modules/vaults/getters.d.ts +287 -14
- package/lib/modules/vaults/getters.js +452 -45
- package/lib/modules/vaults/main.d.ts +18 -1
- package/lib/modules/vaults/main.js +23 -0
- package/lib/modules/vaults/write.actions.d.ts +119 -29
- package/lib/modules/vaults/write.actions.js +384 -77
- package/lib/sdk.d.ts +11674 -11107
- package/lib/services/layerzero/deposits.d.ts +6 -0
- package/lib/services/layerzero/deposits.js +8 -0
- package/lib/services/subgraph/schema.d.ts +5 -2
- package/lib/services/subgraph/schema.js +22 -10
- package/lib/services/subgraph/vaults.d.ts +23 -1
- package/lib/services/subgraph/vaults.js +65 -0
- package/lib/types/subgraph.d.ts +10 -0
- package/lib/types/vaults.d.ts +38 -6
- package/package.json +1 -1
|
@@ -58,6 +58,11 @@ exports.assertPnlTokenPrice = assertPnlTokenPrice;
|
|
|
58
58
|
exports.sumDepositsAtFaceValue = sumDepositsAtFaceValue;
|
|
59
59
|
exports.selectPeerTransfers = selectPeerTransfers;
|
|
60
60
|
exports.transferCostBasis = transferCostBasis;
|
|
61
|
+
exports.historyCoversMints = historyCoversMints;
|
|
62
|
+
exports.selectReserveDeposits = selectReserveDeposits;
|
|
63
|
+
exports.countWithdrawalRequestsOnce = countWithdrawalRequestsOnce;
|
|
64
|
+
exports.settleV2Redemptions = settleV2Redemptions;
|
|
65
|
+
exports.pnlCostBasis = pnlCostBasis;
|
|
61
66
|
exports.getVaultUserLifetimePnl = getVaultUserLifetimePnl;
|
|
62
67
|
exports.getVaultPnl = getVaultPnl;
|
|
63
68
|
exports.getVaultHistoricalTimeseries = getVaultHistoricalTimeseries;
|
|
@@ -2388,12 +2393,15 @@ function sumDepositsAtFaceValue(depositsByAsset, assetDecimals, vaultDecimals) {
|
|
|
2388
2393
|
* another wallet. Mints (from the zero address), burns (to it) and transfers to
|
|
2389
2394
|
* or from the vault itself are dropped: those are the deposit and withdrawal
|
|
2390
2395
|
* legs the subgraph history already records, and counting them again would
|
|
2391
|
-
* double the cost basis.
|
|
2396
|
+
* double the cost basis. Transfers to or from `exclude` are dropped too.
|
|
2392
2397
|
*
|
|
2393
2398
|
* @param transfers - Receipt-token transfers touching `wallet`, as returned by
|
|
2394
2399
|
* the vault's transfer subgraph.
|
|
2395
2400
|
* @param wallet - The wallet whose position is being valued.
|
|
2396
2401
|
* @param vault - The vault contract address.
|
|
2402
|
+
* @param exclude - Other counterparties whose transfers are not peer
|
|
2403
|
+
* transfers, such as a LayerZero OVault's hub composer and share OFT
|
|
2404
|
+
* (`hubComposer` / `hubShareOFT` in `LAYERZERO_VAULTS`). Defaults to none.
|
|
2397
2405
|
* @returns The peer-to-peer transfers, in input order. Addresses compare
|
|
2398
2406
|
* case-insensitively.
|
|
2399
2407
|
* @example
|
|
@@ -2401,9 +2409,9 @@ function sumDepositsAtFaceValue(depositsByAsset, assetDecimals, vaultDecimals) {
|
|
|
2401
2409
|
* const peer = selectPeerTransfers(transfers, wallet, vault);
|
|
2402
2410
|
* ```
|
|
2403
2411
|
*/
|
|
2404
|
-
function selectPeerTransfers(transfers, wallet, vault) {
|
|
2412
|
+
function selectPeerTransfers(transfers, wallet, vault, exclude = []) {
|
|
2405
2413
|
const self = wallet.toLowerCase();
|
|
2406
|
-
const excluded = new Set([ethers_1.ZeroAddress.
|
|
2414
|
+
const excluded = new Set([ethers_1.ZeroAddress, vault, ...exclude].map((address) => address.toLowerCase()));
|
|
2407
2415
|
return transfers.filter((transfer) => {
|
|
2408
2416
|
const from = transfer.from.toLowerCase();
|
|
2409
2417
|
const to = transfer.to.toLowerCase();
|
|
@@ -2423,9 +2431,11 @@ function selectPeerTransfers(transfers, wallet, vault) {
|
|
|
2423
2431
|
* profit. A day with no usable ratio takes the latest earlier day's, and a
|
|
2424
2432
|
* transfer older than the history takes the earliest day's.
|
|
2425
2433
|
*
|
|
2426
|
-
* Sent shares count only against a received basis. A wallet that
|
|
2427
|
-
* but received none gets a zero basis, so the lifetime-PnL history
|
|
2428
|
-
* decides for it instead of reporting the sent shares as profit.
|
|
2434
|
+
* Sent shares count only against a received basis by default. A wallet that
|
|
2435
|
+
* sent shares but received none gets a zero basis, so the lifetime-PnL history
|
|
2436
|
+
* guard still decides for it instead of reporting the sent shares as profit.
|
|
2437
|
+
* `countSentWithoutReceipt: true` reports them anyway, for a caller that knows
|
|
2438
|
+
* the wallet also deposited.
|
|
2429
2439
|
*
|
|
2430
2440
|
* Pure and synchronous. Assumes, as the getter does, that shares and assets
|
|
2431
2441
|
* share the vault's decimals.
|
|
@@ -2446,7 +2456,7 @@ function selectPeerTransfers(transfers, wallet, vault) {
|
|
|
2446
2456
|
* });
|
|
2447
2457
|
* ```
|
|
2448
2458
|
*/
|
|
2449
|
-
function transferCostBasis({ transfers, wallet, vault, history, }) {
|
|
2459
|
+
function transferCostBasis({ transfers, wallet, vault, history, countSentWithoutReceipt = false, }) {
|
|
2450
2460
|
const ratios = Object.entries(history)
|
|
2451
2461
|
.filter(([, day]) => Number.isFinite(day.asset_share_ratio) &&
|
|
2452
2462
|
(day.asset_share_ratio ?? 0) > 0)
|
|
@@ -2478,29 +2488,341 @@ function transferCostBasis({ transfers, wallet, vault, history, }) {
|
|
|
2478
2488
|
sentRaw += assetsAt(shares, transfer.timestamp_);
|
|
2479
2489
|
}
|
|
2480
2490
|
}
|
|
2481
|
-
if (receivedRaw === BigInt(0))
|
|
2491
|
+
if (receivedRaw === BigInt(0) && !countSentWithoutReceipt) {
|
|
2482
2492
|
return { receivedRaw, sentRaw: BigInt(0) };
|
|
2493
|
+
}
|
|
2483
2494
|
return { receivedRaw, sentRaw };
|
|
2484
2495
|
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Check that a wallet's indexed deposit history accounts for every share mint
|
|
2498
|
+
* its share-token ledger shows.
|
|
2499
|
+
*
|
|
2500
|
+
* A deposit mints shares to the wallet in the same transaction, so a mint with
|
|
2501
|
+
* no deposit of the same hash means the history is missing that deposit (an
|
|
2502
|
+
* older deposit the subgraph never indexed, or one a third party made for the
|
|
2503
|
+
* wallet). Folding peer transfers into such a history would credit the wallet
|
|
2504
|
+
* for shares it sent while missing what it paid for them, so
|
|
2505
|
+
* {@link getVaultUserLifetimePnl} only folds transfers for a depositor when
|
|
2506
|
+
* this holds.
|
|
2507
|
+
*
|
|
2508
|
+
* Pure and synchronous. Hashes and addresses compare case-insensitively.
|
|
2509
|
+
*
|
|
2510
|
+
* @param params - See {@link HistoryCoversMintsParams}.
|
|
2511
|
+
* @returns `true` when every mint to `wallet` has a matching deposit, including
|
|
2512
|
+
* when there are no mints.
|
|
2513
|
+
* @example
|
|
2514
|
+
* ```ts
|
|
2515
|
+
* const covered = historyCoversMints({
|
|
2516
|
+
* transfers,
|
|
2517
|
+
* wallet,
|
|
2518
|
+
* depositTransactionHashes: history
|
|
2519
|
+
* .filter((item) => item.type === 'deposit')
|
|
2520
|
+
* .map((item) => item.transactionHash_),
|
|
2521
|
+
* });
|
|
2522
|
+
* ```
|
|
2523
|
+
*/
|
|
2524
|
+
function historyCoversMints({ transfers, wallet, depositTransactionHashes, }) {
|
|
2525
|
+
const self = wallet.toLowerCase();
|
|
2526
|
+
const zero = ethers_1.ZeroAddress.toLowerCase();
|
|
2527
|
+
const deposits = new Set(depositTransactionHashes.map((hash) => hash.toLowerCase()));
|
|
2528
|
+
return transfers.every((transfer) => transfer.from.toLowerCase() !== zero ||
|
|
2529
|
+
transfer.to.toLowerCase() !== self ||
|
|
2530
|
+
deposits.has(transfer.transactionHash_.toLowerCase()));
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Keep the `ReserveDeposit` events that explain a share mint the indexed
|
|
2534
|
+
* deposit history misses.
|
|
2535
|
+
*
|
|
2536
|
+
* High Growth ETH mints shares for deposits made through Kelp's gain adapter
|
|
2537
|
+
* with a `ReserveDeposit` event instead of an ERC-4626 `Deposit`, so those
|
|
2538
|
+
* deposits are absent from the user history and fail {@link historyCoversMints}.
|
|
2539
|
+
* An event counts only when its transaction mints shares to `wallet` and holds
|
|
2540
|
+
* no indexed deposit, so a deposit is never counted twice.
|
|
2541
|
+
*
|
|
2542
|
+
* Pure and synchronous. Hashes and addresses compare case-insensitively.
|
|
2543
|
+
*
|
|
2544
|
+
* @param params - See {@link SelectReserveDepositsParams}.
|
|
2545
|
+
* @returns The matching events, in input order.
|
|
2546
|
+
* @example
|
|
2547
|
+
* ```ts
|
|
2548
|
+
* const reserve = selectReserveDeposits({
|
|
2549
|
+
* reserveDeposits: await getSubgraphUserReserveDeposits(wallet, provider, vault),
|
|
2550
|
+
* transfers,
|
|
2551
|
+
* wallet,
|
|
2552
|
+
* depositTransactionHashes,
|
|
2553
|
+
* });
|
|
2554
|
+
* ```
|
|
2555
|
+
*/
|
|
2556
|
+
function selectReserveDeposits({ reserveDeposits, transfers, wallet, depositTransactionHashes, }) {
|
|
2557
|
+
const self = wallet.toLowerCase();
|
|
2558
|
+
const zero = ethers_1.ZeroAddress.toLowerCase();
|
|
2559
|
+
const deposits = new Set(depositTransactionHashes.map((hash) => hash.toLowerCase()));
|
|
2560
|
+
const unmatchedMints = new Set(transfers
|
|
2561
|
+
.filter((transfer) => transfer.from.toLowerCase() === zero &&
|
|
2562
|
+
transfer.to.toLowerCase() === self &&
|
|
2563
|
+
!deposits.has(transfer.transactionHash_.toLowerCase()))
|
|
2564
|
+
.map((transfer) => transfer.transactionHash_.toLowerCase()));
|
|
2565
|
+
return reserveDeposits.filter((deposit) => deposit.account.toLowerCase() === self &&
|
|
2566
|
+
unmatchedMints.has(deposit.transactionHash_.toLowerCase()));
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Drop withdrawal-request rows the vault logged more than once for a single
|
|
2570
|
+
* request.
|
|
2571
|
+
*
|
|
2572
|
+
* A redemption requested through Kelp's gain adapter makes High Growth ETH emit
|
|
2573
|
+
* `WithdrawalRequested` twice for one share transfer, and the subgraph indexes
|
|
2574
|
+
* both. In a transaction where the share ledger shows the wallet sending shares
|
|
2575
|
+
* to the vault `n` times, at most `n` request rows are kept. A transaction the
|
|
2576
|
+
* ledger shows no such transfer for keeps every row, so a missing ledger leaves
|
|
2577
|
+
* the history unchanged.
|
|
2578
|
+
*
|
|
2579
|
+
* Pure and synchronous. Hashes and addresses compare case-insensitively.
|
|
2580
|
+
*
|
|
2581
|
+
* @param params - See {@link CountWithdrawalRequestsOnceParams}.
|
|
2582
|
+
* @returns The history without the surplus request rows, in input order.
|
|
2583
|
+
* @example
|
|
2584
|
+
* ```ts
|
|
2585
|
+
* const history = countWithdrawalRequestsOnce({ history: userHistory, transfers, wallet, vault });
|
|
2586
|
+
* ```
|
|
2587
|
+
*/
|
|
2588
|
+
function countWithdrawalRequestsOnce({ history, transfers, wallet, vault, }) {
|
|
2589
|
+
const self = wallet.toLowerCase();
|
|
2590
|
+
const target = vault.toLowerCase();
|
|
2591
|
+
const remaining = new Map();
|
|
2592
|
+
for (const transfer of transfers) {
|
|
2593
|
+
if (transfer.from.toLowerCase() !== self ||
|
|
2594
|
+
transfer.to.toLowerCase() !== target)
|
|
2595
|
+
continue;
|
|
2596
|
+
const hash = transfer.transactionHash_.toLowerCase();
|
|
2597
|
+
remaining.set(hash, (remaining.get(hash) ?? 0) + 1);
|
|
2598
|
+
}
|
|
2599
|
+
return history.filter((item) => {
|
|
2600
|
+
if (item.type !== 'withdraw-request')
|
|
2601
|
+
return true;
|
|
2602
|
+
const hash = item.transactionHash_.toLowerCase();
|
|
2603
|
+
const left = remaining.get(hash);
|
|
2604
|
+
if (left === undefined)
|
|
2605
|
+
return true;
|
|
2606
|
+
if (left === 0)
|
|
2607
|
+
return false;
|
|
2608
|
+
remaining.set(hash, left - 1);
|
|
2609
|
+
return true;
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Value a wallet's Tokenized Vault V2 redemption requests, which carry only
|
|
2614
|
+
* shares.
|
|
2615
|
+
*
|
|
2616
|
+
* Every processed withdrawal (`WithdrawalProcessed.assetsAmount`) counts at the
|
|
2617
|
+
* assets the vault paid. Its shares, estimated at the vault's
|
|
2618
|
+
* `asset_share_ratio` on the day it was processed, retire the oldest requests
|
|
2619
|
+
* first. Request shares left over count at the ratio on their claim date (see
|
|
2620
|
+
* `computeClaimableDate`) once claimable: the subgraph lacks some processed rows
|
|
2621
|
+
* (none for earnAUSD before 2026-02-01), and that ratio lands within about
|
|
2622
|
+
* 0.05% of what the vault paid. Shares not yet claimable are left to the
|
|
2623
|
+
* current share price. Matching by amount, not by claim date, keeps the result
|
|
2624
|
+
* right when a vault processes a request before the date today's `lagDuration`
|
|
2625
|
+
* implies. A leftover under 0.5% of a request that a later processed
|
|
2626
|
+
* withdrawal followed is rounding from the daily ratio, so it is dropped.
|
|
2627
|
+
*
|
|
2628
|
+
* Pure and synchronous. Assumes, as the getter does, that shares and assets
|
|
2629
|
+
* share the vault's decimals. A day with no usable ratio takes the latest
|
|
2630
|
+
* earlier day's, and a date older than the history takes the earliest day's.
|
|
2631
|
+
*
|
|
2632
|
+
* @param params - See {@link SettleV2RedemptionsParams}.
|
|
2633
|
+
* @returns The settled assets and the shares left to the current price; every
|
|
2634
|
+
* requested share is pending when `ratios` holds no usable day.
|
|
2635
|
+
* @example
|
|
2636
|
+
* ```ts
|
|
2637
|
+
* const { settledAssetsRaw, pendingShares } = settleV2Redemptions({
|
|
2638
|
+
* history,
|
|
2639
|
+
* lagDuration: 259_200,
|
|
2640
|
+
* ratios: (await fetchVaultHistoricalTimeseries(vault, nDays)).data,
|
|
2641
|
+
* now: Math.floor(Date.now() / 1000),
|
|
2642
|
+
* });
|
|
2643
|
+
* ```
|
|
2644
|
+
*/
|
|
2645
|
+
function settleV2Redemptions({ history, lagDuration, ratios, now, }) {
|
|
2646
|
+
const requests = history
|
|
2647
|
+
.filter((item) => item.type === 'withdraw-request')
|
|
2648
|
+
.sort((a, b) => Number(a.timestamp_) - Number(b.timestamp_));
|
|
2649
|
+
const days = Object.entries(ratios)
|
|
2650
|
+
.filter(([, day]) => Number.isFinite(day.asset_share_ratio) &&
|
|
2651
|
+
(day.asset_share_ratio ?? 0) > 0)
|
|
2652
|
+
.map(([date, day]) => ({
|
|
2653
|
+
date,
|
|
2654
|
+
ratio: BigInt(Math.round(day.asset_share_ratio * 1e18)),
|
|
2655
|
+
}))
|
|
2656
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
2657
|
+
if (days.length === 0) {
|
|
2658
|
+
return {
|
|
2659
|
+
settledAssetsRaw: BigInt(0),
|
|
2660
|
+
pendingShares: requests.reduce((sum, item) => sum + BigInt(item.amount), BigInt(0)),
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
const scale = BigInt(10) ** BigInt(18);
|
|
2664
|
+
const ratioOn = (date) => days.filter((day) => day.date <= date).pop()?.ratio ?? days[0].ratio;
|
|
2665
|
+
const utcDay = (timestamp) => new Date(Number(timestamp) * 1000).toISOString().slice(0, 10);
|
|
2666
|
+
let settledAssetsRaw = BigInt(0);
|
|
2667
|
+
let retiredShares = BigInt(0);
|
|
2668
|
+
let lastProcessedAt = Number.NEGATIVE_INFINITY;
|
|
2669
|
+
for (const item of history) {
|
|
2670
|
+
if (item.type !== 'withdraw-processed' || item.isInstant)
|
|
2671
|
+
continue;
|
|
2672
|
+
const assets = BigInt(item.amount);
|
|
2673
|
+
settledAssetsRaw += assets;
|
|
2674
|
+
retiredShares += (assets * scale) / ratioOn(utcDay(item.timestamp_));
|
|
2675
|
+
lastProcessedAt = Math.max(lastProcessedAt, Number(item.timestamp_));
|
|
2676
|
+
}
|
|
2677
|
+
let pendingShares = BigInt(0);
|
|
2678
|
+
for (const item of requests) {
|
|
2679
|
+
const shares = BigInt(item.amount);
|
|
2680
|
+
const retired = retiredShares < shares ? retiredShares : shares;
|
|
2681
|
+
retiredShares -= retired;
|
|
2682
|
+
const open = shares - retired;
|
|
2683
|
+
// A sliver left on a request processed later is ratio rounding.
|
|
2684
|
+
if (open === BigInt(0) ||
|
|
2685
|
+
(open * BigInt(200) <= shares &&
|
|
2686
|
+
lastProcessedAt >= Number(item.timestamp_)))
|
|
2687
|
+
continue;
|
|
2688
|
+
const claim = (0, date_utils_1.computeClaimableDate)(Number(item.timestamp_), lagDuration);
|
|
2689
|
+
if ((0, date_utils_1.isClaimableNow)(claim.epoch, now)) {
|
|
2690
|
+
const key = (0, date_utils_1.formatDateKey)(claim.year, claim.month, claim.day);
|
|
2691
|
+
settledAssetsRaw += (open * ratioOn(key)) / scale;
|
|
2692
|
+
}
|
|
2693
|
+
else {
|
|
2694
|
+
pendingShares += open;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return { settledAssetsRaw, pendingShares };
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* Fold peer share transfers into a wallet's lifetime-PnL cost basis.
|
|
2701
|
+
*
|
|
2702
|
+
* Shares a wallet received from another holder (a migration airdrop, a DEX
|
|
2703
|
+
* purchase, a transfer from another wallet) cost the wallet their value on
|
|
2704
|
+
* arrival, so they add to the deposited amount; shares it sent away count as
|
|
2705
|
+
* withdrawn. This applies whether or not the wallet also deposited, so a holder
|
|
2706
|
+
* who received shares and then topped up is not credited with the received
|
|
2707
|
+
* shares as profit, and a depositor who moved its shares elsewhere (a lending
|
|
2708
|
+
* market, a DEX) is not shown as having lost them. A wallet with no deposit and
|
|
2709
|
+
* no received shares keeps a zero basis, so the history guard still decides.
|
|
2710
|
+
*
|
|
2711
|
+
* A depositor whose history misses a mint ({@link historyCoversMints}) is the
|
|
2712
|
+
* exception: its transfers do not count, so the result matches what the getter
|
|
2713
|
+
* returned before transfers counted for depositors. On a LayerZero OVault the
|
|
2714
|
+
* caller leaves the hub composer and share OFT out of the transfers (see
|
|
2715
|
+
* {@link selectPeerTransfers}): the composer's deliveries are already counted
|
|
2716
|
+
* as LayerZero deposits, and shares bridged to a spoke chain still count in the
|
|
2717
|
+
* position.
|
|
2718
|
+
*
|
|
2719
|
+
* Pure and synchronous.
|
|
2720
|
+
*
|
|
2721
|
+
* @param params - See {@link PnlCostBasisParams}.
|
|
2722
|
+
* @returns The deposited and withdrawn amounts to compute PnL from, and the
|
|
2723
|
+
* transfer-priced part of the deposited amount (zero when transfers did not
|
|
2724
|
+
* count).
|
|
2725
|
+
* @example
|
|
2726
|
+
* ```ts
|
|
2727
|
+
* const basis = pnlCostBasis({
|
|
2728
|
+
* depositedRaw,
|
|
2729
|
+
* withdrawnAssetsRaw,
|
|
2730
|
+
* hasDeposits: depositedRaw > BigInt(0),
|
|
2731
|
+
* mintsMatchHistory: historyCoversMints({
|
|
2732
|
+
* transfers,
|
|
2733
|
+
* wallet,
|
|
2734
|
+
* depositTransactionHashes,
|
|
2735
|
+
* }),
|
|
2736
|
+
* transfers: transferCostBasis({
|
|
2737
|
+
* transfers,
|
|
2738
|
+
* wallet,
|
|
2739
|
+
* vault,
|
|
2740
|
+
* history,
|
|
2741
|
+
* countSentWithoutReceipt: true,
|
|
2742
|
+
* }),
|
|
2743
|
+
* });
|
|
2744
|
+
* ```
|
|
2745
|
+
*/
|
|
2746
|
+
function pnlCostBasis({ depositedRaw, withdrawnAssetsRaw, hasDeposits, mintsMatchHistory, transfers, }) {
|
|
2747
|
+
if (hasDeposits && !mintsMatchHistory) {
|
|
2748
|
+
return {
|
|
2749
|
+
depositedRaw,
|
|
2750
|
+
withdrawnAssetsRaw,
|
|
2751
|
+
receivedByTransferRaw: BigInt(0),
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
const sentRaw = hasDeposits || transfers.receivedRaw > BigInt(0)
|
|
2755
|
+
? transfers.sentRaw
|
|
2756
|
+
: BigInt(0);
|
|
2757
|
+
return {
|
|
2758
|
+
depositedRaw: depositedRaw + transfers.receivedRaw,
|
|
2759
|
+
withdrawnAssetsRaw: withdrawnAssetsRaw + sentRaw,
|
|
2760
|
+
receivedByTransferRaw: transfers.receivedRaw,
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2485
2763
|
// One receipt-transfer subgraph query, plus one backend timeseries request when
|
|
2486
2764
|
// peer transfers exist. Any failure yields a zero basis, which leaves the
|
|
2487
2765
|
// getter exactly where it was without transfers (the history guard decides).
|
|
2488
|
-
async function fetchTransferCostBasis(vault, wallet, provider, headers) {
|
|
2766
|
+
async function fetchTransferCostBasis(vault, wallet, provider, headers, exclude = []) {
|
|
2489
2767
|
const none = { receivedRaw: BigInt(0), sentRaw: BigInt(0) };
|
|
2768
|
+
let shareTransfers = [];
|
|
2490
2769
|
try {
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
}), wallet, vault);
|
|
2770
|
+
shareTransfers = await (0, vaults_1.getSubgraphUserTransfers)(wallet, provider, vault, undefined, { receiptSubgraph: true });
|
|
2771
|
+
const transfers = selectPeerTransfers(shareTransfers, wallet, vault, exclude);
|
|
2494
2772
|
if (transfers.length === 0)
|
|
2495
|
-
return none;
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2773
|
+
return { basis: none, shareTransfers };
|
|
2774
|
+
const data = await fetchShareRatios(vault, Math.min(...transfers.map((t) => Number(t.timestamp_))), headers);
|
|
2775
|
+
return {
|
|
2776
|
+
basis: transferCostBasis({
|
|
2777
|
+
transfers,
|
|
2778
|
+
wallet,
|
|
2779
|
+
vault,
|
|
2780
|
+
history: data,
|
|
2781
|
+
countSentWithoutReceipt: true,
|
|
2782
|
+
}),
|
|
2783
|
+
shareTransfers,
|
|
2784
|
+
};
|
|
2500
2785
|
}
|
|
2501
2786
|
catch (e) {
|
|
2502
2787
|
core_1.Logger.log.warn('getVaultUserLifetimePnl:transferCostBasis', `Failed to price shares received by transfer: ${e}`);
|
|
2503
|
-
return none;
|
|
2788
|
+
return { basis: none, shareTransfers };
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
// The vault's daily history from `since` (unix seconds) on. A vault whose
|
|
2792
|
+
// history stops before then (a closed vault) is read in full instead, so the
|
|
2793
|
+
// latest earlier day still prices it.
|
|
2794
|
+
async function fetchShareRatios(vault, since, headers) {
|
|
2795
|
+
const nDays = Math.ceil((Date.now() / 1000 - since) / 86_400) + 1;
|
|
2796
|
+
const { data } = await (0, core_1.fetchVaultHistoricalTimeseries)(vault, nDays, headers);
|
|
2797
|
+
const usable = Object.values(data ?? {}).some((day) => Number.isFinite(day.asset_share_ratio) &&
|
|
2798
|
+
(day.asset_share_ratio ?? 0) > 0);
|
|
2799
|
+
if (usable)
|
|
2800
|
+
return data;
|
|
2801
|
+
return (await (0, core_1.fetchVaultHistoricalTimeseries)(vault, 3650, headers)).data;
|
|
2802
|
+
}
|
|
2803
|
+
// Values a Tokenized Vault V2 wallet's redemption requests: one lagDuration()
|
|
2804
|
+
// read and one backend timeseries request. Undefined when either fails, so the
|
|
2805
|
+
// caller values every request at the current share price.
|
|
2806
|
+
async function readV2Redemptions(provider, vault, history, headers) {
|
|
2807
|
+
try {
|
|
2808
|
+
const contract = (0, core_1.createContract)({
|
|
2809
|
+
provider,
|
|
2810
|
+
address: vault,
|
|
2811
|
+
abi: TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2,
|
|
2812
|
+
});
|
|
2813
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2814
|
+
const earliest = Math.min(...history
|
|
2815
|
+
.filter((item) => item.type === 'withdraw-request')
|
|
2816
|
+
.map((item) => Number(item.timestamp_)));
|
|
2817
|
+
const [lagDuration, data] = await Promise.all([
|
|
2818
|
+
readLagDuration(contract, vault, 'getVaultUserLifetimePnl'),
|
|
2819
|
+
fetchShareRatios(vault, earliest, headers),
|
|
2820
|
+
]);
|
|
2821
|
+
return settleV2Redemptions({ history, lagDuration, ratios: data, now });
|
|
2822
|
+
}
|
|
2823
|
+
catch (e) {
|
|
2824
|
+
core_1.Logger.log.warn('getVaultUserLifetimePnl:v2Redemptions', `Failed to value redemption requests: ${e}`);
|
|
2825
|
+
return undefined;
|
|
2504
2826
|
}
|
|
2505
2827
|
}
|
|
2506
2828
|
/**
|
|
@@ -2524,13 +2846,39 @@ async function fetchTransferCostBasis(vault, wallet, provider, headers) {
|
|
|
2524
2846
|
* from the price server. There is no stand-in price: when none resolves the
|
|
2525
2847
|
* getter throws rather than returning USD values it cannot know.
|
|
2526
2848
|
*
|
|
2527
|
-
* Shares
|
|
2528
|
-
*
|
|
2529
|
-
*
|
|
2530
|
-
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2849
|
+
* Shares moved by transfer: peer transfers of the share token (see
|
|
2850
|
+
* {@link selectPeerTransfers}) are valued at the vault's daily share ratio
|
|
2851
|
+
* ({@link transferCostBasis}) and folded in by {@link pnlCostBasis} — received
|
|
2852
|
+
* shares add to `totalDeposited` (and are reported again as
|
|
2853
|
+
* `receivedByTransfer`), sent shares add to `totalWithdrawn`. This holds
|
|
2854
|
+
* alongside indexed deposits, so a wallet that received shares and also
|
|
2855
|
+
* deposited is not credited with the received shares as profit. LayerZero
|
|
2856
|
+
* OVaults count transfers only when history holds no deposit, because their
|
|
2857
|
+
* composer delivers cross-chain deposits by transfer.
|
|
2858
|
+
*
|
|
2859
|
+
* Gaps in the indexed history: a share mint with no `Deposit` event counts as
|
|
2860
|
+
* a deposit of the assets in its `ReserveDeposit` event
|
|
2861
|
+
* ({@link selectReserveDeposits}; High Growth ETH emits it for Kelp gain-adapter
|
|
2862
|
+
* deposits), and a withdrawal request the vault logged twice counts once
|
|
2863
|
+
* ({@link countWithdrawalRequestsOnce}). Transfers fold in for a depositor only
|
|
2864
|
+
* once every mint is explained.
|
|
2865
|
+
*
|
|
2866
|
+
* Tokenized Vault V2 redemptions carry only shares, so a settled one counts at
|
|
2867
|
+
* the assets the vault paid, or at the share ratio on its claim date when the
|
|
2868
|
+
* subgraph has no processed row for it ({@link settleV2Redemptions}). Only a
|
|
2869
|
+
* request not yet claimable is valued at the current share price. When the lag
|
|
2870
|
+
* or the ratio history cannot be read, every request is valued at the current
|
|
2871
|
+
* share price instead.
|
|
2872
|
+
*
|
|
2873
|
+
* Request cost: one receipt-transfer subgraph query (and a `symbol()` RPC read)
|
|
2874
|
+
* per call, plus one backend timeseries request when peer transfers exist and
|
|
2875
|
+
* one `reserveDeposits` subgraph query when a mint has no indexed deposit, and
|
|
2876
|
+
* for a Tokenized Vault V2 wallet with redemption requests, a `lagDuration()`
|
|
2877
|
+
* read plus one backend timeseries request. A timeseries window with no usable
|
|
2878
|
+
* ratio (a closed vault whose history stopped earlier) is read again in full,
|
|
2879
|
+
* so the latest earlier day still prices it. For
|
|
2880
|
+
* non-LayerZero vaults the transfer query runs in parallel with the history
|
|
2881
|
+
* read; for LayerZero vaults it runs only when history holds no deposit.
|
|
2534
2882
|
*
|
|
2535
2883
|
* @param vault - Vault contract address
|
|
2536
2884
|
* @param wallet - User wallet address
|
|
@@ -2554,6 +2902,17 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2554
2902
|
if (!(0, ethers_1.isAddress)(wallet))
|
|
2555
2903
|
throw new Error(`Wallet input parameter is not an address: ${String(wallet)}`);
|
|
2556
2904
|
const provider = (0, core_1.createProvider)(options.rpcUrl);
|
|
2905
|
+
const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
|
|
2906
|
+
const headers = options?.headers;
|
|
2907
|
+
// Peer transfers always count (see pnlCostBasis), so start that read
|
|
2908
|
+
// alongside the history instead of after it. A LayerZero OVault's hub
|
|
2909
|
+
// composer and share OFT are not peers.
|
|
2910
|
+
const transferCostBasisPromise = fetchTransferCostBasis(vault, wallet, provider, headers, lzVaultKey
|
|
2911
|
+
? [
|
|
2912
|
+
deposits_1.LAYERZERO_VAULTS[lzVaultKey].hubComposer,
|
|
2913
|
+
deposits_1.LAYERZERO_VAULTS[lzVaultKey].hubShareOFT,
|
|
2914
|
+
]
|
|
2915
|
+
: []);
|
|
2557
2916
|
// Parallelize independent operations
|
|
2558
2917
|
const [decimals, userHistory, positions, tokenizedVaultResult] = await Promise.all([
|
|
2559
2918
|
(0, core_1.getDecimals)(provider, vault),
|
|
@@ -2579,7 +2938,6 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2579
2938
|
throw new Error(`Failed to resolve decimals for vault ${vault}`);
|
|
2580
2939
|
}
|
|
2581
2940
|
// Fetch LayerZero deposits/redeems for supported vaults
|
|
2582
|
-
const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
|
|
2583
2941
|
let lzDeposits = [];
|
|
2584
2942
|
let lzRedeems = [];
|
|
2585
2943
|
if (lzVaultKey) {
|
|
@@ -2639,19 +2997,25 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2639
2997
|
}
|
|
2640
2998
|
return { underlyingTokenSymbol, underlyingAssetAddress };
|
|
2641
2999
|
})();
|
|
3000
|
+
const { basis: transfers, shareTransfers } = await transferCostBasisPromise;
|
|
2642
3001
|
// Process history - count all withdrawal requests (both processed and pending)
|
|
2643
|
-
// We don't count processed withdrawals separately to avoid double counting
|
|
3002
|
+
// We don't count processed withdrawals separately to avoid double counting,
|
|
3003
|
+
// except on Tokenized Vault V2 (below), whose requests carry only shares.
|
|
2644
3004
|
// Group deposit amounts by asset address to handle different decimals
|
|
2645
3005
|
const depositsByAsset = new Map();
|
|
2646
3006
|
// Separate withdrawal tracking: shares need conversion, assets don't
|
|
2647
3007
|
let totalWithdrawalsInShares = BigInt(0);
|
|
2648
3008
|
let totalWithdrawalsInAssets = BigInt(0);
|
|
2649
|
-
|
|
3009
|
+
let requestedShares = BigInt(0);
|
|
3010
|
+
// ignore bad transactions when calculating pnl
|
|
3011
|
+
const history = countWithdrawalRequestsOnce({
|
|
3012
|
+
history: userHistory,
|
|
3013
|
+
transfers: shareTransfers,
|
|
3014
|
+
wallet,
|
|
3015
|
+
vault,
|
|
3016
|
+
}).filter((item) => !(0, core_1.isBadTransaction)(item.transactionHash_, tokenizedVault.chain));
|
|
3017
|
+
for (const historyItem of history) {
|
|
2650
3018
|
const amount = BigInt(historyItem.amount);
|
|
2651
|
-
// ignore bad transactions when calculating pnl
|
|
2652
|
-
if ((0, core_1.isBadTransaction)(historyItem.transactionHash_, tokenizedVault.chain)) {
|
|
2653
|
-
continue;
|
|
2654
|
-
}
|
|
2655
3019
|
if (historyItem.type === 'deposit') {
|
|
2656
3020
|
// Use assetIn if available (V2 vaults), otherwise use a default key
|
|
2657
3021
|
// Use proper type guard instead of type assertion
|
|
@@ -2663,7 +3027,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2663
3027
|
}
|
|
2664
3028
|
else if (historyItem.type === 'withdraw-request') {
|
|
2665
3029
|
// Withdraw requests are in shares, need conversion to assets
|
|
2666
|
-
|
|
3030
|
+
requestedShares += amount;
|
|
2667
3031
|
}
|
|
2668
3032
|
else if (historyItem.type === 'withdraw-processed' &&
|
|
2669
3033
|
historyItem.isInstant) {
|
|
@@ -2671,6 +3035,18 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2671
3035
|
totalWithdrawalsInAssets += amount;
|
|
2672
3036
|
}
|
|
2673
3037
|
}
|
|
3038
|
+
// A V2 request is worth what the vault paid when it settled it, not the
|
|
3039
|
+
// current share price (see settleV2Redemptions).
|
|
3040
|
+
const v2Redemptions = version === 'evm-2' && requestedShares > BigInt(0)
|
|
3041
|
+
? await readV2Redemptions(provider, vault, history, headers)
|
|
3042
|
+
: undefined;
|
|
3043
|
+
if (v2Redemptions) {
|
|
3044
|
+
totalWithdrawalsInShares += v2Redemptions.pendingShares;
|
|
3045
|
+
totalWithdrawalsInAssets += v2Redemptions.settledAssetsRaw;
|
|
3046
|
+
}
|
|
3047
|
+
else {
|
|
3048
|
+
totalWithdrawalsInShares += requestedShares;
|
|
3049
|
+
}
|
|
2674
3050
|
// Include LayerZero cross-chain deposits and redeems
|
|
2675
3051
|
if (lzVaultKey && lzDeposits.length > 0) {
|
|
2676
3052
|
for (const lzDeposit of lzDeposits) {
|
|
@@ -2689,15 +3065,35 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2689
3065
|
totalWithdrawalsInShares += BigInt(lzRedeem.shareAmt);
|
|
2690
3066
|
}
|
|
2691
3067
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
3068
|
+
const depositTransactionHashes = userHistory
|
|
3069
|
+
.filter((item) => item.type === 'deposit')
|
|
3070
|
+
.map((item) => item.transactionHash_);
|
|
3071
|
+
let mintsMatchHistory = historyCoversMints({
|
|
3072
|
+
transfers: shareTransfers,
|
|
3073
|
+
wallet,
|
|
3074
|
+
depositTransactionHashes,
|
|
3075
|
+
});
|
|
3076
|
+
if (!mintsMatchHistory) {
|
|
3077
|
+
const reserveDeposits = selectReserveDeposits({
|
|
3078
|
+
reserveDeposits: await (0, vaults_1.getSubgraphUserReserveDeposits)(wallet, provider, vault),
|
|
3079
|
+
transfers: shareTransfers,
|
|
3080
|
+
wallet,
|
|
3081
|
+
depositTransactionHashes,
|
|
3082
|
+
}).filter((deposit) => !(0, core_1.isBadTransaction)(deposit.transactionHash_, tokenizedVault.chain));
|
|
3083
|
+
for (const deposit of reserveDeposits) {
|
|
3084
|
+
depositsByAsset.set('default', (depositsByAsset.get('default') ?? BigInt(0)) +
|
|
3085
|
+
BigInt(deposit.assets));
|
|
3086
|
+
}
|
|
3087
|
+
mintsMatchHistory = historyCoversMints({
|
|
3088
|
+
transfers: shareTransfers,
|
|
3089
|
+
wallet,
|
|
3090
|
+
depositTransactionHashes: [
|
|
3091
|
+
...depositTransactionHashes,
|
|
3092
|
+
...reserveDeposits.map((deposit) => deposit.transactionHash_),
|
|
3093
|
+
],
|
|
3094
|
+
});
|
|
2700
3095
|
}
|
|
3096
|
+
const hasDeposits = depositsByAsset.size > 0;
|
|
2701
3097
|
// Fetch decimals for each unique asset in parallel
|
|
2702
3098
|
const assetDecimals = new Map();
|
|
2703
3099
|
await Promise.all(Array.from(depositsByAsset.keys()).map(async (assetAddress) => {
|
|
@@ -2845,7 +3241,14 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2845
3241
|
// For non-evm-2 or when share price is 0, treat shares as assets
|
|
2846
3242
|
totalWithdrawnRaw += totalWithdrawalsInShares;
|
|
2847
3243
|
}
|
|
2848
|
-
const
|
|
3244
|
+
const costBasis = pnlCostBasis({
|
|
3245
|
+
depositedRaw: totalDepositedNativeRaw,
|
|
3246
|
+
withdrawnAssetsRaw: totalWithdrawnRaw,
|
|
3247
|
+
hasDeposits,
|
|
3248
|
+
mintsMatchHistory,
|
|
3249
|
+
transfers,
|
|
3250
|
+
});
|
|
3251
|
+
const totalWithdrawn = (0, core_1.toNormalizedBn)(costBasis.withdrawnAssetsRaw, decimals);
|
|
2849
3252
|
// Guard the 2026-07-09 over-inflation incident: a live position with no
|
|
2850
3253
|
// deposits *and* no withdrawals means the history failed to load, and the
|
|
2851
3254
|
// PnL formula would report the whole position as profit. Fail loudly with a
|
|
@@ -2853,8 +3256,8 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2853
3256
|
assertPnlHistoryConsistent({
|
|
2854
3257
|
vault,
|
|
2855
3258
|
wallet,
|
|
2856
|
-
hasDeposits:
|
|
2857
|
-
totalWithdrawnRaw,
|
|
3259
|
+
hasDeposits: hasDeposits || costBasis.receivedByTransferRaw > BigInt(0),
|
|
3260
|
+
totalWithdrawnRaw: costBasis.withdrawnAssetsRaw,
|
|
2858
3261
|
currentPositionRaw: BigInt(currentPositionValue.raw),
|
|
2859
3262
|
});
|
|
2860
3263
|
const tokenPriceUsd = assertPnlTokenPrice({
|
|
@@ -2867,8 +3270,10 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2867
3270
|
// computed above) — convert to USD with the same underlying price used
|
|
2868
3271
|
// for the other two legs below, so all three legs share one conversion
|
|
2869
3272
|
// factor instead of drifting independently against each other.
|
|
2870
|
-
const totalDeposited = (0, core_1.toNormalizedBn)(
|
|
3273
|
+
const totalDeposited = (0, core_1.toNormalizedBn)(costBasis.depositedRaw, decimals);
|
|
2871
3274
|
const totalDepositedUsd = Number(totalDeposited.normalized) * tokenPriceUsd;
|
|
3275
|
+
const receivedByTransfer = (0, core_1.toNormalizedBn)(costBasis.receivedByTransferRaw, decimals);
|
|
3276
|
+
const receivedByTransferUsd = Number(receivedByTransfer.normalized) * tokenPriceUsd;
|
|
2872
3277
|
const totalWithdrawnUsd = Number(totalWithdrawn.normalized) * tokenPriceUsd;
|
|
2873
3278
|
const currentPositionValueUsd = Number(currentPositionValue.normalized) * tokenPriceUsd;
|
|
2874
3279
|
// Calculate lifetimePnl in USD first (since deposits may be in different tokens with different prices)
|
|
@@ -2896,6 +3301,8 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
2896
3301
|
lifetimePnl,
|
|
2897
3302
|
lifetimePnlUsd,
|
|
2898
3303
|
pnlPercentage,
|
|
3304
|
+
receivedByTransfer,
|
|
3305
|
+
receivedByTransferUsd,
|
|
2899
3306
|
};
|
|
2900
3307
|
}
|
|
2901
3308
|
catch (e) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Export getters
|
|
8
8
|
*/
|
|
9
9
|
export * from './getters';
|
|
10
|
-
import type { IAddress, IChainId, INormalizedNumber, IVaultHistoricalParams, IVaultPosition, IVaultUserHistoryItem } from '../../types';
|
|
10
|
+
import type { IAddress, IChainId, IGetOriginInfoOptions, INormalizedNumber, IOriginInfo, IVaultHistoricalParams, IVaultPosition, IVaultUserHistoryItem } from '../../types';
|
|
11
11
|
import type SolanaAdapter from '../../adapters/solana';
|
|
12
12
|
import { AugustBase, type IAugustBase } from '../../core';
|
|
13
13
|
import SuiAdapter from '../../adapters/sui';
|
|
@@ -382,6 +382,23 @@ export declare class AugustVaults extends AugustBase {
|
|
|
382
382
|
getSwapRouterEligibleVaults(chainId: number, options?: {
|
|
383
383
|
fromBlock?: number;
|
|
384
384
|
}): Promise<IAddress[]>;
|
|
385
|
+
/**
|
|
386
|
+
* Read a SwapRouter origin code's on-chain registration through this
|
|
387
|
+
* instance's provider for `chainId`. See {@link getOriginInfo} for the
|
|
388
|
+
* registration rule and caching.
|
|
389
|
+
*
|
|
390
|
+
* @param options - `chainId` and the `bytes32` `originCode` to look up.
|
|
391
|
+
* @returns {@link IOriginInfo}.
|
|
392
|
+
* @throws AugustValidationError (`INVALID_CHAIN`) when no provider is
|
|
393
|
+
* configured for `chainId`; (`INVALID_INPUT`) for a malformed code.
|
|
394
|
+
* @throws AugustSDKError when the `origins(bytes32)` read fails.
|
|
395
|
+
* @worstCaseRpcCalls 1 — one `origins(bytes32)` read, cached for 5 minutes.
|
|
396
|
+
* @example
|
|
397
|
+
* ```ts
|
|
398
|
+
* const info = await augustSdk.getOriginInfo({ chainId: 1, originCode });
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
getOriginInfo(options: Omit<IGetOriginInfoOptions, 'rpcUrl'>): Promise<IOriginInfo>;
|
|
385
402
|
/**
|
|
386
403
|
* @function getStakingPositions gets all available reward staking positions
|
|
387
404
|
* @param wallet optionally passed user wallet address
|