Solana
Introduction
Here is an example of how to use @solana/wallet-adapter-base
, @solana/wallet-adapter-react
, and @solana/wallet-adapter-react-ui
to support the various wallets in the Solana ecosystem with the wormhole-kit.
⚠️ Note For Solana, the current setting is Testnet configured as Devnet. Therefore, you will need to set your wallet to the Devnet environment for proper testing.
⚠️ Warning: In the case of the Phantom wallet, a warning may appear, but you can proceed despite the warning. With the Solflare wallet, however, the warning will prevent you from proceeding.
Installation
bash
npm install @zktx.io/wormhole-kit @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/web3.js @solana/wallet-adapter-phantom
bash
yarn add @zktx.io/wormhole-kit @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/web3.js @solana/wallet-adapter-phantom
Set up providers
typescript
// index.tsx
import { StrictMode } from 'react';
import { WhProvider } from '@zktx.io/wormhole-kit';
import { SnackbarProvider } from 'notistack';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Wallet from './Wallet';
import '@mysten/dapp-kit/dist/index.css';
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(
<StrictMode>
<WhProvider
network="Testnet" // !!!!!! Set TEST NET !!!!!!
chains={['Sui', 'Sepolia', 'Solana', 'Aptos', 'Celo', 'Polygon']}
config={{
chains: {
Ethereum: {
rpc: 'https://eth-goerli.public.blastapi.io',
},
Polygon: {
rpc: 'https://polygon-mumbai.api.onfinality.io/public',
},
},
}}
mode="dark"
>
<SnackbarProvider
anchorOrigin={{ horizontal: 'left', vertical: 'top' }}
/>
<Wallet>
<App />
</Wallet>
</WhProvider>
</StrictMode>,
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
typescript
// wallet.tsx
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
import {
ConnectionProvider,
WalletProvider,
} from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { clusterApiUrl } from '@solana/web3.js';
// Default styles that can be overridden by your app
require('@solana/wallet-adapter-react-ui/styles.css');
function Wallet({ children }: { children: ReactNode }) {
// The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
const network = WalletAdapterNetwork.Devnet; // !!!!!! Set DEV NET !!!!!!
// You can also provide a custom RPC endpoint.
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(
() => [new PhantomWalletAdapter()],
// eslint-disable-next-line react-hooks/exhaustive-deps
[network],
);
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
}
export default Wallet;
Use Components
Transfer Asset Modal
typescript
// const { connection } = useConnection();
// const { publicKey, signAllTransactions } = useWallet();
<WhTransferModal
chain="Solana"
address={address} // publicKey.toBase58()
trigger={<button>Transfer</button>}
handleUnsignedTxs={handleUnsignedTxs}
/>
Redeem Asset Modal
typescript
// const { connection } = useConnection();
// const { publicKey, signAllTransactions } = useWallet();
<WhRedeemModal
chain="Solana"
address={address} // publicKey.toBase58()
trigger={<button>Redeem</button>}
handleUnsignedTxs={handleUnsignedTxs}
/>
Sign Transaction
typescript
// import { PublicKey } from '@solana/web3.js';
// import type { Signer } from '@solana/web3.js';
const handleUnsignedTxs = async (unsignedTxs: IUnsignedTx[]): Promise<void> => {
try {
const {
context: { slot: minContextSlot },
value: { blockhash, lastValidBlockHeight },
} = await connection.getLatestBlockhashAndContext();
const txs = unsignedTxs.map(({ signers, transaction }) => {
transaction.recentBlockhash = blockhash;
transaction.lastValidBlockHeight = lastValidBlockHeight;
return { signers, transaction };
});
if (signAllTransactions) {
const signedTxs = await signAllTransactions(
txs.map((tx) => tx.transaction),
);
for (let i = 0; i < signedTxs.length; i++) {
const signedTx = signedTxs[i];
const signers: Signer[] | undefined =
txs[i].signers &&
(txs[i].signers as any[]).map((item) => {
return {
publicKey: new PublicKey(item._keypair.publicKey),
secretKey: item._keypair.secretKey,
};
});
signers && signedTx.partialSign(...signers);
const signature = await connection.sendRawTransaction(
signedTx.serialize(),
{
minContextSlot,
// skipPreflight: true,
},
);
enqueueSnackbar(signature, {
variant: "success",
});
}
}
} catch (error) {
enqueueSnackbar(`${error}`, {
variant: "error",
});
}
};