@augustdigital/sdk 9.2.0 → 9.2.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.
@@ -48,7 +48,14 @@ const utils_2 = require("../../modules/vaults/utils");
48
48
  * @returns Formatted vault object with Solana-specific fields
49
49
  */
50
50
  const getSolanaVault = async (tokenizedVault, options) => {
51
- const solanaRpcUrl = options.rpcUrl;
51
+ // `options.rpcUrl` is resolved upstream as `providers[vault.chain]`, and that
52
+ // map is keyed by EVM chain id — Solana's synthetic id is never in it, so the
53
+ // field arrives undefined for every Solana vault however well the app has
54
+ // configured its Solana RPC. The connection built from that RPC *is* passed
55
+ // (`solanaService`), so take the endpoint off it rather than fetching against
56
+ // `undefined`.
57
+ const solanaRpcUrl = (options.rpcUrl ??
58
+ options.solanaService?.connection?.rpcEndpoint);
52
59
  const solanaMetadata = tokenizedVault.solana_vault_metadata;
53
60
  const programId = utils_1.SolanaUtils.resolveProgramId(tokenizedVault.address, solanaMetadata);
54
61
  // Fallback decimals: prefer backend metadata, then the application default
@@ -333,6 +333,17 @@ async function getVaultMints({ vaultProgramId, vaultAddress, connection, idl, })
333
333
  }
334
334
  }
