Route kind access control
This commit is contained in:
parent
6aa831d298
commit
ede30702c5
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue