Through BlockChain RPC to call Solana contract
Obtain RPC Address
First, visit https://zan.top/service/apikeys to obtain the RPC address:
You need to log in to ZAN, which will automatically create a free Access Key for you. The process is similar for most other Blockchain RPC service providers—you must first obtain an address, which serves as the endpoint for calling RPC services.
Access via Command Line
You can test the connectivity of the RPC directly in the Shell command line using the following code:
curl --request POST \
--url https://api.zan.top/node/v1/solana/mainnet/${YOUR_ACCESS_KEY} \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"id": 1,
"jsonrpc": "2.0",
"method": "getBlockHeight"
}
'
Note that ${YOUR_ACCESS_KEY}
must be replaced with your own key. You can also run the code provided directly on the ZAN website.
The output should resemble the following:
{"result":333804817,"id":1,"jsonrpc":"2.0"}
This indicates a successful RPC call.
Call Contracts via JS SDK
Solana provides an official SDK, @solana/rpc
, which is part of the @solana/kit
package.
You can call the Solana chain RPC as follows:
import { createSolanaRpc } from '@solana/kit';
// Create an RPC client.
const rpc = createSolanaRpc('https://api.zan.top/node/v1/solana/mainnet/${YOUR_ACCESS_KEY}');
// Send a request.
const slot = await rpc.getSlot().send();
If you need to trigger a wallet for signing in a browser environment, you will also need to use @wallet-standard/react
to retrieve the wallet and obtain the signature.
Below is a complete React component example:
import { Blockquote, Box, Button, Dialog, Flex, Link, Select, Text, TextField } from '@radix-ui/themes';
import {
address,
appendTransactionMessageInstruction,
assertIsTransactionMessageWithSingleSendingSigner,
createTransactionMessage,
getBase58Decoder,
lamports,
pipe,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signAndSendTransactionMessageWithSigners,
} from '@solana/kit';
import { useWalletAccountTransactionSendingSigner } from '@solana/react';
import { getTransferSolInstruction } from '@solana-program/system';
import { getUiWalletAccountStorageKey, type UiWalletAccount, useWallets } from '@wallet-standard/react';
import type { SyntheticEvent } from 'react';
import { useContext, useId, useMemo, useRef, useState } from 'react';
import { useSWRConfig } from 'swr';
import { ChainContext } from '../context/ChainContext';
import { RpcContext } from '../context/RpcContext';
import { ErrorDialog } from './ErrorDialog';
import { WalletMenuItemContent } from './WalletMenuItemContent';
type Props = Readonly<{
account: UiWalletAccount;
}>;
function solStringToLamports(solQuantityString: string) {
if (Number.isNaN(parseFloat(solQuantityString))) {
throw new Error('Could not parse token quantity: ' + String(solQuantityString));
}
const numDecimals = BigInt(solQuantityString.split('.')[1]?.length ?? 0);
const bigIntLamports = BigInt(solQuantityString.replace('.', '')) * 10n ** (9n - numDecimals);
return lamports(bigIntLamports);
}
export function SolanaSignAndSendTransactionFeaturePanel({ account }: Props) {
const { mutate } = useSWRConfig();
const { current: NO_ERROR } = useRef(Symbol());
const { rpc } = useContext(RpcContext);
const wallets = useWallets();
const [isSendingTransaction, setIsSendingTransaction] = useState(false);
const [error, setError] = useState(NO_ERROR);
const [lastSignature, setLastSignature] = useState<Uint8Array | undefined>();
const [solQuantityString, setSolQuantityString] = useState<string>('');
const [recipientAccountStorageKey, setRecipientAccountStorageKey] = useState<string | undefined>();
const recipientAccount = useMemo(() => {
if (recipientAccountStorageKey) {
for (const wallet of wallets) {
for (const account of wallet.accounts) {
if (getUiWalletAccountStorageKey(account) === recipientAccountStorageKey) {
return account;
}
}
}
}
}, [recipientAccountStorageKey, wallets]);
const { chain: currentChain, solanaExplorerClusterName } = useContext(ChainContext);
const transactionSendingSigner = useWalletAccountTransactionSendingSigner(account, currentChain);
const lamportsInputId = useId();
const recipientSelectId = useId();
return (
<Flex asChild gap="2" direction={{ initial: 'column', sm: 'row' }} style={{ width: '100%' }}>
<form
onSubmit={async e => {
e.preventDefault();
setError(NO_ERROR);
setIsSendingTransaction(true);
try {
const amount = solStringToLamports(solQuantityString);
if (!recipientAccount) {
throw new Error('The address of the recipient could not be found');
}
const { value: latestBlockhash } = await rpc
.getLatestBlockhash({ commitment: 'confirmed' })
.send();
const message = pipe(
createTransactionMessage({ version: 0 }),
m => setTransactionMessageFeePayerSigner(transactionSendingSigner, m),
m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
m =>
appendTransactionMessageInstruction(
getTransferSolInstruction({
amount,
destination: address(recipientAccount.address),
source: transactionSendingSigner,
}),
m,
),
);
assertIsTransactionMessageWithSingleSendingSigner(message);
const signature = await signAndSendTransactionMessageWithSigners(message);
void mutate({ address: transactionSendingSigner.address, chain: currentChain });
void mutate({ address: recipientAccount.address, chain: currentChain });
setLastSignature(signature);
setSolQuantityString('');
} catch (e) {
setLastSignature(undefined);
setError(e);
} finally {
setIsSendingTransaction(false);
}
}}
>
<Box flexGrow="1" overflow="hidden">
<Flex gap="3" align="center">
<Box flexGrow="1" minWidth="90px" maxWidth="130px">
<TextField.Root
disabled={isSendingTransaction}
id={lamportsInputId}
placeholder="Amount"
onChange={(e: SyntheticEvent<HTMLInputElement>) =>
setSolQuantityString(e.currentTarget.value)
}
style={{ width: 'auto' }}
type="number"
value={solQuantityString}
>
<TextField.Slot side="right">{'\u25ce'}</TextField.Slot>
</TextField.Root>
</Box>
<Box flexShrink="0">
<Text as="label" color="gray" htmlFor={recipientSelectId} weight="medium">
To Account
</Text>
</Box>
<Select.Root
disabled={isSendingTransaction}
onValueChange={setRecipientAccountStorageKey}
value={recipientAccount ? getUiWalletAccountStorageKey(recipientAccount) : undefined}
>
<Select.Trigger
style={{ flexGrow: 1, flexShrink: 1, overflow: 'hidden' }}
placeholder="Select a Connected Account"
/>
<Select.Content>
{wallets.flatMap(wallet =>
wallet.accounts
.filter(({ chains }) => chains.includes(currentChain))
.map(account => {
const key = getUiWalletAccountStorageKey(account);
return (
<Select.Item key={key} value={key}>
<WalletMenuItemContent wallet={wallet}>
{account.address}
</WalletMenuItemContent>
</Select.Item>
);
}),
)}
</Select.Content>
</Select.Root>
</Flex>
</Box>
<Dialog.Root
open={!!lastSignature}
onOpenChange={open => {
if (!open) {
setLastSignature(undefined);
}
}}
>
<Dialog.Trigger>
<Button
color={error ? undefined : 'red'}
disabled={solQuantityString === '' || !recipientAccount}
loading={isSendingTransaction}
type="submit"
>
Transfer
</Button>
</Dialog.Trigger>
{lastSignature ? (
<Dialog.Content
onClick={e => {
e.stopPropagation();
}}
>
<Dialog.Title>You transferred tokens!</Dialog.Title>
<Flex direction="column" gap="2">
<Text>Signature:</Text>
<Blockquote>{getBase58Decoder().decode(lastSignature)}</Blockquote>
<Text>
<Link
href={`https://explorer.solana.com/tx/${getBase58Decoder().decode(
lastSignature,
)}?cluster=${solanaExplorerClusterName}`}
target="_blank"
>
View this transaction
</Link>{' '}
on Explorer
</Text>
</Flex>
<Flex gap="3" mt="4" justify="end">
<Dialog.Close>
<Button>Cool!</Button>
</Dialog.Close>
</Flex>
</Dialog.Content>
) : null}
</Dialog.Root>
{error !== NO_ERROR ? (
<ErrorDialog error={error} onClose={() => setError(NO_ERROR)} title="Transfer failed" />
) : null}
</form>
</Flex>
);
}
For more documents and examples, refer to Solana's official GitHub repository: https://github.com/anza-xyz/kit.