This document describes the current solana-agent-runtime.v1 contract.
The scope of this spec is narrow:
- it does not restate raw instruction structure
- it does not try to be a full workflow language
It only describes the deterministic runtime layer that sits on top of Codama.
This document only covers:
*.runtime.json
It assumes Codama already exists for the same protocol and already owns:
- instruction structure
- named accounts
- signer metadata
- fixed/default accounts
- PDA-backed defaults when declared in Codama
Program-specific runtime logic lives in:
public/idl/<protocol>.runtime.json
Examples:
orca_whirlpool.runtime.jsonpump_amm.runtime.jsonpump_core.runtime.json
What the runtime pack adds on top of Codama:
- named
views - named
writes - named reusable
transforms - exact view input contracts
- deterministic ordered runtime steps
- optional
pre/postenvelope instructions - mapping from derived values into Codama-shaped write args/accounts
What it does not restate:
- raw instruction account schema
- signer metadata already declared in Codama
- fixed/default/PDA-backed accounts already declared in Codama
Required top-level keys:
{
"$schema": "/idl/solana_agent_runtime.schema.v1.json",
"schema": "solana-agent-runtime.v1",
"protocol_id": "orca-whirlpool-mainnet",
"program_id": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"codama_path": "/idl/orca_whirlpool.codama.json",
"label": "Orca Whirlpools",
"views": {},
"writes": {},
"transforms": {}
}Top-level attributes:
$schema- optional schema path string
schema- required
- must be
solana-agent-runtime.v1
protocol_id- required
- logical protocol identifier for the runtime pack
program_id- required
- Solana program id this runtime pack targets
codama_path- required
/idl/*path to the Codama IDL used by this runtime pack
label- optional
- human-friendly protocol label
views- required
- object map from operation id to
viewSpec
writes- required
- object map from operation id to
writeSpec
transforms- required
- object map from transform id to an array of
transformStepSpec
No other top-level attributes are allowed.
Both viewSpec and writeSpec share the same ordered preparation phase:
steps- optional
- array of
operationStepSpec
Each operation step is either:
- a direct runtime step like
wallet_pubkey,decode_account,decode_accounts,ata,pda, etc. - or a transform fragment invocation:
{ "kind": "transform", "transform": "quote_exact_in__quote_math" }This is intentional:
- operations can interleave loading and transform execution in a fixed order
- both views and writes can reuse the same named root transforms
- the runtime no longer splits per-operation prep into separate
loadandtransformarrays
A view operation has these attributes:
load_instruction- optional
- Codama instruction name whose account context should be loaded for the view
load_instruction_bindings- optional
- explicit bindings used to populate that instruction context
- supports:
argsaccounts
inputs- optional
- map of input name -> type string
steps- optional
- ordered array of
operationStepSpec
pre- optional
- array of
preInstructionSpec
post- optional
- array of
postInstructionSpec
output- required
- typed output contract for the view
pre / post on a view are useful when the view is not only computing a quote, but also previewing the transaction envelope implied by a Codama instruction context.
These types are used only by views[*].output.
Used inside output.object_schema.fields.
Attributes:
type- required
- string
description- optional
- string
Typed output contract for a view operation.
Attributes:
type- required
- one of:
array,object,scalar
source- required
- runtime expression string
object_schema- optional
- object schema for
type = object
item_schema- optional
- schema for array items when
type = array
scalar_type- optional
- scalar type string for
type = scalar
A contract write operation has these attributes:
instruction- required
- target instruction name in Codama
steps- optional
- ordered array of
operationStepSpec
args- optional
- map of arg name -> scalar binding
accounts- optional
- map of account name -> string binding
remaining_accounts- optional
- either:
- string reference, or
- array of
{ pubkey, isSigner?, isWritable? }
pre- optional
- array of
preInstructionSpec
post- optional
- array of
postInstructionSpec
Write inputs are not declared explicitly in the runtime file.
Instead, the visible write input surface is sourced from Codama and narrowed to the Codama args/accounts still referenced through $input.* inside the write.
When a write needs extra helper inputs that are not part of the underlying Codama instruction shape, it may also declare inputs explicitly.
This is meant for higher-level helper writes that still compile down to one Codama instruction, but need extra deterministic context during step preparation.
Top-level transforms is the only place where reusable transform fragments are declared.
Each entry is:
- a named array of
transformStepSpec
Operations reference these entries from ordered steps by name.
Example:
{
"transforms": {
"swap_direction": [
{
"name": "a_to_b",
"kind": "compare.equals",
"left": "$whirlpool_data.token_mint_a",
"right": "$input.token_in_mint"
}
]
},
"views": {
"quote_exact_in": {
"steps": [
{ "kind": "transform", "transform": "swap_direction" }
]
}
},
"writes": {
"swap_exact_in": {
"steps": [
{ "kind": "transform", "transform": "swap_direction" }
]
}
}
}The current runtime supports these important step families:
- load / resolution:
wallet_pubkeydecode_accountdecode_accountsaccount_infosaccount_ownertoken_accounts_by_owneratapda
- arithmetic:
math.addmath.summath.mulmath.submath.minmath.maxmath.div_round_upmath.modmath.mul_div_floormath.mul_div_ceilmath.shift_leftmath.shift_rightmath.bit_and
- list / collection:
list.range_maplist.maplist.flat_maplist.reducelist.filterlist.find_firstlist.sort_bylist.concatlist.firstlist.get
- object:
object.createobject.merge
- comparison / control:
compare.equalscompare.not_equalscompare.gtcompare.gtecompare.ltcompare.ltelogic.ifcoalesceassert.not_null
- nested transform invocation inside a transform fragment:
transform
The JSON schema is the source of truth for the full step catalog and exact attributes for each kind.
Reads parsed SPL token accounts for a wallet owner from the RPC node.
Required attributes:
namekind = "token_accounts_by_owner"owner
Optional attributes:
minttoken_program
Behavior:
- if
token_programis provided, it queries only that token program - if
mintis provided, it uses the owner+mint lookup and filters the returned rows - if neither is provided, it queries both
Tokenkeg...andToken-2022
Returned rows are normalized objects with:
pubkeytokenProgrammintowneramountdecimalsuiAmountStringstateisNative
Reads lightweight account existence / owner metadata for a batch of addresses.
Required attributes:
namekind = "account_infos"addresses
Returns normalized rows with:
addressexistsownerlamportsexecutabledataLength
list.map, list.flat_map, and list.reduce can execute nested steps inside the collection scope.
Example:
{
"name": "flattened",
"kind": "list.flat_map",
"items": "$groups",
"item_as": "group",
"output": "$group.values"
}These scoped collection steps can also invoke another named transform with explicit bindings:
{
"name": "quote_row",
"kind": "transform",
"transform": "quote_tick",
"bindings": {
"tick": "$item",
"direction": "$a_to_b"
},
"output": "$payload"
}This is one of the main runtime improvements that the Orca harness ended up exercising heavily.
when conditions in pre and post use metaConditionSpec.
Allowed forms:
equals
{ "equals": ["$a", "$b"] }all
{ "all": [ { "equals": ["$a", true] }, { "equals": ["$b", false] } ] }any
{ "any": [ ... ] }not
{ "not": { "equals": ["$a", "$b"] } }Required attributes:
namekind = "spl_ata_create_idempotent"payerataownermint
Optional attributes:
token_programassociated_token_programwhen
Required attributes:
namekind = "system_transfer"fromtolamports
Optional attributes:
when
Required attributes:
namekind = "spl_token_sync_native"account
Optional attributes:
token_programwhen
Required attributes:
namekind = "spl_token_close_account"accountdestinationowner
Optional attributes:
token_programwhen
The Orca runtime pack is now a good boundary test for this spec.
It exercises:
- exact-input quotes
- exact-output quotes
- ordered operation steps
- scoped collection transforms
- Codama-backed write preparation
- preview envelope instructions around a quote
In the current Orca pack:
-
quote_exact_in- loads Codama instruction context for
swap_v2 - decodes the
Whirlpool - derives tick arrays
- decodes those tick arrays
- runs quote math
- previews
pre/post - returns a typed quote object
- loads Codama instruction context for
-
swap_exact_in- targets Codama instruction
swap_v2 - consumes raw Codama-shaped write inputs
- materializes final args/accounts
- targets Codama instruction
Minimal structural excerpt:
{
"protocol_id": "orca-whirlpool-mainnet",
"program_id": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"codama_path": "/idl/orca_whirlpool.codama.json",
"transforms": {
"quote_exact_in__derive_tick_arrays": [],
"quote_exact_in__quote_math": []
},
"views": {
"quote_exact_in": {
"load_instruction": "swap_v2",
"inputs": {
"token_in_mint": "token_mint",
"token_out_mint": "token_mint",
"amount_in": "u64",
"slippage_bps": "u16",
"whirlpool": "pubkey",
"unwrap_sol_output": "bool"
},
"load_instruction_bindings": {
"args": {
"amount": "$input.amount_in"
},
"accounts": {
"whirlpool": "$input.whirlpool"
}
},
"steps": [
{ "name": "wallet", "kind": "wallet_pubkey" },
{
"name": "whirlpool_data",
"kind": "decode_account",
"address": "$input.whirlpool",
"account_type": "Whirlpool"
},
{ "kind": "transform", "transform": "quote_exact_in__derive_tick_arrays" },
{
"name": "tick_arrays_data",
"kind": "decode_accounts",
"addresses": "$tick_arrays",
"account_type": "TickArray"
},
{ "kind": "transform", "transform": "quote_exact_in__quote_math" }
],
"pre": [
{
"kind": "spl_ata_create_idempotent",
"payer": "$wallet",
"ata": "$instruction_accounts.token_owner_account_a",
"owner": "$wallet",
"mint": "$whirlpool_data.token_mint_a"
}
],
"output": {
"type": "object",
"source": "$derived",
"object_schema": {
"entity_type": "orca_quote_exact_in",
"identity_fields": ["whirlpool"],
"fields": {
"whirlpool": { "type": "pubkey" },
"token_in_mint": { "type": "pubkey" },
"token_out_mint": { "type": "pubkey" },
"amount_in": { "type": "u64" },
"slippage_bps": { "type": "u16" },
"estimated_out": { "type": "u64" },
"minimum_out": { "type": "u64" },
"a_to_b": { "type": "bool" },
"pool_fee_bps": { "type": "number" }
}
}
}
}
},
"writes": {
"swap_exact_in": {
"instruction": "swap_v2",
"args": {
"amount": "$input.amount",
"other_amount_threshold": "$input.other_amount_threshold",
"sqrt_price_limit": "$input.sqrt_price_limit",
"amount_specified_is_input": "$input.amount_specified_is_input",
"a_to_b": "$input.a_to_b",
"remaining_accounts_info": null
},
"accounts": {
"whirlpool": "$input.whirlpool",
"tick_array0": "$input.tick_array0",
"tick_array1": "$input.tick_array1",
"tick_array2": "$input.tick_array2"
}
}
}
}This excerpt is intentionally structural. The real Orca pack is larger and currently includes:
quote_exact_inquote_exact_out- swap writes
- liquidity parity / decrease / collect writes
Real Orca fragment ids used by the current quote flows include:
quote_exact_in__derive_tick_arraysquote_exact_in__quote_mathquote_exact_out__derive_tick_arraysquote_exact_out__quote_math
Useful references:
- runtime pack:
/idl/orca_whirlpool.runtime.json - Codama IDL:
/idl/orca_whirlpool.codama.json - live inspect UI:
/#compute
Put logic in Codama when it is:
- instruction structure
- account metadata
- signer metadata
- fixed/default account resolution
- PDA-backed defaults
Put logic in the runtime spec when it is:
- deterministic protocol-specific transform
- reusable deterministic transform fragments shared by views and writes
- dynamic value materialization around a Codama write
- small transaction-envelope logic around a write or quote preview
Anything that requires:
- transaction A
- then read live state
- then transaction B
belongs in a higher-level runtime, not in this spec.