This program and it's associated client and blink permit users to register for a token presale.
UPDATE (2024-07-17): Think blink is now complete but untested, testing.py
has been updated to also install the blink.
UPDATE (2024-07-16): It occurred to me that deploying without the program's keypair would result in errors when trying to invoke, I have removed ./install.sh
and replaced it with testing.py
that will setup a test-validator and populate tests/fixtures
and replace the program id in lib.rs with one generated by the script using sed
, this script will terminate the test validator after running in a somewhat in-elegant manner (invoking pkill) be wary of this if you have another test-validator running on your system.
The reason for populating fixtures is there seems to be some limitations using CPIs to the system program when using solana-program-test
. Testing creating associated token accounts results in empty accounts, although the test succeeds, as the associated token program calls the system program with invoke-signed
to create token accounts, tests will be updated to reflect this shortly.
testing.py
is mostly only for testing utilising cargo test
, although it does compile both the CLI and the program with the generated program id for further testing purposes. It will not remove the test-pid.json
so if you wish to test functionality on a your own with solana-test-validator
you may do so by redeploying the program to your selected network. As the program will already be compiled all that is required is running solana program deploy ./program/target/fsp_wl.so --program-id test-pid.json
from the project root directory.
This program allows for a whitelist-gated token sale. It supports both spl_token and spl_token_2022 accounts and is intended to be as feature-rich as possible while enabling a large range of customization options.
The program is also optimised for parallel execution to avoid the typical bottlenecks: when registration begins and the token sale itself, these bottlenecks are caused by writes to the program's account state which may only occur once per block. This program avoids these bottlenecks by enabling the seller to preload tokens into the ticket accounts so that when a token sale begins transfers are made from the ticket account to the user's token account and SOL is transferred from the user wallet to the ticket account's wallet. This avoids writes to the same account. The program is able to recognise whether the ticket account has been pre-loaded, if ticket accounts are not pre-loaded the program will transfer from the token vault instead. A seller can then sweep the accounts after the token sale has completed, all SOL and rent will be transferred into the designated treasury account.
A seller may also define whether or not to permit users to register for the whitelist or add them manually by setting the flag allow_registration
.
When set to false, users will not be able to register, this may also be used to freeze registration for whatever purpose.
A seller can set timestamps to automatically commence registration or token sales, or manually trigger these themselves using the StartRegistration
or StartTokenSale
instructions.
It should be noted that providing a duration for each phase of the sale is optional, but is not recommended for the token sale, as a user will not be able to
withdraw tokens/sol unless all tokens are distributed. There does however, exist a workaround, where a seller may terminate the user's ticket using the RemoveUser
instruction to "burn" a ticket and transfer all tokens and sol back to themselves.
A seller may also amend the times and durations, however attempting to amend a time after it's original value has elapsed will throw an error.
A seller may set a whitelist size and the client will check for the number of registered users via a call to the rpc method get_program_account
, the client will
search for accounts associated with the whitelist. I may implement this search to be multi-threaded in a future release and improve the array traversal, but for
the purpose of this SOW this should be suitable.
Deployment of this program costs approximately 2.61 SOL.
Initialisation of a whitelist has a negligible costs.
Please ensure you have enough SOL in your wallet on the respective network (testnet/devnet/mainnet-beta).
To begin please clone this repo by copying:
git clone https://github.com/serfrae/fsp-whitelist
After cloning the repo navigate into it:
cd fsp-whitelist
To setup the CLI and deploy the program, an installation script has been provided for your convenience,
please ensure to change the wallet addresses to the path of the wallet you wish to use for the program-id and mints.
The script will automatically utilise the wallet found at $XDG_CONFIG_HOME/solana/id.json
as the authority.
After you have edited the script with your desired addresses/wallets, ensure that it is executable using:
chmod +x install.sh
then install by using:
./install.sh
at the top-level directory to begin deployment and installation of the CLI. The script will not setup the blinks, as of this writing they are not tested.
After installation the CLI can be invoked from the command line with:
fsp-wl
The CLI will automatically utilise the wallet address at $XDG_CONFIG_HOME/solana/id.json
when being invoked. As the program itself is quite feature-full and this was developed in two (2) days I have not had time to enable reading from configs or pointing to custom wallet
addresses from within the CLI, although I may implement this at a later date.
fsp-wl --help
will provide information on each command and subcommand
fsp-wl init <MINT> <TREASURY> <PRICE> <BUY_LIMIT> <WHITELIST_SIZE> <ALLOW_REGISTRATION> [REGISTRATION_START_TIME] [REGISTRATION_END_TIME] [SALE_START_TIME] [SALE_END_TIME]
MINT
: The public key of the mint for the token that is to be sold, this field is used for all commands, the whitelist address is derived from it.TREASURY
: The target for both SOL and token withdrawals when burning tickets.PRICE
: Price in SOL per tokenBUY_LIMIT
: Number of tokens a ticket is allowed to purchase.WHITELIST_SIZE
: The size of the whitelist, i.e. how many users can register for the token sale.ALLOW_REGISTRATION
: (values:"true" / "yes" / "y", "false" / "no" / "n"
) Permit users to register for the whitelist.- [optional]
REGISTRATION_START_TIME
(format: YYYY-MM-DD HH:MM:SS): When registration commences, a 0 value will allow immediate registration- Requires flag
--registration-start-time
- Requires flag
- [optional]
REGISTRATION_END_TIME
(format: YYYY-MM-DD HH:MM:SS): When registration ends, a 0 value means that registration does not end.- Requires flag
--registration-end-time
- Requires flag
- [optional]
SALE_START_TIME
(format: YYYY-MM-DD HH:MM:SS): When the token sale starts, a 0 value means that the sale immediately starts.- Requires flag
--sale-start-time
- Requires flag
- [optional]
SALE_END_TIME
(format: YYYY-MM-DD HH:MM:SS): When the token sale ends, a 0 value means that registration does not end. (WARNING: NOT RECOMMENDED).- Requires flag
--sale-end-time
- Requires flag
fsp-wl user add <MINT> <USER>
fsp-wl user remove <MINT> <USER>
add
: Add a user to the whitelist associated with the provided mint whereMINT
is the mint address of the token being sold andUSER
is the user's wallet address.remove
: Remove a user from the whitelist and claim rent whereMINT
is the mint address of the token being sold andUSER
is the user's wallet address.
fsp-wl deposit <MINT> <AMOUNT>
- Deposits tokens into the whitelist vault, where
MINT
is the mint address of the token being sold andAMOUNT
is the amount of tokens to transfer into the vault.
fsp-wl withdraw <MINT>
- Withdraws tokens from the vault, tokens may not be withdrawn from the vault after the token sale begins.
MINT
is the mint address of the token being sold.
fsp-wl amend size <MINT> <SIZE>
- Amend the whitelist size allowing for more users to register for the token sale. Where
MINT
is the mint address of the token being sold andSIZE
is the number of users permitted to register for the token sale.
fsp-wl amend times [REGISTRATION_START_TIME] [REGISTRATION_END_TIME] [SALE_START_TIME] [SALE_END_TIME]
Note: Each argument must be provided with a flag.
fsp-wl start registration <MINT>
- Commences registration upon successful transaction, will also set the
allow_registration
field totrue
and theregistration_start_timestamp
to the current unix timestamp in the whitelist account's state.MINT
is the mint address of the token for sale.
fsp-wl start sale <MINT>
- Commences the token sale upon successful transaction, will also set
sale_start_timestamp
to the current unix timestamp in the whitelist's account state.MINT
is the mint address of the token for sale.
fsp-wl allow-register <MINT> <ALLOW>
- (
"true" / "yes" / "y", "false / "no" / "n"
) Sets theallow_registration
flag in the whitelist's account state.MINT
is the mint address of the token for sale andALLOW
is one of the values provided where"true"
,"yes"
and"y"
all enable registration while"false"
,"no"
and"n"
disables registration. This may also be used to freeze currently ongoing registrations but may cause errors.
fsp-wl burn single <MINT> <USER>
- Burns a single ticket and retrieves the tokens and SOL associated with the ticket. Tokens and SOL are sent to the treasury address defined in the whitelist's state.
MINT
is the mint address of the token for sale,USER
is the wallet address of the user who purchased the ticket.
fsp-wl burn bulk <MINT>
- Burns all tickets associated with a whitelist and retrieves the tokens and SOL associated with those tickets. Tokens and SOL are sent to the treasury address defined in the whitelist's state.
MINT
is the mint address for the token for sale.
fsp-wl close <MINT> [RECIPIENT]
- Terminates the whitelist and closes all associated accounts reclaiming and tokens and rent to the designated recipient, if no recipient is provided, tokens and rent are transferred to the authority / caller.
MINT
is the mint address of the token for saleRECIPIENT
takes a flag---recipient
to define the address of the account to which rent and tokens should be sent. A whitelist may not be terminated until the token sale has ended.
fsp-wl info whitelist <MINT>
- Retrieves information about the whitelist, mostly the parameters that are set in the whitelist's state.
MINT
is the mint address of the token for sale.
fsp-wl info user <MINT> <USER>
- Retrieves information about a user's ticket.
MINT
is the mint address of the token for sale,USER
is the wallet address of the user you wish to retrieve ticket information about. An error means there is no ticket associated with the provided user wallet address.
There are only four (4) commands relevant to a whitelist subscriber/buyer in the CLI these being:
1. fsp-wl register <MINT>
2. fsp-wl unregister <MINT>
3. fsp-wl token buy <MINT> <AMOUNT>
3. fspw-wl info whitelist/user <PUBKEY> <MINT>
fsp-wl register <MINT>
- Register for the token sale, creating a ticket.
MINT
is the mint address of the token for sale, information about a created ticket can be retrieved usingfsp-wl info user
- see below.
fsp-wl unregister <MINT>
- Unregister for the token sale, burning a ticket. Rent is reclaimed and transferred back to the payer, if the user is the payer, the to the user, else it is transferred to the whitelist's authority.
MINT
is the mint address of the token for sale.
fsp-wl buy <MINT> <AMOUNT>
- Buy tokens, users may only buy tokens if they posses a ticket i.e. are registered to the whitelist, a user may not purchase more tickets than the buy limit / their ticket allowance, doing so will result in transaction failure.
MINT
is the mint address of the token being sold,AMOUNT
is the amount of tokens a user wishes to purchase.
fsp-wl info whitelist <MINT>
- Retrieves information about the whitelist, mostly the parameters that are set in the whitelist's state.
MINT
is the mint address of the token for sale.
fsp-wl info user <MINT> <USER>
- Retrieves information about a user's ticket.
MINT
is the mint address of the token for sale,USER
is the wallet address of the user you wish to retrieve ticket information about. An error means there is no ticket associated with the provided user wallet address.
The blink will run on a localhost by default on port :8080, it can be invoked with wl-blink
after running the testing.py
script, or after building and installing yourself by navigating to the directory and using:
cargo build --release && cargo install --path .
wl-blink <MINT> [URL] [HOST] [CONFIG]
MINT
: The address of the token for sale.- (Optional)
URL
: RPC identifier, values: "t" or "testnet" for testnet, "d" or "devnet" for devnet, "m" or "mainnet" for mainnet-beta, "l" or "local" for localhost, any other string passed can be used if you have a custom RPC you would like to use instead. Default: whatever is set in your config- Requires flag:
--url
or-u
- Requires flag:
- (Optional)
[PORT]
: The exposed port. Default::8080
- Requires flag:
--port
or-p
- Requires flag:
- (Optional)
[CONFIG]
: A full path to your solana config file, defaults to the config found in the config directory- Requires flag:
--config
or-c
- Requires flag: