Skip to content

Commit

Permalink
web: use real data for aum and nav (#55)
Browse files Browse the repository at this point in the history
1. find all token accounts from treasury address, then compute aum from
all holdings
2. get total supply from share class mint
3. nav = aum / totalShares
  • Loading branch information
yurushao authored Apr 6, 2024
1 parent 749f165 commit b767c27
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 27 deletions.
111 changes: 103 additions & 8 deletions web/src/app/glam/glam-data-access.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import * as anchor from "@coral-xyz/anchor";

import {
getMint,
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_2022_PROGRAM_ID,
TOKEN_PROGRAM_ID,
getAssociatedTokenAddressSync
} from "@solana/spl-token";
import { BN, Program } from "@coral-xyz/anchor";
import { ComputeBudgetProgram, Cluster, AccountMeta, PublicKey } from "@solana/web3.js";
import {
GetProgramAccountsFilter,
Connection,
clusterApiUrl,
ComputeBudgetProgram,
Cluster,
AccountMeta,
PublicKey
} from "@solana/web3.js";
import { GlamIDL, getGlamProgramId } from "@glam/anchor";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { useMutation, useQuery } from "@tanstack/react-query";
Expand Down Expand Up @@ -73,7 +81,7 @@ export function useGlamProgram() {
fundName: string;
fundSymbol: string;
manager: PublicKey;
assets: string[],
assets: string[];
assetsStructure: number[];
shareClassMetadata: ShareClassMetadata;
}) => {
Expand All @@ -100,9 +108,11 @@ export function useGlamProgram() {
shareClassMetadata.uri = `https://api.glam.systems/metadata/${sharePDA.toBase58()}`;
shareClassMetadata.imageUri = `https://api.glam.systems/image/${sharePDA.toBase58()}.png`;

const remainingAccounts: Array<AccountMeta> = assets.map(a => (
{ pubkey: new PublicKey(a), isSigner: false, isWritable: false }
));
const remainingAccounts: Array<AccountMeta> = assets.map((a) => ({
pubkey: new PublicKey(a),
isSigner: false,
isWritable: false
}));
return program.methods
.initialize(
fundName,
Expand All @@ -123,7 +133,7 @@ export function useGlamProgram() {
.preInstructions([
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 })
])
.rpc()
.rpc();
},
onSuccess: (tx) => {
console.log(tx);
Expand Down Expand Up @@ -389,6 +399,91 @@ export function useGlamProgramAccount({ fundKey }: { fundKey: PublicKey }) {
};
}

export function getTotalShares(shareClassAddress: PublicKey) {
const { data } = useQuery({
queryKey: ["get-total-shares", shareClassAddress],
queryFn: async () => {
const connection = new Connection(clusterApiUrl("devnet"));
try {
const mintInfo = await getMint(
connection,
shareClassAddress,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
return Number(mintInfo.supply) / 1e9;
} catch (e) {
console.error(e);
}
return 1.0;
}
});

return data;
}

export function getAum(treasuryAddress: string) {
const { data } = useQuery({
queryKey: ["get-aum-in-treasury", treasuryAddress],
queryFn: async () => {
const connection = new Connection(clusterApiUrl("devnet"));
try {
const filters: GetProgramAccountsFilter[] = [
{
dataSize: 165 //size of account (bytes)
},
{
memcmp: {
offset: 32,
bytes: treasuryAddress
}
}
];
const accounts = await connection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID,
{ filters: filters }
);
console.log(
`Found ${accounts.length} token account(s) in treasury ${treasuryAddress}`
);
let aum = 0.0;
const response = await fetch("https://api.glam.systems/prices");
const { btc, usdc, sol } = await response.json();

accounts.forEach((account) => {
const parsedAccountInfo: any = account.account.data;
const mintAddress: string =
parsedAccountInfo["parsed"]["info"]["mint"];
const tokenBalance: number =
parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];

switch (mintAddress) {
case "So11111111111111111111111111111111111111112": // sol
console.log("sol balance", tokenBalance, tokenBalance * sol);
aum += tokenBalance * sol;
break;
case "8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2": // usdc
console.log("usdc balance", tokenBalance, tokenBalance * usdc);
aum += tokenBalance * usdc;
break;
case "3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv": // btc
console.log("btc balance", tokenBalance, tokenBalance * btc);
aum += tokenBalance * btc;
break;
default:
console.log(`Unknown mint address: ${mintAddress}`);
}
});
return aum;
} catch (e) {
console.error(e);
}
return 0;
}
});
return data;
}

