Solana Raw Transactions

A general Solana transaction is used mainly for invoking non-transfers transactions.

Fordefi can handle any single-signature Solana transaction. While raw Solana transactions can be used to create transfers, it is recommended to use their request formats.

Fordefi supports both legacy and v0 versions. To invoke a general transaction using the Fordefi platform, you need to provide the following:

  • The version
  • An array of instructions
  • The interacted accounts list
  • An address table, if relevant

Any transaction must have at least one instruction.

πŸ“˜

Note

API users must strongly authenticate transaction requests that are created programmatically by signing them. Learn more.

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"
    }
}