335
335
  async function getToken({ mintAddress, endpoint, connection, }) {
336
+ // `endpoint` is optional on ISolanaConnectionOptions, so a caller with no
337
+ // Solana RPC configured reaches `fetch(undefined, ...)`, which throws
338
+ // "Cannot read properties of undefined (reading 'toString')" from inside the
339
+ // runtime's fetch wrapper — an unactionable error masquerading as a token
340
+ // failure. Bail before the request instead.
341
+ if (!endpoint) {
342
+ core_1.Logger.log.warn('getToken', 'No Solana RPC endpoint configured', {
343
+ mintAddress: String(mintAddress),
344
+ });
345
+ return null;
346
+ }
336
347
  try {
337
348
  const cacheKey = `solana_token-${mintAddress}`;
338
349
  const cachedData = await (0, core_1.getSafeCache)(cacheKey);
@@ -425,8 +436,11 @@ async function getToken({ mintAddress, endpoint, connection, }) {
425
436
  return null;
426
437
  }
427
438
  catch (error) {
428
- core_1.Logger.log.error('getToken', error, {
429
- message: 'Error fetching token metadata',
439
+ // Callers fall back to backend metadata when this returns null, so a failed
440
+ // lookup is degraded-but-handled — warn, don't raise it as an error.
441
+ core_1.Logger.log.warn('getToken', 'Error fetching token metadata', {
442
+ mintAddress: String(mintAddress),
443
+ error,
430
444
  });
431
445
  return null;
432
446
  }
@@ -143,13 +143,34 @@ const ERROR_DEDUPE_MAX_KEYS = 500;
143
143
  * true rate. `resetAnalytics()` clears this map for the whole process.
144
144
  */
145
145
  const errorDedupeState = new Map();
146
+ /**
147
+ * Strip the per-request variables out of an error message.
148
+ *
149
+ * Truncating at 120 characters is not enough on its own: the variable part is
150
+ * usually a vault or wallet address near the FRONT of the message
151
+ * (`Request timeout after 90s: /subaccount/0x3F13…/otc_positions`), so one
152
+ * failure mode still produced a distinct signature — and therefore its own
153
+ * private rate-limit allowance and its own Sentry issue — per address. That is
154
+ * how a single backend timeout became 5k events across a dozen issues in a day.
155
+ *
156
+ * Placeholders are kept structural (`/subaccount/<addr>/otc_positions`) so
157
+ * genuinely different endpoints and failure modes stay separate.
158
+ *
159
+ * @param message - The raw error message.
160
+ * @returns The message with addresses, numbers, and query strings masked.
161
+ */
162
+ function normalizeErrorMessage(message) {
163
+ return message
164
+ .replace(/0x[0-9a-fA-F]{6,}/g, '<addr>')
165
+ .replace(/\?[^\s]*/g, '')
166
+ .replace(/\b\d{4,}\b/g, '<n>');
167
+ }
146
168
  /**
147
169
  * Build a low-cardinality signature for an error.
148
170
  *
149
- * Message tails carry the variable parts (addresses, block ranges, response
150
- * bodies), so only the leading 120 characters participate — enough to separate
151
- * distinct failure modes without splitting one failure mode into thousands of
152
- * unique keys.
171
+ * The message is normalized (see {@link normalizeErrorMessage}) and then capped
172
+ * at 120 characters — enough to separate distinct failure modes without
173
+ * splitting one failure mode into thousands of unique keys.
153
174
  *
154
175
  * @param error - The normalized error being captured.
155
176
  * @param origin - The `sdk.origin` call-site tag, when present.
@@ -157,7 +178,8 @@ const errorDedupeState = new Map();
157
178
  */
158
179
  function errorSignature(error, origin) {
159
180
  const tag = typeof origin === 'string' ? origin : '';
160
- return `${tag}|${error.name}|${(error.message || '').slice(0, 120)}`;
181
+ const message = normalizeErrorMessage(error.message || '').slice(0, 120);
182
+ return `${tag}|${error.name}|${message}`;
161
183
  }
162
184
  /**
163
185
  * Decide whether an error should be sent to Sentry, applying a per-signature
@@ -265,6 +287,17 @@ function createSentrySink() {
265
287
  if (admission.suppressed > 0) {
266
288
  scope.setExtra('sdk.suppressed_since_last', admission.suppressed);
267
289
  }
290
+ // Group by the same normalized signature the rate limiter uses.
291
+ // Sentry's default grouping keys on the raw message, so one backend
292
+ // timeout fans out into an issue per vault/wallet address — the
293
+ // per-issue notification volume that flooded the on-call webhook.
294
+ // Addresses stay recoverable from the event message and tags.
295
+ scope.setFingerprint?.([
296
+ 'sdk',
297
+ typeof context?.tag === 'string' ? context.tag : 'untagged',
298
+ normalized.name,
299
+ normalizeErrorMessage(normalized.message || '').slice(0, 120),
300
+ ]);
268
301
  sdk.captureException(normalized);
269
302
  });
270
303
  }
@@ -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.0";
6
+ export declare const SDK_VERSION = "9.2.2";
@@ -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.0';
9
+ exports.SDK_VERSION = '9.2.2';
10
10
  //# sourceMappingURL=version.js.map
@@ -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
@@ -110,8 +110,12 @@ const errors_1 = require("../../core/errors");
110
110
  * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
111
111
  * Optionally enriches with loan and allocation data.
112
112
  * @param vault - Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
113
- * @param loans - Include active loan data
114
- * @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.
115
119
  * @param options - RPC and service configuration
116
120
  * @param tokenizedVault - Optional pre-fetched backend row for `vault`. When a
117
121
  * caller already holds the row (e.g. `getVaults` fetched the whole list one
@@ -153,24 +157,33 @@ async function getVault({ vault, loans = false, allocations = false, options, lo
153
157
  core_1.Logger.log.error('getVault', err, { vault });
154
158
  throw new Error(`#getVault::${vault}: ${err?.message}`);
155
159
  }
156
- // Loans/allocations enrichment is only supported for EVM vaults.
157
- // 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.
158
167
  const isEvmVault = returnedVault.version !== 'sol-0' &&
159
168
  returnedVault.version !== 'stellar-0' &&
160
169
  returnedVault.version !== 'sui-0';
161
- if (!isEvmVault && (loans || allocations)) {
162
- 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 });
163
176
  }
164
- if (isEvmVault && !(0, core_1.isBadVault)(vault) && (loans || allocations)) {
177
+ if (!(0, core_1.isBadVault)(vault) && (readLoans || readAllocations)) {
165
178
  const [loansResult, allocationsResult] = await Promise.allSettled([
166
- loans
179
+ readLoans
167
180
  ? getVaultLoans(returnedVault, options)
168
181
  : Promise.resolve(undefined),
169
- allocations
182
+ readAllocations
170
183
  ? getVaultAllocations(vault, options)
171
184
  : Promise.resolve(undefined),
172
185
  ]);
173
- if (loans) {
186
+ if (readLoans) {
174
187
  if (loansResult.status === 'fulfilled') {
175
188
  returnedVault = {
176
189
  ...returnedVault,
@@ -183,7 +196,7 @@ async function getVault({ vault, loans = false, allocations = false, options, lo
183
196
  });
184
197
  }
185
198
  }
186
- if (allocations) {
199
+ if (readAllocations) {
187
200
  if (allocationsResult.status === 'fulfilled') {
188
201
  returnedVault = {
189
202
  ...returnedVault,
package/lib/sdk.d.ts CHANGED
@@ -19372,8 +19372,12 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
19372
19372
  * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
19373
19373
  * Optionally enriches with loan and allocation data.
19374
19374
  * @param vault - Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
19375
- * @param loans - Include active loan data
19376
- * @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.
19377
19381
  * @param options - RPC and service configuration
19378
19382
  * @param tokenizedVault - Optional pre-fetched backend row for `vault`. When a
19379
19383
  * caller already holds the row (e.g. `getVaults` fetched the whole list one
@@ -15,8 +15,9 @@
15
15
  export interface IUntangledProtocolExposure {
16
16
  /**
17
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).
18
+ * special: `stellar-wallet` is custody wallet cash, and `upshift` is the
19
+ * vault's own idle buffer plus any position it holds in another Upshift
20
+ * vault — only the part beyond the `idle` location is deployed capital.
20
21
  */
21
22
  protocolId: string;
22
23
  /** USD value held in this protocol. */
@@ -30,13 +30,20 @@ export declare const UNTANGLED_PROTOCOL_REGISTRY: Record<string, IUntangledProto
30
30
  * - Each `byProtocol` entry becomes one position with a single supplying token,
31
31
  * valued in USD (`price: 1`), on the chain from
32
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.
33
+ * - `stellar-wallet` becomes a wallet token, because custody cash is undeployed
34
+ * capital rather than protocol exposure.
35
+ * - `upshift` is split: the vault's own idle buffer (the `idle` location) joins
36
+ * that wallet token, and whatever is left is a real position in another
37
+ * Upshift vault, named after its receipt token. Untangled reports one
38
+ * `upshift` slice however many vaults that is, so a vault holding receipts in
39
+ * two others emits one merged position under the generic name — the value is
40
+ * still right, only the label is coarser.
35
41
  * - Dust is left in place: `filterOutBySize` in the parser applies the same
36
42
  * threshold Stellar and EVM vaults share.
37
43
  *
38
- * `locations`, `asOf`, `stale` and `tvlUsd` are not consumed TVL already comes
39
- * from `getVaultTvl`, and freshness is not yet surfaced on `IVaultAllocations`.
44
+ * Only the `idle` entries of `locations` are consumed, to size the buffer inside
45
+ * `upshift`. `asOf`, `stale` and `tvlUsd` are not TVL already comes from
46
+ * `getVaultTvl`, and freshness is not yet surfaced on `IVaultAllocations`.
40
47
  *
41
48
  * @param exposure - A resolved Untangled report, or `null`/`undefined` when the
42
49
  * fetch failed.
@@ -14,14 +14,87 @@ exports.UNTANGLED_PROTOCOL_REGISTRY = {
14
14
  blend: { name: 'Blend', chain: 'stellar' },
15
15
  templar: { name: 'Templar (NEAR)', chain: 'near' },
16
16
  aquarius: { name: 'Aquarius', chain: 'stellar' },
17
+ upshift: { name: 'Upshift', chain: 'stellar' },
17
18
  };
18
19
  /**
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.
20
+ * Custody wallet cash undeployed capital, which the exposure breakdown renders
21
+ * as wallet holdings, the same treatment EVM vaults get for funds sitting
22
+ * outside a protocol.
23
23
  */
24
- const WALLET_PROTOCOL_IDS = new Set(['stellar-wallet', 'upshift']);
24
+ const CUSTODY_WALLET_PROTOCOL_ID = 'stellar-wallet';
25
+ /**
26
+ * Capital held inside Upshift itself. This slug is NOT purely idle: it is the
27
+ * vault's own idle buffer PLUS any receipt tokens the vault holds in another
28
+ * Upshift vault. Gami earnXLM on 2026-09-11 reported `upshift: $1,371,484` made
29
+ * up of a `$200,572` idle buffer and `$1,170,913` of earnUSDC shares — counting
30
+ * all of it as idle hid a third of the vault's deployed capital and overstated
31
+ * the idle bucket almost sixfold.
32
+ */
33
+ const UPSHIFT_PROTOCOL_ID = 'upshift';
34
+ /** `locations[].label` for the capital still sitting in the vault contract. */
35
+ const IDLE_LOCATION_LABEL = 'idle';
36
+ /** Tightness of the `byAsset` lookup in {@link matchingAssetSymbol}. */
37
+ const ASSET_MATCH_TOLERANCE = 0.001;
38
+ /**
39
+ * Smallest `upshift` remainder treated as a real position rather than as float
40
+ * disagreement between the `idle` location and the `byProtocol` slice. One cent
41
+ * sits orders of magnitude above double-precision noise on million-dollar
42
+ * values and orders of magnitude below any holding worth naming.
43
+ */
44
+ const MIN_POSITION_USD = 0.01;
45
+ /**
46
+ * USD the vault holds in its own contract, summed from the `idle` locations.
47
+ *
48
+ * Returns `null` when no location carries that label, so a caller can tell
49
+ * "nothing is idle" from "this report does not say".
50
+ *
51
+ * The whole sum is charged against the `upshift` slice, which assumes idle
52
+ * capital never belongs to another protocol. That holds for the shape Untangled
53
+ * serves — the buffer sitting in the vault contract is exactly what it files
54
+ * under `upshift` — and the `Math.min` at the call site caps the subtraction, so
55
+ * a report that broke the assumption would understate the position rather than
56
+ * invent one.
57
+ */
58
+ function idleLocationUsd(exposure) {
59
+ let total = null;
60
+ (exposure.locations || []).forEach((location) => {
61
+ if (location?.label?.toLowerCase() !== IDLE_LOCATION_LABEL)
62
+ return;
63
+ total = (total ?? 0) + (Number(location.valueUsd) || 0);
64
+ });
65
+ return total;
66
+ }
67
+ /**
68
+ * The `byAsset` symbol whose value matches this position's, within
69
+ * {@link ASSET_MATCH_TOLERANCE}.
70
+ *
71
+ * Untangled splits assets vault-wide, so a position's asset can normally only be
72
+ * guessed. A vault's holding in another Upshift vault is the exception: the
73
+ * receipt token is its own `byAsset` entry carrying exactly that position's
74
+ * value, so it can be named instead of falling back to the vault's dominant
75
+ * asset.
76
+ *
77
+ * This only ever chooses a *label* — the USD value is taken from `byProtocol`
78
+ * either way — so it is built to abstain rather than guess. Two cases reach it
79
+ * legitimately and get no answer: a vault holding receipts in two other Upshift
80
+ * vaults, which Untangled reports as one `upshift` slice whose remainder matches
81
+ * neither entry on its own, and a remainder small enough that the relative
82
+ * window closes around rounding noise.
83
+ *
84
+ * @param valueUsd - The position's USD value. The caller has already cleared it
85
+ * past {@link MIN_POSITION_USD}, so the relative window is never taken around
86
+ * zero.
87
+ * @returns The matching symbol, or `null` when no entry matches or more than one
88
+ * does — the caller names the position generically instead.
89
+ */
90
+ function matchingAssetSymbol(exposure, valueUsd) {
91
+ const matches = (exposure.byAsset || []).filter((asset) => {
92
+ const assetUsd = Number(asset?.valueUsd) || 0;
93
+ return (assetUsd > 0 &&
94
+ Math.abs(assetUsd - valueUsd) <= valueUsd * ASSET_MATCH_TOLERANCE);
95
+ });
96
+ return matches.length === 1 ? matches[0].symbol || null : null;
97
+ }
25
98
  const DEFAULT_CHAIN = 'stellar';
26
99
  /**
27
100
  * Stellar's asset precision, carried only to satisfy the DeBank token shape.
@@ -78,13 +151,20 @@ function dominantAssetSymbol(exposure) {
78
151
  * - Each `byProtocol` entry becomes one position with a single supplying token,
79
152
  * valued in USD (`price: 1`), on the chain from
80
153
  * {@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.
154
+ * - `stellar-wallet` becomes a wallet token, because custody cash is undeployed
155
+ * capital rather than protocol exposure.
156
+ * - `upshift` is split: the vault's own idle buffer (the `idle` location) joins
157
+ * that wallet token, and whatever is left is a real position in another
158
+ * Upshift vault, named after its receipt token. Untangled reports one
159
+ * `upshift` slice however many vaults that is, so a vault holding receipts in
160
+ * two others emits one merged position under the generic name — the value is
161
+ * still right, only the label is coarser.
83
162
  * - Dust is left in place: `filterOutBySize` in the parser applies the same
84
163
  * threshold Stellar and EVM vaults share.
85
164
  *
86
- * `locations`, `asOf`, `stale` and `tvlUsd` are not consumed TVL already comes
87
- * from `getVaultTvl`, and freshness is not yet surfaced on `IVaultAllocations`.
165
+ * Only the `idle` entries of `locations` are consumed, to size the buffer inside
166
+ * `upshift`. `asOf`, `stale` and `tvlUsd` are not TVL already comes from
167
+ * `getVaultTvl`, and freshness is not yet surfaced on `IVaultAllocations`.
88
168
  *
89
169
  * @param exposure - A resolved Untangled report, or `null`/`undefined` when the
90
170
  * fetch failed.
@@ -106,20 +186,48 @@ function transformUntangledToDebank(exposure) {
106
186
  }
107
187
  const symbol = dominantAssetSymbol(exposure);
108
188
  const vaultChain = exposure.vault?.chain || DEFAULT_CHAIN;
189
+ const idleUsd = idleLocationUsd(exposure);
109
190
  const positions = [];
110
191
  let walletValueUsd = 0;
111
192
  (exposure.byProtocol || []).forEach((entry) => {
112
193
  if (!entry?.protocolId)
113
194
  return;
114
195
  const valueUsd = Number(entry.valueUsd) || 0;
115
- if (WALLET_PROTOCOL_IDS.has(entry.protocolId)) {
196
+ if (entry.protocolId === CUSTODY_WALLET_PROTOCOL_ID) {
116
197
  walletValueUsd += valueUsd;
117
198
  return;
118
199
  }
200
+ let positionUsd = valueUsd;
201
+ let positionSymbol = symbol;
202
+ let receiptSymbol = null;
203
+ if (entry.protocolId === UPSHIFT_PROTOCOL_ID) {
204
+ // A report with no idle location cannot say how much of this is the
205
+ // buffer, so it claims the whole slice. That leaves a remainder of exactly
206
+ // zero, which the guard below turns back into the pre-split behaviour —
207
+ // the two are coupled, so don't change one without the other.
208
+ const idleShare = idleUsd === null ? valueUsd : Math.min(valueUsd, idleUsd);
209
+ positionUsd = valueUsd - idleShare;
210
+ // The buffer reaches us twice, from two different fields, so a vault that
211
+ // holds nothing in another Upshift vault can still leave a sub-cent
212
+ // remainder behind. That is disagreement between the fields, not a
213
+ // position: keep the slice whole in the wallet bucket rather than emit a
214
+ // phantom the parser's size filter would have to clean up. Genuine dust is
215
+ // still passed through for that filter to judge.
216
+ if (positionUsd < MIN_POSITION_USD) {
217
+ walletValueUsd += valueUsd;
218
+ return;
219
+ }
220
+ walletValueUsd += idleShare;
221
+ receiptSymbol = matchingAssetSymbol(exposure, positionUsd);
222
+ positionSymbol = receiptSymbol ?? symbol;
223
+ }
119
224
  const metadata = exports.UNTANGLED_PROTOCOL_REGISTRY[entry.protocolId];
120
225
  const chain = metadata?.chain || vaultChain;
121
- const name = metadata?.name ||
226
+ const baseName = metadata?.name ||
122
227
  entry.protocolId.charAt(0).toUpperCase() + entry.protocolId.slice(1);
228
+ // "Upshift" alone reads as the whole product inside Upshift's own UI, so the
229
+ // slice names the vault it sits in whenever the receipt token identifies it.
230
+ const name = receiptSymbol ? `${baseName} (${receiptSymbol})` : baseName;
123
231
  positions.push({
124
232
  id: entry.protocolId,
125
233
  chain,
@@ -130,13 +238,13 @@ function transformUntangledToDebank(exposure) {
130
238
  {
131
239
  name,
132
240
  stats: {
133
- asset_usd_value: valueUsd,
241
+ asset_usd_value: positionUsd,
134
242
  debt_usd_value: 0,
135
- net_usd_value: valueUsd,
243
+ net_usd_value: positionUsd,
136
244
  },
137
245
  detail: {
138
246
  supply_token_list: [
139
- toDebankToken(symbol, chain, valueUsd, 'supply', entry.protocolId),
247
+ toDebankToken(positionSymbol, chain, positionUsd, 'supply', entry.protocolId),
140
248
  ],
141
249
  borrow_token_list: [],
142
250
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augustdigital/sdk",
3
- "version": "9.2.0",
3
+ "version": "9.2.2",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/sdk.d.ts",
6
6
  "keywords": [