Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added common token bases #88

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ function AppInner() {
const [isConnected, setIsConnected] = useState(false);
const [tokenList, setTokenList] = useState<TokenListContainer | null>(null);

let commonBases: PublicKey[] = [
new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"),
new PublicKey("So11111111111111111111111111111111111111112"),
new PublicKey("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"),
];

const [provider, wallet] = useMemo(() => {
const opts: ConfirmOptions = {
preflightCommitment: "recent",
Expand Down Expand Up @@ -103,7 +110,7 @@ function AppInner() {
return (
<Grid
container
justify="center"
justifyContent="center"
alignItems="center"
className={styles.root}
>
Expand All @@ -114,7 +121,13 @@ function AppInner() {
>
{!isConnected ? "Connect" : "Disconnect"}
</Button>
{tokenList && <Swap provider={provider} tokenList={tokenList} />}
{tokenList && (
<Swap
provider={provider}
tokenList={tokenList}
commonBases={commonBases}
/>
)}
</Grid>
);
}
Expand Down
18 changes: 12 additions & 6 deletions src/components/Swap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useMemo } from "react";
import {
PublicKey,
Keypair,
Expand Down Expand Up @@ -222,6 +222,16 @@ export function SwapTokenForm({
})
: amount;

const tokenDialog = useMemo(() => {
return (
<TokenDialog
setMint={setMint}
open={showTokenDialog}
onClose={() => setShowTokenDialog(false)}
/>
);
}, [showTokenDialog]);

return (
<div className={styles.swapTokenFormContainer} style={style}>
<div className={styles.swapTokenSelectorContainer}>
Expand Down Expand Up @@ -252,11 +262,7 @@ export function SwapTokenForm({
},
}}
/>
<TokenDialog
setMint={setMint}
open={showTokenDialog}
onClose={() => setShowTokenDialog(false)}
/>
{tokenDialog}
</div>
);
}
Expand Down
90 changes: 82 additions & 8 deletions src/components/TokenDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import {
TextField,
List,
ListItem,
ListSubheader,
Typography,
Chip,
Avatar,
Tabs,
Tab,
} from "@material-ui/core";
import { StarOutline, Star } from "@material-ui/icons";
import { TokenIcon } from "./Swap";
import { useSwappableTokens } from "../context/TokenList";
import { useSwappableTokens, useTokenBase } from "../context/TokenList";
import { useMediaQuery } from "@material-ui/core";

const useStyles = makeStyles((theme) => ({
Expand Down Expand Up @@ -54,6 +58,7 @@ export default function TokenDialog({
const styles = useStyles();
const { swappableTokens, swappableTokensSollet, swappableTokensWormhole } =
useSwappableTokens();
const { tokenBase, addNewBase, tokenBaseMap, removeBase } = useTokenBase();
const displayTabs = !useMediaQuery("(max-width:450px)");
const selectedTokens =
tabSelection === 0
Expand Down Expand Up @@ -97,6 +102,17 @@ export default function TokenDialog({
</DialogTitle>
<DialogContent className={styles.dialogContent} dividers={true}>
<List disablePadding>
{tokenBase?.length != 0 && (
<ListSubheader style={{ backgroundColor: "white" }}>
<CommonBases
commonTokenBases={tokenBase}
onClick={(mint) => {
setMint(mint);
onClose();
}}
/>
</ListSubheader>
)}
{tokens.map((tokenInfo: TokenInfo) => (
<TokenListItem
key={tokenInfo.address}
Expand All @@ -105,6 +121,15 @@ export default function TokenDialog({
setMint(mint);
onClose();
}}
addNewBase={(token) => {
addNewBase(token);
}}
isCommonBase={
tokenBaseMap.get(tokenInfo.address.toString()) ? true : false
}
removeBase={(token) => {
removeBase(token);
}}
/>
))}
</List>
Expand Down Expand Up @@ -146,19 +171,41 @@ export default function TokenDialog({
function TokenListItem({
tokenInfo,
onClick,
addNewBase,
removeBase,
isCommonBase,
}: {
tokenInfo: TokenInfo;
onClick: (mint: PublicKey) => void;
addNewBase: (token: TokenInfo) => void;
removeBase: (token: TokenInfo) => void;
isCommonBase: Boolean;
}) {
const mint = new PublicKey(tokenInfo.address);
return (
<ListItem
button
onClick={() => onClick(mint)}
style={{ padding: "10px 20px" }}
>
<TokenIcon mint={mint} style={{ width: "30px", borderRadius: "15px" }} />
<TokenName tokenInfo={tokenInfo} />
<ListItem>
<div
onClick={() => onClick(mint)}
style={{
padding: "10px 20px",
display: "flex",
cursor: "pointer",
width: "100%",
}}
>
<TokenIcon
mint={mint}
style={{ width: "30px", borderRadius: "15px" }}
/>
<TokenName tokenInfo={tokenInfo} />
</div>
<Chip
variant="outlined"
label={isCommonBase ? <Star /> : <StarOutline />}
onClick={() =>
isCommonBase ? removeBase(tokenInfo) : addNewBase(tokenInfo)
}
/>
</ListItem>
);
}
Expand All @@ -175,3 +222,30 @@ function TokenName({ tokenInfo }: { tokenInfo: TokenInfo }) {
</div>
);
}

function CommonBases({
commonTokenBases,
onClick,
}: {
commonTokenBases: TokenInfo[] | undefined;
onClick: (mint: PublicKey) => void;
}) {
return (
<div style={{ padding: "0 20px 20px 20px", position: "sticky" }}>
<h4 style={{ margin: 0 }}>Common bases</h4>
{commonTokenBases?.map((tokenInfo: TokenInfo) => {
const mint = new PublicKey(tokenInfo.address);
return (
<Chip
key={tokenInfo.address}
avatar={<Avatar alt={tokenInfo?.name} src={tokenInfo?.logoURI} />}
variant="outlined"
label={tokenInfo?.symbol}
onClick={() => onClick(mint)}
style={{ margin: "0 1px" }}
/>
);
})}
</div>
);
}
92 changes: 90 additions & 2 deletions src/context/TokenList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, { useContext, useMemo } from "react";
import React, { useContext, useMemo, useState, useEffect } from "react";
import { TokenInfo } from "@solana/spl-token-registry";
import { SOL_MINT } from "../utils/pubkeys";
import { PublicKey } from "@solana/web3.js";
import { LocalStorage } from "../utils/localStorage";

interface TokenCommonBaseInfo extends TokenInfo {
isCommonBase: boolean;
}

type TokenListContext = {
tokenMap: Map<string, TokenInfo>;
Expand All @@ -9,6 +15,10 @@ type TokenListContext = {
swappableTokens: TokenInfo[];
swappableTokensSollet: TokenInfo[];
swappableTokensWormhole: TokenInfo[];
tokenBase: TokenInfo[] | undefined;
addNewBase: (token: TokenInfo) => void;
removeBase: (token: TokenInfo) => void;
tokenBaseMap: Map<string, TokenCommonBaseInfo>;
};
const _TokenListContext = React.createContext<null | TokenListContext>(null);

Expand Down Expand Up @@ -97,6 +107,65 @@ export function TokenListContextProvider(props: any) {
];
}, [tokenList]);

let [tokenBase, setTokenBase] = useState<TokenCommonBaseInfo[] | undefined>(
undefined
);
let [addrList, setValues, removeValue] = LocalStorage("swapui-common-bases");

// Common token bases
useEffect(() => {
if (addrList == null) {
addrList = props.commonBases ?? [];
}
if (props.commonBases) {
props.commonBases.forEach((add: any) => setValues(add.toString()));
addrList.concat(props.commonBases);
}
addrList = addrList.map((e: string) => new PublicKey(e.toString()));
const cb = addrList?.map((add: PublicKey) => {
const token = tokenMap.get(add.toString());
token.isCommonBase = true;
setValues(token.address);
return token;
});
setTokenBase(cb);
return cb;
}, [props.commonBases]);

const addNewBase = (token: TokenInfo) => {
// Check if token already a common base
if (
tokenBase?.some((t) => token.address.toString() === t.address.toString())
) {
return;
}
const c: TokenCommonBaseInfo = { ...token, isCommonBase: true };
setValues(token.address);
setTokenBase((prevState) => [...(prevState as TokenCommonBaseInfo[]), c]);
};

const removeBase = (token: TokenInfo) => {
const index =
tokenBase?.findIndex(
(t) => token.address.toString() === t.address.toString()
) ?? -1;
// return if not found
if (index == -1) return;
const tempTokenBase = tokenBase?.slice();
tempTokenBase?.splice(index, 1);
setTokenBase(tempTokenBase);
removeValue(index);
};

// Token map for quick lookup.
const tokenBaseMap = useMemo(() => {
const tokenBaseMap = new Map();
tokenBase?.forEach((t: TokenCommonBaseInfo) => {
tokenBaseMap.set(t.address, t);
});
return tokenBaseMap;
}, [tokenBase]);

return (
<_TokenListContext.Provider
value={{
Expand All @@ -106,6 +175,10 @@ export function TokenListContextProvider(props: any) {
swappableTokens,
swappableTokensWormhole,
swappableTokensSollet,
tokenBase,
addNewBase,
removeBase,
tokenBaseMap,
}}
>
{props.children}
Expand All @@ -129,5 +202,20 @@ export function useTokenMap(): Map<string, TokenInfo> {
export function useSwappableTokens() {
const { swappableTokens, swappableTokensWormhole, swappableTokensSollet } =
useTokenListContext();
return { swappableTokens, swappableTokensWormhole, swappableTokensSollet };
return {
swappableTokens,
swappableTokensWormhole,
swappableTokensSollet,
};
}

export function useTokenBase() {
const { tokenBase, addNewBase, tokenBaseMap, removeBase } =
useTokenListContext();
return {
tokenBase,
addNewBase,
tokenBaseMap,
removeBase,
};
}
12 changes: 9 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TokenListContainer } from "@solana/spl-token-registry";
import { Provider } from "@project-serum/anchor";
import { Swap as SwapClient } from "@project-serum/swap";
import {
createMuiTheme,
createTheme,
ThemeOptions,
ThemeProvider,
} from "@material-ui/core/styles";
Expand Down Expand Up @@ -52,6 +52,7 @@ export default function Swap(props: SwapProps): ReactElement {
materialTheme,
provider,
tokenList,
commonBases,
fromMint,
toMint,
fromAmount,
Expand All @@ -61,7 +62,7 @@ export default function Swap(props: SwapProps): ReactElement {

// @ts-ignore
const swapClient = new SwapClient(provider, tokenList);
const theme = createMuiTheme(
const theme = createTheme(
materialTheme || {
palette: {
primary: {
Expand All @@ -80,7 +81,7 @@ export default function Swap(props: SwapProps): ReactElement {
);
return (
<ThemeProvider theme={theme}>
<TokenListContextProvider tokenList={tokenList}>
<TokenListContextProvider tokenList={tokenList} commonBases={commonBases}>
<TokenContextProvider provider={provider}>
<DexContextProvider swapClient={swapClient}>
<SwapContextProvider
Expand Down Expand Up @@ -118,6 +119,11 @@ export type SwapProps = {
*/
tokenList: TokenListContainer;

/**
* List of token address that should show up as common base tokens
*/
commonBases?: PublicKey[];

/**
* Wallet address to which referral fees are sent (i.e. a SOL address).
* To receive referral fees, the wallet must *own* associated token
Expand Down
Loading