DEV Community

Cover image for Introducing Gill: The Modern Solana JavaScript client library
Shivam Soni
Shivam Soni

Posted on • Edited on

Introducing Gill: The Modern Solana JavaScript client library

Introduction

Introducing Gill: The new javascript client library to interact with the solana blockchain. It is built on top of modern JavaScript libraries for Solana, which Anza built. it leverages the speed and elegance of web3 js version two and delivers a lightweight and highly abstract solution. it is lightweight, fast, and has tree-shakable architecture. you can use it to build solana d-apps in node, web, React Native or just about any other javascript environment.

Why is Gill useful?

Since Solana Web3.js v2 is great in performance with its tree-shakable architecture but feels verbose in everything you do with its longer function names and hyper-modular sub-packages, Gill is fully compatible with v2 and offers an easier adoption curve with improved developer experience(DevEx).

Gill provides a single entry point for Web3.js v2, popular SPL programs like System, Compute Budget, Memo, Token, Token22, and ecosystem programs like Metaplex’s Token Metadata.

Gill offers Transaction Builders to make it easy to assemble ready-to-sign transactions for common Solana tasks such as creating tokens with metadata, minting tokens, and transferring tokens. These tasks often involve interacting with multiple programs at once.

Gill can be useful for building Solana d-apps in Node, web, React Native, or any other JavaScript environment.

Gill provides a debug mode that you can enable to automatically log additional information helpful for troubleshooting your transactions. You can enable it from the most common places where your code runs, including within your code, Node.js backends, serverless functions, and even directly in the web browser console.

What can I do with Gill?

Gill offers the same features as Solana Web3.js v2 (Solana Kit) but with improved naming and a simplified approach for common Solana tasks.

By using Gill, you can accomplish the following -:

Keypairs/wallets -:

  • Generate random keypairs - Use generateKeyPairSigner to generate a non-extractable keypair for signing transactions. Non-extractable means that the private key cannot be retrieved from the instance, providing enhanced security for keypairs.

  • Generate random extractable keypairs - Use generateExtractableKeyPairSigner to generate extractable keypairs. These are less secure because they allow the secret key material to be extracted.

  • Load a wallet from a file - Use loadKeypairSignerFromFile to load a keypair signer from a filesystem wallet JSON file. By default, this function loads the wallet keypair stored in the file used by the Solana CLI: ~/.config/solana/id.json. It also resolves relative paths that use the ~ character for the user's home directory.

  • Load a keypair from an environment variable - Load a keypairSigner from environment-stored bytes using loadKeypairSignerFromEnvironment, consistent with standard Solana tools.

Client Connection and Rpc Calls-:

  • Create a solana RPC connection - Use createSolanaClient to create a Solana client with your preferred RPC endpoint URL or a standard Solana network moniker (e.g., devnet, localnet, mainnet, etc.).

  • To make an RPC call, the RPC client requires you to call .send() on the RPC method to send the request to your RPC provider and receive a response.

Transactions

  • Create a transaction - Use the createTransaction method to easily create a transaction with all standard parameters.

  • Sign and Send transaction - After you have a signable transaction, you can sign it using signTransactionMessageWithSigners, retrieve the signature using getSignatureFromTransaction (even before sending it to the network), and send the transaction using sendAndConfirmTransaction.

  • Get a Solana Explorer link - Use the getExplorerLink method to generate a Solana Explorer link for transactions, accounts, or blocks.

  • Transaction builders - To simplify common Solana tasks like creating tokens, minting tokens, and transferring SPL tokens, Gill introduces Transaction Builders to help easily assemble ready-to-sign transactions for these tasks.

Debug mode

  • Dubug solana transaction - Gill introduced debug mode that will make it easier to troubleshoot the solana transaction. Debug mode is disabled by default to minimize additional logs for your application. But with its flexible debug controller, you can enable it from the most common places your code will be run.

Example Comparision

At this point, we've learned what Gill is, why it's helpful for developers, and the common tasks it can accomplish. Now, let’s look at an example of creating a token using Gill and Web3.js v2:

