Route kind access control

This commit is contained in:
armaniferrante 2021-05-16 15:52:38 -07:00
parent 6aa831d298
commit ede30702c5
No known key found for this signature in database
GPG Key ID: 58BEF301E91F7828
6 changed files with 118 additions and 40 deletions

View File

@ -10,7 +10,7 @@
"@project-serum/anchor": "^0.5.1-beta.2",
"@project-serum/serum": "^0.13.34",
"@project-serum/sol-wallet-adapter": "^0.2.0",
"@project-serum/swap": "^0.1.0-alpha.5",
"@project-serum/swap": "^0.1.0-alpha.7",
"@solana/spl-token": "^0.1.4",
"@solana/spl-token-registry": "^0.2.86",
"@solana/web3.js": "^1.10.1",

View File

@ -17,11 +17,17 @@ import {
DexContextProvider,
useDexContext,
useOpenOrders,
useRoute,
useRouteVerbose,
useMarket,
} from "./context/Dex";
import { MintContextProvider, useMint } from "./context/Mint";
import { TokenListContextProvider, useTokenMap } from "./context/TokenList";
import {
TokenListContextProvider,
useTokenMap,
useTokenListContext,
SPL_REGISTRY_SOLLET_TAG,
SPL_REGISTRY_WORM_TAG,
} from "./context/TokenList";
import { TokenContextProvider, useOwnedTokenAccount } from "./context/Token";
import TokenDialog from "./TokenDialog";
import { SettingsButton } from "./Settings";
@ -59,10 +65,18 @@ export default function Swap({
style,
provider,
tokenList,
fromMint,
toMint,
fromAmount,
toAmount,
}: {
style?: any;
provider: Provider;
tokenList: TokenListContainer;
fromMint?: PublicKey;
toMint?: PublicKey;
fromAmount?: number;
toAmount?: number;
style?: any;
}) {
const swapClient = new SwapClient(provider, tokenList);
return (
@ -70,7 +84,12 @@ export default function Swap({
<MintContextProvider provider={provider}>
<TokenContextProvider provider={provider}>
<DexContextProvider swapClient={swapClient}>
<SwapContextProvider>
<SwapContextProvider
fromMint={fromMint}
toMint={toMint}
fromAmount={fromAmount}
toAmount={toAmount}
>
<SwapCard style={style} />
</SwapContextProvider>
</DexContextProvider>
@ -269,16 +288,48 @@ function SwapButton() {
const fromMintInfo = useMint(fromMint);
const toMintInfo = useMint(toMint);
const openOrders = useOpenOrders();
const route = useRoute(fromMint, toMint);
const fromMarket = useMarket(route ? route[0] : undefined);
const toMarket = useMarket(route ? route[1] : undefined);
const route = useRouteVerbose(fromMint, toMint);
const fromMarket = useMarket(
route && route.markets ? route.markets[0] : undefined
);
const toMarket = useMarket(
route && route.markets ? route.markets[1] : undefined
);
const { wormholeMap, solletMap } = useTokenListContext();
// True iff the button should be activated.
const enabled =
// Mints are distinct.
fromMint.equals(toMint) === false &&
// Wallet is connected.
swapClient.program.provider.wallet.publicKey !== null &&
// Trade amounts greater than zero.
fromAmount > 0 &&
toAmount > 0 &&
// Trade route exists.
route !== null &&
// Wormhole <-> native markets must have the wormhole token as the
// *from* address since they're one-sided markets.
(route.kind !== "wormhole-native" ||
wormholeMap
.get(fromMint.toString())
?.tags?.includes(SPL_REGISTRY_WORM_TAG) !== undefined) &&
// Wormhole <-> sollet markets must have the sollet token as the
// *from* address since they're one sided markets.
(route.kind !== "wormhole-sollet" ||
solletMap
.get(fromMint.toString())
?.tags?.includes(SPL_REGISTRY_SOLLET_TAG) !== undefined);
const sendSwapTransaction = async () => {
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
}
const amount = new BN(fromAmount).muln(10 ** fromMintInfo.decimals);
const minExpectedSwapAmount = new BN(toAmount * 10 ** toMintInfo.decimals)
const amount = new BN(fromAmount).mul(
new BN(10).pow(new BN(fromMintInfo.decimals))
);
const minExpectedSwapAmount = new BN(toAmount)
.mul(new BN(10).pow(new BN(toMintInfo.decimals)))
.muln(100 - slippage)
.divn(100);
const fromOpenOrders = fromMarket
@ -307,9 +358,7 @@ function SwapButton() {
variant="contained"
className={styles.swapButton}
onClick={sendSwapTransaction}
disabled={
swapClient.program.provider.wallet.publicKey === null || route === null
}
disabled={!enabled}
>
Swap
</Button>

View File

@ -272,6 +272,17 @@ export function useFairRoute(
return toFair / fromFair;
}
export function useRoute(
fromMint: PublicKey,
toMint: PublicKey
): Array<PublicKey> | null {
const route = useRouteVerbose(fromMint, toMint);
if (route === null) {
return null;
}
return route.markets;
}
// Types of routes.
//
// 1. Direct trades on USDC quoted markets.
@ -279,24 +290,30 @@ export function useFairRoute(
// 3. Wormhole <-> Sollet one-to-one swap markets.
// 4. Wormhole <-> Native one-to-one swap markets.
//
export function useRoute(
export function useRouteVerbose(
fromMint: PublicKey,
toMint: PublicKey
): Array<PublicKey> | null {
): { markets: Array<PublicKey>; kind: RouteKind } | null {
const { swapClient } = useDexContext();
const { wormholeMap, solletMap } = useTokenListContext();
const asyncRoute = useAsync(async () => {
const wormholeMarket = await wormholeSwapMarket(
const swapMarket = await wormholeSwapMarket(
swapClient.program.provider.connection,
fromMint,
toMint,
wormholeMap,
solletMap
);
if (wormholeMarket !== null) {
return [wormholeMarket];
if (swapMarket !== null) {
const [wormholeMarket, kind] = swapMarket;
return { markets: [wormholeMarket], kind };
}
return swapClient.route(fromMint, toMint);
const markets = swapClient.route(fromMint, toMint);
if (markets === null) {
return null;
}
const kind: RouteKind = "usdx";
return { markets, kind };
}, [fromMint, toMint, swapClient]);
if (asyncRoute.result) {
@ -312,6 +329,8 @@ type Orderbook = {
// Wormhole utils.
type RouteKind = "wormhole-native" | "wormhole-sollet" | "usdx";
// Maps fromMint || toMint (in sort order) to swap market public key.
// All markets for wormhole<->native tokens should be here, e.g.
// USDC <-> wUSDC.
@ -326,31 +345,35 @@ function wormKey(fromMint: PublicKey, toMint: PublicKey): string {
return first.toString() + second.toString();
}
function wormholeNativeMarket(
fromMint: PublicKey,
toMint: PublicKey
): PublicKey | undefined {
return WORMHOLE_NATIVE_MAP.get(wormKey(fromMint, toMint));
}
async function wormholeSwapMarket(
conn: Connection,
fromMint: PublicKey,
toMint: PublicKey,
wormholeMap: Map<string, TokenInfo>,
solletMap: Map<string, TokenInfo>
): Promise<PublicKey | null> {
): Promise<[PublicKey, RouteKind] | null> {
let market = wormholeNativeMarket(fromMint, toMint);
if (market !== undefined) {
return market;
if (market !== null) {
return [market, "wormhole-native"];
}
return await wormholeSolletMarket(
market = await wormholeSolletMarket(
conn,
fromMint,
toMint,
wormholeMap,
solletMap
);
if (market === null) {
return null;
}
return [market, "wormhole-sollet"];
}
function wormholeNativeMarket(
fromMint: PublicKey,
toMint: PublicKey
): PublicKey | null {
return WORMHOLE_NATIVE_MAP.get(wormKey(fromMint, toMint)) ?? null;
}
// Returns the market address of the 1-1 sollet<->wormhole swap market if it

View File

@ -45,10 +45,10 @@ export type SwapContext = {
const _SwapContext = React.createContext<null | SwapContext>(null);
export function SwapContextProvider(props: any) {
const [fromMint, setFromMint] = useState(SRM_MINT);
const [toMint, setToMint] = useState(USDC_MINT);
const [fromAmount, _setFromAmount] = useState(0);
const [toAmount, _setToAmount] = useState(0);
const [fromMint, setFromMint] = useState(props.fromMint ?? SRM_MINT);
const [toMint, setToMint] = useState(props.toMint ?? USDC_MINT);
const [fromAmount, _setFromAmount] = useState(props.fromAmount ?? 0);
const [toAmount, _setToAmount] = useState(props.toAmount ?? 0);
const [isClosingNewAccounts, setIsClosingNewAccounts] = useState(false);
// Percent units.
const [slippage, setSlippage] = useState(DEFAULT_SLIPPAGE_PERCENT);

View File

@ -12,6 +12,12 @@ type TokenListContext = {
};
const _TokenListContext = React.createContext<null | TokenListContext>(null);
// Tag in the spl-token-registry for sollet wrapped tokens.
export const SPL_REGISTRY_SOLLET_TAG = "wrapped-sollet";
// Tag in the spl-token-registry for wormhole wrapped tokens.
export const SPL_REGISTRY_WORM_TAG = "wormhole";
export function TokenListContextProvider(props: any) {
const tokenList = useMemo(
() => props.tokenList.filterByClusterSlug("mainnet-beta").getList(),
@ -50,7 +56,7 @@ export function TokenListContextProvider(props: any) {
// Sollet wrapped tokens.
const [swappableTokensSollet, solletMap] = useMemo(() => {
const tokens = tokenList.filter((t: TokenInfo) => {
const isSollet = t.tags?.includes("wrapped-sollet");
const isSollet = t.tags?.includes(SPL_REGISTRY_SOLLET_TAG);
return isSollet;
});
tokens.sort((a: TokenInfo, b: TokenInfo) =>
@ -65,7 +71,7 @@ export function TokenListContextProvider(props: any) {
// Wormhole wrapped tokens.
const [swappableTokensWormhole, wormholeMap] = useMemo(() => {
const tokens = tokenList.filter((t: TokenInfo) => {
const isSollet = t.tags?.includes("wormhole");
const isSollet = t.tags?.includes(SPL_REGISTRY_WORM_TAG);
return isSollet;
});
tokens.sort((a: TokenInfo, b: TokenInfo) =>

View File

@ -1588,10 +1588,10 @@
bs58 "^4.0.1"
eventemitter3 "^4.0.4"
"@project-serum/swap@^0.1.0-alpha.5":
version "0.1.0-alpha.5"
resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.5.tgz#106fdf5354c3c17f1832ab623122739fd45e2e52"
integrity sha512-ZJW9XNlZyhIq/C8pwKhFvf7duKth8dfu7vkgVtfUp281dBgSXx6IwrigpYcJ9x/VZMr9LMrkUVW1LiXd8XZdEQ==
"@project-serum/swap@^0.1.0-alpha.7":
version "0.1.0-alpha.7"
resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.7.tgz#82bdd06e57814b9a42cf127c26b53bfc3b48438b"
integrity sha512-oZU9bA0znbIcxCKM1sxjOjxHCV1n5iPNowbYhtmsbhH6DczgjIsPO3gGJ00TJOBPkOn7gdtynpDAhGar3tSBPw==
dependencies:
"@project-serum/anchor" "^0.5.1-beta.2"
"@project-serum/serum" "^0.13.34"