Solana Token Swap
_repo: https://github.com/CacheMeIfYouCan1/solanatokenswap_
Table of Contents:
Introduction:
This is a Proof of Concept for swapping custom tokens on the Solana blockchain for SOL and
vice-versa without an exchange. The system provides the opportunity to quickly
perform token swaps directly on the blockchain, eliminating the barriers and complexities typically associated with centralized exchanges.
It is part of a different project, which will not be disclosed yet. But since the swap system could easily be adapted to serve other purposes, I decided to make it publicly available.
It consists of two handlers and two smart contracts written in Rust as well as two monitoring applications written in Python.
The python applications continuously monitor the Solana blockchain for relevant swap transactions.
Upon detecting a swap transaction, they trigger a corresponding Rust handler that interacts with a smart contract to perform the token swap.
The current Python applications are only used for demonstation and are not secure and the code is not clean. They were created quickly to demonstrate the functionality of the swap system. If the Solana Token Swap system needs to be included into a productive environment, it is necessary to create a custom solution.
Addidtional considerations regarding security:
This system acts as a proof of concept, that means it is designed to run locally, without generating costs. Therefore the private keys are stored locally and passed as arguments to the handlers. This is not secure. Do not use this method with funded wallets on the mainnet!
There are secure ways to manage private keys for these scenarios, some of them are Cloud KMS solutions or private Keystore servers, where the keys are encrypted. Storing private keys in environmental variables can also be done safely if its done correctly.
Please feel free to contact me, if assistance in developing a custom solution and/or customization of the given system is needed.
Setup:
Clone the project:
initially we need to clone the gihub repository by running:
$ git clone https://github.com/CacheMeIfYouCan1/solanatokenswap/
Rust base configuration
Rust needs to be correctly set up for developing smart contracts on the solana blockchain. To ensure this, build-essentials need to be installed.
to do this, update your system and run:
$ sudo apt install -y build-essential curl
Afterwards we need to install rustup. Rustup is a tool to manage Rust versions and components. To Install rustup, run:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Finally we can source the environment:
$ source $HOME/.cargo/env
Now we can check if the installation was successfull:
$ rustc --version
Solana configuration
First we need to install the solana CLI. To do this we can run following command:
$ sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
afterwards we need to update the PATH:
$ echo 'export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc
Now we can check if it is properly installed:
$ solana --version
If solana is properly configured, we need to install the rustup component
$rustup component add rust-src
and also install spl-token:
$ cargo install spl-token-cli
Python configuration
Make sure Python is installed and properly configured. Afterwards we need to install virtualvenv by running the following command.
$ pip install virtualenv
now we can create a virtual environment for the monitoring systems and source it by running this from /monitoring/
$ python3 -m venv solanatokenswapVenv
$ source solanatokenswapVenv/bin/activate
and finally install the required libraries by running following command
$ pip install -r requirements.txt
compilation:
To compile all parts of this system you can run following command from solanatokenswap/ directory:
$ bash build.sh
Test validator and wallet configuration
To run the local demo, we need to configure the local environment and propagate the global variables used in the monitoring scripts and passed to the handlers.
To do so, we need to start the test validator first:
$ solana-test-validator
in addition to that we need to configure solana to use the test validator:
$ solana config set --url http://127.0.0.1:8899
now we need to create a wallet, which will manage the token, make it the main wallet and airdrop it some SOL:
$ solana-keygen new --outfile ~/token-manager.json
$ solana config set --keypair ~/token-manager.json
$ solana airdrop 100
next we need a wallet which will act as a user which swaps SOL to our token and our token to SOL. This wallet also needs some SOL.
$ solana-keygen new --outfile ~/user-wallet.json
$ solana airdrop 100 <WALLET_PUBLIC_KEY>
next we need a wallet which will act as a comission wallet, collecting the fees.
$ solana-keygen new --outfile ~/comission-wallet.json
$ solana airdrop 100 <WALLET_PUBLIC_KEY>
now we are set to create our demo-token:
$ spl-token create-token --owner ~/token-manager.json
after creating a token, we need to create an associated token account, which will hold our token-liquidity:
$ spl-token create-account <TOKEN_ADDRESS> --owner ~/token-manager.json
finally we have to mint some tokens as a base supply:
$ spl-token mint <TOKEN_ADDRESS>
deploying smart contracts:
At this point we need to deploy our smart contracts. Our system relies on two smart contracts which are :
send_sol:
This smart contract is called if a client swaps tokens back to SOL, or if fees are paid.
It can be deployed with following command from ./smart_contracts/send_sol/target/deploy/:
$ solana program deploy send_sol.so
send_token:
This smart contract is called if a client swaps SOL to tokens
It can be deployed with following command from ./smart_contracts/send_token/target/deploy/:
$ solana program deploy send_token.so
important note: make sure to save the program ID's
final steps
Now we are finished and can populate the global variables with the values from our previous setp. The global variables are stored in /config.json and are used by our proof of concept. Below you can see an example config.json with the values that have been created for the setup used to create this documentation. The commission_rate and the swap_rate can be individually chosen and are not derived from the setup.
fyi: The setup runs fully local, no actual secrets were shared
{
"send_sol_handler": "/home/main/solanatokenswap/handling/send_sol_handler/target/debug/send_sol_handler",
"send_token_handler": "/home/main/solanatokenswap/handling/send_token_handler/target/debug/send_token_handler",
"send_sol_program_id": "BPP6ULzktGPJJNdXJAjv5o1uAAeUgFYWVEeZisyCDJmo",
"send_token_program_id": "u8STJAr3h6hqb7XnYMiNqvzJrULgzfMxLLftGmCzGVy",
"keypair_file": "/home/main/token-manager.json",
"mint_account": "28wGsufhHpuQDBkeTXpNVLQz2zPUBoLc1gDpu7wBtejg",
"owner": "58ZEW3nzhg3CQ8xYrqavsV71r6ErKX7a4PyJrrxc4pbE",
"token_receiver_wallet": "3vSighS9MqPBcZJbYWnw8AWvYw3NnvY58nf9QbNmUPnr",
"sol_receiver_wallet": "58ZEW3nzhg3CQ8xYrqavsV71r6ErKX7a4PyJrrxc4pbE",
"commission_wallet": "58ZEW3nzhg3CQ8xYrqavsV71r6ErKX7a4PyJrrxc4pbE",
"token_acc_str": "28wGsufhHpuQDBkeTXpNVLQz2zPUBoLc1gDpu7wBtejg",
"rpc_client_str": "http://127.0.0.1:8899",
"ws_client_str": "ws://127.0.0.1:8900",
"commission_rate": "0.1",
"swap_rate": "200"
}
Usage:
To swap SOL for the custom, token using our local ledger, we will first start the two monitoring scripts, in two seperate terminals:
~/solanatokenswap/monitoring $ solanatokenswapVenv/bin/python3 scripts/token_monitor.py
~/solanatokenswap/monitoring $ solanatokenswapVenv/bin/python3 scripts/sol_monitor.py
before any transactions happen, the monitoring should look like this:
![[sts\_init.png]](/projects/4/postPage/sts_init.png)
Now we can check the initial SOL and token balances of our user wallet:
![[sts\_wallet\_init\_balance.png]](/projects/4/postPage/sts_wallet_init_balance.png)
To swap SOL for our custom token, we can transfer SOL from our user wallet to our token management wallet. The monitoring systems will register the transaction and execute the corresponding handlers, which will call the smart contracts to perform the token swap.
![[sts\_sol\_tx\_user\_wallet.png]](/projects/4/postPage/sts_sol_tx_user_wallet.png)
![[sts\_sol\_tx\_monitoring.png]](/projects/4/postPage/sts_sol_tx_monitoring.png)
after the transactions are finished we can check the new balances of our user wallet:
We can also swap our custom token to SOL by sending the custom token to the associated token account of our token management wallet. The monitoring systems will also register this transaction and perform the swap by calling the corresponding smart contracts.
![[sts\_tx\_token\_user\_wallet.png]](/projects/4/postPage/sts_tx_token_user_wallet.png)
![[sts\_updated\_monitoring\_token\_tx.png]](/projects/4/postPage/sts_updated_monitoring_token_tx.png)
afterwards we can also check the updated balances of our user wallet:
Documentation:
Structure
solanatokenswap/
|- build.sh
|- handling/
| |- send_sol_handler/
| | |- Cargo.toml
| | |- src/
| | | |- main.rs
| |- send_token_handler
| | |- Cargo.toml
| | |- src/
| | | |- main.rs
|- smart_contracts/
| |- send_sol/
| | |- Cargo.toml
| | |- src/
| | | |- lib.rs
| |- send_token/
| | |- Cargo.toml
| | |- src/
| | | |-lib.rs
|- monitoring/
| |- requirements.txt
| |- scripts/
| | |- config.json
| | |- token_monitor.py
| | |- sol_monitor.py
| | |- resources/
| | | |- transaction_processing.py
| | | |- transaction_handling.py
| |- solanatokenswapVenv/
| | |- (...)
Monitoring
The monitoring system is used to demonstrate the functionality of this Proof of concept by managing the interactions between the Rust handlers and the smart contracts. It consists of four components, to monitor the transactions, as well as hantle swapping from SOL to the custom token, as well as swapping the custom token to SOL.
Initialisation:
sol_monitor.py and token_monitor.py are used to initialize the monitoring systems. They import the TransactionHandling class and are structured as following:
sol_monitor.py:
async def main():
try:
with open('config.json', 'r') as file:
config = json.load(file)
transaction_handling = TransactionHandling(
config["sol_receiver_wallet"],
"sol_to_token"
)
listener_task = asyncio.create_task(transaction_handling.listen_for_transactions())
processor_task = asyncio.create_task(transaction_handling.process_transactions())
await asyncio.gather(listener_task, processor_task)
except Exception as err:
print("ann error occured: ", err)
asyncio.run(main())
This script initializes the monitoring and processing of swap transactions from Solana (SOL) to a custom token. It loads configuration from a config.json file, including the receiver wallet and transaction type, then creates a TransactionHandling object.
Two asynchronous tasks are created:
- listen_for_transactions(): Monitors incoming transactions.
- process_transactions(): Processes the transactions.
Both tasks run concurrently using asyncio.gather(), allowing real-time handling of transactions.
token_monitor.py:
async def main():
try:
with open('config.json', 'r') as file:
config = json.load(file)
transaction_handling = TransactionHandling(
config["token_receiver_wallet"],
"token_to_sol"
)
listener_task = asyncio.create_task(transaction_handling.listen_for_transactions())
processor_task = asyncio.create_task(transaction_handling.process_transactions())
await asyncio.gather(listener_task, processor_task)
except Exception as err:
print("an error occured: ", err)
asyncio.run(main())
This script works similar to sol_monitor.py, but instead of initialising the monitoring and processing of swap transactions from SOL to a custom token, it initializes the monitoring and processing of swap transactions from a custom token to SOL.
Transaction Handling:
transaction_handling.py:
Handling of the transaction is managed through the TransactionHandling class. This involves listening for incoming transactions, fetching the transaction details as well as processing the swap, based on the given transaction type. It takes the receiver wallet and the transaction types as arguments.
__innit__():
def __init__(self, receiver_wallet, transaction_type):
self.receiver_wallet = receiver_wallet
self.transaction_type = transaction_type
self.transaction_queue = asyncio.Queue()
self.rate_limit = asyncio.Semaphore(5)
with open('config.json', 'r') as file:
config = json.load(file)
self.client = Client(config["rpc_client_str"])
self.socket = config["ws_client_str"]
self.send_token_program_id = config["send_token_program_id"]
self.send_sol_program_id = config["send_sol_program_id"]
self.last_tx_id = "0";
Initializes the class with essential parameters and sets up internal resources.
Parameters:
receiver_wallet: The Solana wallet address that will receive the transaction, initializing the swap.
transaction_type: Defines the transaction direction, either "sol_to_token" or "token_to_sol".
Key Actions:
Creates an asyncio.Queue (transaction_queue) to store incoming transactions, sets up a rate limit using an asyncio.Semaphore to manage concurrent requests (maximum of 5 simultaneous requests) and Loads configuration from config.json to retrieve:
RPC client connection string (rpc_client_str).
WebSocket URL (ws_client_str) for listening to transaction logs.
Program IDs for token and SOL transactions (send_token_program_id, send_sol_program_id).
Initializes the last_tx_id to track the last processed transaction.
listen_for_transactions():
async def listen_for_transactions(self):
async with websockets.connect(self.socket) as websocket:
subscription_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "logsSubscribe",
"params": [
{
"mentions": [f"{self.receiver_wallet}"]
},
{
"commitment": "finalized"
}
]
}
await websocket.send(json.dumps(subscription_request))
while True:
response = await websocket.recv()
self.tx_signature_raw = json.loads(response)
print("Transaction log received:", self.tx_signature_raw)
await self.transaction_queue.put(self.tx_signature_raw)
print(f"Transaction added to queue: {self.tx_signature_raw}")
Listens for incoming transactions involving the receiver wallet by subscribing to the blockchain logs.
Key Actions:
This function runs indefinitely in an asynchronous loop to monitor real-time transactions and Continuously listens for incoming log data, by establishing a WebSocket connection to the configured socket URL, sending a subscription request to the blockchain logs and filtering for transactions involving the receiver_wallet. When a transaction is detected, it adds the transaction details (tx_signature_raw) to the transaction_queue.
process_transactions():
async def process_transactions(self):
while True:
self.tx_signature_raw = await self.transaction_queue.get()
print(f"Dequeued transaction: {self.tx_signature_raw}")
await self.rate_limit.acquire()
try:
await self.fetch_transaction_data_raw()
finally:
self.rate_limit.release()
self.transaction_queue.task_done()
Continuously fetches transactions from the queue and processes them with rate-limiting controls.
Key Actions:
This function runs in an infinite loop, ensuring that the system continuously processes transactions as they are received. It asynchronously dequeues a transaction from transaction_queue, Acquires a Semaphore to ensure no more than 5 transactions are processed at once. Calls fetch_transaction_data_raw() for each transaction in the queue and releases the rate limit (Semaphore) after processing the transaction to allow the next one.
fetch_transaction_data_raw():
async def fetch_transaction_data_raw(self):
print(f"processing transaction: {self.tx_signature_raw}")
await asyncio.sleep(1)
if "method" in self.tx_signature_raw and self.tx_signature_raw["method"] == "logsNotification":
print("if = true")
tx_signature_str = self.tx_signature_raw["params"]["result"]["value"]["signature"]
print(f"Transaction Signature: {tx_signature_str}")
transaction_details = await self.fetch_transaction_details(tx_signature_str)
transaction_processing = TransactionProcessing()
if transaction_details != None:
if self.last_tx_id != tx_signature_str:
self.last_tx_id = tx_signature_str
if self.transaction_type == "sol_to_token":
await transaction_processing.process_sol_to_token(transaction_details, tx_signature_str, self.receiver_wallet)
if self.transaction_type == "token_to_sol":
await transaction_processing.process_token_to_sol(transaction_details, tx_signature_str, self.receiver_wallet)
else:
print("")
print("already done, skipping")
print("")
else:
print("")
print("transaction details could not be fetched, skipping")
print("")
else:
print("transaction is empty")
Processes a raw transaction log and extracts the relevant details for further processing.
Key Actions:
If the transaction is valid and contains a method (logsNotification), it extracts the transaction signature (tx_signature_str)
and then calls fetch_transaction_details() to retrieve the full transaction details.
After fetchung the full transaction details, it initializes the TransactionProcessing class to handle the specific
processing logic based on the transaction type. If the full transaction details can not be fetched, the processing is skipped.
Transaction Types:
sol_to_token: processes the transaction using process_sol_to_token().
token_to_sol: processes the transaction using process_token_to_sol().
Processing is skipped, if the transaction has already been processed (if the signature matches last_tx_id). This ensures that each transaction is processed only once, preventing duplication.
fetch_transaction_details():
async def fetch_transaction_details(self, tx_signature_str):
tx_signature = Signature.from_string(tx_signature_str)
for i in range(10):
transaction = self.client.get_transaction(tx_signature)
if transaction.value:
return transaction
await asyncio.sleep(1)
return None
Fetches detailed information for a given transaction using its signature.
Key Actions:
Converts the provided transaction signature (tx_signature_str) into a Signature object and a
ttempts to fetch the transaction details using the client (self.client.get_transaction(tx_signature)), retrying up to 10 times with a
1-second delay between each attempt. If transaction details are found, it returns the transaction; if not, it returns None after 10 attempts.
This ensures the system can handle potential network issues or delays when fetching data from the blockchain.
transaction_handling.py:
This class handles the processing of Solana transactions, including the conversion of SOL to tokens and tokens back to SOL. It uses data fetched from transaction logs and interacts with smart contracts to facilitate the conversion and manage commissions.
interaction with smart contracts is done by using subprocesses, calling external handlers (e.g., send_sol_handler, send_token_handler) for token transfers and SOL transfers.Commission rates and swap rates are applied to calculate the amounts to be sent or received. Transactions are skipped if they have already been processed (based on the transaction signature)
__init__():
The constructor initializes the TransactionProcessing class with transaction details and transaction signature. It also loads configuration settings from a config.json file, which are used to interact with the smart contracts and manage wallet addresses.
Key Actions:
Loads configuration settings from config.json, extracts transaction information like account keys and instructions and initializes necessary fields, such as the wallet addresses and program IDs.
async process_sol_to_token():
async def process_sol_to_token(self, sol_receiver_wallet):
if self.transaction_details and self.transaction_details.value:
for instruction in self.instructions:
sender_index = instruction.accounts[0]
sender_account = self.account_keys[sender_index]
commission_wallet = self.commission_wallet
sender_account_str = str(sender_account)
if sender_account_str != self.token_account_str:
if sender_account_str != sol_receiver_wallet:
pre_balances = self.transaction_details.value.transaction.meta.pre_balances
post_balances = self.transaction_details.value.transaction.meta.post_balances
lamports_sent = pre_balances[0]-post_balances[0]
sol_to_send_lamports_int = int(lamports_sentfloat(self.commission_rate))
tokens_to_send_lamports = lamports_sentint(self.swap_rate)
print("Token purchased, executing token transfer")
call_token_smartcontract = [
self.send_token_handler,
"--receiver", str(sender_account),
"--amount", str(tokens_to_send_lamports).lstrip('-'),
"--program-id-arg", self.send_token_program_id,
"--rpc-url-arg", self.rpc_client_str,
"--keypair-file", self.keypair_file,
"--mint-account-arg", self.mint_account,
"--owner-arg", self.commission_wallet
]
result_token = subprocess.run(call_token_smartcontract, capture_output= True, text= True)
print(f"result token tx: {result_token.stdout}")
print(f"{result_token.stderr}")
print("executing transfer of commission")
call_\sol_smartcontract = [
self.send_sol_handler,
"--receiver", self.commission_wallet,
"--amount", str(sol_to_send_lamports_int).lstrip('-'),
"--program-id-arg", self.send_sol_program_id,
"--rpc-url-arg", self.rpc_client_str,
"--keypair-file", self.keypair_file
]
result_sol = subprocess.run(call_sol_smartcontract, capture_output= True, text= True)
print(f"result sol tx: {result_sol.stdout}")
print(f" {result_sol.stderr}")
else:
print("")
print("Sender is receiver, skipping")
print("")
break
else:
print("")
print("token transaction, skipping")
print("")
else:
print("")
print("Transaction details not found after multiple retries, skipping")
print("")
Processes a transaction where SOL is sent and exchanged for tokens. If the transaction is valid and the sender is not the receiver, it executes a smart contract to transfer tokens. The method also ensures that a commission is sent to a specified commission wallet.
Key Actions:
Loops through the transaction's instructions to identify the sender andhecks if the sender is the receiver; if not, it calculates the amount of SOL to convert into tokens. Then executes a smart contract to transfer tokens using the send_token_handler and sends a commission to the commission wallet via another smart contract. If the sender is the receiver or if it’s already a token transaction, the transaction gets skipped.
async process_token_to_sol():
async def process_token_to_sol(self, token_receiver_wallet):
if self.transaction_details and self.transaction_details.value:
for instruction in self.instructions:
if instruction.accounts:
sender_index = instruction.accounts[0]
sender_associated_account_owner = self.account_keys[0]
for balance_meta in self.transaction_details.value.transaction.meta.pre_token_balances:
if balance_meta.account_index == sender_index:
sender_associated_account_owner = balance_meta.owner
break
else:
sender_associated_account_owner = None
for pre_balance_meta in self.transaction_details.value.transaction.meta.pre_token_balances:
if pre_balance_meta.account_index == sender_index:
pre_token_balance = pre_balance_meta.ui_token_amount.amount
break
else:
pre_token_balance = None
for post_balance_meta in self.transaction_details.value.transaction.meta.post_token_balances:
if post_balance_meta.account_index == sender_index:
post_token_balance = post_balance_meta.ui_token_amount.amount
break
else:
post_token_balance = None
if sender_associated_account_owner is not None:
lamports_sent = int(pre_token_balance) - int(post_token_balance)
sol_to_send_lamports_int = int(lamports_sent/int(self.swap_rate))
sol_to_send_lamports_str = str(sol_to_send_lamports_int)
sender_associated_account_owner_str = str(sender_associated_account_owner)
if sender_associated_account_owner != token_receiver_wallet:
print("Token sold: executing SOL transfer")
call_sol_smartcontract = [
self.send_sol_handler,
"--receiver", str(sender_associated_account_owner),
"--amount", sol_to_send_lamports_str,
"--program-id-arg", self.send_sol_program_id,
"--rpc-url-arg", self.rpc_client_str,
"--keypair-file", self.keypair_file
]
result_sol = subprocess.run(call_sol_smartcontract, capture_output= True, text= True)
print(f"result sol: {result_sol.stdout}")
print(f"error sol: {result_sol.stderr}")
break
else:
print("")
print("Sender is receiver, skipping")
print("")
break
else:
print("")
print("instruction is empty, skipping")
print("")
else:
print("")
print("token purchased, skipping")
print("")
else:
print("")
print("Transaction details not found after multiple retries.")
print("")
Processes a transaction where tokens are sold and converted back into SOL. It identifies the sender’s associated account and calculates the amount of SOL to transfer. A smart contract is executed to send the SOL to the sender’s wallet.
Key Actions:
Loops through the transaction’s token balances to determine the amount of tokens transferred and calculates the corresponding amount of SOL to be sent to the sender.
Then executes a smart contract to send the SOL to the sender. If the sender is the receiver or if no valid token balances are found, the transaction is skipped.
Handling
The Rust handler programs serve as the core components of the system, facilitating interactions with Solana's smart contracts to manage SOL-to-token and token-to-SOL transactions. They are responsible for constructing, signing, and sending Solana transactions that either convert SOL to tokens or vice versa.
send_sol_handler:
invoke_smart_contract():
fn invoke_smart_contract(
client: &RpcClient,
payer_keypair: &Keypair,
sol_recipient: Pubkey,
sol_quantity: SolQuantity) -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let program_id = opts.program_id_arg.parse().unwrap();
let accounts = vec![
AccountMeta::new(payer_keypair.pubkey(), true),
AccountMeta::new(sol_recipient, false),
AccountMeta::new_readonly(system_program::id(), false),
]; // Add relevant accounts
// Create the instruction
let instruction = Instruction {
program_id,
accounts,
data: sol_quantity.amount.to_le_bytes().to_vec(), // Convert the amount to bytes
};
// Create the transaction
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer_keypair.pubkey()));
println!("{}", payer_keypair.pubkey());
// Sign the transaction
let recent_blockhash = client.get_latest_blockhash().unwrap();
transaction.sign(&[payer_keypair], recent_blockhash);
// Send the transaction
let signature = client.send_and_confirm_transaction(&transaction).unwrap();
println!("Transaction signature: {:?}", signature);
Ok(())
}
}
This function constructs and sends a Solana transaction invoking a smart contract to transfer SOL from the payer to a recipient. It uses the provided RPC client, payer keypair, recipient’s public key, and amount of SOL to create and send the transaction.
Key Actions:
Parses command-line arguments using Opts and builds a transaction instruction to transfer SOL from the payer to the recipient. Then Signs the transaction with the payer's keypair and sends it to the Solana network afterwards prints the transaction signature upon successful submission.
Parameters:
client: The RPC client to interact with the Solana network.
payer_keypair: The payer’s keypair, which is used to sign the transaction.
sol_recipient: The recipient’s public key (Solana address) where the SOL will be sent.
sol_quantity: The amount of SOL to be transferred.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) on success, or an error if the transaction fails.
Tranaction Flow:
The program ID is parsed from command-line arguments.
The instruction is constructed with the relevant accounts and data (SOL amount).
A transaction is created, signed, and sent to the Solana network using the provided RPC client.
main():
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let rpc_url = opts.rpc_url_arg;
let client = RpcClient::new(rpc_url);
let payer_keypair = read_keypair_file(opts.keypair_file);
let amount_lamp = opts.amount;
let sol_recipient = Pubkey::from_str(&opts.receiver);
invoke_smart_contract(&client, &payer_keypair?, sol_recipient?, SolQuantity { amount: amount_lamp });
Ok(())
}
The entry point for the program. It parses the command-line arguments, sets up the RPC client, reads the payer's keypair from a file, and invokes the smart contract to transfer SOL to a recipient.
Key Actions:
Parses the command-line arguments using Opts and creates an RpcClient with the provided RPC URL. Reads the payer’s keypair from the specified file and calls invoke_smart_contract to initiate the SOL transfer.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) if the program executes successfully, or an error if any operation fails.
send_token_handler:
invoke_smart_contract():
fn invoke_smart_contract(
client: &RpcClient,
mint_account: Pubkey,
from_associated_token_account: Pubkey,
to_associated_token_account: Pubkey,
owner: Pubkey, //owner is the from wallet which owns the token acc which sends
recipient: Pubkey, //recipient is the sol wallet which owns the token acc which will receive balance
payer_keypair: &Keypair,
token_amount: TokenAmount) -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let program_id = opts.program_id_arg.parse().unwrap();
let accounts = vec![
AccountMeta::new(mint_account, false),
AccountMeta::new(from_associated_token_account, false),
AccountMeta::new(to_associated_token_account, false),
AccountMeta::new(owner, false),
AccountMeta::new(recipient, false),
AccountMeta::new(payer_keypair.pubkey(), true),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(TOKEN_PROGRAM_ID, false),
AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false),
]; // Add relevant accounts
println!("starting program");
let amount = token_amount.token_amount;
println!("Create the instruction");
// Create the instruction
let instruction = Instruction {
program_id,
accounts,
data: amount.to_le_bytes().to_vec(),
};
println!("successfully created instruction");
println!("createing tx");
// Create the transaction
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer_keypair.pubkey()));
println!("get latest blockhash");
// Sign the transaction
let recent_blockhash = client.get_latest_blockhash().unwrap();
println!("got blockhash");
println!("sign tx");
let signer = [payer_keypair];
println!("{}", payer_keypair.pubkey());
transaction.sign(&[payer_keypair], recent_blockhash);
println!("signed tx");
// Send the transaction
let signature = client.send_and_confirm_transaction(&transaction).unwrap();
println!("Transaction signature: {:?}", signature);
Ok(())
}
This function creates and sends a transaction that interacts with a Solana smart contract to transfer tokens from one associated token account to another. It is designed to handle transfers of a specified token between the owner (sender) and the recipient (receiver) on the Solana blockchain.
Key Actions:
Parses the command-line arguments using Opts and constructs a list of relevant accounts needed for the transaction, including the mint account, sender’s and recipient’s associated token accounts, and the payer's keypair. Creates a Solana transaction instruction with the appropriate accounts and data (token amount). Signs the transaction and sends it to the Solana network using the provided RPC client.
Parameters:
client: The RPC client used to interact with the Solana network.
mint_account: The public key of the token’s mint account, which specifies the token type being transferred.
from_associated_token_account: The sender’s associated token account.
to_associated_token_account: The recipient’s associated token account.
owner: The public key of the wallet that owns the token account from which tokens are being sent.
recipient: The public key of the wallet receiving the tokens.
payer_keypair: The payer’s keypair used to sign the transaction.
token_amount: The amount of tokens to be transferred.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) on successful transaction submission, or an error if the transaction fails.
Tranaction Flow:
Command-line arguments are parsed to obtain necessary parameters like program ID, token amounts, and wallet keys.
A transaction instruction is created to transfer tokens from the sender to the recipient.
The transaction is signed using the payer’s keypair and sent to the Solana network.
The transaction signature is printed upon success.
main():
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let rpc_url = opts.rpc_url_arg;
let client = RpcClient::new(rpc_url);
// create variables
let mint_account = Pubkey::from_str(&opts.mint_account_arg)?;
let owner = Pubkey::from_str(&opts.owner_arg)?;//same as payer in our case
let recipient = Pubkey::from_str(&opts.receiver).expect("Invalid Key provided");
let payer_keypair = read_keypair_file(opts.keypair_file);
let from_associated_token_account = spl_associated_token_account::get_associated_token_address(&owner, &mint_account);
let to_associated_token_account = spl_associated_token_account::get_associated_token_address(&recipient, &mint_account);
let amount_lamp = opts.amount;
invoke_smart_contract(
&client,
mint_account,
from_associated_token_account,
to_associated_token_account,
owner,
recipient,
&payer_keypair?,
TokenAmount { token_amount: amount_lamp }
);
Ok(())
}
The entry point for the program. This function sets up the RPC client, reads the payer’s keypair from the file, and invokes the invoke_smart_contract function to transfer tokens between two Solana wallets.
Key Actions:
Parses the command-line arguments using Opts and creates an RpcClient with the provided RPC URL. Reads the payer’s keypair from the specified file and calls invoke_smart_contract to initiate the SOL transfer.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) if the program executes successfully, or an error if any operation fails.
Smart Contracts:
These contracts handle the logic for transferring SOL and custom tokens between accounts.
send_sol:
fn transfer_sol_with_cpi(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let payer = next_account_info(accounts_iter)?;
let sol_recipient = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let data = transferSolArgs::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
invoke(
&system_instruction::transfer(payer.key, sol_recipient.key, data.sol_quantity),
&[payer.clone(), sol_recipient.clone(), system_program.clone()],
)?;
Ok(())
}
This smart contract facilitates the transfer of SOL between accounts on the Solana blockchain, utilizing a Cross-Program Invocation (CPI) to execute the transfer via the Solana system program. The contract accepts an instruction that specifies the amount of SOL to be transferred and invokes the system_instruction::transfer function to perform the transaction.
The main components of the smart contract include:
transferSolArgs: A struct used to deserialize the instruction data, which contains the quantity of SOL to transfer.
CPI Execution: calls the system program via CPI to perform the actual SOL transfer between the payer (sender) and the recipient.
Account Validation: checks for the presence of necessary accounts, including the payer's account, the recipient's account, and the system program account.
Error Handling: If the instruction data is invalid or if the transfer cannot be executed, the program returns an appropriate error message.
By leveraging CPI, this smart contract enables secure transfers of SOL within the Solana ecosystem.
send_token:
fn transfer_sol_with_cpi(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let payer = next_account_info(accounts_iter)?;
let sol_recipient = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let data = transferSolArgs::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
invoke(
&system_instruction::transfer(payer.key, sol_recipient.key, data.sol_quantity),
&[payer.clone(), sol_recipient.clone(), system_program.clone()],
)?;
Ok(())
}
This smart contract facilitates the transfer of a custom token (such as an SPL token) between accounts on the Solana blockchain.
If the recipient does not have an associated token account for the specific token, the contract will automatically create one.
After ensuring the recipient has a valid associated token account, the contract proceeds to transfer the specified amount of tokens f
rom the sender's associated token account to the recipient's associated token account.
The main components of the smart contract include:
transferTokenArgs: A struct used to deserialize the instruction data, which contains the quantity of tokens to be transferred.
Associated Token Account Creation: Before transferring tokens, the contract checks whether the recipient has an associated token account. If not, the contract invokes the create_associated_token_account instruction to create one.
Token Transfer: The contract utilizes the spl_token::instruction::transfer function to perform the token transfer from the sender to the recipient.
Account Validation: The contract checks the presence and validity of the necessary accounts, including the mint account, token accounts (both sender and recipient), the payer, and relevant program accounts (such as the system program, token program, and associated token program).
Error Handling: If the associated token account does not exist, it is created. If any errors occur during the transfer, they are propagated to the caller.
By leveraging CPI (Cross-Program Invocation) and the associated token account functionality, this contract ensures a secure token transfer process, even if the recipient doesn't have an existing associated token account.
License:
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
Solana Token Swap
_repo: https://github.com/CacheMeIfYouCan1/solanatokenswap_
Table of Contents:
Introduction:
This is a Proof of Concept for swapping custom tokens on the Solana blockchain for SOL and
vice-versa without an exchange. The system provides the opportunity to quickly
perform token swaps directly on the blockchain, eliminating the barriers and complexities typically associated with centralized exchanges.
It is part of a different project, which will not be disclosed yet. But since the swap system could easily be adapted to serve other purposes, I decided to make it publicly available.
It consists of two handlers and two smart contracts written in Rust as well as two monitoring applications written in Python.
The python applications continuously monitor the Solana blockchain for relevant swap transactions.
Upon detecting a swap transaction, they trigger a corresponding Rust handler that interacts with a smart contract to perform the token swap.
The current Python applications are only used for demonstation and are not secure and the code is not clean. They were created quickly to demonstrate the functionality of the swap system. If the Solana Token Swap system needs to be included into a productive environment, it is necessary to create a custom solution.
Addidtional considerations regarding security:
This system acts as a proof of concept, that means it is designed to run locally, without generating costs. Therefore the private keys are stored locally and passed as arguments to the handlers. This is not secure. Do not use this method with funded wallets on the mainnet!
There are secure ways to manage private keys for these scenarios, some of them are Cloud KMS solutions or private Keystore servers, where the keys are encrypted. Storing private keys in environmental variables can also be done safely if its done correctly.
Please feel free to contact me, if assistance in developing a custom solution and/or customization of the given system is needed.
Setup:
Clone the project:
initially we need to clone the gihub repository by running:
$ git clone https://github.com/CacheMeIfYouCan1/solanatokenswap/
Rust base configuration
Rust needs to be correctly set up for developing smart contracts on the solana blockchain. To ensure this, build-essentials need to be installed.
to do this, update your system and run:
$ sudo apt install -y build-essential curl
Afterwards we need to install rustup. Rustup is a tool to manage Rust versions and components. To Install rustup, run:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Finally we can source the environment:
$ source $HOME/.cargo/env
Now we can check if the installation was successfull:
$ rustc --version
Solana configuration
First we need to install the solana CLI. To do this we can run following command:
$ sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
afterwards we need to update the PATH:
$ echo 'export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc
Now we can check if it is properly installed:
$ solana --version
If solana is properly configured, we need to install the rustup component
$rustup component add rust-src
and also install spl-token:
$ cargo install spl-token-cli
Python configuration
Make sure Python is installed and properly configured. Afterwards we need to install virtualvenv by running the following command.
$ pip install virtualenv
now we can create a virtual environment for the monitoring systems and source it by running this from /monitoring/
$ python3 -m venv solanatokenswapVenv
$ source solanatokenswapVenv/bin/activate
and finally install the required libraries by running following command
$ pip install -r requirements.txt
compilation:
To compile all parts of this system you can run following command from solanatokenswap/ directory:
$ bash build.sh
Test validator and wallet configuration
To run the local demo, we need to configure the local environment and propagate the global variables used in the monitoring scripts and passed to the handlers.
To do so, we need to start the test validator first:
$ solana-test-validator
in addition to that we need to configure solana to use the test validator:
$ solana config set --url http://127.0.0.1:8899
now we need to create a wallet, which will manage the token, make it the main wallet and airdrop it some SOL:
$ solana-keygen new --outfile ~/token-manager.json
$ solana config set --keypair ~/token-manager.json
$ solana airdrop 100
next we need a wallet which will act as a user which swaps SOL to our token and our token to SOL. This wallet also needs some SOL.
$ solana-keygen new --outfile ~/user-wallet.json
$ solana airdrop 100 <WALLET_PUBLIC_KEY>
next we need a wallet which will act as a comission wallet, collecting the fees.
$ solana-keygen new --outfile ~/comission-wallet.json
$ solana airdrop 100 <WALLET_PUBLIC_KEY>
now we are set to create our demo-token:
$ spl-token create-token --owner ~/token-manager.json
after creating a token, we need to create an associated token account, which will hold our token-liquidity:
$ spl-token create-account <TOKEN_ADDRESS> --owner ~/token-manager.json
finally we have to mint some tokens as a base supply:
$ spl-token mint <TOKEN_ADDRESS>
deploying smart contracts:
At this point we need to deploy our smart contracts. Our system relies on two smart contracts which are :
send_sol:
This smart contract is called if a client swaps tokens back to SOL, or if fees are paid.
It can be deployed with following command from ./smart_contracts/send_sol/target/deploy/:
$ solana program deploy send_sol.so
send_token:
This smart contract is called if a client swaps SOL to tokens
It can be deployed with following command from ./smart_contracts/send_token/target/deploy/:
$ solana program deploy send_token.so
important note: make sure to save the program ID's
final steps
Now we are finished and can populate the global variables with the values from our previous setp. The global variables are stored in /config.json and are used by our proof of concept. Below you can see an example config.json with the values that have been created for the setup used to create this documentation. The commission_rate and the swap_rate can be individually chosen and are not derived from the setup.
fyi: The setup runs fully local, no actual secrets were shared
{
"send_sol_handler": "/home/main/solanatokenswap/handling/send_sol_handler/target/debug/send_sol_handler",
"send_token_handler": "/home/main/solanatokenswap/handling/send_token_handler/target/debug/send_token_handler",
"send_sol_program_id": "BPP6ULzktGPJJNdXJAjv5o1uAAeUgFYWVEeZisyCDJmo",
"send_token_program_id": "u8STJAr3h6hqb7XnYMiNqvzJrULgzfMxLLftGmCzGVy",
"keypair_file": "/home/main/token-manager.json",
"mint_account": "28wGsufhHpuQDBkeTXpNVLQz2zPUBoLc1gDpu7wBtejg",
"owner": "58ZEW3nzhg3CQ8xYrqavsV71r6ErKX7a4PyJrrxc4pbE",
"token_receiver_wallet": "3vSighS9MqPBcZJbYWnw8AWvYw3NnvY58nf9QbNmUPnr",
"sol_receiver_wallet": "58ZEW3nzhg3CQ8xYrqavsV71r6ErKX7a4PyJrrxc4pbE",
"commission_wallet": "58ZEW3nzhg3CQ8xYrqavsV71r6ErKX7a4PyJrrxc4pbE",
"token_acc_str": "28wGsufhHpuQDBkeTXpNVLQz2zPUBoLc1gDpu7wBtejg",
"rpc_client_str": "http://127.0.0.1:8899",
"ws_client_str": "ws://127.0.0.1:8900",
"commission_rate": "0.1",
"swap_rate": "200"
}
Usage:
To swap SOL for the custom, token using our local ledger, we will first start the two monitoring scripts, in two seperate terminals:
~/solanatokenswap/monitoring $ solanatokenswapVenv/bin/python3 scripts/token_monitor.py
~/solanatokenswap/monitoring $ solanatokenswapVenv/bin/python3 scripts/sol_monitor.py
before any transactions happen, the monitoring should look like this:
![[sts\_init.png]](/projects/4/postPage/sts_init.png)
Now we can check the initial SOL and token balances of our user wallet:
![[sts\_wallet\_init\_balance.png]](/projects/4/postPage/sts_wallet_init_balance.png)
To swap SOL for our custom token, we can transfer SOL from our user wallet to our token management wallet. The monitoring systems will register the transaction and execute the corresponding handlers, which will call the smart contracts to perform the token swap.
![[sts\_sol\_tx\_user\_wallet.png]](/projects/4/postPage/sts_sol_tx_user_wallet.png)
![[sts\_sol\_tx\_monitoring.png]](/projects/4/postPage/sts_sol_tx_monitoring.png)
after the transactions are finished we can check the new balances of our user wallet:
We can also swap our custom token to SOL by sending the custom token to the associated token account of our token management wallet. The monitoring systems will also register this transaction and perform the swap by calling the corresponding smart contracts.
![[sts\_tx\_token\_user\_wallet.png]](/projects/4/postPage/sts_tx_token_user_wallet.png)
![[sts\_updated\_monitoring\_token\_tx.png]](/projects/4/postPage/sts_updated_monitoring_token_tx.png)
afterwards we can also check the updated balances of our user wallet:
Documentation:
Structure
solanatokenswap/
|- build.sh
|- handling/
| |- send_sol_handler/
| | |- Cargo.toml
| | |- src/
| | | |- main.rs
| |- send_token_handler
| | |- Cargo.toml
| | |- src/
| | | |- main.rs
|- smart_contracts/
| |- send_sol/
| | |- Cargo.toml
| | |- src/
| | | |- lib.rs
| |- send_token/
| | |- Cargo.toml
| | |- src/
| | | |-lib.rs
|- monitoring/
| |- requirements.txt
| |- scripts/
| | |- config.json
| | |- token_monitor.py
| | |- sol_monitor.py
| | |- resources/
| | | |- transaction_processing.py
| | | |- transaction_handling.py
| |- solanatokenswapVenv/
| | |- (...)
Monitoring
The monitoring system is used to demonstrate the functionality of this Proof of concept by managing the interactions between the Rust handlers and the smart contracts. It consists of four components, to monitor the transactions, as well as hantle swapping from SOL to the custom token, as well as swapping the custom token to SOL.
Initialisation:
sol_monitor.py and token_monitor.py are used to initialize the monitoring systems. They import the TransactionHandling class and are structured as following:
sol_monitor.py:
async def main():
try:
with open('config.json', 'r') as file:
config = json.load(file)
transaction_handling = TransactionHandling(
config["sol_receiver_wallet"],
"sol_to_token"
)
listener_task = asyncio.create_task(transaction_handling.listen_for_transactions())
processor_task = asyncio.create_task(transaction_handling.process_transactions())
await asyncio.gather(listener_task, processor_task)
except Exception as err:
print("ann error occured: ", err)
asyncio.run(main())
This script initializes the monitoring and processing of swap transactions from Solana (SOL) to a custom token. It loads configuration from a config.json file, including the receiver wallet and transaction type, then creates a TransactionHandling object.
Two asynchronous tasks are created:
- listen_for_transactions(): Monitors incoming transactions.
- process_transactions(): Processes the transactions.
Both tasks run concurrently using asyncio.gather(), allowing real-time handling of transactions.
token_monitor.py:
async def main():
try:
with open('config.json', 'r') as file:
config = json.load(file)
transaction_handling = TransactionHandling(
config["token_receiver_wallet"],
"token_to_sol"
)
listener_task = asyncio.create_task(transaction_handling.listen_for_transactions())
processor_task = asyncio.create_task(transaction_handling.process_transactions())
await asyncio.gather(listener_task, processor_task)
except Exception as err:
print("an error occured: ", err)
asyncio.run(main())
This script works similar to sol_monitor.py, but instead of initialising the monitoring and processing of swap transactions from SOL to a custom token, it initializes the monitoring and processing of swap transactions from a custom token to SOL.
Transaction Handling:
transaction_handling.py:
Handling of the transaction is managed through the TransactionHandling class. This involves listening for incoming transactions, fetching the transaction details as well as processing the swap, based on the given transaction type. It takes the receiver wallet and the transaction types as arguments.
__innit__():
def __init__(self, receiver_wallet, transaction_type):
self.receiver_wallet = receiver_wallet
self.transaction_type = transaction_type
self.transaction_queue = asyncio.Queue()
self.rate_limit = asyncio.Semaphore(5)
with open('config.json', 'r') as file:
config = json.load(file)
self.client = Client(config["rpc_client_str"])
self.socket = config["ws_client_str"]
self.send_token_program_id = config["send_token_program_id"]
self.send_sol_program_id = config["send_sol_program_id"]
self.last_tx_id = "0";
Initializes the class with essential parameters and sets up internal resources.
Parameters:
receiver_wallet: The Solana wallet address that will receive the transaction, initializing the swap.
transaction_type: Defines the transaction direction, either "sol_to_token" or "token_to_sol".
Key Actions:
Creates an asyncio.Queue (transaction_queue) to store incoming transactions, sets up a rate limit using an asyncio.Semaphore to manage concurrent requests (maximum of 5 simultaneous requests) and Loads configuration from config.json to retrieve:
RPC client connection string (rpc_client_str).
WebSocket URL (ws_client_str) for listening to transaction logs.
Program IDs for token and SOL transactions (send_token_program_id, send_sol_program_id).
Initializes the last_tx_id to track the last processed transaction.
listen_for_transactions():
async def listen_for_transactions(self):
async with websockets.connect(self.socket) as websocket:
subscription_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "logsSubscribe",
"params": [
{
"mentions": [f"{self.receiver_wallet}"]
},
{
"commitment": "finalized"
}
]
}
await websocket.send(json.dumps(subscription_request))
while True:
response = await websocket.recv()
self.tx_signature_raw = json.loads(response)
print("Transaction log received:", self.tx_signature_raw)
await self.transaction_queue.put(self.tx_signature_raw)
print(f"Transaction added to queue: {self.tx_signature_raw}")
Listens for incoming transactions involving the receiver wallet by subscribing to the blockchain logs.
Key Actions:
This function runs indefinitely in an asynchronous loop to monitor real-time transactions and Continuously listens for incoming log data, by establishing a WebSocket connection to the configured socket URL, sending a subscription request to the blockchain logs and filtering for transactions involving the receiver_wallet. When a transaction is detected, it adds the transaction details (tx_signature_raw) to the transaction_queue.
process_transactions():
async def process_transactions(self):
while True:
self.tx_signature_raw = await self.transaction_queue.get()
print(f"Dequeued transaction: {self.tx_signature_raw}")
await self.rate_limit.acquire()
try:
await self.fetch_transaction_data_raw()
finally:
self.rate_limit.release()
self.transaction_queue.task_done()
Continuously fetches transactions from the queue and processes them with rate-limiting controls.
Key Actions:
This function runs in an infinite loop, ensuring that the system continuously processes transactions as they are received. It asynchronously dequeues a transaction from transaction_queue, Acquires a Semaphore to ensure no more than 5 transactions are processed at once. Calls fetch_transaction_data_raw() for each transaction in the queue and releases the rate limit (Semaphore) after processing the transaction to allow the next one.
fetch_transaction_data_raw():
async def fetch_transaction_data_raw(self):
print(f"processing transaction: {self.tx_signature_raw}")
await asyncio.sleep(1)
if "method" in self.tx_signature_raw and self.tx_signature_raw["method"] == "logsNotification":
print("if = true")
tx_signature_str = self.tx_signature_raw["params"]["result"]["value"]["signature"]
print(f"Transaction Signature: {tx_signature_str}")
transaction_details = await self.fetch_transaction_details(tx_signature_str)
transaction_processing = TransactionProcessing()
if transaction_details != None:
if self.last_tx_id != tx_signature_str:
self.last_tx_id = tx_signature_str
if self.transaction_type == "sol_to_token":
await transaction_processing.process_sol_to_token(transaction_details, tx_signature_str, self.receiver_wallet)
if self.transaction_type == "token_to_sol":
await transaction_processing.process_token_to_sol(transaction_details, tx_signature_str, self.receiver_wallet)
else:
print("")
print("already done, skipping")
print("")
else:
print("")
print("transaction details could not be fetched, skipping")
print("")
else:
print("transaction is empty")
Processes a raw transaction log and extracts the relevant details for further processing.
Key Actions:
If the transaction is valid and contains a method (logsNotification), it extracts the transaction signature (tx_signature_str)
and then calls fetch_transaction_details() to retrieve the full transaction details.
After fetchung the full transaction details, it initializes the TransactionProcessing class to handle the specific
processing logic based on the transaction type. If the full transaction details can not be fetched, the processing is skipped.
Transaction Types:
sol_to_token: processes the transaction using process_sol_to_token().
token_to_sol: processes the transaction using process_token_to_sol().
Processing is skipped, if the transaction has already been processed (if the signature matches last_tx_id). This ensures that each transaction is processed only once, preventing duplication.
fetch_transaction_details():
async def fetch_transaction_details(self, tx_signature_str):
tx_signature = Signature.from_string(tx_signature_str)
for i in range(10):
transaction = self.client.get_transaction(tx_signature)
if transaction.value:
return transaction
await asyncio.sleep(1)
return None
Fetches detailed information for a given transaction using its signature.
Key Actions:
Converts the provided transaction signature (tx_signature_str) into a Signature object and a
ttempts to fetch the transaction details using the client (self.client.get_transaction(tx_signature)), retrying up to 10 times with a
1-second delay between each attempt. If transaction details are found, it returns the transaction; if not, it returns None after 10 attempts.
This ensures the system can handle potential network issues or delays when fetching data from the blockchain.
transaction_handling.py:
This class handles the processing of Solana transactions, including the conversion of SOL to tokens and tokens back to SOL. It uses data fetched from transaction logs and interacts with smart contracts to facilitate the conversion and manage commissions.
interaction with smart contracts is done by using subprocesses, calling external handlers (e.g., send_sol_handler, send_token_handler) for token transfers and SOL transfers.Commission rates and swap rates are applied to calculate the amounts to be sent or received. Transactions are skipped if they have already been processed (based on the transaction signature)
__init__():
The constructor initializes the TransactionProcessing class with transaction details and transaction signature. It also loads configuration settings from a config.json file, which are used to interact with the smart contracts and manage wallet addresses.
Key Actions:
Loads configuration settings from config.json, extracts transaction information like account keys and instructions and initializes necessary fields, such as the wallet addresses and program IDs.
async process_sol_to_token():
async def process_sol_to_token(self, sol_receiver_wallet):
if self.transaction_details and self.transaction_details.value:
for instruction in self.instructions:
sender_index = instruction.accounts[0]
sender_account = self.account_keys[sender_index]
commission_wallet = self.commission_wallet
sender_account_str = str(sender_account)
if sender_account_str != self.token_account_str:
if sender_account_str != sol_receiver_wallet:
pre_balances = self.transaction_details.value.transaction.meta.pre_balances
post_balances = self.transaction_details.value.transaction.meta.post_balances
lamports_sent = pre_balances[0]-post_balances[0]
sol_to_send_lamports_int = int(lamports_sentfloat(self.commission_rate))
tokens_to_send_lamports = lamports_sentint(self.swap_rate)
print("Token purchased, executing token transfer")
call_token_smartcontract = [
self.send_token_handler,
"--receiver", str(sender_account),
"--amount", str(tokens_to_send_lamports).lstrip('-'),
"--program-id-arg", self.send_token_program_id,
"--rpc-url-arg", self.rpc_client_str,
"--keypair-file", self.keypair_file,
"--mint-account-arg", self.mint_account,
"--owner-arg", self.commission_wallet
]
result_token = subprocess.run(call_token_smartcontract, capture_output= True, text= True)
print(f"result token tx: {result_token.stdout}")
print(f"{result_token.stderr}")
print("executing transfer of commission")
call_\sol_smartcontract = [
self.send_sol_handler,
"--receiver", self.commission_wallet,
"--amount", str(sol_to_send_lamports_int).lstrip('-'),
"--program-id-arg", self.send_sol_program_id,
"--rpc-url-arg", self.rpc_client_str,
"--keypair-file", self.keypair_file
]
result_sol = subprocess.run(call_sol_smartcontract, capture_output= True, text= True)
print(f"result sol tx: {result_sol.stdout}")
print(f" {result_sol.stderr}")
else:
print("")
print("Sender is receiver, skipping")
print("")
break
else:
print("")
print("token transaction, skipping")
print("")
else:
print("")
print("Transaction details not found after multiple retries, skipping")
print("")
Processes a transaction where SOL is sent and exchanged for tokens. If the transaction is valid and the sender is not the receiver, it executes a smart contract to transfer tokens. The method also ensures that a commission is sent to a specified commission wallet.
Key Actions:
Loops through the transaction's instructions to identify the sender andhecks if the sender is the receiver; if not, it calculates the amount of SOL to convert into tokens. Then executes a smart contract to transfer tokens using the send_token_handler and sends a commission to the commission wallet via another smart contract. If the sender is the receiver or if it’s already a token transaction, the transaction gets skipped.
async process_token_to_sol():
async def process_token_to_sol(self, token_receiver_wallet):
if self.transaction_details and self.transaction_details.value:
for instruction in self.instructions:
if instruction.accounts:
sender_index = instruction.accounts[0]
sender_associated_account_owner = self.account_keys[0]
for balance_meta in self.transaction_details.value.transaction.meta.pre_token_balances:
if balance_meta.account_index == sender_index:
sender_associated_account_owner = balance_meta.owner
break
else:
sender_associated_account_owner = None
for pre_balance_meta in self.transaction_details.value.transaction.meta.pre_token_balances:
if pre_balance_meta.account_index == sender_index:
pre_token_balance = pre_balance_meta.ui_token_amount.amount
break
else:
pre_token_balance = None
for post_balance_meta in self.transaction_details.value.transaction.meta.post_token_balances:
if post_balance_meta.account_index == sender_index:
post_token_balance = post_balance_meta.ui_token_amount.amount
break
else:
post_token_balance = None
if sender_associated_account_owner is not None:
lamports_sent = int(pre_token_balance) - int(post_token_balance)
sol_to_send_lamports_int = int(lamports_sent/int(self.swap_rate))
sol_to_send_lamports_str = str(sol_to_send_lamports_int)
sender_associated_account_owner_str = str(sender_associated_account_owner)
if sender_associated_account_owner != token_receiver_wallet:
print("Token sold: executing SOL transfer")
call_sol_smartcontract = [
self.send_sol_handler,
"--receiver", str(sender_associated_account_owner),
"--amount", sol_to_send_lamports_str,
"--program-id-arg", self.send_sol_program_id,
"--rpc-url-arg", self.rpc_client_str,
"--keypair-file", self.keypair_file
]
result_sol = subprocess.run(call_sol_smartcontract, capture_output= True, text= True)
print(f"result sol: {result_sol.stdout}")
print(f"error sol: {result_sol.stderr}")
break
else:
print("")
print("Sender is receiver, skipping")
print("")
break
else:
print("")
print("instruction is empty, skipping")
print("")
else:
print("")
print("token purchased, skipping")
print("")
else:
print("")
print("Transaction details not found after multiple retries.")
print("")
Processes a transaction where tokens are sold and converted back into SOL. It identifies the sender’s associated account and calculates the amount of SOL to transfer. A smart contract is executed to send the SOL to the sender’s wallet.
Key Actions:
Loops through the transaction’s token balances to determine the amount of tokens transferred and calculates the corresponding amount of SOL to be sent to the sender.
Then executes a smart contract to send the SOL to the sender. If the sender is the receiver or if no valid token balances are found, the transaction is skipped.
Handling
The Rust handler programs serve as the core components of the system, facilitating interactions with Solana's smart contracts to manage SOL-to-token and token-to-SOL transactions. They are responsible for constructing, signing, and sending Solana transactions that either convert SOL to tokens or vice versa.
send_sol_handler:
invoke_smart_contract():
fn invoke_smart_contract(
client: &RpcClient,
payer_keypair: &Keypair,
sol_recipient: Pubkey,
sol_quantity: SolQuantity) -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let program_id = opts.program_id_arg.parse().unwrap();
let accounts = vec![
AccountMeta::new(payer_keypair.pubkey(), true),
AccountMeta::new(sol_recipient, false),
AccountMeta::new_readonly(system_program::id(), false),
]; // Add relevant accounts
// Create the instruction
let instruction = Instruction {
program_id,
accounts,
data: sol_quantity.amount.to_le_bytes().to_vec(), // Convert the amount to bytes
};
// Create the transaction
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer_keypair.pubkey()));
println!("{}", payer_keypair.pubkey());
// Sign the transaction
let recent_blockhash = client.get_latest_blockhash().unwrap();
transaction.sign(&[payer_keypair], recent_blockhash);
// Send the transaction
let signature = client.send_and_confirm_transaction(&transaction).unwrap();
println!("Transaction signature: {:?}", signature);
Ok(())
}
}
This function constructs and sends a Solana transaction invoking a smart contract to transfer SOL from the payer to a recipient. It uses the provided RPC client, payer keypair, recipient’s public key, and amount of SOL to create and send the transaction.
Key Actions:
Parses command-line arguments using Opts and builds a transaction instruction to transfer SOL from the payer to the recipient. Then Signs the transaction with the payer's keypair and sends it to the Solana network afterwards prints the transaction signature upon successful submission.
Parameters:
client: The RPC client to interact with the Solana network.
payer_keypair: The payer’s keypair, which is used to sign the transaction.
sol_recipient: The recipient’s public key (Solana address) where the SOL will be sent.
sol_quantity: The amount of SOL to be transferred.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) on success, or an error if the transaction fails.
Tranaction Flow:
The program ID is parsed from command-line arguments.
The instruction is constructed with the relevant accounts and data (SOL amount).
A transaction is created, signed, and sent to the Solana network using the provided RPC client.
main():
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let rpc_url = opts.rpc_url_arg;
let client = RpcClient::new(rpc_url);
let payer_keypair = read_keypair_file(opts.keypair_file);
let amount_lamp = opts.amount;
let sol_recipient = Pubkey::from_str(&opts.receiver);
invoke_smart_contract(&client, &payer_keypair?, sol_recipient?, SolQuantity { amount: amount_lamp });
Ok(())
}
The entry point for the program. It parses the command-line arguments, sets up the RPC client, reads the payer's keypair from a file, and invokes the smart contract to transfer SOL to a recipient.
Key Actions:
Parses the command-line arguments using Opts and creates an RpcClient with the provided RPC URL. Reads the payer’s keypair from the specified file and calls invoke_smart_contract to initiate the SOL transfer.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) if the program executes successfully, or an error if any operation fails.
send_token_handler:
invoke_smart_contract():
fn invoke_smart_contract(
client: &RpcClient,
mint_account: Pubkey,
from_associated_token_account: Pubkey,
to_associated_token_account: Pubkey,
owner: Pubkey, //owner is the from wallet which owns the token acc which sends
recipient: Pubkey, //recipient is the sol wallet which owns the token acc which will receive balance
payer_keypair: &Keypair,
token_amount: TokenAmount) -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let program_id = opts.program_id_arg.parse().unwrap();
let accounts = vec![
AccountMeta::new(mint_account, false),
AccountMeta::new(from_associated_token_account, false),
AccountMeta::new(to_associated_token_account, false),
AccountMeta::new(owner, false),
AccountMeta::new(recipient, false),
AccountMeta::new(payer_keypair.pubkey(), true),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(TOKEN_PROGRAM_ID, false),
AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false),
]; // Add relevant accounts
println!("starting program");
let amount = token_amount.token_amount;
println!("Create the instruction");
// Create the instruction
let instruction = Instruction {
program_id,
accounts,
data: amount.to_le_bytes().to_vec(),
};
println!("successfully created instruction");
println!("createing tx");
// Create the transaction
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer_keypair.pubkey()));
println!("get latest blockhash");
// Sign the transaction
let recent_blockhash = client.get_latest_blockhash().unwrap();
println!("got blockhash");
println!("sign tx");
let signer = [payer_keypair];
println!("{}", payer_keypair.pubkey());
transaction.sign(&[payer_keypair], recent_blockhash);
println!("signed tx");
// Send the transaction
let signature = client.send_and_confirm_transaction(&transaction).unwrap();
println!("Transaction signature: {:?}", signature);
Ok(())
}
This function creates and sends a transaction that interacts with a Solana smart contract to transfer tokens from one associated token account to another. It is designed to handle transfers of a specified token between the owner (sender) and the recipient (receiver) on the Solana blockchain.
Key Actions:
Parses the command-line arguments using Opts and constructs a list of relevant accounts needed for the transaction, including the mint account, sender’s and recipient’s associated token accounts, and the payer's keypair. Creates a Solana transaction instruction with the appropriate accounts and data (token amount). Signs the transaction and sends it to the Solana network using the provided RPC client.
Parameters:
client: The RPC client used to interact with the Solana network.
mint_account: The public key of the token’s mint account, which specifies the token type being transferred.
from_associated_token_account: The sender’s associated token account.
to_associated_token_account: The recipient’s associated token account.
owner: The public key of the wallet that owns the token account from which tokens are being sent.
recipient: The public key of the wallet receiving the tokens.
payer_keypair: The payer’s keypair used to sign the transaction.
token_amount: The amount of tokens to be transferred.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) on successful transaction submission, or an error if the transaction fails.
Tranaction Flow:
Command-line arguments are parsed to obtain necessary parameters like program ID, token amounts, and wallet keys.
A transaction instruction is created to transfer tokens from the sender to the recipient.
The transaction is signed using the payer’s keypair and sent to the Solana network.
The transaction signature is printed upon success.
main():
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let rpc_url = opts.rpc_url_arg;
let client = RpcClient::new(rpc_url);
// create variables
let mint_account = Pubkey::from_str(&opts.mint_account_arg)?;
let owner = Pubkey::from_str(&opts.owner_arg)?;//same as payer in our case
let recipient = Pubkey::from_str(&opts.receiver).expect("Invalid Key provided");
let payer_keypair = read_keypair_file(opts.keypair_file);
let from_associated_token_account = spl_associated_token_account::get_associated_token_address(&owner, &mint_account);
let to_associated_token_account = spl_associated_token_account::get_associated_token_address(&recipient, &mint_account);
let amount_lamp = opts.amount;
invoke_smart_contract(
&client,
mint_account,
from_associated_token_account,
to_associated_token_account,
owner,
recipient,
&payer_keypair?,
TokenAmount { token_amount: amount_lamp }
);
Ok(())
}
The entry point for the program. This function sets up the RPC client, reads the payer’s keypair from the file, and invokes the invoke_smart_contract function to transfer tokens between two Solana wallets.
Key Actions:
Parses the command-line arguments using Opts and creates an RpcClient with the provided RPC URL. Reads the payer’s keypair from the specified file and calls invoke_smart_contract to initiate the SOL transfer.
Return:
Result<(), Box<dyn std::error::Error>>: Returns Ok(()) if the program executes successfully, or an error if any operation fails.
Smart Contracts:
These contracts handle the logic for transferring SOL and custom tokens between accounts.
send_sol:
fn transfer_sol_with_cpi(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let payer = next_account_info(accounts_iter)?;
let sol_recipient = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let data = transferSolArgs::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
invoke(
&system_instruction::transfer(payer.key, sol_recipient.key, data.sol_quantity),
&[payer.clone(), sol_recipient.clone(), system_program.clone()],
)?;
Ok(())
}
This smart contract facilitates the transfer of SOL between accounts on the Solana blockchain, utilizing a Cross-Program Invocation (CPI) to execute the transfer via the Solana system program. The contract accepts an instruction that specifies the amount of SOL to be transferred and invokes the system_instruction::transfer function to perform the transaction.
The main components of the smart contract include:
transferSolArgs: A struct used to deserialize the instruction data, which contains the quantity of SOL to transfer.
CPI Execution: calls the system program via CPI to perform the actual SOL transfer between the payer (sender) and the recipient.
Account Validation: checks for the presence of necessary accounts, including the payer's account, the recipient's account, and the system program account.
Error Handling: If the instruction data is invalid or if the transfer cannot be executed, the program returns an appropriate error message.
By leveraging CPI, this smart contract enables secure transfers of SOL within the Solana ecosystem.
send_token:
fn transfer_sol_with_cpi(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let payer = next_account_info(accounts_iter)?;
let sol_recipient = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let data = transferSolArgs::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
invoke(
&system_instruction::transfer(payer.key, sol_recipient.key, data.sol_quantity),
&[payer.clone(), sol_recipient.clone(), system_program.clone()],
)?;
Ok(())
}
This smart contract facilitates the transfer of a custom token (such as an SPL token) between accounts on the Solana blockchain.
If the recipient does not have an associated token account for the specific token, the contract will automatically create one.
After ensuring the recipient has a valid associated token account, the contract proceeds to transfer the specified amount of tokens f
rom the sender's associated token account to the recipient's associated token account.
The main components of the smart contract include:
transferTokenArgs: A struct used to deserialize the instruction data, which contains the quantity of tokens to be transferred.
Associated Token Account Creation: Before transferring tokens, the contract checks whether the recipient has an associated token account. If not, the contract invokes the create_associated_token_account instruction to create one.
Token Transfer: The contract utilizes the spl_token::instruction::transfer function to perform the token transfer from the sender to the recipient.
Account Validation: The contract checks the presence and validity of the necessary accounts, including the mint account, token accounts (both sender and recipient), the payer, and relevant program accounts (such as the system program, token program, and associated token program).
Error Handling: If the associated token account does not exist, it is created. If any errors occur during the transfer, they are propagated to the caller.
By leveraging CPI (Cross-Program Invocation) and the associated token account functionality, this contract ensures a secure token transfer process, even if the recipient doesn't have an existing associated token account.
License:
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.