Create ‘OPOS’ token with Gill-:


import {
  createSolanaClient,
  signTransactionMessageWithSigners,
  getSignatureFromTransaction,
  getExplorerLink,
  createKeyPairSignerFromBytes,
  getBase58Codec,
  generateKeyPairSigner
} from "gill";
import { buildCreateTokenTransaction, TOKEN_2022_PROGRAM_ADDRES } from "gill/programs/token";

const { rpc, sendAndConfirmTransaction } = createSolanaClient({
  urlOrMoniker: "devnet",
});

// get the latest blockhash
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const keypairBase58feePayer = "your_secret_key";
const keypairBytesfeePayer = getBase58Codec().encode(keypairBase58feePayer);
const feepayersigner = await createKeyPairSignerFromBytes(keypairBytesfeePayer);

async function createTokenFunc() {

  const mint = await generateKeyPairSigner();

  // create tokens
  const createTokenTx = await buildCreateTokenTransaction({
    feePayer: feepayersigner,
    latestBlockhash,
    mint: mint,  // Use mintKey here
    metadata: {
      isMutable: true, // if the `updateAuthority` can change this metadata in the future
      name: "Only Possible On Solana",
      symbol: "OPOS",
      uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/Climate/metadata.json",
    },
    decimals: 2,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  const signedTransaction = await signTransactionMessageWithSigners(createTokenTx);
  const signature: string = getSignatureFromTransaction(signedTransaction);
  console.log(getExplorerLink({ transaction: signature }));

  // default commitment level of `confirmed`
  await sendAndConfirmTransaction(signedTransaction);
}

createTokenFunc().catch(console.error);

Enter fullscreen mode Exit fullscreen mode

Create ‘OPOS’ token without Gill:


import {
    airdropFactory,
    appendTransactionMessageInstructions,
    createSolanaRpc,
    createSolanaRpcSubscriptions,
    createTransactionMessage,
    generateKeyPairSigner,
    getSignatureFromTransaction,
    lamports,
    pipe,
    sendAndConfirmTransactionFactory,
    setTransactionMessageFeePayerSigner,
    setTransactionMessageLifetimeUsingBlockhash,
    signTransactionMessageWithSigners,
    some,
  } from "@solana/web3.js";
  import { getCreateAccountInstruction } from "@solana-program/system";
  import {
    getInitializeMintInstruction,
    getMintSize,
    TOKEN_2022_PROGRAM_ADDRESS,
    extension,
    getInitializeMetadataPointerInstruction,
    getInitializeTokenMetadataInstruction,
    tokenMetadataField,
    getUpdateTokenMetadataFieldInstruction,
  } from "@solana-program/token-2022";

  const rpc = createSolanaRpc("http://127.0.0.1:8899");
  const rpcSubscriptions = createSolanaRpcSubscriptions("ws://127.0.0.1:8900");

  const feePayer = await generateKeyPairSigner();
  console.log(feePayer.address);
  const mint = await generateKeyPairSigner();

  await airdropFactory({ rpc, rpcSubscriptions })({
    recipientAddress: feePayer.address,
    lamports: lamports(1_000_000_000n),
    commitment: "confirmed",
  });

  const balance = await rpc.getBalance(feePayer.address).send();
  console.log("balance:", balance.value);

  const metadataPointerExtension = extension("MetadataPointer", {
    authority: some(feePayer.address),
    metadataAddress: some(mint.address),
  });

  const tokenMetadataExtension = extension("TokenMetadata", {
    updateAuthority: some(feePayer.address),
    mint: mint.address,
    name: "Only Possible On Solana",
    symbol: "OPOS",
    uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/Climate/metadata.json",
    additionalMetadata: new Map<string, string>([["description", "Only Possible On Solana"]]),
  });

  const spaceWithoutMetadata = BigInt(getMintSize([metadataPointerExtension]));

  const spaceWithMetadata = BigInt(getMintSize([metadataPointerExtension, tokenMetadataExtension]));

  const rent = await rpc.getMinimumBalanceForRentExemption(spaceWithMetadata).send();

  const createAccountInstruction = getCreateAccountInstruction({
    payer: feePayer,
    newAccount: mint,
    lamports: rent,
    space: spaceWithoutMetadata,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS,
  });

  const initializeMetadataPointerInstruction = getInitializeMetadataPointerInstruction({
    mint: mint.address,
    authority: feePayer.address,
    metadataAddress: mint.address,
  });

  const initializeMintInstruction = getInitializeMintInstruction({
    mint: mint.address,
    decimals: 2,
    mintAuthority: feePayer.address,
  });

  const initializeTokenMetadataInstruction = getInitializeTokenMetadataInstruction({
    metadata: mint.address,
    updateAuthority: feePayer.address,
    mint: mint.address,
    mintAuthority: feePayer,
    name: tokenMetadataExtension.name,
    symbol: tokenMetadataExtension.symbol,
    uri: tokenMetadataExtension.uri,
  });

  const updateTokenMetadataInstruction = getUpdateTokenMetadataFieldInstruction({
    metadata: mint.address,
    updateAuthority: feePayer,
    field: tokenMetadataField("Key", ["description"]),
    value: "Only Possible On Solana",
  });

  const instructions = [
    createAccountInstruction,
    initializeMetadataPointerInstruction,
    initializeMintInstruction,
    initializeTokenMetadataInstruction,
    updateTokenMetadataInstruction,
  ];

  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

  const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (message) => setTransactionMessageFeePayerSigner(feePayer, message),
    (message) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message),
    (message) => appendTransactionMessageInstructions(instructions, message),
  );

  const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);

  const transactionSignature = getSignatureFromTransaction(signedTransaction);

  await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
    commitment: "confirmed",
    skipPreflight: true,
  });

  console.log("Transaction Signature:", `https://explorer.solana.com/tx/${transactionSignature}?cluster=custom`);

