@liberfi.io/ui-portfolio 3.0.26 → 3.0.28

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.
@@ -1,2 +1,2 @@
1
- 'use strict';var utils=require('@liberfi.io/utils');function i(r){if(!r)return 0;let e=Number(r);return Number.isFinite(e)?e:0}var a=class{constructor(e){this.endpoint=e;}async getOverview(e){let n=l(e),o=await utils.httpGet(`${this.endpoint}/portfolio/overview${n}`);return {totalValue:i(o.totalBalanceUsd),uPnl:i(o.unrealizedPnl),realizedPnl:i(o.realizedPnl),totalProfit:i(o.totalProfitUsd),winRate:i(o.winRate)}}async getSpotHoldings(e){let n=l(e);return {holdings:((await utils.httpGet(`${this.endpoint}/portfolio/spot/holdings${n}`)).holdings??[]).map(t=>({tokenAddress:t.tokenAddress,name:t.name,symbol:t.symbol,image:t.imageUrl??"",chain:t.chain??"",balance:i(t.balance),price:i(t.priceUsd),value:i(t.valueUsd),change24h:i(t.priceChange24h),unrealizedPnl:i(t.unrealizedPnl),realizedPnl:i(t.realizedPnl),walletAddress:t.walletAddress??"",verified:false}))}}async getSpotHistory(e){let n=u(e),o=await utils.httpGet(`${this.endpoint}/portfolio/spot/history${n}`);return {trades:(o.trades??[]).map(t=>({type:c(t.type),tokenSymbol:t.tokenSymbol??"",tokenName:t.tokenName??"",tokenImageUrl:t.tokenImageUrl??"",tokenAddress:t.tokenAddress??"",tokenAmount:i(t.tokenAmount),valueUsd:i(t.valueUsd),priceUsd:i(t.priceUsd),sideTokenSymbol:t.sideTokenSymbol??"",sideTokenAmount:i(t.sideTokenAmount),dex:t.dex??"",txHash:t.txHash??"",chain:t.chain??"",walletAddress:t.walletAddress??"",timestamp:d(t.timestamp)})),nextCursor:o.nextCursor??"",hasNext:o.hasNext??false}}async getChartData(e){let n=new URLSearchParams;if(n.set("period",e.period.toString()),e.walletAddresses?.length)for(let t of e.walletAddresses)n.append("walletAddresses",t);return e.chain&&n.set("chain",e.chain),{points:((await utils.httpGet(`${this.endpoint}/portfolio/chart?${n.toString()}`)).dataPoints??[]).map(t=>({timestamp:d(t.timestamp),netWorth:i(t.netWorth),change:i(t.change),changePercent:i(t.changePercent)}))}}async getPerpsPositions(e){return {positions:[],totalValue:0}}async getPerpsHistory(e){return {records:[],hasMore:false}}async getPredictionBets(e){return {bets:[],totalValue:0}}async getPredictionSettled(e){return {records:[],hasMore:false}}};function l(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let o of r.walletAddresses)e.append("walletAddresses",o);r.chain&&e.set("chain",r.chain);let n=e.toString();return n?`?${n}`:""}function u(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let o of r.walletAddresses)e.append("walletAddresses",o);r.chain&&e.set("chain",r.chain),r.cursor&&e.set("cursor",r.cursor),r.limit!==void 0&&e.set("limit",r.limit.toString());let n=e.toString();return n?`?${n}`:""}function c(r){let e=r?.toLowerCase();return e==="send"||e==="transfer_out"?"send":e==="receive"||e==="transfer_in"?"receive":"swap"}function d(r){if(!r)return 0;let e=Number(r);if(Number.isFinite(e))return e<1e12?e*1e3:e;let n=Date.parse(r);return Number.isFinite(n)?n:0}exports.PortfolioClient=a;//# sourceMappingURL=index.js.map
1
+ 'use strict';var utils=require('@liberfi.io/utils');function n(r){if(!r)return 0;let e=Number(r);return Number.isFinite(e)?e:0}var a=class{constructor(e){this.endpoint=e;}async getOverview(e){let o=l(e),i=await utils.httpGet(`${this.endpoint}/portfolio/overview${o}`);return {totalValue:n(i.totalBalanceUsd),uPnl:n(i.unrealizedPnl),realizedPnl:n(i.realizedPnl),totalProfit:n(i.totalProfitUsd),winRate:n(i.winRate)}}async getSpotHoldings(e){let o=l(e);return {holdings:((await utils.httpGet(`${this.endpoint}/portfolio/spot/holdings${o}`)).holdings??[]).map(t=>({tokenAddress:t.tokenAddress,name:t.name,symbol:t.symbol,image:t.imageUrl??"",chain:t.chain??"",balance:n(t.balance),price:n(t.priceUsd),value:n(t.valueUsd),change24h:n(t.priceChange24h),unrealizedPnl:n(t.unrealizedPnl),realizedPnl:n(t.realizedPnl),walletAddress:t.walletAddress??"",verified:false}))}}async getSpotHistory(e){let o=u(e),i=await utils.httpGet(`${this.endpoint}/portfolio/spot/history${o}`);return {trades:(i.trades??[]).map(t=>({type:c(t.type),tokenSymbol:t.tokenSymbol??"",tokenName:t.tokenName??"",tokenImageUrl:t.tokenImageUrl??"",tokenAddress:t.tokenAddress??"",tokenAmount:n(t.tokenAmount),valueUsd:n(t.valueUsd),priceUsd:n(t.priceUsd),sideTokenSymbol:t.sideTokenSymbol??"",sideTokenAmount:n(t.sideTokenAmount),dex:t.dex??"",txHash:t.txHash??"",chain:t.chain??"",walletAddress:t.walletAddress??"",timestamp:d(t.timestamp)})),nextCursor:i.nextCursor??"",hasNext:i.hasNext??false}}async getChartData(e){let o=new URLSearchParams;if(o.set("period",e.period.toString()),e.walletAddresses?.length)for(let t of e.walletAddresses)o.append("walletAddresses",t);return e.chain&&o.set("chain",e.chain),{points:((await utils.httpGet(`${this.endpoint}/portfolio/chart?${o.toString()}`)).dataPoints??[]).map(t=>({timestamp:d(t.timestamp),netWorth:n(t.netWorth),change:n(t.change),changePercent:n(t.changePercent)}))}}async getPerpsPositions(e){return {positions:[],totalValue:0}}async getPerpsHistory(e){return {records:[],hasMore:false}}async getPredictionBets(e){return {bets:[],totalValue:0}}async getPredictionSettled(e){return {records:[],hasMore:false}}};function l(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let i of r.walletAddresses)e.append("walletAddresses",i);r.chain&&e.set("chain",r.chain);let o=e.toString();return o?`?${o}`:""}function u(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let i of r.walletAddresses)e.append("walletAddresses",i);r.chain&&e.set("chain",r.chain),r.cursor&&e.set("cursor",r.cursor),r.limit!==void 0&&e.set("limit",r.limit.toString());let o=e.toString();return o?`?${o}`:""}function c(r){let e=r?.toLowerCase();return e==="send"||e==="transfer_out"?"send":e==="receive"||e==="transfer_in"?"receive":"swap"}function d(r){if(!r)return 0;let e=Number(r);if(Number.isFinite(e))return e<1e12?e*1e3:e;let o=Date.parse(r);return Number.isFinite(o)?o:0}exports.PortfolioClient=a;//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/index.ts","../../src/client/index.ts"],"names":["parseDecimal","raw","n","PortfolioClient","endpoint","query","qs","buildQuery","dto","httpGet","h","buildHistoryQuery","parseHistoryType","parseTimestamp","params","addr","p","_query","str","lower","d"],"mappings":"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","file":"index.js","sourcesContent":["import type { DistributionData, SpotHolding } from \"../types\";\n\n/**\n * Parse a decimal string to a number, returning 0 for empty / invalid values.\n */\nexport function parseDecimal(raw: string | undefined | null): number {\n if (!raw) return 0;\n const n = Number(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\n/**\n * Format a number as a USD currency string.\n * - >= 1M → \"$1.2M\"\n * - >= 1K → \"$4.2K\"\n * - >= 1 → \"$1,234.56\"\n * - >= 0.01 → \"$0.42\"\n * - < 0.01 and > 0 → \"<$0.01\"\n * - 0 → \"$0.00\"\n */\nexport function formatUsd(value: number): string {\n const abs = Math.abs(value);\n const sign = value < 0 ? \"-\" : \"\";\n\n if (abs >= 1_000_000) {\n return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;\n }\n if (abs >= 10_000) {\n return `${sign}$${(abs / 1_000).toFixed(1)}K`;\n }\n if (abs >= 1) {\n return `${sign}$${abs.toLocaleString(\"en-US\", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;\n }\n if (abs >= 0.01) {\n return `${sign}$${abs.toFixed(2)}`;\n }\n if (abs > 0) {\n return `${sign}<$0.01`;\n }\n return \"$0.00\";\n}\n\n/**\n * Format a signed USD value with + / - prefix for PnL display.\n */\nexport function formatSignedUsd(value: number): string {\n const formatted = formatUsd(Math.abs(value));\n if (value > 0) return `+${formatted}`;\n if (value < 0) return `-${formatted.replace(\"-\", \"\")}`;\n return formatted;\n}\n\n/**\n * Format a percentage value.\n * Returns \"+3.24%\" or \"-1.50%\"\n */\nexport function formatPercent(value: number): string {\n const sign = value > 0 ? \"+\" : \"\";\n return `${sign}${value.toFixed(2)}%`;\n}\n\n/**\n * Split a USD value into integer and decimal parts for display.\n * e.g., 12847.63 → { integer: \"12,847\", decimal: \".63\", sign: \"$\" }\n */\nexport function splitUsd(value: number): {\n sign: string;\n integer: string;\n decimal: string;\n} {\n const abs = Math.abs(value);\n const parts = abs.toFixed(2).split(\".\");\n const integer = Number(parts[0]).toLocaleString(\"en-US\");\n const decimal = `.${parts[1]}`;\n const sign = value < 0 ? \"-$\" : \"$\";\n return { sign, integer, decimal };\n}\n\n/**\n * Truncate an address to \"xxxx…xxxx\" format.\n */\nexport function truncateAddress(address: string, start = 4, end = 4): string {\n if (address.length <= start + end + 3) return address;\n return `${address.slice(0, start)}…${address.slice(-end)}`;\n}\n\n/**\n * Format a token balance with appropriate precision.\n */\nexport function formatTokenBalance(value: number): string {\n if (value === 0) return \"0\";\n if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M`;\n if (value >= 1_000)\n return value.toLocaleString(\"en-US\", { maximumFractionDigits: 2 });\n if (value >= 1) return value.toFixed(2);\n if (value >= 0.0001) return value.toFixed(4);\n return \"<0.0001\";\n}\n\n/**\n * Build a Solana explorer URL for a transaction hash.\n * Defaults to Solscan. Can be extended for other chains in the future.\n */\nexport function getExplorerUrl(txHash: string, _chain?: string): string {\n return `https://solscan.io/tx/${txHash}`;\n}\n\n// ── Distribution Helpers ────────────────────────────────────────────────────\n\nconst DISTRIBUTION_COLORS = [\n \"#C8FF00\",\n \"#00E676\",\n \"#0066FF\",\n \"#FF6B9D\",\n \"#8B7BFF\",\n \"#FF5252\",\n \"#FFB74D\",\n \"#4DD0E1\",\n];\n\nconst OTHER_COLOR = \"#3A3A4E\";\n\n/**\n * Compute asset distribution from spot holdings for the donut chart.\n * Groups tokens with < 2% into \"Other\".\n */\nexport function computeDistribution(holdings: SpotHolding[]): DistributionData {\n const totalValue = holdings.reduce((sum, h) => sum + h.value, 0);\n if (totalValue <= 0) return { items: [] };\n\n const sorted = [...holdings].sort((a, b) => b.value - a.value);\n\n const items: DistributionData[\"items\"] = [];\n let otherValue = 0;\n let colorIdx = 0;\n\n for (const h of sorted) {\n const pct = (h.value / totalValue) * 100;\n if (pct < 2) {\n otherValue += h.value;\n } else {\n items.push({\n name: h.name,\n symbol: h.symbol,\n percent: pct,\n value: h.value,\n color: DISTRIBUTION_COLORS[colorIdx % DISTRIBUTION_COLORS.length],\n });\n colorIdx++;\n }\n }\n\n if (otherValue > 0) {\n items.push({\n name: \"Other\",\n symbol: \"OTHER\",\n percent: (otherValue / totalValue) * 100,\n value: otherValue,\n color: OTHER_COLOR,\n });\n }\n\n return { items };\n}\n\n/**\n * Format an epoch-ms timestamp for display in history tables.\n */\nexport function formatTime(epochMs: number): string {\n const d = new Date(epochMs);\n return d.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n}\n","import { httpGet } from \"@liberfi.io/utils\";\nimport { SpotHistoryType } from \"../types\";\nimport type {\n CurveData,\n CurveQuery,\n GetPortfolioChartReply,\n GetSpotHoldingsReply,\n GetTradeHistoryReply,\n IPortfolioClient,\n PerpsHistoryData,\n PerpsHistoryQuery,\n PerpsPositionsData,\n PortfolioOverview,\n PortfolioOverviewDTO,\n PortfolioQuery,\n PredictionBetsData,\n PredictionSettledData,\n PredictionSettledQuery,\n SpotHistoryData,\n SpotHistoryQuery,\n SpotHoldingsData,\n} from \"../types\";\nimport { parseDecimal } from \"../utils\";\n\n// ── Client ──────────────────────────────────────────────────────────────────\n\nexport class PortfolioClient implements IPortfolioClient {\n constructor(private readonly endpoint: string) {}\n\n // ── Overview ──────────────────────────────────────────────────────────\n\n async getOverview(query?: PortfolioQuery): Promise<PortfolioOverview> {\n const qs = buildQuery(query);\n const dto = await httpGet<PortfolioOverviewDTO>(\n `${this.endpoint}/portfolio/overview${qs}`,\n );\n return {\n totalValue: parseDecimal(dto.totalBalanceUsd),\n uPnl: parseDecimal(dto.unrealizedPnl),\n realizedPnl: parseDecimal(dto.realizedPnl),\n totalProfit: parseDecimal(dto.totalProfitUsd),\n winRate: parseDecimal(dto.winRate),\n };\n }\n\n // ── Spot Holdings ─────────────────────────────────────────────────────\n\n async getSpotHoldings(query?: PortfolioQuery): Promise<SpotHoldingsData> {\n const qs = buildQuery(query);\n const dto = await httpGet<GetSpotHoldingsReply>(\n `${this.endpoint}/portfolio/spot/holdings${qs}`,\n );\n return {\n holdings: (dto.holdings ?? []).map((h) => ({\n tokenAddress: h.tokenAddress,\n name: h.name,\n symbol: h.symbol,\n image: h.imageUrl ?? \"\",\n chain: h.chain ?? \"\",\n balance: parseDecimal(h.balance),\n price: parseDecimal(h.priceUsd),\n value: parseDecimal(h.valueUsd),\n change24h: parseDecimal(h.priceChange24h),\n unrealizedPnl: parseDecimal(h.unrealizedPnl),\n realizedPnl: parseDecimal(h.realizedPnl),\n walletAddress: h.walletAddress ?? \"\",\n verified: false,\n })),\n };\n }\n\n // ── Spot History ──────────────────────────────────────────────────────\n\n async getSpotHistory(query?: SpotHistoryQuery): Promise<SpotHistoryData> {\n const qs = buildHistoryQuery(query);\n const dto = await httpGet<GetTradeHistoryReply>(\n `${this.endpoint}/portfolio/spot/history${qs}`,\n );\n return {\n trades: (dto.trades ?? []).map((t) => ({\n type: parseHistoryType(t.type),\n tokenSymbol: t.tokenSymbol ?? \"\",\n tokenName: t.tokenName ?? \"\",\n tokenImageUrl: t.tokenImageUrl ?? \"\",\n tokenAddress: t.tokenAddress ?? \"\",\n tokenAmount: parseDecimal(t.tokenAmount),\n valueUsd: parseDecimal(t.valueUsd),\n priceUsd: parseDecimal(t.priceUsd),\n sideTokenSymbol: t.sideTokenSymbol ?? \"\",\n sideTokenAmount: parseDecimal(t.sideTokenAmount),\n dex: t.dex ?? \"\",\n txHash: t.txHash ?? \"\",\n chain: t.chain ?? \"\",\n walletAddress: t.walletAddress ?? \"\",\n timestamp: parseTimestamp(t.timestamp),\n })),\n nextCursor: dto.nextCursor ?? \"\",\n hasNext: dto.hasNext ?? false,\n };\n }\n\n // ── Chart Data ────────────────────────────────────────────────────────\n\n async getChartData(query: CurveQuery): Promise<CurveData> {\n const params = new URLSearchParams();\n params.set(\"period\", query.period.toString());\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const dto = await httpGet<GetPortfolioChartReply>(\n `${this.endpoint}/portfolio/chart?${params.toString()}`,\n );\n return {\n points: (dto.dataPoints ?? []).map((p) => ({\n timestamp: parseTimestamp(p.timestamp),\n netWorth: parseDecimal(p.netWorth),\n change: parseDecimal(p.change),\n changePercent: parseDecimal(p.changePercent),\n })),\n };\n }\n\n // ── Future APIs (stubs — backend not available yet) ───────────────────\n\n async getPerpsPositions(\n _query?: PortfolioQuery,\n ): Promise<PerpsPositionsData> {\n return { positions: [], totalValue: 0 };\n }\n\n async getPerpsHistory(_query?: PerpsHistoryQuery): Promise<PerpsHistoryData> {\n return { records: [], hasMore: false };\n }\n\n async getPredictionBets(\n _query?: PortfolioQuery,\n ): Promise<PredictionBetsData> {\n return { bets: [], totalValue: 0 };\n }\n\n async getPredictionSettled(\n _query?: PredictionSettledQuery,\n ): Promise<PredictionSettledData> {\n return { records: [], hasMore: false };\n }\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction buildQuery(query?: PortfolioQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction buildHistoryQuery(query?: SpotHistoryQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n if (query.cursor) {\n params.set(\"cursor\", query.cursor);\n }\n if (query.limit !== undefined) {\n params.set(\"limit\", query.limit.toString());\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction parseHistoryType(raw: string): SpotHistoryType {\n const lower = raw?.toLowerCase();\n if (lower === \"send\" || lower === \"transfer_out\") return SpotHistoryType.SEND;\n if (lower === \"receive\" || lower === \"transfer_in\")\n return SpotHistoryType.RECEIVE;\n return SpotHistoryType.SWAP;\n}\n\nfunction parseTimestamp(raw: string): number {\n if (!raw) return 0;\n const n = Number(raw);\n // If value looks like epoch seconds (< 1e12), convert to ms\n if (Number.isFinite(n)) {\n return n < 1e12 ? n * 1000 : n;\n }\n // Try ISO date string\n const d = Date.parse(raw);\n return Number.isFinite(d) ? d : 0;\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/index.ts","../../src/client/index.ts"],"names":["parseDecimal","raw","n","PortfolioClient","endpoint","query","qs","buildQuery","dto","httpGet","h","buildHistoryQuery","parseHistoryType","parseTimestamp","params","addr","p","_query","str","lower","d"],"mappings":"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","file":"index.js","sourcesContent":["import { formatAmount, formatAmountInUsd } from \"@liberfi.io/utils\";\nimport type { DistributionData, SpotHolding } from \"../types\";\n\n/**\n * Parse a decimal string to a number, returning 0 for empty / invalid values.\n */\nexport function parseDecimal(raw: string | undefined | null): number {\n if (!raw) return 0;\n const n = Number(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\n/**\n * Format a number as a USD currency string.\n * - >= 1M → \"$1.2M\"\n * - >= 1K → \"$4.2K\"\n * - >= 1 → \"$1,234.56\"\n * - >= 0.01 → \"$0.42\"\n * - < 0.01 and > 0 → \"<$0.01\"\n * - 0 → \"$0.00\"\n */\nexport function formatUsd(value: number): string {\n return formatAmountInUsd(value);\n}\n\n/**\n * Format a signed USD value with + / - prefix for PnL display.\n */\nexport function formatSignedUsd(value: number): string {\n const formatted = formatUsd(Math.abs(value));\n if (value > 0) return `+${formatted}`;\n if (value < 0) return `-${formatted.replace(\"-\", \"\")}`;\n return formatted;\n}\n\n/**\n * Format a percentage value.\n * Returns \"+3.24%\" or \"-1.50%\"\n */\nexport function formatPercent(value: number): string {\n const sign = value > 0 ? \"+\" : \"\";\n return `${sign}${value.toFixed(2)}%`;\n}\n\n/**\n * Split a USD value into integer and decimal parts for display.\n * e.g., 12847.63 → { integer: \"12,847\", decimal: \".63\", sign: \"$\" }\n */\nexport function splitUsd(value: number): {\n sign: string;\n integer: string;\n decimal: string;\n} {\n const abs = Math.abs(value);\n const parts = abs.toFixed(2).split(\".\");\n const integer = Number(parts[0]).toLocaleString(\"en-US\");\n const decimal = `.${parts[1]}`;\n const sign = value < 0 ? \"-$\" : \"$\";\n return { sign, integer, decimal };\n}\n\n/**\n * Truncate an address to \"xxxx…xxxx\" format.\n */\nexport function truncateAddress(address: string, start = 4, end = 4): string {\n if (address.length <= start + end + 3) return address;\n return `${address.slice(0, start)}…${address.slice(-end)}`;\n}\n\n/**\n * Format a token balance with appropriate precision.\n */\nexport function formatTokenBalance(value: number): string {\n return formatAmount(value);\n}\n\n/**\n * Build a Solana explorer URL for a transaction hash.\n * Defaults to Solscan. Can be extended for other chains in the future.\n */\nexport function getExplorerUrl(txHash: string, _chain?: string): string {\n return `https://solscan.io/tx/${txHash}`;\n}\n\n// ── Distribution Helpers ────────────────────────────────────────────────────\n\nconst DISTRIBUTION_COLORS = [\n \"#C8FF00\",\n \"#00E676\",\n \"#0066FF\",\n \"#FF6B9D\",\n \"#8B7BFF\",\n \"#FF5252\",\n \"#FFB74D\",\n \"#4DD0E1\",\n];\n\nconst OTHER_COLOR = \"#3A3A4E\";\n\n/**\n * Compute asset distribution from spot holdings for the donut chart.\n * Groups tokens with < 2% into \"Other\".\n */\nexport function computeDistribution(holdings: SpotHolding[]): DistributionData {\n const totalValue = holdings.reduce((sum, h) => sum + h.value, 0);\n if (totalValue <= 0) return { items: [] };\n\n const sorted = [...holdings].sort((a, b) => b.value - a.value);\n\n const items: DistributionData[\"items\"] = [];\n let otherValue = 0;\n let colorIdx = 0;\n\n for (const h of sorted) {\n const pct = (h.value / totalValue) * 100;\n if (pct < 2) {\n otherValue += h.value;\n } else {\n items.push({\n name: h.name,\n symbol: h.symbol,\n percent: pct,\n value: h.value,\n color: DISTRIBUTION_COLORS[colorIdx % DISTRIBUTION_COLORS.length],\n });\n colorIdx++;\n }\n }\n\n if (otherValue > 0) {\n items.push({\n name: \"Other\",\n symbol: \"OTHER\",\n percent: (otherValue / totalValue) * 100,\n value: otherValue,\n color: OTHER_COLOR,\n });\n }\n\n return { items };\n}\n\n/**\n * Format an epoch-ms timestamp for display in history tables.\n */\nexport function formatTime(epochMs: number): string {\n const d = new Date(epochMs);\n return d.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n}\n","import { httpGet } from \"@liberfi.io/utils\";\nimport { SpotHistoryType } from \"../types\";\nimport type {\n CurveData,\n CurveQuery,\n GetPortfolioChartReply,\n GetSpotHoldingsReply,\n GetTradeHistoryReply,\n IPortfolioClient,\n PerpsHistoryData,\n PerpsHistoryQuery,\n PerpsPositionsData,\n PortfolioOverview,\n PortfolioOverviewDTO,\n PortfolioQuery,\n PredictionBetsData,\n PredictionSettledData,\n PredictionSettledQuery,\n SpotHistoryData,\n SpotHistoryQuery,\n SpotHoldingsData,\n} from \"../types\";\nimport { parseDecimal } from \"../utils\";\n\n// ── Client ──────────────────────────────────────────────────────────────────\n\nexport class PortfolioClient implements IPortfolioClient {\n constructor(private readonly endpoint: string) {}\n\n // ── Overview ──────────────────────────────────────────────────────────\n\n async getOverview(query?: PortfolioQuery): Promise<PortfolioOverview> {\n const qs = buildQuery(query);\n const dto = await httpGet<PortfolioOverviewDTO>(\n `${this.endpoint}/portfolio/overview${qs}`,\n );\n return {\n totalValue: parseDecimal(dto.totalBalanceUsd),\n uPnl: parseDecimal(dto.unrealizedPnl),\n realizedPnl: parseDecimal(dto.realizedPnl),\n totalProfit: parseDecimal(dto.totalProfitUsd),\n winRate: parseDecimal(dto.winRate),\n };\n }\n\n // ── Spot Holdings ─────────────────────────────────────────────────────\n\n async getSpotHoldings(query?: PortfolioQuery): Promise<SpotHoldingsData> {\n const qs = buildQuery(query);\n const dto = await httpGet<GetSpotHoldingsReply>(\n `${this.endpoint}/portfolio/spot/holdings${qs}`,\n );\n return {\n holdings: (dto.holdings ?? []).map((h) => ({\n tokenAddress: h.tokenAddress,\n name: h.name,\n symbol: h.symbol,\n image: h.imageUrl ?? \"\",\n chain: h.chain ?? \"\",\n balance: parseDecimal(h.balance),\n price: parseDecimal(h.priceUsd),\n value: parseDecimal(h.valueUsd),\n change24h: parseDecimal(h.priceChange24h),\n unrealizedPnl: parseDecimal(h.unrealizedPnl),\n realizedPnl: parseDecimal(h.realizedPnl),\n walletAddress: h.walletAddress ?? \"\",\n verified: false,\n })),\n };\n }\n\n // ── Spot History ──────────────────────────────────────────────────────\n\n async getSpotHistory(query?: SpotHistoryQuery): Promise<SpotHistoryData> {\n const qs = buildHistoryQuery(query);\n const dto = await httpGet<GetTradeHistoryReply>(\n `${this.endpoint}/portfolio/spot/history${qs}`,\n );\n return {\n trades: (dto.trades ?? []).map((t) => ({\n type: parseHistoryType(t.type),\n tokenSymbol: t.tokenSymbol ?? \"\",\n tokenName: t.tokenName ?? \"\",\n tokenImageUrl: t.tokenImageUrl ?? \"\",\n tokenAddress: t.tokenAddress ?? \"\",\n tokenAmount: parseDecimal(t.tokenAmount),\n valueUsd: parseDecimal(t.valueUsd),\n priceUsd: parseDecimal(t.priceUsd),\n sideTokenSymbol: t.sideTokenSymbol ?? \"\",\n sideTokenAmount: parseDecimal(t.sideTokenAmount),\n dex: t.dex ?? \"\",\n txHash: t.txHash ?? \"\",\n chain: t.chain ?? \"\",\n walletAddress: t.walletAddress ?? \"\",\n timestamp: parseTimestamp(t.timestamp),\n })),\n nextCursor: dto.nextCursor ?? \"\",\n hasNext: dto.hasNext ?? false,\n };\n }\n\n // ── Chart Data ────────────────────────────────────────────────────────\n\n async getChartData(query: CurveQuery): Promise<CurveData> {\n const params = new URLSearchParams();\n params.set(\"period\", query.period.toString());\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const dto = await httpGet<GetPortfolioChartReply>(\n `${this.endpoint}/portfolio/chart?${params.toString()}`,\n );\n return {\n points: (dto.dataPoints ?? []).map((p) => ({\n timestamp: parseTimestamp(p.timestamp),\n netWorth: parseDecimal(p.netWorth),\n change: parseDecimal(p.change),\n changePercent: parseDecimal(p.changePercent),\n })),\n };\n }\n\n // ── Future APIs (stubs — backend not available yet) ───────────────────\n\n async getPerpsPositions(\n _query?: PortfolioQuery,\n ): Promise<PerpsPositionsData> {\n return { positions: [], totalValue: 0 };\n }\n\n async getPerpsHistory(_query?: PerpsHistoryQuery): Promise<PerpsHistoryData> {\n return { records: [], hasMore: false };\n }\n\n async getPredictionBets(\n _query?: PortfolioQuery,\n ): Promise<PredictionBetsData> {\n return { bets: [], totalValue: 0 };\n }\n\n async getPredictionSettled(\n _query?: PredictionSettledQuery,\n ): Promise<PredictionSettledData> {\n return { records: [], hasMore: false };\n }\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction buildQuery(query?: PortfolioQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction buildHistoryQuery(query?: SpotHistoryQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n if (query.cursor) {\n params.set(\"cursor\", query.cursor);\n }\n if (query.limit !== undefined) {\n params.set(\"limit\", query.limit.toString());\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction parseHistoryType(raw: string): SpotHistoryType {\n const lower = raw?.toLowerCase();\n if (lower === \"send\" || lower === \"transfer_out\") return SpotHistoryType.SEND;\n if (lower === \"receive\" || lower === \"transfer_in\")\n return SpotHistoryType.RECEIVE;\n return SpotHistoryType.SWAP;\n}\n\nfunction parseTimestamp(raw: string): number {\n if (!raw) return 0;\n const n = Number(raw);\n // If value looks like epoch seconds (< 1e12), convert to ms\n if (Number.isFinite(n)) {\n return n < 1e12 ? n * 1000 : n;\n }\n // Try ISO date string\n const d = Date.parse(raw);\n return Number.isFinite(d) ? d : 0;\n}\n"]}
@@ -1,2 +1,2 @@
1
- import {httpGet}from'@liberfi.io/utils';function i(r){if(!r)return 0;let e=Number(r);return Number.isFinite(e)?e:0}var a=class{constructor(e){this.endpoint=e;}async getOverview(e){let n=l(e),o=await httpGet(`${this.endpoint}/portfolio/overview${n}`);return {totalValue:i(o.totalBalanceUsd),uPnl:i(o.unrealizedPnl),realizedPnl:i(o.realizedPnl),totalProfit:i(o.totalProfitUsd),winRate:i(o.winRate)}}async getSpotHoldings(e){let n=l(e);return {holdings:((await httpGet(`${this.endpoint}/portfolio/spot/holdings${n}`)).holdings??[]).map(t=>({tokenAddress:t.tokenAddress,name:t.name,symbol:t.symbol,image:t.imageUrl??"",chain:t.chain??"",balance:i(t.balance),price:i(t.priceUsd),value:i(t.valueUsd),change24h:i(t.priceChange24h),unrealizedPnl:i(t.unrealizedPnl),realizedPnl:i(t.realizedPnl),walletAddress:t.walletAddress??"",verified:false}))}}async getSpotHistory(e){let n=u(e),o=await httpGet(`${this.endpoint}/portfolio/spot/history${n}`);return {trades:(o.trades??[]).map(t=>({type:c(t.type),tokenSymbol:t.tokenSymbol??"",tokenName:t.tokenName??"",tokenImageUrl:t.tokenImageUrl??"",tokenAddress:t.tokenAddress??"",tokenAmount:i(t.tokenAmount),valueUsd:i(t.valueUsd),priceUsd:i(t.priceUsd),sideTokenSymbol:t.sideTokenSymbol??"",sideTokenAmount:i(t.sideTokenAmount),dex:t.dex??"",txHash:t.txHash??"",chain:t.chain??"",walletAddress:t.walletAddress??"",timestamp:d(t.timestamp)})),nextCursor:o.nextCursor??"",hasNext:o.hasNext??false}}async getChartData(e){let n=new URLSearchParams;if(n.set("period",e.period.toString()),e.walletAddresses?.length)for(let t of e.walletAddresses)n.append("walletAddresses",t);return e.chain&&n.set("chain",e.chain),{points:((await httpGet(`${this.endpoint}/portfolio/chart?${n.toString()}`)).dataPoints??[]).map(t=>({timestamp:d(t.timestamp),netWorth:i(t.netWorth),change:i(t.change),changePercent:i(t.changePercent)}))}}async getPerpsPositions(e){return {positions:[],totalValue:0}}async getPerpsHistory(e){return {records:[],hasMore:false}}async getPredictionBets(e){return {bets:[],totalValue:0}}async getPredictionSettled(e){return {records:[],hasMore:false}}};function l(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let o of r.walletAddresses)e.append("walletAddresses",o);r.chain&&e.set("chain",r.chain);let n=e.toString();return n?`?${n}`:""}function u(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let o of r.walletAddresses)e.append("walletAddresses",o);r.chain&&e.set("chain",r.chain),r.cursor&&e.set("cursor",r.cursor),r.limit!==void 0&&e.set("limit",r.limit.toString());let n=e.toString();return n?`?${n}`:""}function c(r){let e=r?.toLowerCase();return e==="send"||e==="transfer_out"?"send":e==="receive"||e==="transfer_in"?"receive":"swap"}function d(r){if(!r)return 0;let e=Number(r);if(Number.isFinite(e))return e<1e12?e*1e3:e;let n=Date.parse(r);return Number.isFinite(n)?n:0}export{a as PortfolioClient};//# sourceMappingURL=index.mjs.map
1
+ import {httpGet}from'@liberfi.io/utils';function n(r){if(!r)return 0;let e=Number(r);return Number.isFinite(e)?e:0}var a=class{constructor(e){this.endpoint=e;}async getOverview(e){let o=l(e),i=await httpGet(`${this.endpoint}/portfolio/overview${o}`);return {totalValue:n(i.totalBalanceUsd),uPnl:n(i.unrealizedPnl),realizedPnl:n(i.realizedPnl),totalProfit:n(i.totalProfitUsd),winRate:n(i.winRate)}}async getSpotHoldings(e){let o=l(e);return {holdings:((await httpGet(`${this.endpoint}/portfolio/spot/holdings${o}`)).holdings??[]).map(t=>({tokenAddress:t.tokenAddress,name:t.name,symbol:t.symbol,image:t.imageUrl??"",chain:t.chain??"",balance:n(t.balance),price:n(t.priceUsd),value:n(t.valueUsd),change24h:n(t.priceChange24h),unrealizedPnl:n(t.unrealizedPnl),realizedPnl:n(t.realizedPnl),walletAddress:t.walletAddress??"",verified:false}))}}async getSpotHistory(e){let o=u(e),i=await httpGet(`${this.endpoint}/portfolio/spot/history${o}`);return {trades:(i.trades??[]).map(t=>({type:c(t.type),tokenSymbol:t.tokenSymbol??"",tokenName:t.tokenName??"",tokenImageUrl:t.tokenImageUrl??"",tokenAddress:t.tokenAddress??"",tokenAmount:n(t.tokenAmount),valueUsd:n(t.valueUsd),priceUsd:n(t.priceUsd),sideTokenSymbol:t.sideTokenSymbol??"",sideTokenAmount:n(t.sideTokenAmount),dex:t.dex??"",txHash:t.txHash??"",chain:t.chain??"",walletAddress:t.walletAddress??"",timestamp:d(t.timestamp)})),nextCursor:i.nextCursor??"",hasNext:i.hasNext??false}}async getChartData(e){let o=new URLSearchParams;if(o.set("period",e.period.toString()),e.walletAddresses?.length)for(let t of e.walletAddresses)o.append("walletAddresses",t);return e.chain&&o.set("chain",e.chain),{points:((await httpGet(`${this.endpoint}/portfolio/chart?${o.toString()}`)).dataPoints??[]).map(t=>({timestamp:d(t.timestamp),netWorth:n(t.netWorth),change:n(t.change),changePercent:n(t.changePercent)}))}}async getPerpsPositions(e){return {positions:[],totalValue:0}}async getPerpsHistory(e){return {records:[],hasMore:false}}async getPredictionBets(e){return {bets:[],totalValue:0}}async getPredictionSettled(e){return {records:[],hasMore:false}}};function l(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let i of r.walletAddresses)e.append("walletAddresses",i);r.chain&&e.set("chain",r.chain);let o=e.toString();return o?`?${o}`:""}function u(r){if(!r)return "";let e=new URLSearchParams;if(r.walletAddresses?.length)for(let i of r.walletAddresses)e.append("walletAddresses",i);r.chain&&e.set("chain",r.chain),r.cursor&&e.set("cursor",r.cursor),r.limit!==void 0&&e.set("limit",r.limit.toString());let o=e.toString();return o?`?${o}`:""}function c(r){let e=r?.toLowerCase();return e==="send"||e==="transfer_out"?"send":e==="receive"||e==="transfer_in"?"receive":"swap"}function d(r){if(!r)return 0;let e=Number(r);if(Number.isFinite(e))return e<1e12?e*1e3:e;let o=Date.parse(r);return Number.isFinite(o)?o:0}export{a as PortfolioClient};//# sourceMappingURL=index.mjs.map
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/index.ts","../../src/client/index.ts"],"names":["parseDecimal","raw","n","PortfolioClient","endpoint","query","qs","buildQuery","dto","httpGet","h","buildHistoryQuery","parseHistoryType","parseTimestamp","params","addr","p","_query","str","lower","d"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { DistributionData, SpotHolding } from \"../types\";\n\n/**\n * Parse a decimal string to a number, returning 0 for empty / invalid values.\n */\nexport function parseDecimal(raw: string | undefined | null): number {\n if (!raw) return 0;\n const n = Number(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\n/**\n * Format a number as a USD currency string.\n * - >= 1M → \"$1.2M\"\n * - >= 1K → \"$4.2K\"\n * - >= 1 → \"$1,234.56\"\n * - >= 0.01 → \"$0.42\"\n * - < 0.01 and > 0 → \"<$0.01\"\n * - 0 → \"$0.00\"\n */\nexport function formatUsd(value: number): string {\n const abs = Math.abs(value);\n const sign = value < 0 ? \"-\" : \"\";\n\n if (abs >= 1_000_000) {\n return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;\n }\n if (abs >= 10_000) {\n return `${sign}$${(abs / 1_000).toFixed(1)}K`;\n }\n if (abs >= 1) {\n return `${sign}$${abs.toLocaleString(\"en-US\", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;\n }\n if (abs >= 0.01) {\n return `${sign}$${abs.toFixed(2)}`;\n }\n if (abs > 0) {\n return `${sign}<$0.01`;\n }\n return \"$0.00\";\n}\n\n/**\n * Format a signed USD value with + / - prefix for PnL display.\n */\nexport function formatSignedUsd(value: number): string {\n const formatted = formatUsd(Math.abs(value));\n if (value > 0) return `+${formatted}`;\n if (value < 0) return `-${formatted.replace(\"-\", \"\")}`;\n return formatted;\n}\n\n/**\n * Format a percentage value.\n * Returns \"+3.24%\" or \"-1.50%\"\n */\nexport function formatPercent(value: number): string {\n const sign = value > 0 ? \"+\" : \"\";\n return `${sign}${value.toFixed(2)}%`;\n}\n\n/**\n * Split a USD value into integer and decimal parts for display.\n * e.g., 12847.63 → { integer: \"12,847\", decimal: \".63\", sign: \"$\" }\n */\nexport function splitUsd(value: number): {\n sign: string;\n integer: string;\n decimal: string;\n} {\n const abs = Math.abs(value);\n const parts = abs.toFixed(2).split(\".\");\n const integer = Number(parts[0]).toLocaleString(\"en-US\");\n const decimal = `.${parts[1]}`;\n const sign = value < 0 ? \"-$\" : \"$\";\n return { sign, integer, decimal };\n}\n\n/**\n * Truncate an address to \"xxxx…xxxx\" format.\n */\nexport function truncateAddress(address: string, start = 4, end = 4): string {\n if (address.length <= start + end + 3) return address;\n return `${address.slice(0, start)}…${address.slice(-end)}`;\n}\n\n/**\n * Format a token balance with appropriate precision.\n */\nexport function formatTokenBalance(value: number): string {\n if (value === 0) return \"0\";\n if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M`;\n if (value >= 1_000)\n return value.toLocaleString(\"en-US\", { maximumFractionDigits: 2 });\n if (value >= 1) return value.toFixed(2);\n if (value >= 0.0001) return value.toFixed(4);\n return \"<0.0001\";\n}\n\n/**\n * Build a Solana explorer URL for a transaction hash.\n * Defaults to Solscan. Can be extended for other chains in the future.\n */\nexport function getExplorerUrl(txHash: string, _chain?: string): string {\n return `https://solscan.io/tx/${txHash}`;\n}\n\n// ── Distribution Helpers ────────────────────────────────────────────────────\n\nconst DISTRIBUTION_COLORS = [\n \"#C8FF00\",\n \"#00E676\",\n \"#0066FF\",\n \"#FF6B9D\",\n \"#8B7BFF\",\n \"#FF5252\",\n \"#FFB74D\",\n \"#4DD0E1\",\n];\n\nconst OTHER_COLOR = \"#3A3A4E\";\n\n/**\n * Compute asset distribution from spot holdings for the donut chart.\n * Groups tokens with < 2% into \"Other\".\n */\nexport function computeDistribution(holdings: SpotHolding[]): DistributionData {\n const totalValue = holdings.reduce((sum, h) => sum + h.value, 0);\n if (totalValue <= 0) return { items: [] };\n\n const sorted = [...holdings].sort((a, b) => b.value - a.value);\n\n const items: DistributionData[\"items\"] = [];\n let otherValue = 0;\n let colorIdx = 0;\n\n for (const h of sorted) {\n const pct = (h.value / totalValue) * 100;\n if (pct < 2) {\n otherValue += h.value;\n } else {\n items.push({\n name: h.name,\n symbol: h.symbol,\n percent: pct,\n value: h.value,\n color: DISTRIBUTION_COLORS[colorIdx % DISTRIBUTION_COLORS.length],\n });\n colorIdx++;\n }\n }\n\n if (otherValue > 0) {\n items.push({\n name: \"Other\",\n symbol: \"OTHER\",\n percent: (otherValue / totalValue) * 100,\n value: otherValue,\n color: OTHER_COLOR,\n });\n }\n\n return { items };\n}\n\n/**\n * Format an epoch-ms timestamp for display in history tables.\n */\nexport function formatTime(epochMs: number): string {\n const d = new Date(epochMs);\n return d.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n}\n","import { httpGet } from \"@liberfi.io/utils\";\nimport { SpotHistoryType } from \"../types\";\nimport type {\n CurveData,\n CurveQuery,\n GetPortfolioChartReply,\n GetSpotHoldingsReply,\n GetTradeHistoryReply,\n IPortfolioClient,\n PerpsHistoryData,\n PerpsHistoryQuery,\n PerpsPositionsData,\n PortfolioOverview,\n PortfolioOverviewDTO,\n PortfolioQuery,\n PredictionBetsData,\n PredictionSettledData,\n PredictionSettledQuery,\n SpotHistoryData,\n SpotHistoryQuery,\n SpotHoldingsData,\n} from \"../types\";\nimport { parseDecimal } from \"../utils\";\n\n// ── Client ──────────────────────────────────────────────────────────────────\n\nexport class PortfolioClient implements IPortfolioClient {\n constructor(private readonly endpoint: string) {}\n\n // ── Overview ──────────────────────────────────────────────────────────\n\n async getOverview(query?: PortfolioQuery): Promise<PortfolioOverview> {\n const qs = buildQuery(query);\n const dto = await httpGet<PortfolioOverviewDTO>(\n `${this.endpoint}/portfolio/overview${qs}`,\n );\n return {\n totalValue: parseDecimal(dto.totalBalanceUsd),\n uPnl: parseDecimal(dto.unrealizedPnl),\n realizedPnl: parseDecimal(dto.realizedPnl),\n totalProfit: parseDecimal(dto.totalProfitUsd),\n winRate: parseDecimal(dto.winRate),\n };\n }\n\n // ── Spot Holdings ─────────────────────────────────────────────────────\n\n async getSpotHoldings(query?: PortfolioQuery): Promise<SpotHoldingsData> {\n const qs = buildQuery(query);\n const dto = await httpGet<GetSpotHoldingsReply>(\n `${this.endpoint}/portfolio/spot/holdings${qs}`,\n );\n return {\n holdings: (dto.holdings ?? []).map((h) => ({\n tokenAddress: h.tokenAddress,\n name: h.name,\n symbol: h.symbol,\n image: h.imageUrl ?? \"\",\n chain: h.chain ?? \"\",\n balance: parseDecimal(h.balance),\n price: parseDecimal(h.priceUsd),\n value: parseDecimal(h.valueUsd),\n change24h: parseDecimal(h.priceChange24h),\n unrealizedPnl: parseDecimal(h.unrealizedPnl),\n realizedPnl: parseDecimal(h.realizedPnl),\n walletAddress: h.walletAddress ?? \"\",\n verified: false,\n })),\n };\n }\n\n // ── Spot History ──────────────────────────────────────────────────────\n\n async getSpotHistory(query?: SpotHistoryQuery): Promise<SpotHistoryData> {\n const qs = buildHistoryQuery(query);\n const dto = await httpGet<GetTradeHistoryReply>(\n `${this.endpoint}/portfolio/spot/history${qs}`,\n );\n return {\n trades: (dto.trades ?? []).map((t) => ({\n type: parseHistoryType(t.type),\n tokenSymbol: t.tokenSymbol ?? \"\",\n tokenName: t.tokenName ?? \"\",\n tokenImageUrl: t.tokenImageUrl ?? \"\",\n tokenAddress: t.tokenAddress ?? \"\",\n tokenAmount: parseDecimal(t.tokenAmount),\n valueUsd: parseDecimal(t.valueUsd),\n priceUsd: parseDecimal(t.priceUsd),\n sideTokenSymbol: t.sideTokenSymbol ?? \"\",\n sideTokenAmount: parseDecimal(t.sideTokenAmount),\n dex: t.dex ?? \"\",\n txHash: t.txHash ?? \"\",\n chain: t.chain ?? \"\",\n walletAddress: t.walletAddress ?? \"\",\n timestamp: parseTimestamp(t.timestamp),\n })),\n nextCursor: dto.nextCursor ?? \"\",\n hasNext: dto.hasNext ?? false,\n };\n }\n\n // ── Chart Data ────────────────────────────────────────────────────────\n\n async getChartData(query: CurveQuery): Promise<CurveData> {\n const params = new URLSearchParams();\n params.set(\"period\", query.period.toString());\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const dto = await httpGet<GetPortfolioChartReply>(\n `${this.endpoint}/portfolio/chart?${params.toString()}`,\n );\n return {\n points: (dto.dataPoints ?? []).map((p) => ({\n timestamp: parseTimestamp(p.timestamp),\n netWorth: parseDecimal(p.netWorth),\n change: parseDecimal(p.change),\n changePercent: parseDecimal(p.changePercent),\n })),\n };\n }\n\n // ── Future APIs (stubs — backend not available yet) ───────────────────\n\n async getPerpsPositions(\n _query?: PortfolioQuery,\n ): Promise<PerpsPositionsData> {\n return { positions: [], totalValue: 0 };\n }\n\n async getPerpsHistory(_query?: PerpsHistoryQuery): Promise<PerpsHistoryData> {\n return { records: [], hasMore: false };\n }\n\n async getPredictionBets(\n _query?: PortfolioQuery,\n ): Promise<PredictionBetsData> {\n return { bets: [], totalValue: 0 };\n }\n\n async getPredictionSettled(\n _query?: PredictionSettledQuery,\n ): Promise<PredictionSettledData> {\n return { records: [], hasMore: false };\n }\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction buildQuery(query?: PortfolioQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction buildHistoryQuery(query?: SpotHistoryQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n if (query.cursor) {\n params.set(\"cursor\", query.cursor);\n }\n if (query.limit !== undefined) {\n params.set(\"limit\", query.limit.toString());\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction parseHistoryType(raw: string): SpotHistoryType {\n const lower = raw?.toLowerCase();\n if (lower === \"send\" || lower === \"transfer_out\") return SpotHistoryType.SEND;\n if (lower === \"receive\" || lower === \"transfer_in\")\n return SpotHistoryType.RECEIVE;\n return SpotHistoryType.SWAP;\n}\n\nfunction parseTimestamp(raw: string): number {\n if (!raw) return 0;\n const n = Number(raw);\n // If value looks like epoch seconds (< 1e12), convert to ms\n if (Number.isFinite(n)) {\n return n < 1e12 ? n * 1000 : n;\n }\n // Try ISO date string\n const d = Date.parse(raw);\n return Number.isFinite(d) ? d : 0;\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/index.ts","../../src/client/index.ts"],"names":["parseDecimal","raw","n","PortfolioClient","endpoint","query","qs","buildQuery","dto","httpGet","h","buildHistoryQuery","parseHistoryType","parseTimestamp","params","addr","p","_query","str","lower","d"],"mappings":"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","file":"index.mjs","sourcesContent":["import { formatAmount, formatAmountInUsd } from \"@liberfi.io/utils\";\nimport type { DistributionData, SpotHolding } from \"../types\";\n\n/**\n * Parse a decimal string to a number, returning 0 for empty / invalid values.\n */\nexport function parseDecimal(raw: string | undefined | null): number {\n if (!raw) return 0;\n const n = Number(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\n/**\n * Format a number as a USD currency string.\n * - >= 1M → \"$1.2M\"\n * - >= 1K → \"$4.2K\"\n * - >= 1 → \"$1,234.56\"\n * - >= 0.01 → \"$0.42\"\n * - < 0.01 and > 0 → \"<$0.01\"\n * - 0 → \"$0.00\"\n */\nexport function formatUsd(value: number): string {\n return formatAmountInUsd(value);\n}\n\n/**\n * Format a signed USD value with + / - prefix for PnL display.\n */\nexport function formatSignedUsd(value: number): string {\n const formatted = formatUsd(Math.abs(value));\n if (value > 0) return `+${formatted}`;\n if (value < 0) return `-${formatted.replace(\"-\", \"\")}`;\n return formatted;\n}\n\n/**\n * Format a percentage value.\n * Returns \"+3.24%\" or \"-1.50%\"\n */\nexport function formatPercent(value: number): string {\n const sign = value > 0 ? \"+\" : \"\";\n return `${sign}${value.toFixed(2)}%`;\n}\n\n/**\n * Split a USD value into integer and decimal parts for display.\n * e.g., 12847.63 → { integer: \"12,847\", decimal: \".63\", sign: \"$\" }\n */\nexport function splitUsd(value: number): {\n sign: string;\n integer: string;\n decimal: string;\n} {\n const abs = Math.abs(value);\n const parts = abs.toFixed(2).split(\".\");\n const integer = Number(parts[0]).toLocaleString(\"en-US\");\n const decimal = `.${parts[1]}`;\n const sign = value < 0 ? \"-$\" : \"$\";\n return { sign, integer, decimal };\n}\n\n/**\n * Truncate an address to \"xxxx…xxxx\" format.\n */\nexport function truncateAddress(address: string, start = 4, end = 4): string {\n if (address.length <= start + end + 3) return address;\n return `${address.slice(0, start)}…${address.slice(-end)}`;\n}\n\n/**\n * Format a token balance with appropriate precision.\n */\nexport function formatTokenBalance(value: number): string {\n return formatAmount(value);\n}\n\n/**\n * Build a Solana explorer URL for a transaction hash.\n * Defaults to Solscan. Can be extended for other chains in the future.\n */\nexport function getExplorerUrl(txHash: string, _chain?: string): string {\n return `https://solscan.io/tx/${txHash}`;\n}\n\n// ── Distribution Helpers ────────────────────────────────────────────────────\n\nconst DISTRIBUTION_COLORS = [\n \"#C8FF00\",\n \"#00E676\",\n \"#0066FF\",\n \"#FF6B9D\",\n \"#8B7BFF\",\n \"#FF5252\",\n \"#FFB74D\",\n \"#4DD0E1\",\n];\n\nconst OTHER_COLOR = \"#3A3A4E\";\n\n/**\n * Compute asset distribution from spot holdings for the donut chart.\n * Groups tokens with < 2% into \"Other\".\n */\nexport function computeDistribution(holdings: SpotHolding[]): DistributionData {\n const totalValue = holdings.reduce((sum, h) => sum + h.value, 0);\n if (totalValue <= 0) return { items: [] };\n\n const sorted = [...holdings].sort((a, b) => b.value - a.value);\n\n const items: DistributionData[\"items\"] = [];\n let otherValue = 0;\n let colorIdx = 0;\n\n for (const h of sorted) {\n const pct = (h.value / totalValue) * 100;\n if (pct < 2) {\n otherValue += h.value;\n } else {\n items.push({\n name: h.name,\n symbol: h.symbol,\n percent: pct,\n value: h.value,\n color: DISTRIBUTION_COLORS[colorIdx % DISTRIBUTION_COLORS.length],\n });\n colorIdx++;\n }\n }\n\n if (otherValue > 0) {\n items.push({\n name: \"Other\",\n symbol: \"OTHER\",\n percent: (otherValue / totalValue) * 100,\n value: otherValue,\n color: OTHER_COLOR,\n });\n }\n\n return { items };\n}\n\n/**\n * Format an epoch-ms timestamp for display in history tables.\n */\nexport function formatTime(epochMs: number): string {\n const d = new Date(epochMs);\n return d.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n}\n","import { httpGet } from \"@liberfi.io/utils\";\nimport { SpotHistoryType } from \"../types\";\nimport type {\n CurveData,\n CurveQuery,\n GetPortfolioChartReply,\n GetSpotHoldingsReply,\n GetTradeHistoryReply,\n IPortfolioClient,\n PerpsHistoryData,\n PerpsHistoryQuery,\n PerpsPositionsData,\n PortfolioOverview,\n PortfolioOverviewDTO,\n PortfolioQuery,\n PredictionBetsData,\n PredictionSettledData,\n PredictionSettledQuery,\n SpotHistoryData,\n SpotHistoryQuery,\n SpotHoldingsData,\n} from \"../types\";\nimport { parseDecimal } from \"../utils\";\n\n// ── Client ──────────────────────────────────────────────────────────────────\n\nexport class PortfolioClient implements IPortfolioClient {\n constructor(private readonly endpoint: string) {}\n\n // ── Overview ──────────────────────────────────────────────────────────\n\n async getOverview(query?: PortfolioQuery): Promise<PortfolioOverview> {\n const qs = buildQuery(query);\n const dto = await httpGet<PortfolioOverviewDTO>(\n `${this.endpoint}/portfolio/overview${qs}`,\n );\n return {\n totalValue: parseDecimal(dto.totalBalanceUsd),\n uPnl: parseDecimal(dto.unrealizedPnl),\n realizedPnl: parseDecimal(dto.realizedPnl),\n totalProfit: parseDecimal(dto.totalProfitUsd),\n winRate: parseDecimal(dto.winRate),\n };\n }\n\n // ── Spot Holdings ─────────────────────────────────────────────────────\n\n async getSpotHoldings(query?: PortfolioQuery): Promise<SpotHoldingsData> {\n const qs = buildQuery(query);\n const dto = await httpGet<GetSpotHoldingsReply>(\n `${this.endpoint}/portfolio/spot/holdings${qs}`,\n );\n return {\n holdings: (dto.holdings ?? []).map((h) => ({\n tokenAddress: h.tokenAddress,\n name: h.name,\n symbol: h.symbol,\n image: h.imageUrl ?? \"\",\n chain: h.chain ?? \"\",\n balance: parseDecimal(h.balance),\n price: parseDecimal(h.priceUsd),\n value: parseDecimal(h.valueUsd),\n change24h: parseDecimal(h.priceChange24h),\n unrealizedPnl: parseDecimal(h.unrealizedPnl),\n realizedPnl: parseDecimal(h.realizedPnl),\n walletAddress: h.walletAddress ?? \"\",\n verified: false,\n })),\n };\n }\n\n // ── Spot History ──────────────────────────────────────────────────────\n\n async getSpotHistory(query?: SpotHistoryQuery): Promise<SpotHistoryData> {\n const qs = buildHistoryQuery(query);\n const dto = await httpGet<GetTradeHistoryReply>(\n `${this.endpoint}/portfolio/spot/history${qs}`,\n );\n return {\n trades: (dto.trades ?? []).map((t) => ({\n type: parseHistoryType(t.type),\n tokenSymbol: t.tokenSymbol ?? \"\",\n tokenName: t.tokenName ?? \"\",\n tokenImageUrl: t.tokenImageUrl ?? \"\",\n tokenAddress: t.tokenAddress ?? \"\",\n tokenAmount: parseDecimal(t.tokenAmount),\n valueUsd: parseDecimal(t.valueUsd),\n priceUsd: parseDecimal(t.priceUsd),\n sideTokenSymbol: t.sideTokenSymbol ?? \"\",\n sideTokenAmount: parseDecimal(t.sideTokenAmount),\n dex: t.dex ?? \"\",\n txHash: t.txHash ?? \"\",\n chain: t.chain ?? \"\",\n walletAddress: t.walletAddress ?? \"\",\n timestamp: parseTimestamp(t.timestamp),\n })),\n nextCursor: dto.nextCursor ?? \"\",\n hasNext: dto.hasNext ?? false,\n };\n }\n\n // ── Chart Data ────────────────────────────────────────────────────────\n\n async getChartData(query: CurveQuery): Promise<CurveData> {\n const params = new URLSearchParams();\n params.set(\"period\", query.period.toString());\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const dto = await httpGet<GetPortfolioChartReply>(\n `${this.endpoint}/portfolio/chart?${params.toString()}`,\n );\n return {\n points: (dto.dataPoints ?? []).map((p) => ({\n timestamp: parseTimestamp(p.timestamp),\n netWorth: parseDecimal(p.netWorth),\n change: parseDecimal(p.change),\n changePercent: parseDecimal(p.changePercent),\n })),\n };\n }\n\n // ── Future APIs (stubs — backend not available yet) ───────────────────\n\n async getPerpsPositions(\n _query?: PortfolioQuery,\n ): Promise<PerpsPositionsData> {\n return { positions: [], totalValue: 0 };\n }\n\n async getPerpsHistory(_query?: PerpsHistoryQuery): Promise<PerpsHistoryData> {\n return { records: [], hasMore: false };\n }\n\n async getPredictionBets(\n _query?: PortfolioQuery,\n ): Promise<PredictionBetsData> {\n return { bets: [], totalValue: 0 };\n }\n\n async getPredictionSettled(\n _query?: PredictionSettledQuery,\n ): Promise<PredictionSettledData> {\n return { records: [], hasMore: false };\n }\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction buildQuery(query?: PortfolioQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction buildHistoryQuery(query?: SpotHistoryQuery): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n if (query.walletAddresses?.length) {\n for (const addr of query.walletAddresses) {\n params.append(\"walletAddresses\", addr);\n }\n }\n if (query.chain) {\n params.set(\"chain\", query.chain);\n }\n if (query.cursor) {\n params.set(\"cursor\", query.cursor);\n }\n if (query.limit !== undefined) {\n params.set(\"limit\", query.limit.toString());\n }\n const str = params.toString();\n return str ? `?${str}` : \"\";\n}\n\nfunction parseHistoryType(raw: string): SpotHistoryType {\n const lower = raw?.toLowerCase();\n if (lower === \"send\" || lower === \"transfer_out\") return SpotHistoryType.SEND;\n if (lower === \"receive\" || lower === \"transfer_in\")\n return SpotHistoryType.RECEIVE;\n return SpotHistoryType.SWAP;\n}\n\nfunction parseTimestamp(raw: string): number {\n if (!raw) return 0;\n const n = Number(raw);\n // If value looks like epoch seconds (< 1e12), convert to ms\n if (Number.isFinite(n)) {\n return n < 1e12 ? n * 1000 : n;\n }\n // Try ISO date string\n const d = Date.parse(raw);\n return Number.isFinite(d) ? d : 0;\n}\n"]}