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:
Validates accounts and arguments against your IDL
Serializes instruction data using Anchor’s encoding
Constructs a complete transaction
Returns a base64-encoded transaction ready to sign
Basic Transaction Building
List Available Instructions
First, discover what instructions are available: curl https://api.orquestra.so/api/{projectId}/instructions
Get Instruction Details
Retrieve account and argument requirements: curl https://api.orquestra.so/api/{projectId}/instructions/{name}
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:
Primitive Types
Struct Types
Array Types
"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"
}'
{
"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
Missing Account
Type Mismatch
Invalid Pubkey
{
"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
Cache project metadata : Fetch instruction details once and reuse
Derive PDAs beforehand : Pre-compute PDAs to reduce API calls
Handle rate limits gracefully : Implement exponential backoff
Validate inputs client-side : Check types before calling the API
Use TypeScript types : Generate types from your IDL for type safety
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