Reorganize context providers
This commit is contained in:
parent
265ee7f720
commit
94ded3ab69
|
@ -1,322 +0,0 @@
|
|||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { useAsync } from "react-async-hook";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { Provider } from "@project-serum/anchor";
|
||||
import { Swap as SwapClient } from "@project-serum/swap";
|
||||
import { Market, OpenOrders } from "@project-serum/serum";
|
||||
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");
|
||||
export const USDC_MINT = new PublicKey(
|
||||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
);
|
||||
export const USDT_MINT = new PublicKey(
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||||
);
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
|
||||
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 [slippage, setSlippage] = useState(0.5);
|
||||
|
||||
// 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,
|
||||
]);
|
||||
|
||||
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,
|
||||
slippage,
|
||||
setSlippage,
|
||||
}}
|
||||
>
|
||||
{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;
|
||||
slippage: number;
|
||||
setSlippage: (n: number) => void;
|
||||
};
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
const MintContext = React.createContext<null | MintContext>(null);
|
||||
type MintContext = {
|
||||
mintCache: Map<string, MintInfo>;
|
||||
setMintCache: (m: Map<string, MintInfo>) => void;
|
||||
provider: Provider;
|
||||
};
|
||||
|
||||
export function MintContextProvider(props: any) {
|
||||
const provider = props.provider;
|
||||
const [mintCache, setMintCache] = useState(new Map<string, MintInfo>());
|
||||
|
||||
return (
|
||||
<MintContext.Provider
|
||||
value={{
|
||||
mintCache,
|
||||
setMintCache,
|
||||
provider,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</MintContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useMint(mint: PublicKey): MintInfo | undefined | null {
|
||||
const ctx = useContext(MintContext);
|
||||
if (ctx === null) {
|
||||
throw new Error("Mint context not found");
|
||||
}
|
||||
|
||||
// Lazy load the mint account if needeed.
|
||||
const asyncMintInfo = useAsync(async () => {
|
||||
if (ctx.mintCache.get(mint.toString())) {
|
||||
return ctx.mintCache.get(mint.toString());
|
||||
}
|
||||
const mintClient = new Token(
|
||||
ctx.provider.connection,
|
||||
mint,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new Account()
|
||||
);
|
||||
const mintInfo = await mintClient.getMintInfo();
|
||||
|
||||
let cache = new Map(ctx.mintCache);
|
||||
cache.set(mint.toString(), mintInfo);
|
||||
ctx.setMintCache(cache);
|
||||
|
||||
return mintInfo;
|
||||
}, [ctx.provider.connection, mint]);
|
||||
|
||||
if (asyncMintInfo.result) {
|
||||
return asyncMintInfo.result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const SerumDexContext = React.createContext<SerumDexContext | null>(null);
|
||||
type SerumDexContext = {
|
||||
// Maps market address to open orders accounts.
|
||||
openOrders: Map<string, Array<OpenOrders>>;
|
||||
marketCache: Map<string, Market>;
|
||||
};
|
||||
|
||||
export function useOpenOrders(): Map<string, Array<OpenOrders>> {
|
||||
const ctx = useContext(SerumDexContext);
|
||||
if (ctx === null) {
|
||||
throw new Error("Context not available");
|
||||
}
|
||||
return ctx.openOrders;
|
||||
}
|
||||
|
||||
export function useMarket(market: PublicKey): Market | undefined {
|
||||
const ctx = useContext(SerumDexContext);
|
||||
if (ctx === null) {
|
||||
throw new Error("Context not available");
|
||||
}
|
||||
return ctx.marketCache.get(market.toString());
|
||||
}
|
||||
|
||||
export function SerumDexContextProvider(props: any) {
|
||||
const [ooAccounts, setOoAccounts] = useState<Map<string, Array<OpenOrders>>>(
|
||||
new Map()
|
||||
);
|
||||
const [marketCache, setMarketCache] = useState<Map<string, Market>>(
|
||||
new Map()
|
||||
);
|
||||
const provider = props.provider;
|
||||
|
||||
// Two operations:
|
||||
// 1. Fetch all open orders accounts for the connected wallet.
|
||||
// 2. Batch fetch all market accounts.
|
||||
useEffect(() => {
|
||||
OpenOrders.findForOwner(
|
||||
provider.connection,
|
||||
provider.wallet.publicKey,
|
||||
DEX_PID
|
||||
).then(async (openOrders) => {
|
||||
const newOoAccounts = new Map();
|
||||
let markets = new Set<string>();
|
||||
openOrders.forEach((oo) => {
|
||||
markets.add(oo.market.toString());
|
||||
if (newOoAccounts.get(oo.market.toString())) {
|
||||
newOoAccounts.get(oo.market.toString()).push(oo);
|
||||
} else {
|
||||
newOoAccounts.set(oo.market.toString(), [oo]);
|
||||
}
|
||||
});
|
||||
if (markets.size > 100) {
|
||||
// Punt request chunking until there's user demand.
|
||||
throw new Error(
|
||||
"Too many markets. Please file an issue to update this"
|
||||
);
|
||||
}
|
||||
const marketAccounts = (
|
||||
await anchor.utils.getMultipleAccounts(
|
||||
provider.connection,
|
||||
// @ts-ignore
|
||||
[...markets].map((m) => new PublicKey(m))
|
||||
)
|
||||
).map((programAccount) => {
|
||||
return {
|
||||
publicKey: programAccount?.publicKey,
|
||||
account: new Market(
|
||||
Market.getLayout(DEX_PID).decode(programAccount?.account.data),
|
||||
-1, // Not used so don't bother fetching.
|
||||
-1, // Not used so don't bother fetching.
|
||||
provider.opts,
|
||||
DEX_PID
|
||||
),
|
||||
};
|
||||
});
|
||||
const newMarketCache = new Map(marketCache);
|
||||
marketAccounts.forEach((m) => {
|
||||
newMarketCache.set(m.publicKey!.toString(), m.account);
|
||||
});
|
||||
|
||||
setMarketCache(newMarketCache);
|
||||
setOoAccounts(newOoAccounts);
|
||||
});
|
||||
}, [provider.connection, provider.wallet.publicKey, DEX_PID]);
|
||||
return (
|
||||
<SerumDexContext.Provider
|
||||
value={{
|
||||
openOrders: ooAccounts,
|
||||
marketCache,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SerumDexContext.Provider>
|
||||
);
|
||||
}
|
|
@ -25,13 +25,10 @@ import {
|
|||
} from "@material-ui/core";
|
||||
import { SettingsOutlined as Settings } from "@material-ui/icons";
|
||||
import PopupState, { bindTrigger, bindPopover } from "material-ui-popup-state";
|
||||
import {
|
||||
useOpenOrders,
|
||||
useMarket,
|
||||
useMint,
|
||||
useTokenList,
|
||||
useSwapContext,
|
||||
} from "./Context";
|
||||
import { useSwapContext } from "./context/Swap";
|
||||
import { useMarket, useOpenOrders } from "./context/Dex";
|
||||
import { useTokenList } from "./context/TokenList";
|
||||
import { useMint } from "./context/Mint";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tab: {
|
||||
|
@ -161,7 +158,7 @@ function OpenOrdersAccounts() {
|
|||
<TableCell align="right">Base Free</TableCell>
|
||||
<TableCell align="right">Quote Used</TableCell>
|
||||
<TableCell align="right">Quote Free</TableCell>
|
||||
<TableCell align="right">Open Orders</TableCell>
|
||||
<TableCell align="right">Open Orders Account</TableCell>
|
||||
<TableCell align="right">Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
@ -191,13 +188,13 @@ function OpenOrdersRow({
|
|||
const [ooAccount, setOoAccount] = useState(openOrders[0]);
|
||||
const marketClient = useMarket(market);
|
||||
const tokenList = useTokenList();
|
||||
const base = useMint(marketClient!.baseMintAddress);
|
||||
const quote = useMint(marketClient!.quoteMintAddress);
|
||||
const base = useMint(marketClient?.baseMintAddress);
|
||||
const quote = useMint(marketClient?.quoteMintAddress);
|
||||
const baseTicker = tokenList
|
||||
.filter((t) => t.address === marketClient!.baseMintAddress.toString())
|
||||
.filter((t) => t.address === marketClient?.baseMintAddress.toString())
|
||||
.map((t) => t.symbol)[0];
|
||||
const quoteTicker = tokenList
|
||||
.filter((t) => t.address === marketClient!.quoteMintAddress.toString())
|
||||
.filter((t) => t.address === marketClient?.quoteMintAddress.toString())
|
||||
.map((t) => t.symbol)[0];
|
||||
const marketName =
|
||||
baseTicker && quoteTicker
|
||||
|
|
|
@ -12,16 +12,11 @@ import {
|
|||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import { Info, ExpandMore } from "@material-ui/icons";
|
||||
import {
|
||||
MintContextProvider,
|
||||
SwapContextProvider,
|
||||
TokenListContextProvider,
|
||||
SerumDexContextProvider,
|
||||
useSwapContext,
|
||||
useTokenList,
|
||||
useOwnedTokenAccount,
|
||||
useMint,
|
||||
} from "./Context";
|
||||
import { SwapContextProvider, useSwapContext } from "./context/Swap";
|
||||
import { DexContextProvider } from "./context/Dex";
|
||||
import { MintContextProvider, useMint } from "./context/Mint";
|
||||
import { TokenListContextProvider, useTokenList } from "./context/TokenList";
|
||||
import { TokenContextProvider, useOwnedTokenAccount } from "./context/Token";
|
||||
import TokenDialog from "./TokenDialog";
|
||||
import { SettingsButton } from "./Settings";
|
||||
|
||||
|
@ -72,19 +67,21 @@ export default function Swap({
|
|||
}) {
|
||||
const swapClient = new SwapClient(provider, tokenList);
|
||||
return (
|
||||
<MintContextProvider provider={provider}>
|
||||
<SwapContextProvider swapClient={swapClient}>
|
||||
<TokenListContextProvider tokenList={tokenList}>
|
||||
<SerumDexContextProvider provider={provider}>
|
||||
<SwapInner style={style} />
|
||||
</SerumDexContextProvider>
|
||||
</TokenListContextProvider>
|
||||
</SwapContextProvider>
|
||||
</MintContextProvider>
|
||||
<TokenListContextProvider tokenList={tokenList}>
|
||||
<MintContextProvider provider={provider}>
|
||||
<TokenContextProvider provider={provider}>
|
||||
<DexContextProvider provider={provider}>
|
||||
<SwapContextProvider swapClient={swapClient}>
|
||||
<SwapCard style={style} />
|
||||
</SwapContextProvider>
|
||||
</DexContextProvider>
|
||||
</TokenContextProvider>
|
||||
</MintContextProvider>
|
||||
</TokenListContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function SwapInner({ style }: { style?: any }) {
|
||||
function SwapCard({ style }: { style?: any }) {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<div style={style}>
|
||||
|
@ -295,7 +292,7 @@ function TokenName({ mint }: { mint: PublicKey }) {
|
|||
|
||||
function SwapButton() {
|
||||
const styles = useStyles();
|
||||
const { fromMint, toMint, fromAmount, minExpectedAmount } = useSwapContext();
|
||||
const { fromMint, toMint, fromAmount, slippage } = useSwapContext();
|
||||
|
||||
const sendSwapTransaction = async () => {
|
||||
console.log("sending swap");
|
||||
|
|
|
@ -10,8 +10,9 @@ import {
|
|||
ListItem,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { useSwapContext, useTokenList, USDC_MINT, USDT_MINT } from "./Context";
|
||||
import { TokenIcon } from "./Swap";
|
||||
import { useSwapContext, USDC_MINT, USDT_MINT } from "./context/Swap";
|
||||
import { useTokenList } from "./context/TokenList";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
dialogContent: {
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { useAsync } from "react-async-hook";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { Provider } from "@project-serum/anchor";
|
||||
import { Market, OpenOrders } from "@project-serum/serum";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
|
||||
const _DexContext = React.createContext<DexContext | null>(null);
|
||||
type DexContext = {
|
||||
// Maps market address to open orders accounts.
|
||||
openOrders: Map<string, Array<OpenOrders>>;
|
||||
marketCache: Map<string, Market>;
|
||||
setMarketCache: (c: Map<string, Market>) => void;
|
||||
provider: Provider;
|
||||
};
|
||||
|
||||
export function DexContextProvider(props: any) {
|
||||
const [ooAccounts, setOoAccounts] = useState<Map<string, Array<OpenOrders>>>(
|
||||
new Map()
|
||||
);
|
||||
const [marketCache, setMarketCache] = useState<Map<string, Market>>(
|
||||
new Map()
|
||||
);
|
||||
const provider = props.provider;
|
||||
|
||||
// Two operations:
|
||||
// 1. Fetch all open orders accounts for the connected wallet.
|
||||
// 2. Batch fetch all market accounts.
|
||||
useEffect(() => {
|
||||
OpenOrders.findForOwner(
|
||||
provider.connection,
|
||||
provider.wallet.publicKey,
|
||||
DEX_PID
|
||||
).then(async (openOrders) => {
|
||||
const newOoAccounts = new Map();
|
||||
let markets = new Set<string>();
|
||||
openOrders.forEach((oo) => {
|
||||
markets.add(oo.market.toString());
|
||||
if (newOoAccounts.get(oo.market.toString())) {
|
||||
newOoAccounts.get(oo.market.toString()).push(oo);
|
||||
} else {
|
||||
newOoAccounts.set(oo.market.toString(), [oo]);
|
||||
}
|
||||
});
|
||||
if (markets.size > 100) {
|
||||
// Punt request chunking until there's user demand.
|
||||
throw new Error(
|
||||
"Too many markets. Please file an issue to update this"
|
||||
);
|
||||
}
|
||||
const marketAccounts = (
|
||||
await anchor.utils.getMultipleAccounts(
|
||||
provider.connection,
|
||||
// @ts-ignore
|
||||
[...markets].map((m) => new PublicKey(m))
|
||||
)
|
||||
).map((programAccount) => {
|
||||
return {
|
||||
publicKey: programAccount?.publicKey,
|
||||
account: new Market(
|
||||
Market.getLayout(DEX_PID).decode(programAccount?.account.data),
|
||||
-1, // Not used so don't bother fetching.
|
||||
-1, // Not used so don't bother fetching.
|
||||
provider.opts,
|
||||
DEX_PID
|
||||
),
|
||||
};
|
||||
});
|
||||
setMarketCache((marketCache) => {
|
||||
const newMarketCache = new Map(marketCache);
|
||||
marketAccounts.forEach((m) => {
|
||||
newMarketCache.set(m.publicKey!.toString(), m.account);
|
||||
});
|
||||
return newMarketCache;
|
||||
});
|
||||
setOoAccounts(newOoAccounts);
|
||||
});
|
||||
}, [provider.connection, provider.wallet.publicKey, provider.opts]);
|
||||
return (
|
||||
<_DexContext.Provider
|
||||
value={{
|
||||
openOrders: ooAccounts,
|
||||
marketCache,
|
||||
setMarketCache,
|
||||
provider,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</_DexContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useOpenOrders(): Map<string, Array<OpenOrders>> {
|
||||
const ctx = useContext(_DexContext);
|
||||
if (ctx === null) {
|
||||
throw new Error("Context not available");
|
||||
}
|
||||
return ctx.openOrders;
|
||||
}
|
||||
|
||||
export function useMarket(market: PublicKey): Market | undefined {
|
||||
const ctx = useContext(_DexContext);
|
||||
if (ctx === null) {
|
||||
throw new Error("Context not available");
|
||||
}
|
||||
|
||||
const asyncMarket = useAsync(async () => {
|
||||
if (ctx.marketCache.get(market.toString())) {
|
||||
return ctx.marketCache.get(market.toString());
|
||||
}
|
||||
const marketClient = await Market.load(
|
||||
ctx.provider.connection,
|
||||
market,
|
||||
undefined,
|
||||
DEX_PID
|
||||
);
|
||||
|
||||
let cache = new Map(ctx.marketCache);
|
||||
cache.set(market.toString(), marketClient);
|
||||
ctx.setMarketCache(cache);
|
||||
|
||||
return marketClient;
|
||||
}, [ctx.provider.connection, market]);
|
||||
|
||||
if (asyncMarket.result) {
|
||||
return asyncMarket.result;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import { useAsync } from "react-async-hook";
|
||||
import { Provider } from "@project-serum/anchor";
|
||||
import { PublicKey, Account } from "@solana/web3.js";
|
||||
import { MintInfo, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
|
||||
const _MintContext = React.createContext<null | MintContext>(null);
|
||||
type MintContext = {
|
||||
mintCache: Map<string, MintInfo>;
|
||||
setMintCache: (m: Map<string, MintInfo>) => void;
|
||||
provider: Provider;
|
||||
};
|
||||
|
||||
export function MintContextProvider(props: any) {
|
||||
const provider = props.provider;
|
||||
const [mintCache, setMintCache] = useState(new Map<string, MintInfo>());
|
||||
|
||||
return (
|
||||
<_MintContext.Provider
|
||||
value={{
|
||||
mintCache,
|
||||
setMintCache,
|
||||
provider,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</_MintContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useMint(mint?: PublicKey): MintInfo | undefined | null {
|
||||
const ctx = useContext(_MintContext);
|
||||
if (ctx === null) {
|
||||
throw new Error("Mint context not found");
|
||||
}
|
||||
|
||||
// Lazy load the mint account if needeed.
|
||||
const asyncMintInfo = useAsync(async () => {
|
||||
if (!mint) {
|
||||
return undefined;
|
||||
}
|
||||
if (ctx.mintCache.get(mint.toString())) {
|
||||
return ctx.mintCache.get(mint.toString());
|
||||
}
|
||||
const mintClient = new Token(
|
||||
ctx.provider.connection,
|
||||
mint,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new Account()
|
||||
);
|
||||
const mintInfo = await mintClient.getMintInfo();
|
||||
|
||||
let cache = new Map(ctx.mintCache);
|
||||
cache.set(mint.toString(), mintInfo);
|
||||
ctx.setMintCache(cache);
|
||||
|
||||
return mintInfo;
|
||||
}, [ctx.provider.connection, mint]);
|
||||
|
||||
if (asyncMintInfo.result) {
|
||||
return asyncMintInfo.result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import { Swap as SwapClient } from "@project-serum/swap";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { MintInfo } from "@solana/spl-token";
|
||||
|
||||
const SRM_MINT = new PublicKey("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt");
|
||||
export const USDC_MINT = new PublicKey(
|
||||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
);
|
||||
export const USDT_MINT = new PublicKey(
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||||
);
|
||||
|
||||
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);
|
||||
// Percent units.
|
||||
const [slippage, setSlippage] = useState(0.5);
|
||||
|
||||
const swapToFromMints = () => {
|
||||
const oldFrom = fromMint;
|
||||
const oldFromAmount = fromAmount;
|
||||
const oldTo = toMint;
|
||||
const oldToAmount = toAmount;
|
||||
setFromMint(oldTo);
|
||||
setToMint(oldFrom);
|
||||
_setFromAmount(oldToAmount);
|
||||
_setToAmount(oldFromAmount);
|
||||
};
|
||||
|
||||
const setFromAmount = (amount: number) => {
|
||||
_setFromAmount(amount);
|
||||
};
|
||||
|
||||
const setToAmount = (amount: number) => {
|
||||
_setToAmount(amount);
|
||||
};
|
||||
|
||||
return (
|
||||
<_SwapContext.Provider
|
||||
value={{
|
||||
swapClient,
|
||||
fromMint,
|
||||
setFromMint,
|
||||
toMint,
|
||||
setToMint,
|
||||
fromAmount,
|
||||
setFromAmount,
|
||||
toAmount,
|
||||
setToAmount,
|
||||
swapToFromMints,
|
||||
slippage,
|
||||
setSlippage,
|
||||
}}
|
||||
>
|
||||
{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;
|
||||
swapToFromMints: () => void;
|
||||
fromMintAccount?: MintInfo;
|
||||
toMintAccount?: MintInfo;
|
||||
slippage: number;
|
||||
setSlippage: (n: number) => void;
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { AccountInfo as TokenAccount } from "@solana/spl-token";
|
||||
import { getOwnedTokenAccounts } from "../../utils/tokens";
|
||||
|
||||
const _TokenContext = React.createContext<TokenContext | null>(null);
|
||||
|
||||
export function TokenContextProvider(props: any) {
|
||||
const provider = props.provider;
|
||||
const [ownedTokenAccounts, setOwnedTokenAccounts] = useState(undefined);
|
||||
|
||||
// Fetch all the owned token accounts for the wallet.
|
||||
useEffect(() => {
|
||||
getOwnedTokenAccounts(provider.connection, provider.wallet.publicKey).then(
|
||||
setOwnedTokenAccounts
|
||||
);
|
||||
}, [provider.wallet.publicKey, provider.connection]);
|
||||
|
||||
return (
|
||||
<_TokenContext.Provider
|
||||
value={{
|
||||
ownedTokenAccounts,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</_TokenContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export type TokenContext = {
|
||||
ownedTokenAccounts:
|
||||
| { publicKey: PublicKey; account: TokenAccount }[]
|
||||
| undefined;
|
||||
};
|
||||
|
||||
// Null => none exists.
|
||||
// Undefined => loading.
|
||||
export function useOwnedTokenAccount(
|
||||
mint: PublicKey
|
||||
): { publicKey: PublicKey; account: TokenAccount } | null | undefined {
|
||||
const ctx = useContext(_TokenContext);
|
||||
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 < b.account.amount
|
||||
? -1
|
||||
: a.account.amount > b.account.amount
|
||||
? 1
|
||||
: 0
|
||||
);
|
||||
return tokenAccounts[0];
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React, { useContext } from "react";
|
||||
import { TokenListContainer, TokenInfo } from "@solana/spl-token-registry";
|
||||
|
||||
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();
|
||||
}
|
Loading…
Reference in New Issue