@augustdigital/sdk 9.2.2 → 9.3.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.
@@ -3,4 +3,4 @@
3
3
  * Generated during publish from package.json version
4
4
  * This file is gitignored and created at publish time
5
5
  */
6
- export declare const SDK_VERSION = "9.2.2";
6
+ export declare const SDK_VERSION = "9.3.0";
@@ -6,5 +6,5 @@ exports.SDK_VERSION = void 0;
6
6
  * Generated during publish from package.json version
7
7
  * This file is gitignored and created at publish time
8
8
  */
9
- exports.SDK_VERSION = '9.2.2';
9
+ exports.SDK_VERSION = '9.3.0';
10
10
  //# sourceMappingURL=version.js.map
@@ -480,6 +480,16 @@ async function getVaultAllocations(vault, options) {
480
480
  const netValue = { value: 0 };
481
481
  let debankErr = false;
482
482
  let unfilteredTokens = [];
483
+ // Untangled publishes the vault's asset composition as its own decomposition of
484
+ // tvlUsd, with no join to `byProtocol`. It therefore cannot be accumulated into
485
+ // `tokenExposure` alongside the synthesized position tokens without
486
+ // double-counting, so it replaces those rows below for Stellar vaults.
487
+ let untangledAssets = null;
488
+ // How much of `tokenExposure` the Untangled parse produced. Only that prefix is
489
+ // replaced: the accumulator is shared with the per-borrower loop that runs
490
+ // afterwards, and a borrower on a chain with portfolio coverage would otherwise
491
+ // have its rows silently dropped rather than double-counted.
492
+ let untangledTokenCount = 0;
483
493
  const borrowerPortfolioFetchers = {
484
494
  solana: async (borrower) => {
485
495
  const portfolios = await (0, octavfi_1.fetchOctavfiPortfolios)([borrower]);
@@ -567,6 +577,8 @@ async function getVaultAllocations(vault, options) {
567
577
  const untangledRes = (0, untangled_1.transformUntangledToDebank)(untangledExposure);
568
578
  (0, debank_1.parseVaultLevelDebank)(untangledRes, protocolExposure, tokenExposure, vault, exposurePerCategory, netValue);
569
579
  unfilteredTokens = untangledRes.subaccount.tokens;
580
+ untangledAssets = (0, untangled_1.untangledAssetExposure)(untangledExposure);
581
+ untangledTokenCount = tokenExposure.length;
570
582
  defiPerBorrower[vault] = (0, debank_1.parseLoanLevelDebank)(untangledRes);
571
583
  }
572
584
  }
@@ -658,7 +670,9 @@ async function getVaultAllocations(vault, options) {
658
670
  defi: protocolExposure,
659
671
  cefi: cefiExposure,
660
672
  otc: otcPositions,
661
- tokens: tokenExposure,
673
+ tokens: untangledAssets?.length
674
+ ? [...untangledAssets, ...tokenExposure.slice(untangledTokenCount)]
675
+ : tokenExposure,
662
676
  defiPerBorrower,
663
677
  exposurePerCategory,
664
678
  netValue: netValue.value,
package/lib/sdk.d.ts CHANGED
@@ -23170,6 +23170,16 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
23170
23170
  */
23171
23171
  export declare interface IVaultAllocations {
23172
23172
  defi: IDebankProtocolExposure[];
23173
+ /**
23174
+ * The vault's asset composition, in USD (`amount` is a dollar value).
23175
+ *
23176
+ * On Stellar this is Untangled's `byAsset` breakdown, which decomposes the
23177
+ * same `tvlUsd` that `defi` does along a different axis — assets rather than
23178
+ * venues. The two do not join: which venue holds which asset is not published,
23179
+ * so this is the only place a Stellar vault's assets are stated, and reading a
23180
+ * per-venue asset split out of `exposurePerCategory` is not supported for
23181
+ * multi-asset Stellar vaults.
23182
+ */
23173
23183
  tokens: IDebankTokenExposure[];
23174
23184
  cefi: IWSSubaccountCefi[];
23175
23185
  unfilteredTokens: IDebankTokenExposure[];
@@ -1,3 +1,4 @@
1
+ import type { IDebankTokenExposure } from '../../types';
1
2
  import type { IDebankNormalizedResponse } from '../debank/utils';
2
3
  import type { IUntangledVaultExposure } from './types';
3
4
  /** Display metadata for an Untangled protocol slug. */
@@ -16,6 +17,31 @@ export interface IUntangledProtocolMetadata {
16
17
  * instead of silently vanishing from the breakdown.
17
18
  */
18
19
  export declare const UNTANGLED_PROTOCOL_REGISTRY: Record<string, IUntangledProtocolMetadata>;
20
+ /**
21
+ * The vault's asset composition, as vault-level token exposure.
22
+ *
23
+ * This is the one asset breakdown Untangled actually publishes, and it is
24
+ * authoritative: `byAsset` decomposes the same `tvlUsd` that `byProtocol` does.
25
+ * It is returned separately from {@link transformUntangledToDebank} because it
26
+ * cannot be folded into that structure — attributing an asset to a position
27
+ * would require a join the API does not provide, and adding these on top of the
28
+ * synthesized position tokens would double-count the vault.
29
+ *
30
+ * Negative rows are kept. A vault that has moved capital into another Upshift
31
+ * vault is reported holding the receipt token and owing the asset it spent —
32
+ * earnXLM carries `earnUSDC +$1,170,913` against `USDC -$1,170,271` — and those
33
+ * only sum back to `tvlUsd` together. Dropping the liability would overstate the
34
+ * vault's assets by the full size of the position.
35
+ *
36
+ * @param exposure - A resolved Untangled report, or `null`/`undefined` when the
37
+ * fetch failed.
38
+ * @returns One row per asset clearing `filterOutBySize` in absolute terms,
39
+ * largest first, with `amount` denominated in USD. Empty when `exposure` is
40
+ * absent. `id` carries the asset symbol rather than an address — Untangled
41
+ * reports assets by ticker and publishes no contract address for them — so it
42
+ * is safe as a display key and must not be read as an on-chain address.
43
+ */
44
+ export declare function untangledAssetExposure(exposure: IUntangledVaultExposure | null | undefined): IDebankTokenExposure[];
19
45
  /**
20
46
  * Transform an Untangled vault exposure report into the DeBank-normalized shape
21
47
  * that `parseVaultLevelDebank` and `parseLoanLevelDebank` consume.
@@ -29,7 +55,10 @@ export declare const UNTANGLED_PROTOCOL_REGISTRY: Record<string, IUntangledProto
29
55
  * Mapping:
30
56
  * - Each `byProtocol` entry becomes one position with a single supplying token,
31
57
  * valued in USD (`price: 1`), on the chain from
32
- * {@link UNTANGLED_PROTOCOL_REGISTRY}.
58
+ * {@link UNTANGLED_PROTOCOL_REGISTRY}. The token is labelled with the vault's
59
+ * asset only when one asset covers essentially all of it (see
60
+ * {@link vaultAssetSymbol}); otherwise it carries the value with no symbol,
61
+ * and {@link untangledAssetExposure} supplies the composition separately.
33
62
  * - `stellar-wallet` becomes a wallet token, because custody cash is undeployed
34
63
  * capital rather than protocol exposure.
35
64
  * - `upshift` is split: the vault's own idle buffer (the `idle` location) joins
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UNTANGLED_PROTOCOL_REGISTRY = void 0;
4
+ exports.untangledAssetExposure = untangledAssetExposure;
4
5
  exports.transformUntangledToDebank = transformUntangledToDebank;
6
+ const core_1 = require("../../core/helpers/core");
5
7
  /**
6
8
  * Display metadata for the protocol slugs Untangled reports today.
7
9
  *
@@ -42,6 +44,17 @@ const ASSET_MATCH_TOLERANCE = 0.001;
42
44
  * values and orders of magnitude below any holding worth naming.
43
45
  */
44
46
  const MIN_POSITION_USD = 0.01;
47
+ /**
48
+ * Share of `tvlUsd` the largest `byAsset` entry must cover before a synthesized
49
+ * position may be labelled with it.
50
+ *
51
+ * `byProtocol` and `byAsset` are two orthogonal decompositions of the same TVL,
52
+ * and Untangled publishes no join between them, so which venue holds which asset
53
+ * is genuinely unknown. Tagging every position with the largest asset is only
54
+ * honest when there is effectively nothing else in the vault; below that, the
55
+ * label would assert a holding the report does not support.
56
+ */
57
+ const SINGLE_ASSET_SHARE = 0.99;
45
58
  /**
46
59
  * USD the vault holds in its own contract, summed from the `idle` locations.
47
60
  *
@@ -107,14 +120,19 @@ const SYNTHETIC_TOKEN_DECIMALS = 7;
107
120
  *
108
121
  * `parseVaultLevelDebank` computes USD as `price * amount`, so a value already
109
122
  * denominated in dollars is expressed as `price: 1, amount: <usd>`.
123
+ *
124
+ * @param symbol - The asset this slice holds, or `null` when the report cannot
125
+ * say. A null symbol still produces a token — the value has to reach
126
+ * `netValue` and the protocol totals either way — but carries no asset label,
127
+ * so consumers render the slice's value without naming an asset.
110
128
  */
111
129
  function toDebankToken(symbol, chain, valueUsd, exposureType, protocolId) {
112
130
  return {
113
- id: symbol,
131
+ id: symbol ?? '',
114
132
  chain,
115
- name: symbol,
116
- symbol,
117
- optimized_symbol: symbol,
133
+ name: symbol ?? '',
134
+ symbol: symbol ?? '',
135
+ optimized_symbol: symbol ?? '',
118
136
  decimals: SYNTHETIC_TOKEN_DECIMALS,
119
137
  logo_url: '',
120
138
  amount: valueUsd,
@@ -124,18 +142,71 @@ function toDebankToken(symbol, chain, valueUsd, exposureType, protocolId) {
124
142
  };
125
143
  }
126
144
  /**
127
- * The asset holding most of the vault's value.
145
+ * The symbol every synthesized position may be tagged with, or `null` when the
146
+ * report cannot support tagging them at all.
128
147
  *
129
- * Untangled splits assets vault-wide rather than per protocol, so every
130
- * synthesized position is tagged with this one symbol. For the Gami vaults it is
131
- * an exact description (earnUSDC is ~100% USDC); an exact per-protocol split
132
- * needs `byProtocol[].assets[]` from Untangled.
148
+ * Untangled splits assets vault-wide rather than per protocol, so the only
149
+ * defensible label is the dominant asset, and only while it covers essentially
150
+ * the whole vault ({@link SINGLE_ASSET_SHARE}). Gami earnXLM qualifies (99.98%
151
+ * XLM); earnUSDC does not (84.18% USDC, 15.82% deJTRSY), and labelling its
152
+ * positions USDC claimed the whole $25.3M was USDC while $4.0M of deJTRSY went
153
+ * unmentioned. Callers that get `null` emit no asset label and let
154
+ * {@link untangledAssetExposure} carry the composition instead.
155
+ *
156
+ * An exact per-protocol split needs `byProtocol[].assets[]` from Untangled.
133
157
  */
134
- function dominantAssetSymbol(exposure) {
158
+ function vaultAssetSymbol(exposure) {
135
159
  const dominant = (exposure.byAsset || []).reduce((best, asset) => !best || Number(asset?.valueUsd || 0) > Number(best.valueUsd || 0)
136
160
  ? asset
137
161
  : best, null);
138
- return dominant?.symbol || 'UNKNOWN';
162
+ if (!dominant?.symbol)
163
+ return null;
164
+ const tvlUsd = Number(exposure.tvlUsd) || 0;
165
+ if (tvlUsd <= 0)
166
+ return null;
167
+ const share = (Number(dominant.valueUsd) || 0) / tvlUsd;
168
+ return share >= SINGLE_ASSET_SHARE ? dominant.symbol : null;
169
+ }
170
+ /**
171
+ * The vault's asset composition, as vault-level token exposure.
172
+ *
173
+ * This is the one asset breakdown Untangled actually publishes, and it is
174
+ * authoritative: `byAsset` decomposes the same `tvlUsd` that `byProtocol` does.
175
+ * It is returned separately from {@link transformUntangledToDebank} because it
176
+ * cannot be folded into that structure — attributing an asset to a position
177
+ * would require a join the API does not provide, and adding these on top of the
178
+ * synthesized position tokens would double-count the vault.
179
+ *
180
+ * Negative rows are kept. A vault that has moved capital into another Upshift
181
+ * vault is reported holding the receipt token and owing the asset it spent —
182
+ * earnXLM carries `earnUSDC +$1,170,913` against `USDC -$1,170,271` — and those
183
+ * only sum back to `tvlUsd` together. Dropping the liability would overstate the
184
+ * vault's assets by the full size of the position.
185
+ *
186
+ * @param exposure - A resolved Untangled report, or `null`/`undefined` when the
187
+ * fetch failed.
188
+ * @returns One row per asset clearing `filterOutBySize` in absolute terms,
189
+ * largest first, with `amount` denominated in USD. Empty when `exposure` is
190
+ * absent. `id` carries the asset symbol rather than an address — Untangled
191
+ * reports assets by ticker and publishes no contract address for them — so it
192
+ * is safe as a display key and must not be read as an on-chain address.
193
+ */
194
+ function untangledAssetExposure(exposure) {
195
+ if (!exposure)
196
+ return [];
197
+ const chain = exposure.vault?.chain || DEFAULT_CHAIN;
198
+ return (exposure.byAsset || [])
199
+ .filter((asset) => asset?.symbol && (0, core_1.filterOutBySize)(Math.abs(Number(asset.valueUsd) || 0)))
200
+ .map((asset) => ({
201
+ amount: Number(asset.valueUsd) || 0,
202
+ chain,
203
+ decimals: SYNTHETIC_TOKEN_DECIMALS,
204
+ id: asset.symbol,
205
+ logoUrl: '',
206
+ name: asset.symbol,
207
+ symbol: asset.symbol,
208
+ }))
209
+ .sort((a, b) => b.amount - a.amount);
139
210
  }
140
211
  /**
141
212
  * Transform an Untangled vault exposure report into the DeBank-normalized shape
@@ -150,7 +221,10 @@ function dominantAssetSymbol(exposure) {
150
221
  * Mapping:
151
222
  * - Each `byProtocol` entry becomes one position with a single supplying token,
152
223
  * valued in USD (`price: 1`), on the chain from
153
- * {@link UNTANGLED_PROTOCOL_REGISTRY}.
224
+ * {@link UNTANGLED_PROTOCOL_REGISTRY}. The token is labelled with the vault's
225
+ * asset only when one asset covers essentially all of it (see
226
+ * {@link vaultAssetSymbol}); otherwise it carries the value with no symbol,
227
+ * and {@link untangledAssetExposure} supplies the composition separately.
154
228
  * - `stellar-wallet` becomes a wallet token, because custody cash is undeployed
155
229
  * capital rather than protocol exposure.
156
230
  * - `upshift` is split: the vault's own idle buffer (the `idle` location) joins
@@ -184,7 +258,7 @@ function transformUntangledToDebank(exposure) {
184
258
  if (!exposure) {
185
259
  return { subaccount: { positions: [], tokens: [] } };
186
260
  }
187
- const symbol = dominantAssetSymbol(exposure);
261
+ const symbol = vaultAssetSymbol(exposure);
188
262
  const vaultChain = exposure.vault?.chain || DEFAULT_CHAIN;
189
263
  const idleUsd = idleLocationUsd(exposure);
190
264
  const positions = [];
@@ -143,6 +143,16 @@ export type IExposurePerCategory = {
143
143
  */
144
144
  export interface IVaultAllocations {
145
145
  defi: IDebankProtocolExposure[];
146
+ /**
147
+ * The vault's asset composition, in USD (`amount` is a dollar value).
148
+ *
149
+ * On Stellar this is Untangled's `byAsset` breakdown, which decomposes the
150
+ * same `tvlUsd` that `defi` does along a different axis — assets rather than
151
+ * venues. The two do not join: which venue holds which asset is not published,
152
+ * so this is the only place a Stellar vault's assets are stated, and reading a
153
+ * per-venue asset split out of `exposurePerCategory` is not supported for
154
+ * multi-asset Stellar vaults.
155
+ */
146
156
  tokens: IDebankTokenExposure[];
147
157
  cefi: IWSSubaccountCefi[];
148
158
  unfilteredTokens: IDebankTokenExposure[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augustdigital/sdk",
3
- "version": "9.2.2",
3
+ "version": "9.3.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/sdk.d.ts",
6
6
  "keywords": [