212 lines
5.8 KiB
TypeScript
212 lines
5.8 KiB
TypeScript
import React, { useContext, useState, useEffect } from "react";
|
|
import { Swap as SwapClient } from "@project-serum/swap";
|
|
import { PublicKey, Account } from "@solana/web3.js";
|
|
import {
|
|
AccountInfo as TokenAccount,
|
|
MintInfo,
|
|
Token,
|
|
TOKEN_PROGRAM_ID,
|
|
} from "@solana/spl-token";
|
|
import { TokenListContainer, TokenInfo } from "@solana/spl-token-registry";
|
|
import { getOwnedTokenAccounts } from "../utils/tokens";
|
|
|
|
const SRM_MINT = new PublicKey("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt");
|
|
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
|
|
const SwapContext = React.createContext<null | SwapContext>(null);
|
|
|
|
export function SwapContextProvider(props: any) {
|
|
const swapClient = props.swapClient;
|
|
const [fromMint, setFromMint] = useState(SRM_MINT);
|
|
const [toMint, setToMint] = useState(USDC_MINT);
|
|
const [fromAmount, setFromAmount] = useState(0);
|
|
const [toAmount, setToAmount] = useState(0);
|
|
const [fromBalance, setFromBalance] = useState(undefined);
|
|
const [toBalance, setToBalance] = useState(undefined);
|
|
const [minExpectedAmount, setMinExpectedAmount] = useState(0);
|
|
const [ownedTokenAccounts, setOwnedTokenAccounts] = useState(undefined);
|
|
const [mintCache, setMintCache] = useState(new Map<string, MintInfo>());
|
|
|
|
// Fetch all the owned token accounts for the wallet.
|
|
useEffect(() => {
|
|
getOwnedTokenAccounts(
|
|
swapClient.program.provider.connection,
|
|
swapClient.program.provider.wallet.publicKey
|
|
).then(setOwnedTokenAccounts);
|
|
}, [
|
|
swapClient.program.provider.wallet.publicKey,
|
|
swapClient.program.provider.connection,
|
|
]);
|
|
|
|
// Fetch the mint account infos not already in the cache.
|
|
useEffect(() => {
|
|
const fromMintClient = new Token(
|
|
swapClient.program.provider.connection,
|
|
fromMint,
|
|
TOKEN_PROGRAM_ID,
|
|
new Account()
|
|
);
|
|
const toMintClient = new Token(
|
|
swapClient.program.provider.connection,
|
|
toMint,
|
|
TOKEN_PROGRAM_ID,
|
|
new Account()
|
|
);
|
|
|
|
let promises = [];
|
|
if (mintCache.get(fromMint.toString())) {
|
|
promises.push(
|
|
(async (): Promise<MintInfo> => {
|
|
return mintCache.get(fromMint.toString()) as MintInfo;
|
|
})()
|
|
);
|
|
} else {
|
|
promises.push(fromMintClient.getMintInfo());
|
|
}
|
|
if (mintCache.get(toMint.toString())) {
|
|
promises.push(
|
|
(async (): Promise<MintInfo> => {
|
|
return mintCache.get(toMint.toString()) as MintInfo;
|
|
})()
|
|
);
|
|
} else {
|
|
promises.push(toMintClient.getMintInfo());
|
|
}
|
|
|
|
Promise.all(promises as [Promise<MintInfo>, Promise<MintInfo>]).then(
|
|
([fromMintInfo, toMintInfo]: [MintInfo, MintInfo]) => {
|
|
let cache = new Map(mintCache);
|
|
cache.set(fromMint.toString(), fromMintInfo);
|
|
cache.set(toMint.toString(), toMintInfo);
|
|
setMintCache(cache);
|
|
}
|
|
);
|
|
}, [fromMint, toMint]);
|
|
|
|
const swapToFromMints = () => {
|
|
const oldFrom = fromMint;
|
|
const oldFromAmount = fromAmount;
|
|
const oldTo = toMint;
|
|
const oldToAmount = toAmount;
|
|
setFromMint(oldTo);
|
|
setToMint(oldFrom);
|
|
setFromAmount(oldToAmount);
|
|
setToAmount(oldFromAmount);
|
|
};
|
|
|
|
return (
|
|
<SwapContext.Provider
|
|
value={{
|
|
swapClient,
|
|
fromMint,
|
|
setFromMint,
|
|
toMint,
|
|
setToMint,
|
|
fromAmount,
|
|
setFromAmount,
|
|
toAmount,
|
|
setToAmount,
|
|
minExpectedAmount,
|
|
swapToFromMints,
|
|
fromBalance,
|
|
toBalance,
|
|
ownedTokenAccounts,
|
|
mintCache,
|
|
}}
|
|
>
|
|
{props.children}
|
|
</SwapContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useSwapContext(): SwapContext {
|
|
const ctx = useContext(SwapContext);
|
|
if (ctx === null) {
|
|
throw new Error("Context not available");
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
export type SwapContext = {
|
|
swapClient: SwapClient;
|
|
fromMint: PublicKey;
|
|
setFromMint: (m: PublicKey) => void;
|
|
toMint: PublicKey;
|
|
setToMint: (m: PublicKey) => void;
|
|
fromAmount: number;
|
|
setFromAmount: (a: number) => void;
|
|
toAmount: number;
|
|
setToAmount: (a: number) => void;
|
|
minExpectedAmount: number;
|
|
swapToFromMints: () => void;
|
|
fromBalance?: number;
|
|
toBalance?: number;
|
|
fromMintAccount?: MintInfo;
|
|
toMintAccount?: MintInfo;
|
|
ownedTokenAccounts:
|
|
| { publicKey: PublicKey; account: TokenAccount }[]
|
|
| undefined;
|
|
mintCache: Map<string, MintInfo>;
|
|
};
|
|
|
|
const TokenListContext = React.createContext<null | TokenListContext>(null);
|
|
|
|
export function TokenListContextProvider(props: any) {
|
|
return (
|
|
<TokenListContext.Provider value={{ tokenList: props.tokenList }}>
|
|
{props.children}
|
|
</TokenListContext.Provider>
|
|
);
|
|
}
|
|
|
|
type TokenListContext = {
|
|
tokenList: TokenListContainer;
|
|
};
|
|
|
|
export function useTokenList(): TokenInfo[] {
|
|
const ctx = useContext(TokenListContext);
|
|
if (ctx === null) {
|
|
throw new Error("Context not available");
|
|
}
|
|
return ctx.tokenList.getList();
|
|
}
|
|
|
|
// Null => none exists.
|
|
// Undefined => loading.
|
|
export function useOwnedTokenAccount(
|
|
mint: PublicKey
|
|
): { publicKey: PublicKey; account: TokenAccount } | null | undefined {
|
|
const ctx = useContext(SwapContext);
|
|
if (ctx === null) {
|
|
throw new Error("Context not available");
|
|
}
|
|
if (ctx.ownedTokenAccounts === undefined) {
|
|
return undefined;
|
|
}
|
|
const tokenAccounts = ctx.ownedTokenAccounts.filter((account) =>
|
|
account.account.mint.equals(mint)
|
|
);
|
|
|
|
if (tokenAccounts.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// Take the account with the most tokens in it.
|
|
tokenAccounts.sort((a, b) =>
|
|
a.account.amount < a.account.amount
|
|
? -1
|
|
: a.account.amount > b.account.amount
|
|
? 1
|
|
: 0
|
|
);
|
|
return tokenAccounts[0];
|
|
}
|
|
|
|
export function useMintAccount(mint: PublicKey): MintInfo | undefined {
|
|
const ctx = useContext(SwapContext);
|
|
if (ctx === null) {
|
|
throw new Error("Context not available");
|
|
}
|
|
return ctx.mintCache.get(mint.toString());
|
|
}
|