Skip to content

Instantly share code, notes, and snippets.

Created July 8, 2022 11:02
Show Gist options
  • Save sevazhidkov/867580170dd81c8eb8fed7801a6447d5 to your computer and use it in GitHub Desktop.
Save sevazhidkov/867580170dd81c8eb8fed7801a6447d5 to your computer and use it in GitHub Desktop.
const axios = require('axios').default;
const { Keypair, PublicKey, Connection, Transaction, sendAndConfirmRawTransaction, LAMPORTS_PER_SOL } = require('@solana/web3.js');
const {
createMint, getOrCreateAssociatedTokenAccount, Account,
mintTo, createTransferInstruction, createAccount, getAccount,
} = require('@solana/spl-token');
const base58 = require('bs58');
const baseURL = '';
async function main() {
const connection = new Connection('', 'confirmed');
// (1) Preparation
// We need some SPL tokens to pay for the transaction fees and rent instead of SOL.
// On mainnet, we'll enable paying fees with popular and liquid tokens (like USDC).
// On devnet, '' endpoint will accept any token.
// It makes it easier to debug and run e2e tests. However, you'll need to
// pass overrideMint and overrideAccount body params to specify what token and token destination should octane expect.
// Additionally, we ask you to top up the our SOL balances to make sure our fee payer has enough SOL to pay the fees.
// Let's now create a new token that would be used as payment to octane node. We'll need some SOL to create a new token.
const tokenKeypair = Keypair.generate();
const airdropSignature = await connection.requestAirdrop(
tokenKeypair.publicKey, LAMPORTS_PER_SOL,
await connection.confirmTransaction(airdropSignature);
const mint = await createMint(connection, tokenKeypair, tokenKeypair.publicKey, null, 9, undefined, {commitment: 'confirmed'});
// Now, we should create an account that Octane will use to receive payments.
// On mainnet, this account will be already created by node owner (in this case, Breakroom) for each accepted token.
const tokenAccount = await getOrCreateAssociatedTokenAccount(
{commitment: 'confirmed'}
// Let's now connect to Octane instance and get some needed params:
// 1) how much we should pay for transaction in tokens
// 2) which account will pay for transaction in SOL (feePayer)
// Normally, you should also learn which kind of tokens supported and
// get price in each of them, but devnet endpoint accepts any token through
// config overrides.
const response = (await axios.get(baseURL, {
headers: {'Accept': 'application/json'}
const feePayer = new PublicKey(response.feePayer);
const simpleTransactionFee = response.endpoints.transfer.tokens[0].fee;
const newAccountFee = response.endpoints.createAccount.tokens[0].fee; // how much it costs to create a new associated token account
// Only on devnet: add some SOL to octane's fee payer, since it's a shared resource.
// On mainnet, this would taken care by node owner.
const airdropSignature2 = await connection.requestAirdrop(
await connection.confirmTransaction(airdropSignature2);
// (2) Prepare the hypothetical end user account
// We shouldn't airdrop any SOL to this keypair to prove fee paying works
endUserKeypair = Keypair.generate();
endUserTokenAccount = await createAccount(connection, tokenKeypair, mint, endUserKeypair.publicKey, undefined, {commitment: 'confirmed'});
// However, the user still needs some tokens to pay for the transaction.
await mintTo(connection, tokenKeypair, mint, endUserTokenAccount, tokenKeypair.publicKey, 1e9, undefined, {commitment: 'confirmed'});
// (3) Create the transaction — we'll transfer some tokens to targetKeypair as our first gasless transaction
const targetKeypair = Keypair.generate();
const targetAccount = await createAccount(connection, tokenKeypair, mint, targetKeypair.publicKey, undefined, {commitment: 'confirmed'}); // for now, we assume it's already created
// The transaction would consist of two instructions:
// 1) token transfer to octane's token account as payment for running transaction gasless
// 2) the payload, actual instruction we want to execute. in this case, it's also a token transfer to targetKeypair, but it could be anything.
const transaction = new Transaction();
transaction.add(createTransferInstruction(endUserTokenAccount, tokenAccount.address, endUserKeypair.publicKey, simpleTransactionFee));
transaction.add(createTransferInstruction(endUserTokenAccount, targetAccount, endUserKeypair.publicKey, 100));
transaction.feePayer = feePayer;
transaction.recentBlockhash = (await connection.getRecentBlockhash()).blockhash;
transaction.partialSign(endUserKeypair); // in client code, you should use wallet adapter instead
// (4) Send the transaction to octane to sign and attach signature to transaction
const octaneResponse = (await + '/transfer', {
transaction: base58.encode(transaction.serialize({requireAllSignatures: false})),
overrideAccount: tokenAccount.address.toBase58(),
overrideMint: mint.toBase58()
transaction.addSignature(feePayer, base58.decode(octaneResponse.signature));
// (5) Send transaction to network
await sendAndConfirmRawTransaction(connection, transaction.serialize(), { commitment: 'confirmed' });
console.log('Find on devnet explorer ///', octaneResponse.signature);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment