Create associated token accounts and wrap SOL (#59)
This commit is contained in:
parent
c64caec9d5
commit
671225aec8
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@project-serum/swap-ui",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"homepage": "https://github.com/project-serum/swap-ui",
|
||||
|
@ -8,7 +8,7 @@
|
|||
"dependencies": {
|
||||
"@fontsource/roboto": "^4.3.0",
|
||||
"@project-serum/serum": "^0.13.34",
|
||||
"@project-serum/swap": "^0.1.0-alpha.28",
|
||||
"@project-serum/swap": "^0.1.0-alpha.31",
|
||||
"@solana/spl-token": "^0.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -42,7 +42,7 @@
|
|||
"@project-serum/anchor": "^0.7.0",
|
||||
"@project-serum/serum": "^0.13.34",
|
||||
"@project-serum/sol-wallet-adapter": "^0.2.0",
|
||||
"@project-serum/swap": "^0.1.0-alpha.28",
|
||||
"@project-serum/swap": "^0.1.0-alpha.31",
|
||||
"@solana/spl-token": "^0.1.4",
|
||||
"@solana/spl-token-registry": "^0.2.86",
|
||||
"@solana/web3.js": "^1.17.0",
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { useState } from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import {
|
||||
PublicKey,
|
||||
Keypair,
|
||||
Transaction,
|
||||
SystemProgram,
|
||||
Signer,
|
||||
} from "@solana/web3.js";
|
||||
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { BN, Provider } from "@project-serum/anchor";
|
||||
import {
|
||||
makeStyles,
|
||||
Card,
|
||||
|
@ -24,6 +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";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
card: {
|
||||
|
@ -334,9 +342,11 @@ export function SwapButton() {
|
|||
const canSwap = useCanSwap();
|
||||
const referral = useReferral(fromMarket);
|
||||
const fair = useSwapFair();
|
||||
const fromWallet = useOwnedTokenAccount(fromMint);
|
||||
const toWallet = useOwnedTokenAccount(toMint);
|
||||
const quoteMint = useMint(fromMarket && fromMarket.quoteMintAddress);
|
||||
let fromWallet = useOwnedTokenAccount(fromMint);
|
||||
let toWallet = useOwnedTokenAccount(toMint);
|
||||
const quoteMint = fromMarket && fromMarket.quoteMintAddress;
|
||||
const quoteMintInfo = useMint(quoteMint);
|
||||
const quoteWallet = useOwnedTokenAccount(quoteMint);
|
||||
|
||||
// Click handler.
|
||||
const sendSwapTransaction = async () => {
|
||||
|
@ -346,16 +356,43 @@ export function SwapButton() {
|
|||
if (!fair) {
|
||||
throw new Error("Invalid fair");
|
||||
}
|
||||
if (!quoteMint) {
|
||||
if (!quoteMint || !quoteMintInfo) {
|
||||
throw new Error("Quote mint not found");
|
||||
}
|
||||
|
||||
// All transactions to send for the swap.
|
||||
let txs: { tx: Transaction; signers: Array<Signer | undefined> }[] = [];
|
||||
const amount = new BN(fromAmount * 10 ** fromMintInfo.decimals);
|
||||
|
||||
const isSol =
|
||||
fromMint.equals(WRAPPED_SOL_MINT) || toMint.equals(WRAPPED_SOL_MINT);
|
||||
const wrappedSolAccount = isSol ? Keypair.generate() : undefined;
|
||||
|
||||
// Wrap the SOL into an SPL token.
|
||||
if (isSol) {
|
||||
txs.push(
|
||||
await wrapSol(
|
||||
swapClient.program.provider,
|
||||
wrappedSolAccount as Keypair,
|
||||
fromMint,
|
||||
amount
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Build the swap.
|
||||
txs.push(
|
||||
...(await (async () => {
|
||||
if (!fromMarket) {
|
||||
throw new Error("Market undefined");
|
||||
}
|
||||
|
||||
const minExchangeRate = {
|
||||
rate: new BN((10 ** toMintInfo.decimals * FEE_MULTIPLIER) / fair)
|
||||
.muln(100 - slippage)
|
||||
.divn(100),
|
||||
fromDecimals: fromMintInfo.decimals,
|
||||
quoteDecimals: quoteMint.decimals,
|
||||
quoteDecimals: quoteMintInfo.decimals,
|
||||
strict: isStrict,
|
||||
};
|
||||
const fromOpenOrders = fromMarket
|
||||
|
@ -364,23 +401,48 @@ export function SwapButton() {
|
|||
const toOpenOrders = toMarket
|
||||
? openOrders.get(toMarket?.address.toString())
|
||||
: undefined;
|
||||
await swapClient.swap({
|
||||
const fromWalletAddr = fromMint.equals(WRAPPED_SOL_MINT)
|
||||
? wrappedSolAccount!.publicKey
|
||||
: fromWallet
|
||||
? fromWallet.publicKey
|
||||
: undefined;
|
||||
const toWalletAddr = toMint.equals(WRAPPED_SOL_MINT)
|
||||
? wrappedSolAccount!.publicKey
|
||||
: toWallet
|
||||
? toWallet.publicKey
|
||||
: undefined;
|
||||
|
||||
return await swapClient.swapTxs({
|
||||
fromMint,
|
||||
toMint,
|
||||
fromWallet: fromWallet ? fromWallet.publicKey : undefined,
|
||||
toWallet: toWallet ? toWallet.publicKey : undefined,
|
||||
quoteMint,
|
||||
amount,
|
||||
minExchangeRate,
|
||||
referral,
|
||||
// Pass in the below parameters so that the client doesn't perform
|
||||
// wasteful network requests when we already have the data.
|
||||
fromMarket,
|
||||
toMarket,
|
||||
fromOpenOrders: fromOpenOrders ? fromOpenOrders[0].address : undefined,
|
||||
// Automatically created if undefined.
|
||||
fromOpenOrders: fromOpenOrders
|
||||
? fromOpenOrders[0].address
|
||||
: undefined,
|
||||
toOpenOrders: toOpenOrders ? toOpenOrders[0].address : undefined,
|
||||
fromWallet: fromWalletAddr,
|
||||
toWallet: toWalletAddr,
|
||||
quoteWallet: quoteWallet ? quoteWallet.publicKey : undefined,
|
||||
// Auto close newly created open orders accounts.
|
||||
close: isClosingNewAccounts,
|
||||
});
|
||||
})())
|
||||
);
|
||||
|
||||
// Unwrap the SOL.
|
||||
if (isSol) {
|
||||
txs.push(
|
||||
unwrapSol(swapClient.program.provider, wrappedSolAccount as Keypair)
|
||||
);
|
||||
}
|
||||
|
||||
await swapClient.program.provider.sendAll(txs);
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
|
@ -393,3 +455,63 @@ export function SwapButton() {
|
|||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
async function wrapSol(
|
||||
provider: Provider,
|
||||
wrappedSolAccount: Keypair,
|
||||
fromMint: PublicKey,
|
||||
amount: BN
|
||||
): Promise<{ tx: Transaction; signers: Array<Signer | undefined> }> {
|
||||
const tx = new Transaction();
|
||||
const signers = [wrappedSolAccount];
|
||||
// Create new, rent exempt account.
|
||||
tx.add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey: wrappedSolAccount.publicKey,
|
||||
lamports: await Token.getMinBalanceRentForExemptAccount(
|
||||
provider.connection
|
||||
),
|
||||
space: 165,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
})
|
||||
);
|
||||
// Transfer lamports. These will be converted to an SPL balance by the
|
||||
// token program.
|
||||
if (fromMint.equals(WRAPPED_SOL_MINT)) {
|
||||
tx.add(
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
toPubkey: wrappedSolAccount.publicKey,
|
||||
lamports: amount.toNumber(),
|
||||
})
|
||||
);
|
||||
}
|
||||
// Initialize the account.
|
||||
tx.add(
|
||||
Token.createInitAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
WRAPPED_SOL_MINT,
|
||||
wrappedSolAccount.publicKey,
|
||||
provider.wallet.publicKey
|
||||
)
|
||||
);
|
||||
return { tx, signers };
|
||||
}
|
||||
|
||||
function unwrapSol(
|
||||
provider: Provider,
|
||||
wrappedSolAccount: Keypair
|
||||
): { tx: Transaction; signers: Array<Signer | undefined> } {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
Token.createCloseAccountInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
wrappedSolAccount.publicKey,
|
||||
provider.wallet.publicKey,
|
||||
provider.wallet.publicKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
return { tx, signers: [] };
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ export function DexContextProvider(props: any) {
|
|||
mint.publicKey.equals(m.account.baseMintAddress)
|
||||
)[0];
|
||||
const quoteMintInfo = mintInfos.filter((mint) =>
|
||||
mint.publicKey.equals(m.account.baseMintAddress)
|
||||
mint.publicKey.equals(m.account.quoteMintAddress)
|
||||
)[0];
|
||||
assert.ok(baseMintInfo && quoteMintInfo);
|
||||
// @ts-ignore
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
SPL_REGISTRY_SOLLET_TAG,
|
||||
SPL_REGISTRY_WORM_TAG,
|
||||
} from "./TokenList";
|
||||
import { useOwnedTokenAccount } from "../context/Token";
|
||||
|
||||
const DEFAULT_SLIPPAGE_PERCENT = 0.5;
|
||||
|
||||
|
@ -180,6 +181,7 @@ export function useCanSwap(): boolean {
|
|||
const { fromMint, toMint, fromAmount, toAmount } = useSwapContext();
|
||||
const { swapClient } = useDexContext();
|
||||
const { wormholeMap, solletMap } = useTokenListContext();
|
||||
const fromWallet = useOwnedTokenAccount(fromMint);
|
||||
const fair = useSwapFair();
|
||||
const route = useRouteVerbose(fromMint, toMint);
|
||||
if (route === null) {
|
||||
|
@ -187,6 +189,9 @@ export function useCanSwap(): boolean {
|
|||
}
|
||||
|
||||
return (
|
||||
// From wallet exists.
|
||||
fromWallet !== undefined &&
|
||||
fromWallet !== null &&
|
||||
// Fair price is defined.
|
||||
fair !== undefined &&
|
||||
fair > 0 &&
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useContext, useState, useEffect } from "react";
|
||||
import * as assert from "assert";
|
||||
import { useAsync } from "react-async-hook";
|
||||
import { Provider } from "@project-serum/anchor";
|
||||
import { Provider, BN } from "@project-serum/anchor";
|
||||
import { PublicKey, Account } from "@solana/web3.js";
|
||||
import {
|
||||
MintInfo,
|
||||
|
@ -10,6 +10,7 @@ import {
|
|||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { getOwnedTokenAccounts, parseTokenAccountData } from "../utils/tokens";
|
||||
import { WRAPPED_SOL_MINT } from "../utils/pubkeys";
|
||||
|
||||
export type TokenContext = {
|
||||
provider: Provider;
|
||||
|
@ -27,6 +28,7 @@ export function TokenContextProvider(props: any) {
|
|||
setRefresh((r) => r + 1);
|
||||
return;
|
||||
}
|
||||
// Fetch SPL tokens.
|
||||
getOwnedTokenAccounts(provider.connection, provider.wallet.publicKey).then(
|
||||
(accs) => {
|
||||
if (accs) {
|
||||
|
@ -35,6 +37,22 @@ export function TokenContextProvider(props: any) {
|
|||
}
|
||||
}
|
||||
);
|
||||
// Fetch SOL balance.
|
||||
provider.connection
|
||||
.getAccountInfo(provider.wallet.publicKey)
|
||||
.then((acc: { lamports: number }) => {
|
||||
if (acc) {
|
||||
_OWNED_TOKEN_ACCOUNTS_CACHE.push({
|
||||
publicKey: provider.wallet.publicKey,
|
||||
// @ts-ignore
|
||||
account: {
|
||||
amount: new BN(acc.lamports),
|
||||
mint: WRAPPED_SOL_MINT,
|
||||
},
|
||||
});
|
||||
setRefresh((r) => r + 1);
|
||||
}
|
||||
});
|
||||
}, [provider.wallet.publicKey, provider.connection]);
|
||||
|
||||
return (
|
||||
|
@ -76,12 +94,32 @@ export function useOwnedTokenAccount(
|
|||
: 0
|
||||
);
|
||||
|
||||
const tokenAccount = tokenAccounts[0];
|
||||
let tokenAccount = tokenAccounts[0];
|
||||
const isSol = mint?.equals(WRAPPED_SOL_MINT);
|
||||
|
||||
// Stream updates when the balance changes.
|
||||
useEffect(() => {
|
||||
let listener: number;
|
||||
if (tokenAccount) {
|
||||
// SOL is special cased since it's not an SPL token.
|
||||
if (tokenAccount && isSol) {
|
||||
listener = provider.connection.onAccountChange(
|
||||
provider.wallet.publicKey,
|
||||
(info: { lamports: number }) => {
|
||||
const token = {
|
||||
amount: new BN(info.lamports),
|
||||
mint: WRAPPED_SOL_MINT,
|
||||
} as TokenAccount;
|
||||
if (token.amount !== tokenAccount.account.amount) {
|
||||
const index = _OWNED_TOKEN_ACCOUNTS_CACHE.indexOf(tokenAccount);
|
||||
assert.ok(index >= 0);
|
||||
_OWNED_TOKEN_ACCOUNTS_CACHE[index].account = token;
|
||||
setRefresh((r) => r + 1);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
// SPL tokens.
|
||||
else if (tokenAccount) {
|
||||
listener = provider.connection.onAccountChange(
|
||||
tokenAccount.publicKey,
|
||||
(info) => {
|
||||
|
@ -106,7 +144,7 @@ export function useOwnedTokenAccount(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (tokenAccounts.length === 0) {
|
||||
if (!isSol && tokenAccounts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,7 @@ export function TokenListContextProvider(props: any) {
|
|||
const tokens = tokenList.filter((t: TokenInfo) => {
|
||||
const isUsdxQuoted =
|
||||
t.extensions?.serumV3Usdt || t.extensions?.serumV3Usdc;
|
||||
const isSol = t.address === "So11111111111111111111111111111111111111112";
|
||||
return isUsdxQuoted && !isSol;
|
||||
return isUsdxQuoted;
|
||||
});
|
||||
tokens.sort((a: TokenInfo, b: TokenInfo) =>
|
||||
a.symbol < b.symbol ? -1 : a.symbol > b.symbol ? 1 : 0
|
||||
|
|
|
@ -16,6 +16,10 @@ export const USDT_MINT = new PublicKey(
|
|||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||||
);
|
||||
|
||||
export const WRAPPED_SOL_MINT = new PublicKey(
|
||||
"So11111111111111111111111111111111111111112"
|
||||
);
|
||||
|
||||
export const WORM_MARKET_BASE = new PublicKey(
|
||||
"6a9wpsZpZGxGhFVSQBpcTNjNjytdbSA1iUw1A5KNDxPw"
|
||||
);
|
||||
|
|
|
@ -1654,10 +1654,10 @@
|
|||
bs58 "^4.0.1"
|
||||
eventemitter3 "^4.0.4"
|
||||
|
||||
"@project-serum/swap@^0.1.0-alpha.28":
|
||||
version "0.1.0-alpha.28"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.28.tgz#523bb161c2d0011f4df0317ed31b77107f72ed06"
|
||||
integrity sha512-LJ6roKiK43JyAjjDNejXxyFurngXI+YVNdliTnedKp8MgJm6QFj2S8606xiD0Col3aXI6jq7Y6xw6CczHc1j4g==
|
||||
"@project-serum/swap@^0.1.0-alpha.31":
|
||||
version "0.1.0-alpha.31"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.31.tgz#0656c9959f7be18248731c6ec8025d7619d58b74"
|
||||
integrity sha512-LiepwTqC9+1PYF+Oce2VQJmiia8Li9ysRwbvbaW5+0B4ZmcXS1rc3fqrn8Xyi36XL4NGRbsT7xZa/XoyP5S7fg==
|
||||
dependencies:
|
||||
"@project-serum/serum" "^0.13.34"
|
||||
"@solana/spl-token" "^0.1.3"
|
||||
|
|
Loading…
Reference in New Issue