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

Batch transactions

Batch transactions are currently supported on Solana only, for the purpose of supporting the signAllTranactions flow used by certain Solana DApps.

Batch transactions undergo policy evaluation as a whole: the policy is applied to a “virtual transaction” whose list of instructions is the union of the instructions of the individual transactions in the batch, and whose balance changes are the aggregation of balance changes of the individual transactions.

Batch transactions are always signed jointly and sent to the blockchain according to their order in the batch.

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