This project provides a sample Solana implementation of a delayed FX remittance smart contract.
It allows the exchange of tokens of currency A
to be swapped for tokens of currency B
when certain conditions are met,
i.e. the exchange rate reaches an upper or lower limit, or the validity period for the swap has been reached. This allows users
to optimize for a more opportune FX rate instead of settling for the current market rate.
The program provides a Solana
smart contract implementation, i.e. a program
, & a CLI
tool to interact with the FX program.
Liquidity is provided as a static exchange rate for demo purposes, but can be provided by an external exchange rate oracle, e.g. ChainLink.
-
Install the required platform dependencies:
sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang cmake make libprotobuf-dev protobuf-compiler
-
Install the Solana CLI tools
- e.g.
sh -c "$(curl -sSfL https://release.solana.com/v1.10.31/install)"
- e.g.
-
Install the Rust toolchain
- e.g.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- e.g.
-
Install the SPL token CLI
- e.g.
cargo install spl-token-cli
- e.g.
-
Note: optionally a Dockerfile is provided for a ready to build and use development environment (give it plenty of resources!)
docker build . --tag m10-fx-solana:latest -f ./Dockerfile
docker run --name m10-fx-solana --rm -it -v "$(pwd)":/m10-fx-solana m10-fx-solana
- Use screen to run test-ledger in the background after creating your key
screen -dmS test-ledger solana-test-validator
use the cargo
tool to build the CLI
:
cargo build --release --bin m10-fx-solana-cli
or run it directly using cargo run
, e.g.
cargo run --release --bin m10-fx-solana-cli
Set the cluster to a config of your choice, e.g. for local development:
solana config set -ul
Create a new keypair (~account) for the test-ledger:
solana-keygen new -o ~/.config/solana/id.json
Launch a local Solana test cluster in the directory of your choice using: In a seperate window, launch a local Solana test cluster in the directory of your choice using:
solana-test-validator
Fund the test-ledger account:
solana airdrop 10
Using the cargo
tool, run
cargo build-bpf
which provides the instructions for deployment after compiling successfully, e.g.
export FX_PROGRAM_KEY=$(solana address -k ./keys/program.key)
solana airdrop 10 $FX_PROGRAM_KEY # To fund the program
solana program deploy ./target/deploy/m10_fx_solana.so -k ./keys/program.key
We'll need to set up a few identities before we interact with the program. We'll need to set up two tokens: SAR
& IDR
,
representing the Saudi Riyal
& the Indonesian Rupiah
respectively. For that we'll need to create 2 Mint accounts in the spl-token program.
For each, we'll need to create a liquidity provider account & provide them with a sufficient supply of tokens.
The code in the program is set up to use the keys in the keys
directory.
export IDR_MINT=$(solana address -k ./keys/idr_mint.key) && \
export IDR_LIQUIDITY=$(solana address -k ./keys/idr_liquidity.key) && \
export SAR_MINT=$(solana address -k ./keys/sar_mint.key) && \
export SAR_LIQUIDITY=$(solana address -k ./keys/sar_liquidity.key) && \
export ALICE=$(solana address -k ./keys/alice.key) && \
export BOB=$(solana address -k ./keys/bob.key)
# Generate IDR mint (ignore the "Refusing to overwrite" warnings, those are expected for idempotent behavior)
solana-keygen new --outfile ./keys/idr_mint.key --no-bip39-passphrase
spl-token create-token --decimals 2 ./keys/idr_mint.key --mint-authority ./keys/idr_mint.key
solana airdrop 10 $IDR_MINT
# Generate IDR liquidity holding
solana-keygen new --outfile ./keys/idr_liquidity.key --no-bip39-passphrase
spl-token -v create-account --owner ./keys/idr_liquidity.key $IDR_MINT -- ./keys/idr_liquidity.key
spl-token mint --mint-authority ./keys/idr_mint.key $IDR_MINT 1000000 $IDR_LIQUIDITY
solana airdrop 10 $IDR_LIQUIDITY
# Generate SAR mint
solana-keygen new --outfile ./keys/sar_mint.key --no-bip39-passphrase
spl-token create-token --decimals 2 ./keys/sar_mint.key --mint-authority ./keys/sar_mint.key
solana airdrop 10 $SAR_MINT
# Generate SAR liquidity holding
solana-keygen new --outfile ./keys/sar_liquidity.key --no-bip39-passphrase
spl-token -v create-account --owner ./keys/sar_liquidity.key $SAR_MINT -- ./keys/sar_liquidity.key
spl-token mint --mint-authority ./keys/sar_mint.key $SAR_MINT 10000000 $SAR_LIQUIDITY
solana airdrop 10 $SAR_LIQUIDITY
After that we'll create our two customers, Alice
& Bob
, who will be attempting to exchange between IDR
& SAR
respectively.
# Create an IDR account for Alice
solana-keygen new --outfile ./keys/alice.key --no-bip39-passphrase
spl-token -v create-account --owner ./keys/alice.key $IDR_MINT -- ./keys/alice.key
spl-token mint --mint-authority ./keys/idr_mint.key $IDR_MINT 5000000 $ALICE
solana airdrop 1 $ALICE
# Create a SAR account for Bob
solana-keygen new --outfile ./keys/bob.key --no-bip39-passphrase
spl-token -v create-account --owner ./keys/bob.key $SAR_MINT -- ./keys/bob.key
solana airdrop 1 $BOB
Using the CLI
a user can interact with the fx program. In order for Alice
to Initiate
the contract, she'll need to
invoke the following command:
cargo run --release --bin m10-fx-solana-cli -- initiate \
--signer ./keys/alice.key \
--amount 100000 \
--from $ALICE \
--to $BOB \
--margin 0.10 \
--payer ~/.config/solana/id.json \
--valid-for 30
This proposes an FX swap of 1000.00 SAR
from Alice
's account to the equivalent in IDR
for Bob
. The swap will take place when
either an optimal rate is found or the validity period, defined by valid-for
, has been exceeded. In this case, the swap will run for a maximum of 60 seconds
unless the FX rate moves more than 10% from the current rate, i.e. decreases by or increases by 10%. The payer
argument defines which account
will pay the required sol
fees for the transactions. The signer
argument indicates Alice
invokes the contract.
cargo run --release --bin m10-fx-solana-cli -- initiate --signer ./keys/alice.key -a 100000 -f $ALICE -t $BOB --margin 0.10 --payer ~/.config/solana/id.json --valid-for 30
Running `target/debug/m10-fx-solana-cli initiate --signer ./keys/alice.key -a 100000 -f C7UhfEFxFDUYYai4aEvvUm2Ubx6hqVzX2J6P1jPQjL6Z -t AYPFrwvbJ1RQHnn1xMyHYDqJLghmppEcYFwAo6V6oCig --margin 0.10 --payer ~/.config/solana/id.json --valid-for 30`
Initiate { signer: "./keys/alice.key", payer: "~/.config/solana/id.json", from: C7UhfEFxFDUYYai4aEvvUm2Ubx6hqVzX2J6P1jPQjL6Z, to: AYPFrwvbJ1RQHnn1xMyHYDqJLghmppEcYFwAo6V6oCig, amount: 100000, margin: 0.10, valid_for: Some(30) }
Current exchange rate 0.0002509788173878124686276478
Set margin to 0.10. Limits: [0.0002258809356490312217648830, 0.0002760766991265937154904126]
Created account 5VroT7EPENrznkptphxfH2K3T5tLqH7nDsHNBKTbSzKp with 1000 funds
Created FX account 6QCSzK56UKzDxruzgU81XzPEf4PpVMTTrp7bsfCRBPhp
The initiate
command has created an FX account
(6QCSzK56UKzDxruzgU81XzPEf4PpVMTTrp7bsfCRBPhp
) which will allow executing the contract it contains.
The contract can be pinged by using the execute
command using the SAR liquidity provider
key.
cargo run --release --bin m10-fx-solana-cli -- execute \
--fx-account 6QCSzK56UKzDxruzgU81XzPEf4PpVMTTrp7bsfCRBPhp \
--liquidity ./keys/sar_liquidity.key \
--payer ~/.config/solana/id.json
This tries to execute
the fx program with data stored in the fx-account
. The address of the account is printed at the end of the initiate
command above.
It needs permission to provide liquidity for the SAR
token using the liquidity
argument. Again, the payer
argument defines which account
will pay the required sol
fees for the transactions.
cargo run --release --bin m10-fx-solana-cli -- execute --fx-account 6QCSzK56UKzDxruzgU81XzPEf4PpVMTTrp7bsfCRBPhp --liquidity ./keys/sar_liquidity.key --payer ~/.config/solana/id.json
Running `target/debug/m10-fx-solana-cli execute --fx-account 6QCSzK56UKzDxruzgU81XzPEf4PpVMTTrp7bsfCRBPhp --liquidity ./keys/sar_liquidity.key --payer ~/.config/solana/id.json`
Execute { fx_account: 6QCSzK56UKzDxruzgU81XzPEf4PpVMTTrp7bsfCRBPhp, liquidity: "./keys/sar_liquidity.key", payer: "~/.config/solana/id.json" }
Successfully executed FX swap
In case of a longer duration contract, the execute
command will continue polling every 15 seconds until the program has completed.