Building an SPL-token-faucet on Solana

Maxim Piessen
Credix
Published in
4 min readDec 7, 2021

Faucet: spl-token-faucet.com

What is a token faucet and why do we need it?

Faucets are a must-have when developing dApps to test out token flows. A faucet allows you to receive an arbitrary amount of tokens; always coming from the same Mint. It thus allows you to send ‘free tokens’ to your users to test out your application on Solana testnet or devnet.

At Credix, we’ve built a credit marketplace on Solana. The marketplace is driven by the stable coin USDC. We have deployed our marketplace on Solana testnet and devnet. To test if everything works as expected, we need “fake USDC”. This USDC is provided by the usdc token faucet. However, this USDC faucet has two limiting factors:

  1. It’s only available on Solana testnet, which is there for the Solana team to test network updates, do stress testing, etc. We can thus not use USDC on Solana devnet; which is the go-to-network to test your applications.
  2. The faucet only allows you to get 5 USDC per IP. We want to give our test-users hundreds of thousands of USDC; not just 5.

Those two factors led us to building our own faucet that drops you as many DUMMY tokens as you want on localnet, devnet and testnet.

Start using the Faucet!

If you just want to use the faucet; go ahead! You can get DUMMY tokens on testnet and devnet in seconds: spl-token-faucet.com

If you want to use the DUMMY token in your dApp; the Mint’s PK is Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr on Solana testnet AND Solana devnet.

Using the query parameter token-name, you can customize the UI to your needs. E.g. if we send the faucet to our test-users, we use the url spl-token-faucet.com?token-name=USDC to make the app look like a USDC faucet. If you want to mock the issuance of a TST token, you could send the url spl-token-faucet.com?token-name=TST

Build the faucet using Anchor

Anchor is a framework for Solana’s Sealevel runtime providing several convenient developer tools. It abstracts away a lot of repetitive RUST code to make your life easier. The final program for our faucet is not that complex and looks as follows:

use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{Mint, Token, TokenAccount},
};
declare_id!("4sN8PnN2ki2W4TFXAfzR645FWs8nimmsYeNtxM8RBK6A");#[program]
pub mod spl_token_faucet {
use super::*;
pub fn airdrop(ctx: Context<Airdrop>, mint_bump: u8, amount: u64) -> ProgramResult {
anchor_spl::token::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token::MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.mint.to_account_info(),
},
&[&[&"faucet-mint".as_bytes(), &[mint_bump]]],
),
amount,
)?;
Ok(())
}
}
#[derive(Accounts)]
#[instruction(mint_bump: u8, amount: u64)]
pub struct Airdrop<'info> {
#[account(
init_if_needed,
payer = payer,
seeds = [b"faucet-mint".as_ref()],
bump = mint_bump,
mint::decimals = 6,
mint::authority = mint
)]
pub mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = payer,
associated_token::mint = mint,
associated_token::authority = payer
)]
pub destination: Account<'info, TokenAccount>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
}

Let’s highlight a few important things:

in the Airdrop struct (bottom); we initialise two important accounts if they don’t exist yet on chain (we use the super handy init_if_needed for this check): The mint and the destination accounts.

  1. The mint account is a Token Program-owned Mint account and its authority is the mint account itself. How does this work? The mint account is an account owned by the program, its address being a PDA (program derived address) using the string "faucet-mint" as seed. By making the mint’s PDAthe authority of the mint account itself; new tokens can only be minted (or burned) within the program. We thus don’t have to store; nor pass a key-pair every time we want to mint new DUMMY tokens to a token account.
  2. The destination account is a Token Program-owned Token account and its authority is the system account of the person requesting the tokens (i.e. your account if you’re requesting DUMMY tokens).

Let’s go back to the top of the program. There we implemented the airdrop function. This function gets passed the context which contains the accounts it needs to access (previous part we discussed). It then initiates a CPI (cross program invocation) to mint tokens to the destination account. As the mint authority is the mint's PDA , the program can sign for this transaction without the need for a private key.

Deployment, testing & contributing

All the instructions for deployment and testing can be found in our Github repo. Contributions are more than welcome!

About Credix

CREDIX is a decentralized credit platform that gives borrowers in emerging countries access to previously untapped capital. Our blockchain protocol provides credit lines to high-quality borrowers with an attractive yield for investors globally.

Be sure to pay us a visit at credix.finance and to follow us on twitter to stay updated.

--

--

Maxim Piessen
Credix
Editor for

CTO @ Credix —Building the future of global credit markets | DeFi — Blockchain — AI — Photography | Twitter: @PiessenMaxim