Enter fullscreen mode Exit fullscreen mode

As you can see, creating tokens with Gill is easy thanks to its transaction builders and a higher level of abstraction over Web3.js v2. Using one transaction builder and some line of code, we created an SPL token.

Now that we understand what Gill is, why it matters, and how it enhances the Web3.js v2 developer experience, it's time to code a simple SPL token mint and transfer example. Let's go!

Quick spl token mint and transfer With Gill

As we know, there are various ways to mint or create an SPL token. We can use JavaScript/TypeScript with Solana Web3.js and Solana's developer helpers for SPL tokens, or create a Solana program in Rust/Anchor to mint an SPL token. In this guide, we will use the Gill library to mint an SPL token. We'll also utilize the quickest method, leveraging Gill's pre-built transaction builder methods for various transactions.

What you will need

  • Experience with Solana Web3.js v2
  • Latest Node.js version installed
  • TypeScript and ts-node installed

Dependencies used in this guide

For this project, the dependencies in the package.json file will look like this:


"dependencies": {
    "esrun": "^3.2.26",
    "gill": "^0.6.0"
  },
  "devDependencies": {
    "@types/node": "^22.13.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2"
  }

Enter fullscreen mode Exit fullscreen mode

Let’s set up a new project:

mkdir spl-token && cd spl-token
Enter fullscreen mode Exit fullscreen mode

Initialize your project as a Node.js project:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Install the required dependencies:

pnpm install gill esrun && pnpm install --save-dev @types/node typescript ts-node
Enter fullscreen mode Exit fullscreen mode

Create a new file named spl-token.ts in your project directory:

echo > spl-token.ts
Enter fullscreen mode Exit fullscreen mode

Great! Now, let’s start coding.

Import dependencies

In your spl-token.ts file, let's start by importing the necessary dependencies:

