diff --git a/package.json b/package.json index 4013595..59641d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@project-serum/swap-ui", - "version": "0.1.9", + "version": "0.1.10", "main": "dist/index.js", "types": "dist/index.d.ts", "homepage": "https://github.com/project-serum/swap-ui", diff --git a/src/components/Swap.tsx b/src/components/Swap.tsx index 8dd15b3..c9ddd11 100644 --- a/src/components/Swap.tsx +++ b/src/components/Swap.tsx @@ -31,7 +31,7 @@ import { useCanSwap, useReferral } from "../context/Swap"; import TokenDialog from "./TokenDialog"; import { SettingsButton } from "./Settings"; import { InfoLabel } from "./Info"; -import { WRAPPED_SOL_MINT } from "../utils/pubkeys"; +import { SOL_MINT, WRAPPED_SOL_MINT } from "../utils/pubkeys"; const useStyles = makeStyles((theme) => ({ card: { @@ -364,11 +364,10 @@ export function SwapButton() { let txs: { tx: Transaction; signers: Array }[] = []; const amount = new BN(fromAmount * 10 ** fromMintInfo.decimals); - const isSol = - fromMint.equals(WRAPPED_SOL_MINT) || toMint.equals(WRAPPED_SOL_MINT); + const isSol = fromMint.equals(SOL_MINT) || toMint.equals(SOL_MINT); const wrappedSolAccount = isSol ? Keypair.generate() : undefined; - // Wrap the SOL into an SPL token. + // Wrap the SOL into wrapped SOL. if (isSol) { txs.push( await wrapSol( @@ -401,12 +400,12 @@ export function SwapButton() { const toOpenOrders = toMarket ? openOrders.get(toMarket?.address.toString()) : undefined; - const fromWalletAddr = fromMint.equals(WRAPPED_SOL_MINT) + const fromWalletAddr = fromMint.equals(SOL_MINT) ? wrappedSolAccount!.publicKey : fromWallet ? fromWallet.publicKey : undefined; - const toWalletAddr = toMint.equals(WRAPPED_SOL_MINT) + const toWalletAddr = toMint.equals(SOL_MINT) ? wrappedSolAccount!.publicKey : toWallet ? toWallet.publicKey @@ -478,7 +477,7 @@ async function wrapSol( ); // Transfer lamports. These will be converted to an SPL balance by the // token program. - if (fromMint.equals(WRAPPED_SOL_MINT)) { + if (fromMint.equals(SOL_MINT)) { tx.add( SystemProgram.transfer({ fromPubkey: provider.wallet.publicKey, diff --git a/src/context/Dex.tsx b/src/context/Dex.tsx index 000eacb..3f7508e 100644 --- a/src/context/Dex.tsx +++ b/src/context/Dex.tsx @@ -15,6 +15,8 @@ import { DEX_PID, USDC_MINT, USDT_MINT, + SOL_MINT, + WRAPPED_SOL_MINT, WORM_USDC_MINT, WORM_USDT_MINT, WORM_USDC_MARKET, @@ -389,7 +391,11 @@ export function useFairRoute( if (fromMarket === undefined) { return undefined; } - if (fromMarket?.baseMintAddress.equals(fromMint)) { + if ( + fromMarket?.baseMintAddress.equals(fromMint) || + (fromMarket?.baseMintAddress.equals(WRAPPED_SOL_MINT) && + fromMint.equals(SOL_MINT)) + ) { return fromBbo.bestBid && 1.0 / fromBbo.bestBid; } else { return fromBbo.bestOffer && fromBbo.bestOffer; @@ -442,7 +448,10 @@ export function useRouteVerbose( const [wormholeMarket, kind] = swapMarket; return { markets: [wormholeMarket], kind }; } - const markets = swapClient.route(fromMint, toMint); + const markets = swapClient.route( + fromMint.equals(SOL_MINT) ? WRAPPED_SOL_MINT : fromMint, + toMint.equals(SOL_MINT) ? WRAPPED_SOL_MINT : toMint + ); if (markets === null) { return null; } diff --git a/src/context/Token.tsx b/src/context/Token.tsx index d2e3d3a..f1f22fd 100644 --- a/src/context/Token.tsx +++ b/src/context/Token.tsx @@ -10,7 +10,7 @@ import { TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { getOwnedTokenAccounts, parseTokenAccountData } from "../utils/tokens"; -import { WRAPPED_SOL_MINT } from "../utils/pubkeys"; +import { SOL_MINT } from "../utils/pubkeys"; export type TokenContext = { provider: Provider; @@ -47,7 +47,7 @@ export function TokenContextProvider(props: any) { // @ts-ignore account: { amount: new BN(acc.lamports), - mint: WRAPPED_SOL_MINT, + mint: SOL_MINT, }, }); setRefresh((r) => r + 1); @@ -95,7 +95,7 @@ export function useOwnedTokenAccount( ); let tokenAccount = tokenAccounts[0]; - const isSol = mint?.equals(WRAPPED_SOL_MINT); + const isSol = mint?.equals(SOL_MINT); // Stream updates when the balance changes. useEffect(() => { @@ -107,7 +107,7 @@ export function useOwnedTokenAccount( (info: { lamports: number }) => { const token = { amount: new BN(info.lamports), - mint: WRAPPED_SOL_MINT, + mint: SOL_MINT, } as TokenAccount; if (token.amount !== tokenAccount.account.amount) { const index = _OWNED_TOKEN_ACCOUNTS_CACHE.indexOf(tokenAccount); @@ -190,4 +190,7 @@ const _OWNED_TOKEN_ACCOUNTS_CACHE: Array<{ }> = []; // Cache storing all previously fetched mint infos. -const _MINT_CACHE = new Map>(); +// @ts-ignore +const _MINT_CACHE = new Map>([ + [SOL_MINT.toString(), { decimals: 9 }], +]); diff --git a/src/context/TokenList.tsx b/src/context/TokenList.tsx index 4e20d70..8a6bd54 100644 --- a/src/context/TokenList.tsx +++ b/src/context/TokenList.tsx @@ -1,6 +1,6 @@ import React, { useContext, useMemo } from "react"; import { TokenInfo } from "@solana/spl-token-registry"; -import { USDC_MINT, USDT_MINT } from "../utils/pubkeys"; +import { SOL_MINT } from "../utils/pubkeys"; type TokenListContext = { tokenMap: Map; @@ -18,11 +18,32 @@ 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"; +const SOL_TOKEN_INFO = { + chainId: 101, + address: SOL_MINT.toString(), + name: "Native SOL", + decimals: "9", + symbol: "SOL", + logoURI: + "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/solana/info/logo.png", + tags: [], + extensions: { + website: "https://solana.com/", + serumV3Usdc: "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + serumV3Usdt: "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + coingeckoId: "solana", + waterfallbot: "https://t.me/SOLwaterfall", + }, +}; + export function TokenListContextProvider(props: any) { - const tokenList = useMemo( - () => props.tokenList.filterByClusterSlug("mainnet-beta").getList(), - [props.tokenList] - ); + const tokenList = useMemo(() => { + const list = props.tokenList.filterByClusterSlug("mainnet-beta").getList(); + // Manually add a fake SOL mint for the native token. The component is + // opinionated in that it distinguishes between wrapped SOL and SOL. + list.push(SOL_TOKEN_INFO); + return list; + }, [props.tokenList]); // Token map for quick lookup. const tokenMap = useMemo(() => { diff --git a/src/utils/pubkeys.ts b/src/utils/pubkeys.ts index f99d308..d8e760c 100644 --- a/src/utils/pubkeys.ts +++ b/src/utils/pubkeys.ts @@ -16,6 +16,11 @@ export const USDT_MINT = new PublicKey( "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" ); +// Arbitrary mint to represent SOL (not wrapped SOL). +export const SOL_MINT = new PublicKey( + "Ejmc1UB4EsES5oAaRN63SpoxMJidt3ZGBrqrZk49vjTZ" +); + export const WRAPPED_SOL_MINT = new PublicKey( "So11111111111111111111111111111111111111112" );