import * as solana from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import { SolanaSignTransaction } from "@solana/wallet-standard-features";
import type { PhantomSession, SolanaChain } from "./connect";
const genesisHashes = {
"solana:devnet": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
"solana:mainnet": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
};
export async function prepareV1Transfer(
rpcUrl: string, sender: string, recipient: string, amount: bigint,
chain: SolanaChain = "solana:devnet",
) {
if (amount <= 0n) throw new Error("Enter a positive amount in lamports.");
const rpc = solana.createSolanaRpc(rpcUrl);
if (await rpc.getGenesisHash().send() !== genesisHashes[chain]) {
throw new Error(`RPC endpoint does not match ${chain}.`);
}
const { value: lifetime } = await rpc.getLatestBlockhash({ commitment: "confirmed" }).send();
const message = solana.pipe(
solana.createTransactionMessage({ version: 1 }),
(message) => solana.setTransactionMessageFeePayer(solana.address(sender), message),
(message) => solana.setTransactionMessageLifetimeUsingBlockhash(lifetime, message),
(message) => solana.setTransactionMessageConfig({
computeUnitLimit: 200_000,
loadedAccountsDataSizeLimit: 1_048_576,
priorityFeeLamports: 1_000n, // Total priority fee in lamports.
}, message),
(message) => solana.appendTransactionMessageInstruction(getTransferSolInstruction({
source: solana.createNoopSigner(solana.address(sender)),
destination: solana.address(recipient), amount,
}), message),
);
const transaction = solana.compileTransaction(message);
const { value: simulation } = await rpc.simulateTransaction(
solana.getBase64EncodedWireTransaction(transaction),
{ encoding: "base64", commitment: "confirmed", sigVerify: false },
).send();
if (simulation.err) throw new Error("Simulation failed.", { cause: simulation.err });
const bytes = new Uint8Array(solana.getTransactionEncoder().encode(transaction));
return { rpc, bytes, simulation };
}
export async function sendV1Transfer(
session: PhantomSession, rpcUrl: string,
recipient: string, amount = 1_000_000n, chain: SolanaChain = "solana:devnet",
) {
const { wallet, account } = session;
if (!account.chains.includes(chain)) throw new Error("Account does not support this network.");
const { rpc, bytes } = await prepareV1Transfer(rpcUrl, account.address, recipient, amount, chain);
if (!wallet.accounts.includes(account)) throw new Error("Wallet account changed. Reconnect.");
const [result] = await wallet.features[SolanaSignTransaction].signTransaction({
account, chain, transaction: bytes,
});
if (!result) throw new Error("Phantom did not return a signed transaction.");
const signed = solana.getTransactionDecoder().decode(result.signedTransaction);
const signature = solana.getSignatureFromTransaction(signed);
try {
await rpc.sendTransaction(solana.getBase64EncodedWireTransaction(signed), {
encoding: "base64", preflightCommitment: "confirmed", skipPreflight: false,
}).send();
} catch (cause) {
throw Object.assign(new Error(`Check ${signature} before retrying.`, { cause }), { signature });
}
return signature;
}