The
solana_raw_transaction
is a low-level format that is rarely the right choice for most applications. Instead, you should first try to use thesolana_serialized_transaction_message
format, which simplifies the integration with common Solana SDKs. See Solana Transactionsfor more details.
The solana_raw_transaction
format is a low-level format, allowing you to explicitly provide the following:
- The version (Fordefi supports both legacy and v0 versions)
- An array of instructions
- The interacted accounts list
- An address table, if relevant
Any transaction must have at least one instruction.
The following example creates a transaction request that adds liquidity to Marinade Finance. The request should be inside the body, as demonstrated here. For a better understanding, visit the Solana blockchain explorer to view the transaction that was created by this code.
{
"signer_type": "api_signer",
"type": "solana_transaction",
"details": {
"type": "solana_raw_transaction",
"chain": "solana_mainnet",
"version": "legacy",
"instructions": [{
"program_index": 8,
"data": "tZ1ZQ4+2NEjwSQIAAAAAAA==",
"account_indexes": [1, 3, 7, 6, 4, 0, 2, 5, 9]
}],
"accounts": [{
"address": "8TpGoWWzx9Q7rR7UizToMQ7K3G8TPncHjkJiBBfxwBC4",
"writable": true,
"signer": true
},
{
"address": "8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC",
"writable": true,
"signer": false
},
{
"address": "De2cHMWmygZ6Ss8HDaBXovesoniXBFs5B4MKQVthaVM2",
"writable": true,
"signer": false
},
{
"address": "LPmSozJJ8Jh69ut2WP3XmVohTjL4ipR18yiCzxrUmVj",
"writable": true,
"signer": false
},
{
"address": "UefNb6z6yvArqe4cJHTXCqStRsKmWhGxnZzuHbikP5Q",
"writable": true,
"signer": false
},
{
"address": "11111111111111111111111111111111",
"writable": false,
"signer": false
},
{
"address": "7GgPYjS5Dza89wV6FpZ23kUJRG5vbQ1GM25ezspYFSoE",
"writable": false,
"signer": false
},
{
"address": "HZsepB79dnpvH6qfVgvMpS738EndHw3qSHo4Gv5WX1KA",
"writable": false,
"signer": false
},
{
"address": "MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD",
"writable": false,
"signer": false
},
{
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"writable": false,
"signer": false
}
],
"address_table_lookups":[]
},
"note": "Adding liquidity to Marinade Finance",
"vault_id": "8988893a-cf29-4a02-acc7-5bb723c74f47"
}
solana-program-library for building transaction raw data
Building the transaction raw data from scratch can be challenging. Short-circuit the process by using any of several packages that can help to build Solana transactions with high-level API. One example is the solana-program-library
Python package repository. The following example demonstrates how to create a staking transaction on Solana and sign it partially before sending it to Fordefi.
First, clone solana-program-library
and install the Solana Python package.
git clone [email protected]:solana-labs/solana-program-library.git
pip install solana==0.18.1
Create a main.py
file inside the solana-stake-pool/stake-pool/py
directory, with the content:
import base64
import json
import uuid
import base58
import stake.constants
import stake.instructions
import stake.state
import solana.system_program
import solana.publickey
import solana.transaction
import solana.blockhash
import solana.keypair
import requests
class RpcClient:
def __init__(self, rpc_url: str):
self._rpc_url = rpc_url
def get_recent_blockhash(self) -> solana.blockhash.Blockhash:
body = {
"jsonrpc": "2.0",
"id": 1,
"method": "getRecentBlockhash",
"params": [{"commitment": "finalized"}],
}
response = requests.post(self._rpc_url, json=body)
response.raise_for_status()
return solana.blockhash.Blockhash(
response.json()["result"]["value"]["blockhash"]
)
def generate_stake_instructions(
vault_id: uuid.UUID,
payer_account: str,
lamports: int,
staker_account: str,
withdrawer_account: str,
custodian_account: str,
):
client = RpcClient("https://api.mainnet-beta.solana.com")
stake_account_key_pair = solana.keypair.Keypair()
tx = solana.transaction.Transaction()
tx.fee_payer = solana.publickey.PublicKey(payer_account)
tx.recent_blockhash = client.get_recent_blockhash()
# Add create account instruction.
tx.add(
solana.system_program.create_account(
params=solana.system_program.CreateAccountParams(
from_pubkey=solana.publickey.PublicKey(payer_account),
new_account_pubkey=stake_account_key_pair.public_key,
lamports=lamports,
space=stake.constants.STAKE_LEN,
program_id=stake.constants.STAKE_PROGRAM_ID,
),
)
)
# Add initialize instruction.
tx.add(
stake.instructions.initialize(
params=stake.instructions.InitializeParams(
stake=stake_account_key_pair.public_key,
authorized=stake.state.Authorized(
staker=solana.publickey.PublicKey(staker_account),
withdrawer=solana.publickey.PublicKey(withdrawer_account),
),
lockup=stake.state.Lockup(
epoch=0,
unix_timestamp=0,
custodian=solana.publickey.PublicKey(custodian_account),
),
),
)
)
# Sign transaction by the stake account only.
tx.sign_partial(stake_account_key_pair)
message = tx.compile_message()
signers = message.account_keys[: message.header.num_required_signatures]
signatures: list[dict[str, str | None]] = []
for account in signers:
for signature in tx.signatures:
if signature.pubkey == account and signature.signature is not None:
signatures.append(
{"data": base64.b64encode(signature.signature).decode()}
)
else:
signatures.append({"data": None})
account_keys: list[dict[str, str | bool]] = []
for account in message.account_keys:
account_keys.append(
{
"address": str(account),
"writable": False,
"signer": False,
}
)
for account_key in account_keys[: message.header.num_required_signatures]:
account_key["signer"] = True
for account_key in account_keys[
: message.header.num_required_signatures
- message.header.num_readonly_signed_accounts
]:
account_key["writable"] = True
for account_key in account_keys[
message.header.num_required_signatures : -message.header.num_readonly_unsigned_accounts
]:
account_key["writable"] = True
# Build create_transaction_request_details.
create_transaction_request = {
"vault_id": str(vault_id),
"type": "solana_transaction",
"signer_type": "api_signer",
"details": {
"version": "legacy",
"type": "solana_raw_transaction",
"recent_blockhash": str(tx.recent_blockhash),
"instructions": [
{
"program_index": instruction.program_id_index,
"data": base64.b64encode(
base58.b58decode(instruction.data)
).decode(),
"account_indexes": instruction.accounts,
}
for instruction in message.instructions
],
"accounts": account_keys,
"address_table_lookups": [],
"signatures": signatures if any(signatures) else None,
"chain": "solana_mainnet",
},
}
print(json.dumps(create_transaction_request, indent=4))
if __name__ == "__main__":
generate_stake_instructions(
vault_id=uuid.UUID("54fe3e56-0abd-4fe4-acdc-4cffa38a964f"),
payer_account="BdW9dN3DmaNUFPGrNwS4TzDVQ8Yn3aXB8AAFBhHLPBKm",
lamports=10**9,
staker_account="AC6MBbSTmxVpLcNWnHfxoT12Q5tV3PsX2ofuqepoTjTe",
withdrawer_account="97sdixZoEQ9WZijPBMovrAPtKnX63JSXcRxfVCsDvPz4",
custodian_account="9iNcEjWvwHUe2UpenLdtVjNGvymYxd2gZk1B75torfMM",
)
Running this script will output a staking Create Transaction request body to POST to the Fordefi API. For example:
{
"details": {
"version": "legacy",
"type": "solana_raw_transaction",
"recent_blockhash": "DewqCx36Z59gfWJS7h8qeErAV3Yhv5zxWNoHPmecmDf3",
"instructions": [
{
"program_index": 3,
"data": "AAAAAADKmjsAAAAAyAAAAAAAAAAGodgXkTdUKpg0N73+KnqyVX9TXIp4citopJ3AAAAAAA==",
"account_indexes": [
0,
1
]
},
{
"program_index": 4,
"data": "AAAAAIiQnkM+zinC1MWbWiZFIfvsN+2HH1v+KpvSDfd8/BaBeKA7OrLOtDIw6EoMn9Gwsu2tWpFRUToFx2T9QxOWjQ0AAAAAAAAAAAAAAAAAAAAAgXa9hARP3ZqDnrHuEkjSfKPw3wBLFLFUCb4Oy37hn1w=",
"account_indexes": [
1,
2
]
}
],
"accounts": [
{
"address": "BdW9dN3DmaNUFPGrNwS4TzDVQ8Yn3aXB8AAFBhHLPBKm",
"writable": true,
"signer": true
},
{
"address": "AZRBfyAto1KAEu8nKP5SXecZqzMXhdW3psmw8Ajj2JCB",
"writable": true,
"signer": true
},
{
"address": "SysvarRent111111111111111111111111111111111",
"writable": false,
"signer": false
},
{
"address": "11111111111111111111111111111111",
"writable": false,
"signer": false
},
{
"address": "Stake11111111111111111111111111111111111111",
"writable": false,
"signer": false
}
],
"address_table_lookups": [],
"signatures": [
{
"data": null
},
{
"data": "1a8x6y6Sr6kt8eBnXjvt82KtoYFGNxoWrrH8QGUa0xyX66sVglc6bTS1aa2GLThYvHIFt51EM0ptaM3EgOzcDQ=="
}
],
"chain": "solana_mainnet"
}
}