import {
    address,
    KeyPairSigner,
    getBase58Codec,
    getExplorerLink,
    createSolanaClient,
    generateKeyPairSigner,
    getSignatureFromTransaction,
    createKeyPairSignerFromBytes,
    signTransactionMessageWithSigners,
    setTransactionMessageLifetimeUsingBlockhash,
  } from "gill";

  import {
    buildCreateTokenTransaction,
    buildMintTokensTransaction,
    buildTransferTokensTransaction
  } from "gill/programs/token";

Enter fullscreen mode Exit fullscreen mode

We're importing key functions from the Gill library to create, mint, and transfer SPL tokens.

Create an RPC connection to interact with the blockchain

  // create Rpc connection
  const { rpc, sendAndConfirmTransaction } = createSolanaClient({
    urlOrMoniker: "devnet",
  });

  // get slot
  const slot = await rpc.getSlot().send();

  // get the latest blockhash
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
Enter fullscreen mode Exit fullscreen mode

We are using Gill's createSolanaClient method to establish an RPC connection to Devnet. To call any RPC method, we need to use .send() on the RPC method to send the request to the RPC provider and receive a response.

Generate Keypairs


 const keypairBase58alice = "your_wallet_secret_key";
 const keypairBytesalice = getBase58Codec().encode(keypairBase58alice);
 const aliceKeyPair = await createKeyPairSignerFromBytes(keypairBytesalice);

 // KeyPairs and addresses
 const alice = aliceKeyPair.address;

 const bob = address("4d4zsfq4gtJixDGvisSdFjsY78uH7BypkwmkXL1D8RfT");

 const mint = await generateKeyPairSigner();

Enter fullscreen mode Exit fullscreen mode

Here, we generate the Alice, Bob, and Mint addresses. For Alice, we use a secret key pair to generate her address. In this guide, Alice will create and mint tokens in her wallet, and then send the minted tokens to Bob’s wallet.

Create the Main Function

Next, let's create our main function that will house the logic of our script:


async function main(){


// Create token transaction


// Mint token transaction


// Transfer tokens transaction


}

main().catch(console.error);

Enter fullscreen mode Exit fullscreen mode

Create or mint spl token

Let's add the following code to the main function as the first step:

const createTokenTx = await buildCreateTokenTransaction({
      feePayer: aliceKeyPair,
      latestBlockhash,
      mint: mint,  // Use mintKey here
      metadata: {
        isMutable: true, // if the `updateAuthority` can change this metadata in the future
        name: "Only Possible On Solana",
        symbol: "OPOS",
        uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/Climate/metadata.json",
      },
      decimals: 2,
    });


const signedTransaction = await signTransactionMessageWithSigners(createTokenTx);
const signature: string = getSignatureFromTransaction(signedTransaction);
console.log(getExplorerLink({ transaction: signatureformint }));

// default commitment level of `confirmed`
await sendAndConfirmTransaction(signedTransaction);
Enter fullscreen mode Exit fullscreen mode

Here, we use the buildCreateTokenTransaction tx builder from Gill to create an SPL token, specifying aliceKeypair as the feePayer and providing metadata for the token.

Next, we use signTransactionMessageWithSigners to sign the transaction before sending it to the network, similar to Web3.js. We then obtain the signature using getSignatureFromTransaction and finally send the transaction to the network using sendAndConfirmTransaction.

Congrats! Our OPOS token has been created. Check the Solana Explorer link for details.

Minting Created token to Alice's wallet

Let's add the following code to the main function as the second step:


const mintTokensTx = await buildMintTokensTransaction({
      feePayer: aliceKeyPair,
      latestBlockhash,
      mint,
      mintAuthority: aliceKeyPair,
      amount: 1000, // note: be sure to consider the mint's `decimals` value
      // if decimals=2 => this will mint 10.00 tokens
      // if decimals=4 => this will mint 0.100 tokens
      destination: alice,
      // use the correct token program for the `mint`
      // tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
      // default cu limit set to be optimized, but can be overriden here
      // computeUnitLimit?: number,
      // obtain from your favorite priority fee api
      // computeUnitPrice?: number, // no default set
    });



    const signedTransactionformint = await signTransactionMessageWithSigners(mintTokensTx);
    const signatureformint: string = getSignatureFromTransaction(signedTransactionformint);
    console.log(getExplorerLink({ transaction: signatureformint }));
    // default commitment level of `confirmed`
    await sendAndConfirmTransaction(signedTransactionformint);