export function useFundPerfChartData(fund: string) {
const { data } = useQuery({
queryKey: ["fund_performance", fund],
Expand All @@ -400,7 +495,7 @@ export function useFundPerfChartData(fund: string) {
await response.json();
const chartData = timestamps
.map((ts: any, i: number) => {
const fundValue = fundPerformance[i] * 100;
const fundValue = Number(fundPerformance[i]) * 100;
const btcValue = btcPerformance[i] * 100;
// const solValue = solPerformance[i] * 100;
// const ethValue = ethPerformance[i] * 100;
Expand Down
42 changes: 23 additions & 19 deletions web/src/app/products/product-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,34 @@ import {
Tile
} from "@carbon/react";
import { formatNumber, formatPercent } from "../utils/format-number";
import { useParams, useNavigate, useNavigation } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useWallet } from "@solana/wallet-adapter-react";

import { SideActionBar } from "./SideActionBar";
import { getTokenMetadata } from "@solana/spl-token";
import { gray70Hover } from "@carbon/colors";
import { useQuery } from "@tanstack/react-query";

import {
useGlamProgramAccount,
useFundPerfChartData
useFundPerfChartData,
getAum,
getTotalShares
} from "../glam/glam-data-access";
import { useMemo } from "react";
import { ExplorerLink } from "../cluster/cluster-ui";
import { ellipsify } from "../ui/ui-layout";


class FundModel {
key: PublicKey;
data: any;

constructor(key: PublicKey, data: any) {
this.key = key;
this.data = data || {};
}

getImageUrl() {
const pubkey = this.data?.shareClasses[0].toBase58() || '1111111111111111111111111111111111';
const pubkey =
this.data?.shareClasses[0].toBase58() ||
"1111111111111111111111111111111111";
return `https://api.glam.systems/image/${pubkey}.png`;
}

Expand All @@ -50,10 +50,8 @@ class FundModel {
getPerformanceFee() {
return this.data?.shareClassesMetadata[0].feePerformance / 1_000_000.0;
}

}


export default function ProductPage() {
const grayStyle = {
color: gray70Hover,
Expand All @@ -74,7 +72,10 @@ export default function ProductPage() {
}
const fundId = fundKey.toString();

const fundPerfChartData = useFundPerfChartData(fundId) || [{value: 0}, {value: 0}];
const fundPerfChartData = useFundPerfChartData(fundId) || [
{ value: 0 },
{ value: 0 }
];

const { account } = useGlamProgramAccount({ fundKey });
if (account.isLoading) {
Expand All @@ -87,8 +88,8 @@ export default function ProductPage() {
const { publicKey } = useWallet();
const isManager = publicKey?.toString() == data?.manager?.toString();

const aum = 417.758475 + 0.2*66_891.80;
const totalShares = 1_366.124012344;
const aum = getAum(data!.treasury!.toString());
const totalShares = getTotalShares(data!.shareClasses[0]);

const fund = {
id: fundId,
Expand All @@ -100,11 +101,11 @@ export default function ProductPage() {
shareClass0: data?.shareClasses[0],
investmentObjective:
"The Glam Investment Fund seeks to reflect generally the performance of the price of Bitcoin and Solana.",
nav: aum/totalShares,
nav: aum ? aum / (totalShares || 1.0) : 0,
// dailyNavChange: 2,
dailyNavChange: fundPerfChartData[fundPerfChartData.length-2].value,
dailyNavChange: fundPerfChartData[fundPerfChartData.length - 2].value,
// daily: 0.29,
aum,
aum: aum || 0,
// dailyNetInflows: 13987428,
// "24HourNetInflowChange": 0.0089,
// will optimize looping later once we have all the necessary data
Expand All @@ -116,7 +117,7 @@ export default function ProductPage() {
},
facts: {
launchDate: data?.shareClassesMetadata[0].launchDate,
fundAsset: data?.shareClassesMetadata[0].shareClassAsset,
fundAsset: data?.shareClassesMetadata[0].shareClassAsset
},
terms: {
highWaterMark: false,
Expand Down Expand Up @@ -170,7 +171,8 @@ export default function ProductPage() {
</div>

<div className="flex items-center gap-[16px] mb-[32px]">
<img src={fundModel.getImageUrl()}
<img
src={fundModel.getImageUrl()}
style={{
width: "64px",
height: "64px"
Expand Down Expand Up @@ -218,7 +220,9 @@ export default function ProductPage() {
<Tile className="h-full">
<div className="flex flex-col gap-[12px]">
<p>NAV</p>
<p className="text-xl text-black">{formatNumber(fund.nav)}</p>
<p className="text-xl text-black">
{formatNumber(fund.nav)}
</p>
</div>
<br />
<div className="flex flex-col gap-[12px]">
Expand Down

0 comments on commit b767c27

Please sign in to comment.