Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Madjarx/b5303f60785bb830a263e59e8d4cf8f4 to your computer and use it in GitHub Desktop.

Select an option

Save Madjarx/b5303f60785bb830a263e59e8d4cf8f4 to your computer and use it in GitHub Desktop.
Swap a token in solana
////// CLIENT SIDE
import classnames from 'classnames';
import * as anchor from '@project-serum/anchor';
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { useState, useEffect } from 'react';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import { useWallet, useConnection } from '@solana/wallet-adapter-react';
import { Container, Snackbar, Paper, CircularProgress } from '@material-ui/core';
import CTAButton from '../components/CTAButton';
import toast from 'react-hot-toast';
import axios from 'axios';
import {
Transaction,
PublicKey,
SystemProgram,
LAMPORTS_PER_SOL,
Keypair,
sendAndConfirmRawTransaction
} from '@solana/web3.js';
import bs58 from 'bs58';
import nacl from 'tweetnacl';
import styles from '../styles/Home.module.scss';
const xinTokenMint = new PublicKey(process.env.NEXT_PUBLIC_TOKEN_ADDRESS);
export default function Swop() {
const { connection } = useConnection();
const wallet = useWallet();
const [loading, setLoading] = useState(false);
const [tokenBalance, setTokenBalance] = useState(0);
const [tokens, setTokens] = useState(0);
useEffect(() => {
if (wallet.publicKey) {
updateTokenBalance();
}
}, [wallet.publicKey, wallet.connected])
const swapActive = Boolean(process.env.NEXT_PUBLIC_SWAP_ACTIVE);
async function updateTokenBalance() {
if (wallet && wallet.publicKey) {
const key = wallet.publicKey.toString();
const response = await axios.get(`${process.env.NEXT_PUBLIC_HOST}/api/get-token-balance/${key}`);
const { amount } = response.data;
setTokenBalance(amount);
}
}
function onChange(e) {
let { value } = e.target;
if (value) {
value = parseInt(value, 10);
}
setTokens(value);
}
function setMax(e) {
e.preventDefault()
const max = parseInt(process.env.NEXT_PUBLIC_MAX_TOKEN_SWAP, 10);
const val = tokenBalance > max
? max
: tokenBalance
setTokens(val);
}
function onInputClick(e) {
e.target.setSelectionRange(0, e.target.value.length);
}
async function swap() {
try {
setLoading(true);
const res = await axios.post(`${process.env.NEXT_PUBLIC_HOST}/api/swap-xin`, { tokens, publicKey: wallet.publicKey })
let { transaction } = res.data;
transaction.instructions = transaction.instructions.map(i => {
return {
...i,
data: new Buffer(i.data),
programId: new PublicKey(i.programId),
keys: i.keys.map(key => {
return {
...key,
pubkey: new PublicKey(key.pubkey)
}
})
}
})
transaction.signatures = transaction.signatures.map(s => {
return {
...s,
publicKey: new PublicKey(s.publicKey),
signature: s.signature
? new Buffer(s.signature)
: null
}
});
transaction = new Transaction(transaction);
const signedTransaction = await wallet.signTransaction(transaction, connection);
const isVerifiedSignature = signedTransaction.verifySignatures();
if (!isVerifiedSignature) {
throw new Error('Error signing transaction');
}
const rawTransaction = signedTransaction.serialize();
await sendAndConfirmRawTransaction(connection, rawTransaction);
toast.success('Swap successful!');
setLoading(false);
setTokenBalance(tokenBalance - tokens);
setTokens(0);
} catch (e) {
toast.error('Swap Error:', e.message);
setLoading(false);
}
}
return (
<Container>
<Container maxWidth="xs" style={{ position: 'relative' }}>
<Paper
style={{ padding: 24, backgroundColor: 'rgba(21, 26, 31, 0.8)', borderRadius: 6, marginBottom: 100 }}
>
<main className={styles.main}>
<h2 className={styles.gradient}>The Swappooor</h2>
{
!swapActive
? <h2 className={styles.swapper}>SWAP OFFLINE</h2>
: (
<>
<div className={styles.walletButtons}>
<WalletMultiButton className={classnames({ [styles.disconnected]: !wallet.connected })} />
{
wallet.connected && <h4>$XIN balance: {tokenBalance}</h4>
}
</div>
{
wallet.connected && (
<>
<div className={styles.inputWapper}>
<input
className={styles.xinput}
type="text"
value={tokens}
onChange={onChange}
onClick={onInputClick}
/>
<a href="#" onClick={setMax}>Max</a>
</div>
<p className={styles.exchange}>210 XIN = 0.69 SOL</p>
<CTAButton onClick={swap} disabled={loading || !tokens}>
{
loading
? <CircularProgress style={{ color: '#7C5D1E' }} />
: 'SWOP'
}
</CTAButton>
</>
)
}
</>
)
}
</main>
</Paper>
</Container>
</Container>
);
}
//////////// SERVER SIDE
const web3 = require('@solana/web3.js');
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import getTokenBalance from '../../lib/get-token-balance';
import bs58 from 'bs58';
import nacl from 'tweetnacl';
const xinTokenMint = new web3.PublicKey(process.env.NEXT_PUBLIC_TOKEN_ADDRESS);
const mintPrice = parseInt(process.env.NEXT_PUBLIC_MINT_PRICE, 10);
const fromWallet = web3.Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(bs58.decode(process.env.XIN_WALLET).toString())
)
)
export default async function handler(req, res) {
const { publicKey, tokens } = req.body;
const swapActive = Boolean(process.env.NEXT_PUBLIC_SWAP_ACTIVE);
if (!swapActive) {
return res.status(500).send({ message: 'Swap not currently active' });
}
const tokenBalance = await getTokenBalance(publicKey, process.env.NEXT_PUBLIC_TOKEN_ADDRESS);
if (tokens > tokenBalance / 1000000) {
return res.status(500).send({ message: 'Balance too low' });
}
if (tokens > parseInt(process.env.NEXT_PUBLIC_MAX_TOKEN_SWAP)) {
return res.status(500).send({ message: 'Too much $XIN for one transaction' });
}
const connection = new web3.Connection(process.env.NEXT_PUBLIC_RPC_HOST);
const userWallet = new web3.PublicKey(publicKey);
const xinToken = new Token(
connection,
xinTokenMint,
TOKEN_PROGRAM_ID,
userWallet
);
const fromTokenAccount = await xinToken.getOrCreateAssociatedAccountInfo(userWallet)
const toTokenAccount = await xinToken.getOrCreateAssociatedAccountInfo(fromWallet.publicKey)
const transaction = new web3.Transaction()
.add(
Token.createTransferInstruction(
TOKEN_PROGRAM_ID,
fromTokenAccount.address,
toTokenAccount.address,
userWallet,
[],
tokens * 1000000
)
)
.add(
web3.SystemProgram.transfer({
fromPubkey: fromWallet.publicKey,
toPubkey: userWallet,
lamports: tokens * parseFloat(process.env.NEXT_PUBLIC_XIN_COST_IN_SOL) * web3.LAMPORTS_PER_SOL,
})
)
transaction.recentBlockhash = (await connection.getRecentBlockhash('singleGossip')).blockhash;
transaction.setSigners(userWallet, fromWallet.publicKey);
const signature = nacl.sign.detached(transaction.serializeMessage(), fromWallet.secretKey);
transaction.addSignature(fromWallet.publicKey, signature);
res.status(200).json({ transaction });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment