Skip to main content

Building Transactions

Once you’ve uploaded your IDL, Orquestra provides REST endpoints to build Solana transactions programmatically. This guide explains how to use the transaction builder API.

Understanding the Build Endpoint

For each instruction in your IDL, Orquestra generates a build endpoint:
POST /api/{projectId}/instructions/{instructionName}/build
This endpoint:
  1. Validates accounts and arguments against your IDL
  2. Serializes instruction data using Anchor’s encoding
  3. Constructs a complete transaction
  4. Returns a base64-encoded transaction ready to sign

Basic Transaction Building

1

List Available Instructions

First, discover what instructions are available:
curl https://api.orquestra.so/api/{projectId}/instructions
2

Get Instruction Details

Retrieve account and argument requirements:
curl https://api.orquestra.so/api/{projectId}/instructions/{name}
3

Build the Transaction

Provide accounts, arguments, and a fee payer:
curl -X POST https://api.orquestra.so/api/{projectId}/instructions/{name}/build \
  -H "Content-Type: application/json" \
  -d '{ /* transaction params */ }'

Request Structure

All build requests require three main components:
{
  "accounts": {
    "accountName1": "pubkeyString1",
    "accountName2": "pubkeyString2"
  },
  "args": {
    "argName1": "value1",
    "argName2": { "field": "value" }
  },
  "feePayer": "feePayerPubkeyString",
  "recentBlockhash": "optional_blockhash",
  "network": "mainnet"
}

Accounts

Map each account name from your IDL to a public key string:
"accounts": {
  "user": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
  "systemProgram": "11111111111111111111111111111111"
}
Account names must match exactly as defined in your IDL. Use camelCase if your IDL uses camelCase.

Arguments

Provide values for each instruction argument. Types are automatically validated:
"args": {
  "amount": 1000000,
  "name": "My NFT",
  "enabled": true
}

Network Selection

Specify which Solana network to use for fetching recent blockhashes and account data:
  • "mainnet" or "mainnet-beta": Solana mainnet (default)
  • "devnet": Solana devnet
  • Custom RPC URL: "https://my-rpc.com"

Complete Example

Here’s a full example building a token transfer instruction:
curl -X POST https://api.orquestra.so/api/abc123xyz/instructions/transfer/build \
  -H "Content-Type: application/json" \
  -d '{
    "accounts": {
      "from": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
      "to": "BvZzVzX5ZxWvQJXJHYJJJQZqQ9J9J9J9J9J9J9J9J9J",
      "authority": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
      "tokenProgram": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
    },
    "args": {
      "amount": 1000000
    },
    "feePayer": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
    "network": "mainnet"
  }'

Response Format

{
  "transaction": "base64EncodedTransactionString",
  "blockhash": "5KxR8...JQz7",
  "lastValidBlockHeight": 234567890
}
The response includes:
  • transaction: Base64-encoded serialized transaction
  • blockhash: Recent blockhash used
  • lastValidBlockHeight: Block height until which the transaction is valid

Signing and Sending

After building the transaction, you need to sign and send it:
import { Transaction, Connection } from '@solana/web3.js';

// Deserialize the transaction
const tx = Transaction.from(
  Buffer.from(response.transaction, 'base64')
);

// Sign with wallet
const signed = await wallet.signTransaction(tx);

// Send to network
const connection = new Connection('https://api.mainnet-beta.solana.com');
const signature = await connection.sendRawTransaction(signed.serialize());

// Confirm
await connection.confirmTransaction({
  signature,
  blockhash: response.blockhash,
  lastValidBlockHeight: response.lastValidBlockHeight,
});

console.log('Transaction confirmed:', signature);

Handling PDAs (Program Derived Addresses)

If your instruction uses PDAs, Orquestra can derive them for you:

List PDA Accounts

curl https://api.orquestra.so/api/{projectId}/pda
Response:
{
  "projectId": "abc123xyz",
  "programId": "YourProgramID...",
  "pdaAccounts": [
    {
      "instruction": "initialize",
      "account": "vault",
      "seeds": [
        { "kind": "const", "value": "vault" },
        { "kind": "account", "path": "user" },
        { "kind": "arg", "path": "vaultId" }
      ]
    }
  ]
}

Derive a PDA

curl -X POST https://api.orquestra.so/api/{projectId}/pda/derive \
  -H "Content-Type: application/json" \
  -d '{
    "instruction": "initialize",
    "account": "vault",
    "seedValues": {
      "user": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
      "vaultId": 42
    }
  }'
Response:
{
  "address": "DerivedPDAAddress...",
  "bump": 255
}
Then use the derived address in your build request:
"accounts": {
  "vault": "DerivedPDAAddress...",
  ...
}

Validation and Error Handling

Orquestra validates your request against the IDL:

Common Errors

{
  "error": "Invalid build request",
  "details": [
    "Missing required account: systemProgram"
  ]
}
Always check the instruction details endpoint first to see exact account and argument requirements.

Rate Limits

Transaction building is rate-limited:
  • 30 requests per minute per IP address
Response headers:
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 25
X-RateLimit-Reset: 1710504600
If you exceed the limit:
{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Try again in 45 seconds.",
  "retryAfter": 45
}

Working with Complex Types

Enums

Enums are represented as objects with a single key:
"args": {
  "orderType": { "limit": { "price": 100 } }
}
For unit variants:
"args": {
  "status": { "active": {} }
}

Options (Nullable Values)

Use null for None, or provide the value:
"args": {
  "metadata": null,
  "name": "Some name"
}

Vectors/Arrays

"args": {
  "recipients": [
    "Pubkey1...",
    "Pubkey2..."
  ]
}

Best Practices

  1. Cache project metadata: Fetch instruction details once and reuse
  2. Derive PDAs beforehand: Pre-compute PDAs to reduce API calls
  3. Handle rate limits gracefully: Implement exponential backoff
  4. Validate inputs client-side: Check types before calling the API
  5. Use TypeScript types: Generate types from your IDL for type safety
  6. Specify network explicitly: Don’t rely on defaults in production

Advanced: Batch Transactions

To build multiple transactions in parallel:
const promises = instructions.map((ix) =>
  fetch(`https://api.orquestra.so/api/${projectId}/instructions/${ix.name}/build`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(ix.params),
  }).then((r) => r.json())
);

const transactions = await Promise.all(promises);

Next Steps

API Keys

Set up authentication for production apps

Uploading IDLs

Learn more about IDL management

AI Documentation

Generate docs for AI agents

API Reference

Full API documentation

Build docs developers (and LLMs) love