mango-v4-ui/utils/tokens.ts

155 lines
3.8 KiB
TypeScript
Raw Normal View History

2022-12-14 18:51:21 -08:00
import { PublicKey, Connection } from '@solana/web3.js'
2022-07-05 20:37:49 -07:00
import { TokenInstructions } from '@project-serum/serum'
2023-04-14 04:48:09 -07:00
import {
getAssociatedTokenAddress,
toUiDecimals,
} from '@blockworks-foundation/mango-v4'
import {
Metaplex,
Nft,
Sft,
SftWithToken,
NftWithToken,
Metadata,
JsonMetadata,
} from '@metaplex-foundation/js'
2022-07-05 20:37:49 -07:00
export class TokenAccount {
publicKey!: PublicKey
mint!: PublicKey
owner!: PublicKey
amount!: number
decimals!: number
2022-08-01 12:23:29 -07:00
uiAmount: number
2022-07-05 20:37:49 -07:00
constructor(
publicKey: PublicKey,
decoded: {
mint: PublicKey
owner: PublicKey
amount: number
decimals: number
uiAmount: number
2023-07-21 11:47:53 -07:00
},
) {
2022-07-05 20:37:49 -07:00
this.publicKey = publicKey
2022-08-01 12:23:29 -07:00
this.uiAmount = 0
2022-07-05 20:37:49 -07:00
Object.assign(this, decoded)
}
}
2023-04-14 04:48:09 -07:00
type RawNft = Nft | Sft | SftWithToken | NftWithToken
type NftWithATA = RawNft & {
owner: null | PublicKey
tokenAccountAddress: null | PublicKey
}
function exists<T>(item: T | null | undefined): item is T {
return !!item
}
2022-07-05 20:37:49 -07:00
export async function getTokenAccountsByOwnerWithWrappedSol(
connection: Connection,
2023-07-21 11:47:53 -07:00
owner: PublicKey,
): Promise<TokenAccount[]> {
2022-07-05 20:37:49 -07:00
const solReq = connection.getAccountInfo(owner)
const tokenReq = connection.getParsedTokenAccountsByOwner(owner, {
programId: TokenInstructions.TOKEN_PROGRAM_ID,
})
// fetch data
const [solResp, tokenResp] = await Promise.all([solReq, tokenReq])
// parse token accounts
const tokenAccounts = tokenResp.value.map((t) => {
return {
publicKey: t.pubkey,
mint: t.account.data.parsed.info.mint,
owner: t.account.data.parsed.info.owner,
amount: t.account.data.parsed.info.tokenAmount.amount,
uiAmount: t.account.data.parsed.info.tokenAmount.uiAmount,
decimals: t.account.data.parsed.info.tokenAmount.decimals,
}
})
// create fake wrapped sol account to reflect sol balances in user's wallet
const lamports = solResp?.lamports || 0
const solAccount = new TokenAccount(owner, {
mint: TokenInstructions.WRAPPED_SOL_MINT,
owner,
amount: lamports,
uiAmount: toUiDecimals(lamports, 9),
decimals: 9,
})
// prepend SOL account to beginning of list
return [solAccount].concat(tokenAccounts)
}
2022-08-01 22:32:21 -07:00
2023-04-14 04:48:09 -07:00
const enhanceNFT = (nft: NftWithATA) => {
return {
image: nft.json?.image || '',
name: nft.json?.name || '',
address: nft.metadataAddress.toBase58(),
collectionAddress: nft.collection?.address.toBase58(),
mint: nft.mint.address.toBase58(),
tokenAccount: nft.tokenAccountAddress?.toBase58() || '',
2023-09-07 13:18:25 -07:00
json: nft.json,
2023-04-14 04:48:09 -07:00
}
}
function loadNft(
nft: Metadata<JsonMetadata<string>> | Nft | Sft,
2023-07-21 11:47:53 -07:00
connection: Connection,
2023-04-14 04:48:09 -07:00
) {
const metaplex = new Metaplex(connection)
return Promise.race([
metaplex
.nfts()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2023-04-14 04:48:09 -07:00
// @ts-ignore
.load({ metadata: nft })
.catch((e) => {
console.error(e)
return null
}),
])
}
export async function getNFTsByOwner(owner: PublicKey, connection: Connection) {
const metaplex = new Metaplex(connection)
const rawNfts = await metaplex.nfts().findAllByOwner({
owner,
2022-08-01 22:32:21 -07:00
})
2023-04-14 04:48:09 -07:00
const nfts = await Promise.all(
2023-07-21 11:47:53 -07:00
rawNfts.map((nft) => loadNft(nft, connection)),
2023-04-14 04:48:09 -07:00
).then((nfts) =>
Promise.all(
nfts.filter(exists).map(async (nft) => ({
...nft,
owner,
tokenAccountAddress: await getAssociatedTokenAddress(
nft.mint.address,
owner,
2023-07-21 11:47:53 -07:00
true,
2023-04-14 04:48:09 -07:00
).catch((e) => {
console.error(e)
return null
}),
2023-07-21 11:47:53 -07:00
})),
),
2023-04-14 04:48:09 -07:00
)
return nfts.map(enhanceNFT)
2022-08-01 22:32:21 -07:00
}
2023-04-16 18:04:13 -07:00
export const formatTokenSymbol = (symbol: string) => {
if (symbol.toLowerCase().includes('portal')) {
const truncSymbol = symbol.split(' ')[0].toUpperCase()
2023-05-13 05:17:04 -07:00
return truncSymbol === 'WBTC' ? 'wBTC' : truncSymbol
2023-04-16 18:04:13 -07:00
}
return symbol === 'MSOL' ? 'mSOL' : symbol
}