Enter fullscreen mode Exit fullscreen mode

Here, we use the buildMintTokensTransaction tx builder from Gill to mint tokens to Alice's address, sign the transaction, obtain the signature, and send the transaction to the network.

You don't need to worry about associated token accounts because, in transaction builders, if the destination owner does not have an associated token account (ATA) for the mint, one will be automatically created for them.

Congrats! You have minted 10 OPOS SPL tokens into Alice's token account.

Transfer the spl token to Bob’s wallet

Let's add the following code to the main function as the third step:


const transferTokensTx = await buildTransferTokensTransaction({
      feePayer: aliceKeyPair,
      latestBlockhash,
      mint,
      authority: aliceKeyPair,
      // sourceAta, // default=derived from the `authority`.
      /*
       * if the `sourceAta` is not derived from the `authority` (like for multi-sig wallets),
       * manually derive with `getAssociatedTokenAccountAddress()`
      */
      amount: 900, // note: be sure to consider the mint's `decimals` value
      // if decimals=2 => this will transfer 9.00 tokens
      // if decimals=4 => this will transfer 0.090 tokens
      destination: bob,
      // use the correct token program for the `mint`
      //  tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
      // default cu limit set to be optimized, but can be overriden here
      // computeUnitLimit?: number,
      // obtain from your favorite priority fee api
      // computeUnitPrice?: number, // no default set
    });


    const signedTransactionfortransfer = await signTransactionMessageWithSigners(transferTokensTx);
    const signaturefortransfer: string = getSignatureFromTransaction(signedTransactionfortransfer);
    console.log(getExplorerLink({ transaction: signaturefortransfer }));

    // default commitment level of `confirmed`
    await sendAndConfirmTransaction(signedTransactionfortransfer);

Enter fullscreen mode Exit fullscreen mode

We use the buildTransferTokensTransaction method to transfer SPL tokens from Alice to Bob, Then sign, obtain the signature, and send it to the network. The associated token account (ATA) is also auto-created for Bob's wallet for this mint.

Congrats! You transferred 9 OPOS tokens from Alice's token account to Bob's token account.

Run Your Code

In your Terminal, type:

npx esrun ./index.ts
Enter fullscreen mode Exit fullscreen mode

Upon successful execution, you should see an output of three Solana Explorer links for each operation: create, mint, and transfer of the 'OPOS' token.

https://explorer.solana.com/tx/58KC1GPc1f8aCUox6Pst7YheYRCqrQY9Np2gP6LfqDP5ogQjP5Hy76opzmJ8EKW2PyMdoGh71MYGWHL6oYHLAvdD
https://explorer.solana.com/tx/4DXAFBfsAVCgZ3X3rwEZ72EN81JVWj2zpzsYERCu5xitf1aztuytn9og3cyNbNeR3t5KnaJgneNckFy6MGAoWLGr
https://explorer.solana.com/tx/4PkHW9dSbQicKcempBoD3VCe2xQhJidJ6gptXPNvq4LVRBzJYAQrN5AGcaVfu88NabrczkgV8FrF4x1sVxKB7xSH
Enter fullscreen mode Exit fullscreen mode

Let’s wrap

Gill is a new JavaScript client library for interacting with the Solana blockchain. In this post, we discussed what Gill is, why it matters for Solana developers, and how it helps improve the developer experience. We also explored its key features, provided examples comparing it to Web3.js v2, and demonstrated how to create and mint an SPL token using Gill.

Overall, you'll find that Gill offers a great developer experience when working with Web3.js v2.

Further Resources

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

If this post resonated with you, feel free to hit ❤️ or leave a quick comment to share your thoughts!

Okay