@augustdigital/sdk 9.1.0 → 9.2.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.
@@ -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.1.0";
6
+ export declare const SDK_VERSION = "9.2.1";
@@ -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.1.0';
9
+ exports.SDK_VERSION = '9.2.1';
10
10
  //# sourceMappingURL=version.js.map
package/lib/main.d.ts CHANGED
@@ -126,6 +126,12 @@ export declare class AugustSDK extends AugustBase {
126
126
  * `cefi` / `otc` arrays back the drill-downs. See the "Rendering the Vault
127
127
  * Exposure section" guide in the vaults docs for a faithful reproduction.
128
128
  *
129
+ * Stellar vaults resolve their DeFi exposure through the Untangled portfolio
130
+ * API instead of DeBank — one extra HTTP call, covering the idle buffer,
131
+ * Stellar protocol positions, custody wallets and bridged capital. If that
132
+ * call fails the exposure legs come back empty rather than throwing, so CeFi,
133
+ * OTC and loan allocations still resolve.
134
+ *
129
135
  * @param props - Vault address and chain ID
130
136
  * @returns Detailed breakdown of vault allocations by category
131
137
  * @example
package/lib/main.js CHANGED
@@ -254,6 +254,12 @@ class AugustSDK extends core_1.AugustBase {
254
254
  * `cefi` / `otc` arrays back the drill-downs. See the "Rendering the Vault
255
255
  * Exposure section" guide in the vaults docs for a faithful reproduction.
256
256
  *
257
+ * Stellar vaults resolve their DeFi exposure through the Untangled portfolio
258
+ * API instead of DeBank — one extra HTTP call, covering the idle buffer,
259
+ * Stellar protocol positions, custody wallets and bridged capital. If that
260
+ * call fails the exposure legs come back empty rather than throwing, so CeFi,
261
+ * OTC and loan allocations still resolve.
262
+ *
257
263
  * @param props - Vault address and chain ID
258
264
  * @returns Detailed breakdown of vault allocations by category
259
265
  * @example
@@ -20,8 +20,12 @@ import type { IVaultBaseOptions } from './types';
20
20
  * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
21
21
  * Optionally enriches with loan and allocation data.
22
22
  * @param vault - Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
23
- * @param loans - Include active loan data
24
- * @param allocations - Include DeFi/CeFi allocation breakdowns
23
+ * @param loans - Include active loan data. EVM vaults only — there is no
24
+ * non-EVM loan book to read, so other families keep the empty default.
25
+ * @param allocations - Include DeFi/CeFi allocation breakdowns. Resolved for EVM
26
+ * vaults (via DeBank) and Stellar vaults (via the Untangled portfolio API).
27
+ * Solana and Sui have no vault-level portfolio provider and keep the empty
28
+ * default.
25
29
  * @param options - RPC and service configuration
26
30
  * @param tokenizedVault - Optional pre-fetched backend row for `vault`. When a
27
31
  * caller already holds the row (e.g. `getVaults` fetched the whole list one
@@ -55,6 +59,15 @@ export declare function getVaultSubaccountLoans(vault: VaultAddress | IVault, op
55
59
  * Fetch comprehensive vault asset allocation breakdown.
56
60
  * Includes DeFi protocols (via DeBank), CeFi balances, OTC positions, and loans.
57
61
  * Categorizes exposures by borrowing, supplying, lending, and wallet holdings.
62
+ *
63
+ * DeFi exposure resolves per chain family: EVM vaults via DeBank, Solana
64
+ * borrowers via Octav.fi, and Stellar vaults via the Untangled portfolio API —
65
+ * one vault-level call covering the idle buffer, Stellar protocol positions,
66
+ * custody wallets, and capital bridged to other chains. The Stellar path adds
67
+ * exactly 1 HTTP call and zero for every other chain; when it fails, the
68
+ * exposure section comes back empty and the CeFi, OTC and loan allocations are
69
+ * still returned.
70
+ *
58
71
  * @param vault - Vault address
59
72
  * @param options - RPC configuration
60
73
  * @returns Detailed allocation data with exposure categorization
@@ -84,6 +84,7 @@ const TokenizedVaultV2_1 = require("../../abis/TokenizedVaultV2");
84
84
  const TokenizedVaultV2Receipt_1 = require("../../abis/TokenizedVaultV2Receipt");
85
85
  const ethereum_block_by_date_1 = __importDefault(require("ethereum-block-by-date"));
86
86
  const octavfi_1 = require("../../services/octavfi");
87
+ const untangled_1 = require("../../services/untangled");
87
88
  const date_utils_1 = require("./utils/date-utils");
88
89
  const call_data_decoder_1 = require("./utils/call-data-decoder");
89
90
  const deposits_1 = require("../../services/layerzero/deposits");
@@ -109,8 +110,12 @@ const errors_1 = require("../../core/errors");
109
110
  * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
110
111
  * Optionally enriches with loan and allocation data.
111
112
  * @param vault - Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
112
- * @param loans - Include active loan data
113
- * @param allocations - Include DeFi/CeFi allocation breakdowns
113
+ * @param loans - Include active loan data. EVM vaults only — there is no
114
+ * non-EVM loan book to read, so other families keep the empty default.
115
+ * @param allocations - Include DeFi/CeFi allocation breakdowns. Resolved for EVM
116
+ * vaults (via DeBank) and Stellar vaults (via the Untangled portfolio API).
117
+ * Solana and Sui have no vault-level portfolio provider and keep the empty
118
+ * default.
114
119
  * @param options - RPC and service configuration
115
120
  * @param tokenizedVault - Optional pre-fetched backend row for `vault`. When a
116
121
  * caller already holds the row (e.g. `getVaults` fetched the whole list one
@@ -152,24 +157,33 @@ async function getVault({ vault, loans = false, allocations = false, options, lo
152
157
  core_1.Logger.log.error('getVault', err, { vault });
153
158
  throw new Error(`#getVault::${vault}: ${err?.message}`);
154
159
  }
155
- // Loans/allocations enrichment is only supported for EVM vaults.
156
- // Non-EVM vaults already include empty defaults.
160
+ // Loans enrichment stays EVM-only there is no non-EVM loan book to read.
161
+ // Allocations additionally covers Stellar, whose exposure resolves through the
162
+ // Untangled portfolio API rather than DeBank; gating it on `isEvmVault` left
163
+ // `getVaultAllocations` unreachable for the one chain family that needed the
164
+ // new path, so callers got the empty default while the standalone getter
165
+ // returned real data. Solana and Sui have no vault-level provider, so they
166
+ // keep the empty defaults.
157
167
  const isEvmVault = returnedVault.version !== 'sol-0' &&
158
168
  returnedVault.version !== 'stellar-0' &&
159
169
  returnedVault.version !== 'sui-0';
160
- if (!isEvmVault && (loans || allocations)) {
161
- core_1.Logger.log.warn('getVault', 'Loans/allocations enrichment is not supported for non-EVM vaults — skipping', { vault, version: returnedVault.version });
170
+ const supportsLoans = isEvmVault;
171
+ const supportsAllocations = isEvmVault || returnedVault.version === 'stellar-0';
172
+ const readLoans = loans && supportsLoans;
173
+ const readAllocations = allocations && supportsAllocations;
174
+ if ((loans && !supportsLoans) || (allocations && !supportsAllocations)) {
175
+ core_1.Logger.log.warn('getVault', 'Loans/allocations enrichment is not supported for this vault — skipping', { vault, version: returnedVault.version });
162
176
  }
163
- if (isEvmVault && !(0, core_1.isBadVault)(vault) && (loans || allocations)) {
177
+ if (!(0, core_1.isBadVault)(vault) && (readLoans || readAllocations)) {
164
178
  const [loansResult, allocationsResult] = await Promise.allSettled([
165
- loans
179
+ readLoans
166
180
  ? getVaultLoans(returnedVault, options)
167
181
  : Promise.resolve(undefined),
168
- allocations
182
+ readAllocations
169
183
  ? getVaultAllocations(vault, options)
170
184
  : Promise.resolve(undefined),
171
185
  ]);
172
- if (loans) {
186
+ if (readLoans) {
173
187
  if (loansResult.status === 'fulfilled') {
174
188
  returnedVault = {
175
189
  ...returnedVault,
@@ -182,7 +196,7 @@ async function getVault({ vault, loans = false, allocations = false, options, lo
182
196
  });
183
197
  }
184
198
  }
185
- if (allocations) {
199
+ if (readAllocations) {
186
200
  if (allocationsResult.status === 'fulfilled') {
187
201
  returnedVault = {
188
202
  ...returnedVault,
@@ -433,6 +447,15 @@ function httpStatusFromError(error) {
433
447
  * Fetch comprehensive vault asset allocation breakdown.
434
448
  * Includes DeFi protocols (via DeBank), CeFi balances, OTC positions, and loans.
435
449
  * Categorizes exposures by borrowing, supplying, lending, and wallet holdings.
450
+ *
451
+ * DeFi exposure resolves per chain family: EVM vaults via DeBank, Solana
452
+ * borrowers via Octav.fi, and Stellar vaults via the Untangled portfolio API —
453
+ * one vault-level call covering the idle buffer, Stellar protocol positions,
454
+ * custody wallets, and capital bridged to other chains. The Stellar path adds
455
+ * exactly 1 HTTP call and zero for every other chain; when it fails, the
456
+ * exposure section comes back empty and the CeFi, OTC and loan allocations are
457
+ * still returned.
458
+ *
436
459
  * @param vault - Vault address
437
460
  * @param options - RPC configuration
438
461
  * @returns Detailed allocation data with exposure categorization
@@ -532,6 +555,21 @@ async function getVaultAllocations(vault, options) {
532
555
  }
533
556
  }
534
557
  }
558
+ else if ((0, core_1.getAddressChainType)(vault) === 'stellar') {
559
+ // Untangled reconciles the whole Stellar vault in one call — idle buffer,
560
+ // Stellar protocol positions, custody wallets, and value bridged to other
561
+ // chains. It is keyed by vault, not by borrower, so it runs here rather
562
+ // than in the per-borrower loop below, which would multiply the totals.
563
+ const untangledExposure = await (0, untangled_1.fetchUntangledVaultExposure)(vault);
564
+ // A miss leaves the exposure section empty — never `debankErr`. See the
565
+ // `no_fetcher` branch below for why throwing here was wrong.
566
+ if (untangledExposure) {
567
+ const untangledRes = (0, untangled_1.transformUntangledToDebank)(untangledExposure);
568
+ (0, debank_1.parseVaultLevelDebank)(untangledRes, protocolExposure, tokenExposure, vault, exposurePerCategory, netValue);
569
+ unfilteredTokens = untangledRes.subaccount.tokens;
570
+ defiPerBorrower[vault] = (0, debank_1.parseLoanLevelDebank)(untangledRes);
571
+ }
572
+ }
535
573
  // Fetch portfolio data for non-EVM borrowers (EVM handled by fetchVaultDebankResponse above).
536
574
  // CeFi and OTC fetches below run for ALL borrowers regardless of chain type.
537
575
  for (const borrower of uniqueBorrowers) {
package/lib/sdk.d.ts CHANGED
@@ -16071,6 +16071,12 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
16071
16071
  * `cefi` / `otc` arrays back the drill-downs. See the "Rendering the Vault
16072
16072
  * Exposure section" guide in the vaults docs for a faithful reproduction.
16073
16073
  *
16074
+ * Stellar vaults resolve their DeFi exposure through the Untangled portfolio
16075
+ * API instead of DeBank — one extra HTTP call, covering the idle buffer,
16076
+ * Stellar protocol positions, custody wallets and bridged capital. If that
16077
+ * call fails the exposure legs come back empty rather than throwing, so CeFi,
16078
+ * OTC and loan allocations still resolve.
16079
+ *
16074
16080
  * @param props - Vault address and chain ID
16075
16081
  * @returns Detailed breakdown of vault allocations by category
16076
16082
  * @example
@@ -19366,8 +19372,12 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
19366
19372
  * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
19367
19373
  * Optionally enriches with loan and allocation data.
19368
19374
  * @param vault - Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
19369
- * @param loans - Include active loan data
19370
- * @param allocations - Include DeFi/CeFi allocation breakdowns
19375
+ * @param loans - Include active loan data. EVM vaults only — there is no
19376
+ * non-EVM loan book to read, so other families keep the empty default.
19377
+ * @param allocations - Include DeFi/CeFi allocation breakdowns. Resolved for EVM
19378
+ * vaults (via DeBank) and Stellar vaults (via the Untangled portfolio API).
19379
+ * Solana and Sui have no vault-level portfolio provider and keep the empty
19380
+ * default.
19371
19381
  * @param options - RPC and service configuration
19372
19382
  * @param tokenizedVault - Optional pre-fetched backend row for `vault`. When a
19373
19383
  * caller already holds the row (e.g. `getVaults` fetched the whole list one
@@ -19399,6 +19409,15 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
19399
19409
  * Fetch comprehensive vault asset allocation breakdown.
19400
19410
  * Includes DeFi protocols (via DeBank), CeFi balances, OTC positions, and loans.
19401
19411
  * Categorizes exposures by borrowing, supplying, lending, and wallet holdings.
19412
+ *
19413
+ * DeFi exposure resolves per chain family: EVM vaults via DeBank, Solana
19414
+ * borrowers via Octav.fi, and Stellar vaults via the Untangled portfolio API —
19415
+ * one vault-level call covering the idle buffer, Stellar protocol positions,
19416
+ * custody wallets, and capital bridged to other chains. The Stellar path adds
19417
+ * exactly 1 HTTP call and zero for every other chain; when it fails, the
19418
+ * exposure section comes back empty and the CeFi, OTC and loan allocations are
19419
+ * still returned.
19420
+ *
19402
19421
  * @param vault - Vault address
19403
19422
  * @param options - RPC configuration
19404
19423
  * @returns Detailed allocation data with exposure categorization
@@ -0,0 +1,40 @@
1
+ import type { IUntangledVaultExposure } from './types';
2
+ /**
3
+ * Fetch a vault's full capital exposure from the Untangled portfolio API.
4
+ *
5
+ * Untangled reconciles Stellar vaults end to end — the vault's idle buffer,
6
+ * capital deployed into Stellar protocols, custody wallet balances, and value
7
+ * bridged to other chains. DeBank and Octav.fi cover none of that, which is why
8
+ * Stellar vaults previously rendered an empty exposure breakdown.
9
+ *
10
+ * The endpoint is keyed by **vault** address and already aggregates every
11
+ * borrower, so it must be called once per vault — never once per borrower, which
12
+ * would multiply the same totals.
13
+ *
14
+ * Requests are plain `GET`s with no custom headers. That is deliberate: it keeps
15
+ * them preflight-free in the browser, where the API's `access-control-allow-origin: *`
16
+ * covers the simple request but a bare `OPTIONS` is rejected.
17
+ *
18
+ * Never throws. Every failure — unknown vault, upstream error, transport
19
+ * failure, malformed body — resolves to `null` so callers can degrade to an
20
+ * empty exposure section instead of losing the rest of their response.
21
+ *
22
+ * Worst case: exactly **1 HTTP call**. No retry, no caching (the API is edge
23
+ * cached at `s-maxage=60`, well inside its 60 req/min budget for one call per
24
+ * vault page load).
25
+ *
26
+ * @param vault - Vault address, e.g. a Stellar contract address (`C…`).
27
+ * @returns The resolved exposure report, or `null` when Untangled does not
28
+ * reconcile this vault or the request failed.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const exposure = await fetchUntangledVaultExposure(
33
+ * 'CCL3WITWFFXIHV2I52ECV5DPIEOFSTU3PBPR53ILPLF2IP5KHECXRUTY',
34
+ * );
35
+ * // exposure?.tvlUsd → 24845637.05
36
+ * // exposure?.byProtocol → [{ protocolId: 'stellar-wallet', … }, { protocolId: 'blend', … }, …]
37
+ * const normalized = transformUntangledToDebank(exposure);
38
+ * ```
39
+ */
40
+ export declare function fetchUntangledVaultExposure(vault: string): Promise<IUntangledVaultExposure | null>;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchUntangledVaultExposure = fetchUntangledVaultExposure;
4
+ const core_1 = require("../../core");
5
+ const BASE_URL = 'https://api-portfolio-octopos-mainnet.untangled.finance';
6
+ /**
7
+ * Read a response body as JSON without throwing.
8
+ *
9
+ * Used on the error path, where a malformed or empty body must not mask the
10
+ * HTTP status we actually want to report.
11
+ *
12
+ * @param response - The fetch response to drain.
13
+ * @returns The parsed body, or `null` when it is absent or not JSON.
14
+ */
15
+ async function readJsonSafely(response) {
16
+ try {
17
+ return await response.json();
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * Fetch a vault's full capital exposure from the Untangled portfolio API.
25
+ *
26
+ * Untangled reconciles Stellar vaults end to end — the vault's idle buffer,
27
+ * capital deployed into Stellar protocols, custody wallet balances, and value
28
+ * bridged to other chains. DeBank and Octav.fi cover none of that, which is why
29
+ * Stellar vaults previously rendered an empty exposure breakdown.
30
+ *
31
+ * The endpoint is keyed by **vault** address and already aggregates every
32
+ * borrower, so it must be called once per vault — never once per borrower, which
33
+ * would multiply the same totals.
34
+ *
35
+ * Requests are plain `GET`s with no custom headers. That is deliberate: it keeps
36
+ * them preflight-free in the browser, where the API's `access-control-allow-origin: *`
37
+ * covers the simple request but a bare `OPTIONS` is rejected.
38
+ *
39
+ * Never throws. Every failure — unknown vault, upstream error, transport
40
+ * failure, malformed body — resolves to `null` so callers can degrade to an
41
+ * empty exposure section instead of losing the rest of their response.
42
+ *
43
+ * Worst case: exactly **1 HTTP call**. No retry, no caching (the API is edge
44
+ * cached at `s-maxage=60`, well inside its 60 req/min budget for one call per
45
+ * vault page load).
46
+ *
47
+ * @param vault - Vault address, e.g. a Stellar contract address (`C…`).
48
+ * @returns The resolved exposure report, or `null` when Untangled does not
49
+ * reconcile this vault or the request failed.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const exposure = await fetchUntangledVaultExposure(
54
+ * 'CCL3WITWFFXIHV2I52ECV5DPIEOFSTU3PBPR53ILPLF2IP5KHECXRUTY',
55
+ * );
56
+ * // exposure?.tvlUsd → 24845637.05
57
+ * // exposure?.byProtocol → [{ protocolId: 'stellar-wallet', … }, { protocolId: 'blend', … }, …]
58
+ * const normalized = transformUntangledToDebank(exposure);
59
+ * ```
60
+ */
61
+ async function fetchUntangledVaultExposure(vault) {
62
+ try {
63
+ const response = await fetch(`${BASE_URL}/v1/vaults/${encodeURIComponent(vault)}/exposure`, { method: 'GET' });
64
+ if (!response.ok) {
65
+ const body = await readJsonSafely(response);
66
+ // 404 is the documented "we don't reconcile this vault" answer, not a
67
+ // fault — most vaults are not Untangled-covered. Keep it a breadcrumb so
68
+ // a known, permanent gap does not fill Sentry.
69
+ if (response.status === 404) {
70
+ core_1.Logger.log.warn('fetchUntangledVaultExposure:unresolved', {
71
+ vault,
72
+ status: response.status,
73
+ reason: body?.reason ?? 'unknown_vault',
74
+ });
75
+ return null;
76
+ }
77
+ core_1.Logger.log.error('fetchUntangledVaultExposure', new Error(`HTTP ${response.status} ${response.statusText}`), { vault, status: response.status });
78
+ return null;
79
+ }
80
+ const data = await response.json();
81
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
82
+ core_1.Logger.log.error('fetchUntangledVaultExposure.unexpected-type', new Error(`unexpected response type: ${typeof data}`), { vault });
83
+ return null;
84
+ }
85
+ if (data.resolved !== true) {
86
+ core_1.Logger.log.warn('fetchUntangledVaultExposure:unresolved', {
87
+ vault,
88
+ status: response.status,
89
+ reason: data.reason ?? 'unknown_vault',
90
+ });
91
+ return null;
92
+ }
93
+ if (!Array.isArray(data.byProtocol)) {
94
+ core_1.Logger.log.error('fetchUntangledVaultExposure.malformed', new Error('resolved response is missing byProtocol[]'), { vault });
95
+ return null;
96
+ }
97
+ if (data.stale) {
98
+ core_1.Logger.log.warn('fetchUntangledVaultExposure:stale', {
99
+ vault,
100
+ asOf: data.asOf,
101
+ });
102
+ }
103
+ return data;
104
+ }
105
+ catch (error) {
106
+ core_1.Logger.log.error('fetchUntangledVaultExposure', error, { vault });
107
+ return null;
108
+ }
109
+ }
110
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1,3 @@
1
+ export * from './fetcher';
2
+ export * from './types';
3
+ export * from './utils';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./fetcher"), exports);
18
+ __exportStar(require("./types"), exports);
19
+ __exportStar(require("./utils"), exports);
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Response types for the Untangled portfolio API.
3
+ *
4
+ * Shape verified live on 2026-09-01 against
5
+ * `GET https://api-portfolio-octopos-mainnet.untangled.finance/v1/vaults/{address}/exposure`
6
+ * for the Gami earnUSDC Stellar vault.
7
+ */
8
+ /**
9
+ * One protocol slice of a vault's TVL.
10
+ *
11
+ * Untangled reports the whole vault, so the slices cover capital held in the
12
+ * vault contract itself, in custody wallets, and bridged to other chains — not
13
+ * just on-chain Stellar protocol positions.
14
+ */
15
+ export interface IUntangledProtocolExposure {
16
+ /**
17
+ * Untangled protocol slug, e.g. `blend`, `templar`, `aquarius`. Two slugs are
18
+ * not protocols: `stellar-wallet` (custody wallets) and `upshift` (the vault's
19
+ * own idle buffer).
20
+ */
21
+ protocolId: string;
22
+ /** USD value held in this protocol. */
23
+ valueUsd: number;
24
+ /** Share of the vault's `tvlUsd`, as a percentage (`23.54` means 23.54%). */
25
+ pct: number;
26
+ }
27
+ /**
28
+ * One asset slice of a vault's TVL. Vault-wide — the API does not currently
29
+ * break assets down per protocol.
30
+ */
31
+ export interface IUntangledAssetExposure {
32
+ /** Asset ticker, e.g. `USDC`, `XLM`. */
33
+ symbol: string;
34
+ /** USD value held in this asset across the whole vault. */
35
+ valueUsd: number;
36
+ /** Share of the vault's `tvlUsd`, as a percentage. */
37
+ pct: number;
38
+ }
39
+ /**
40
+ * One address holding part of a vault's capital — the vault contract, a custody
41
+ * wallet, or a bridged account on another chain.
42
+ */
43
+ export interface IUntangledLocation {
44
+ /** Address on `chain`. Not necessarily a Stellar address (NEAR accounts appear here). */
45
+ address: string;
46
+ /** Chain slug the address lives on, e.g. `stellar`, `near`. */
47
+ chain: string;
48
+ /** Human-readable label, e.g. `idle`, `ForDefi`, `Templar`. */
49
+ label: string;
50
+ /** USD value held at this address. */
51
+ valueUsd: number;
52
+ /** ISO-8601 timestamp this location was last reconciled. */
53
+ asOf: string;
54
+ }
55
+ /**
56
+ * A resolved vault exposure report.
57
+ *
58
+ * Untangled answers with `{ resolved: false, reason }` and HTTP 404 for vaults
59
+ * it does not reconcile; `fetchUntangledVaultExposure` maps that to `null`, so
60
+ * `resolved` is always `true` on values that reach a caller.
61
+ */
62
+ export interface IUntangledVaultExposure {
63
+ /** Always `true` — unresolved reports never reach callers. */
64
+ resolved: true;
65
+ /** The vault this report describes. */
66
+ vault: {
67
+ address: string;
68
+ chain: string;
69
+ };
70
+ /** Total USD value Untangled reconciled across every location. */
71
+ tvlUsd: number;
72
+ /** Protocol-level split of `tvlUsd`. */
73
+ byProtocol: IUntangledProtocolExposure[];
74
+ /** Asset-level split of `tvlUsd`, vault-wide. */
75
+ byAsset: IUntangledAssetExposure[];
76
+ /** Per-address drill-down of where the capital sits. */
77
+ locations: IUntangledLocation[];
78
+ /** ISO-8601 timestamp the report was assembled. */
79
+ asOf: string;
80
+ /** `true` when Untangled served a report it could not refresh in time. */
81
+ stale: boolean;
82
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Response types for the Untangled portfolio API.
4
+ *
5
+ * Shape verified live on 2026-09-01 against
6
+ * `GET https://api-portfolio-octopos-mainnet.untangled.finance/v1/vaults/{address}/exposure`
7
+ * for the Gami earnUSDC Stellar vault.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,55 @@
1
+ import type { IDebankNormalizedResponse } from '../debank/utils';
2
+ import type { IUntangledVaultExposure } from './types';
3
+ /** Display metadata for an Untangled protocol slug. */
4
+ export interface IUntangledProtocolMetadata {
5
+ /** Name rendered in the exposure breakdown. */
6
+ name: string;
7
+ /** Chain the protocol position lives on — not always the vault's own chain. */
8
+ chain: string;
9
+ }
10
+ /**
11
+ * Display metadata for the protocol slugs Untangled reports today.
12
+ *
13
+ * Untangled does not return protocol names or chains, so they are mapped here.
14
+ * Unknown slugs fall back to a capitalized slug on the vault's chain rather than
15
+ * being dropped, so a newly integrated protocol still shows up (unlabelled)
16
+ * instead of silently vanishing from the breakdown.
17
+ */
18
+ export declare const UNTANGLED_PROTOCOL_REGISTRY: Record<string, IUntangledProtocolMetadata>;
19
+ /**
20
+ * Transform an Untangled vault exposure report into the DeBank-normalized shape
21
+ * that `parseVaultLevelDebank` and `parseLoanLevelDebank` consume.
22
+ *
23
+ * This is the Stellar equivalent of `transformOctavfiToDebank`: a foreign
24
+ * portfolio shape is mapped onto the one normalized structure the vault
25
+ * allocation parser understands, so Stellar vaults populate the same
26
+ * `exposurePerCategory` / `protocolExposure` / `tokenExposure` outputs as EVM
27
+ * vaults with no parser changes.
28
+ *
29
+ * Mapping:
30
+ * - Each `byProtocol` entry becomes one position with a single supplying token,
31
+ * valued in USD (`price: 1`), on the chain from
32
+ * {@link UNTANGLED_PROTOCOL_REGISTRY}.
33
+ * - `stellar-wallet` and `upshift` are summed into one wallet token, because
34
+ * both are undeployed capital rather than protocol exposure.
35
+ * - Dust is left in place: `filterOutBySize` in the parser applies the same
36
+ * threshold Stellar and EVM vaults share.
37
+ *
38
+ * `locations`, `asOf`, `stale` and `tvlUsd` are not consumed — TVL already comes
39
+ * from `getVaultTvl`, and freshness is not yet surfaced on `IVaultAllocations`.
40
+ *
41
+ * @param exposure - A resolved Untangled report, or `null`/`undefined` when the
42
+ * fetch failed.
43
+ * @returns A DeBank-normalized response. Empty positions and tokens when
44
+ * `exposure` is absent, so callers can pass a failed fetch straight through.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const exposure = await fetchUntangledVaultExposure(vault);
49
+ * const normalized = transformUntangledToDebank(exposure);
50
+ * parseVaultLevelDebank(
51
+ * normalized, protocolExposure, tokenExposure, vault, exposurePerCategory, netValue,
52
+ * );
53
+ * ```
54
+ */
55
+ export declare function transformUntangledToDebank(exposure: IUntangledVaultExposure | null | undefined): IDebankNormalizedResponse;
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UNTANGLED_PROTOCOL_REGISTRY = void 0;
4
+ exports.transformUntangledToDebank = transformUntangledToDebank;
5
+ /**
6
+ * Display metadata for the protocol slugs Untangled reports today.
7
+ *
8
+ * Untangled does not return protocol names or chains, so they are mapped here.
9
+ * Unknown slugs fall back to a capitalized slug on the vault's chain rather than
10
+ * being dropped, so a newly integrated protocol still shows up (unlabelled)
11
+ * instead of silently vanishing from the breakdown.
12
+ */
13
+ exports.UNTANGLED_PROTOCOL_REGISTRY = {
14
+ blend: { name: 'Blend', chain: 'stellar' },
15
+ templar: { name: 'Templar (NEAR)', chain: 'near' },
16
+ aquarius: { name: 'Aquarius', chain: 'stellar' },
17
+ };
18
+ /**
19
+ * Slugs that are not protocol positions: `stellar-wallet` is custody wallet
20
+ * cash and `upshift` is the vault's own idle buffer. Both are undeployed
21
+ * capital, which the exposure breakdown renders as wallet holdings — the same
22
+ * treatment EVM vaults get for funds sitting outside a protocol.
23
+ */
24
+ const WALLET_PROTOCOL_IDS = new Set(['stellar-wallet', 'upshift']);
25
+ const DEFAULT_CHAIN = 'stellar';
26
+ /**
27
+ * Stellar's asset precision, carried only to satisfy the DeBank token shape.
28
+ * The synthesized tokens hold USD values (`price: 1`), so no consumer scales by
29
+ * this.
30
+ */
31
+ const SYNTHETIC_TOKEN_DECIMALS = 7;
32
+ /**
33
+ * Build a DeBank-shaped token carrying a USD value.
34
+ *
35
+ * `parseVaultLevelDebank` computes USD as `price * amount`, so a value already
36
+ * denominated in dollars is expressed as `price: 1, amount: <usd>`.
37
+ */
38
+ function toDebankToken(symbol, chain, valueUsd, exposureType, protocolId) {
39
+ return {
40
+ id: symbol,
41
+ chain,
42
+ name: symbol,
43
+ symbol,
44
+ optimized_symbol: symbol,
45
+ decimals: SYNTHETIC_TOKEN_DECIMALS,
46
+ logo_url: '',
47
+ amount: valueUsd,
48
+ price: 1,
49
+ protocol_id: protocolId ?? '',
50
+ supplying_exposure: exposureType === 'supply' ? 'true' : undefined,
51
+ };
52
+ }
53
+ /**
54
+ * The asset holding most of the vault's value.
55
+ *
56
+ * Untangled splits assets vault-wide rather than per protocol, so every
57
+ * synthesized position is tagged with this one symbol. For the Gami vaults it is
58
+ * an exact description (earnUSDC is ~100% USDC); an exact per-protocol split
59
+ * needs `byProtocol[].assets[]` from Untangled.
60
+ */
61
+ function dominantAssetSymbol(exposure) {
62
+ const dominant = (exposure.byAsset || []).reduce((best, asset) => !best || Number(asset?.valueUsd || 0) > Number(best.valueUsd || 0)
63
+ ? asset
64
+ : best, null);
65
+ return dominant?.symbol || 'UNKNOWN';
66
+ }
67
+ /**
68
+ * Transform an Untangled vault exposure report into the DeBank-normalized shape
69
+ * that `parseVaultLevelDebank` and `parseLoanLevelDebank` consume.
70
+ *
71
+ * This is the Stellar equivalent of `transformOctavfiToDebank`: a foreign
72
+ * portfolio shape is mapped onto the one normalized structure the vault
73
+ * allocation parser understands, so Stellar vaults populate the same
74
+ * `exposurePerCategory` / `protocolExposure` / `tokenExposure` outputs as EVM
75
+ * vaults with no parser changes.
76
+ *
77
+ * Mapping:
78
+ * - Each `byProtocol` entry becomes one position with a single supplying token,
79
+ * valued in USD (`price: 1`), on the chain from
80
+ * {@link UNTANGLED_PROTOCOL_REGISTRY}.
81
+ * - `stellar-wallet` and `upshift` are summed into one wallet token, because
82
+ * both are undeployed capital rather than protocol exposure.
83
+ * - Dust is left in place: `filterOutBySize` in the parser applies the same
84
+ * threshold Stellar and EVM vaults share.
85
+ *
86
+ * `locations`, `asOf`, `stale` and `tvlUsd` are not consumed — TVL already comes
87
+ * from `getVaultTvl`, and freshness is not yet surfaced on `IVaultAllocations`.
88
+ *
89
+ * @param exposure - A resolved Untangled report, or `null`/`undefined` when the
90
+ * fetch failed.
91
+ * @returns A DeBank-normalized response. Empty positions and tokens when
92
+ * `exposure` is absent, so callers can pass a failed fetch straight through.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const exposure = await fetchUntangledVaultExposure(vault);
97
+ * const normalized = transformUntangledToDebank(exposure);
98
+ * parseVaultLevelDebank(
99
+ * normalized, protocolExposure, tokenExposure, vault, exposurePerCategory, netValue,
100
+ * );
101
+ * ```
102
+ */
103
+ function transformUntangledToDebank(exposure) {
104
+ if (!exposure) {
105
+ return { subaccount: { positions: [], tokens: [] } };
106
+ }
107
+ const symbol = dominantAssetSymbol(exposure);
108
+ const vaultChain = exposure.vault?.chain || DEFAULT_CHAIN;
109
+ const positions = [];
110
+ let walletValueUsd = 0;
111
+ (exposure.byProtocol || []).forEach((entry) => {
112
+ if (!entry?.protocolId)
113
+ return;
114
+ const valueUsd = Number(entry.valueUsd) || 0;
115
+ if (WALLET_PROTOCOL_IDS.has(entry.protocolId)) {
116
+ walletValueUsd += valueUsd;
117
+ return;
118
+ }
119
+ const metadata = exports.UNTANGLED_PROTOCOL_REGISTRY[entry.protocolId];
120
+ const chain = metadata?.chain || vaultChain;
121
+ const name = metadata?.name ||
122
+ entry.protocolId.charAt(0).toUpperCase() + entry.protocolId.slice(1);
123
+ positions.push({
124
+ id: entry.protocolId,
125
+ chain,
126
+ name,
127
+ logo_url: '',
128
+ site_url: '',
129
+ portfolio_item_list: [
130
+ {
131
+ name,
132
+ stats: {
133
+ asset_usd_value: valueUsd,
134
+ debt_usd_value: 0,
135
+ net_usd_value: valueUsd,
136
+ },
137
+ detail: {
138
+ supply_token_list: [
139
+ toDebankToken(symbol, chain, valueUsd, 'supply', entry.protocolId),
140
+ ],
141
+ borrow_token_list: [],
142
+ },
143
+ },
144
+ ],
145
+ });
146
+ });
147
+ const tokens = [];
148
+ if (walletValueUsd > 0) {
149
+ tokens.push({
150
+ ...toDebankToken(symbol, vaultChain, walletValueUsd, 'wallet'),
151
+ is_wallet: true,
152
+ is_core: true,
153
+ });
154
+ }
155
+ return { subaccount: { positions, tokens } };
156
+ }
157
+ //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augustdigital/sdk",
3
- "version": "9.1.0",
3
+ "version": "9.2.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/sdk.d.ts",
6
6
  "keywords": [