Skip to content
For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending .md to the URL or sending Accept: text/markdown.

Transaction Relay

For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /a4-server/transaction-relay.md.

arete-server can expose a purpose-built Solana transaction transport on the same HTTP origin as stack reads. It is disabled by default, even when an RPC URL is configured.

The relay provides six fixed routes. It is not a generic JSON-RPC proxy, and it never receives signing keys or signs transactions.

The relay requires the server’s HTTP listener:

use arete_server::Server;
Server::builder()
.spec(my_stack::spec())
.websocket()
.bind("[::]:8877".parse()?)
.http()
.health_monitoring()
.start()
.await?;

Enable transaction routes through environment configuration:

Terminal window
ARETE_TRANSACTIONS_ENABLED=true \
ARETE_TRANSACTION_RPC_URL=https://your-dedicated-solana-rpc.example \
cargo run --release

Or configure them programmatically:

use arete_server::{Server, TransactionConfig};
Server::builder()
.spec(my_stack::spec())
.websocket()
.http()
.transactions_config(TransactionConfig {
enabled: true,
rpc_url: Some(transaction_rpc_url),
..TransactionConfig::default()
})
.start()
.await?;
RouteRequired scopeSolana RPC method
POST /transactions/v1/latest-blockhashtransaction:inspectgetLatestBlockhash
POST /transactions/v1/feetransaction:inspectgetFeeForMessage
POST /transactions/v1/simulatetransaction:inspectsimulateTransaction
POST /transactions/v1/signature-statustransaction:inspectgetSignatureStatuses
POST /transactions/v1/signature-statusestransaction:inspectgetSignatureStatuses
POST /transactions/v1/block-heighttransaction:inspectgetBlockHeight
POST /transactions/v1/gettransaction:inspectgetTransaction
POST /transactions/v1/signaturestransaction:inspectgetSignaturesForAddress
POST /transactions/v1/sendtransaction:sendsendTransaction

Routes accept validated JSON schemas and reject unknown fields. Solana u64 values are decimal strings over HTTP.

signature-statuses resolves up to 256 signatures in one call. Results are positionally aligned with the request: an unknown signature is null in its own slot rather than omitted, so a caller cannot attribute one signature’s status to another. An empty batch answers without contacting the cluster, and reports X-Arete-Upstream-Attempted: false accordingly. More than 256 signatures is refused as batch_limit_exceeded, which is Solana’s own getSignatureStatuses ceiling.

get answers what a confirmed transaction moved, not how: err, slot, blockTime and every resolved account’s pre/post lamport balance. Instructions, logs and token balances are out of scope — a payer that settles inside a CPI is only visible in the balance deltas. The account list is the cluster’s own resolved order, lookup-table entries included, so it lines up with the balances position by position. An unseen signature is null with 200, not an error. maxSupportedTransactionVersion defaults to the highest version the relay can describe (1) and is a JSON number, not a decimal string; commitment accepts confirmed or finalized.

signatures pages an address’s history, newest first: {address, limit, before, until, commitment} in, [{signature, slot, blockTime, err}] out. before/until are signatures and exclusive at both ends, limit is a JSON number capped at Solana’s own 1000, and an address with no history is an empty array rather than an error. Memos and confirmation status are dropped — the page is a cursor, and a caller that wants detail asks get for the signature.

simulate returns loadedAccountsDataSize alongside unitsConsumed, both decimal strings. It is what a caller measures before setting a V1 loadedAccountsDataSizeLimit, so a budget can be derived from the simulation rather than guessed. Absent stays absent: an upstream that does not report the figure is not reporting zero, and the two must not be confused when sizing a limit.

For production, configure SignedSessionAuthPlugin through the HTTP auth plugin boundary. Read, inspect, and send scopes are independent. A read-only token cannot inspect or submit transactions.

Do not expose a production relay with development allow_all behavior. Self-hosted local development may deliberately omit the plugin when the listener is not publicly reachable.

When running behind a trusted proxy, configure ARETE_TRUSTED_PROXY_CIDRS. Forwarded client IP headers are ignored unless the direct peer belongs to one of those networks.

VariableDefaultPurpose
ARETE_TRANSACTIONS_ENABLEDfalseExpose transaction routes
ARETE_TRANSACTION_RPC_URLunsetPreferred upstream transaction RPC
SOLANA_RPC_URL / RPC_URLunsetLegacy transaction RPC fallback
ARETE_TRANSACTION_MAX_BODY_BYTES32768Maximum JSON request body
ARETE_TRANSACTION_MAX_BYTES4096Maximum decoded message/transaction bytes
ARETE_TRANSACTION_INSPECT_TIMEOUT_MS10000Inspection upstream timeout
ARETE_TRANSACTION_SEND_TIMEOUT_MS15000Submission upstream timeout
ARETE_TRANSACTION_STATUS_TIMEOUT_MS10000Status/block-height timeout
ARETE_TRANSACTION_INSPECT_CONCURRENCY64Global inspect concurrency
ARETE_TRANSACTION_SEND_CONCURRENCY16Global send concurrency
ARETE_TRANSACTION_INSPECT_REQUESTS_PER_MINUTE600Server inspect clamp
ARETE_TRANSACTION_SEND_REQUESTS_PER_MINUTE60Server send clamp
ARETE_TRANSACTION_STATUS_REQUESTS_PER_MINUTE600Server status clamp
ARETE_TRUSTED_PROXY_CIDRSemptyComma-separated trusted proxy networks
ARETE_TRANSACTION_USAGE_ENABLEDfalseEnable transaction usage delivery
ARETE_TRANSACTION_USAGE_ENDPOINTunsetUsage ingestion endpoint
ARETE_TRANSACTION_USAGE_TOKENunsetUsage ingestion bearer token
ARETE_TRANSACTION_USAGE_SPOOL_CAPACITY1000In-memory usage queue capacity

Transaction traffic never falls back to ARETE_READ_RPC_URL. Use a dedicated provider credential when possible so read and transaction quotas are isolated.

The send route:

  • accepts only bounded base64 wire transactions
  • requires nonzero signatures
  • derives the first transaction signature before contacting upstream, from the leading shortvec for legacy and v0 and from the tail signature array for v1 (SIMD-0385, version byte 129)
  • calls upstream sendTransaction once
  • leaves provider maxRetries unset, allowing the upstream RPC to rebroadcast until the blockhash expires
  • verifies that the provider returns the expected signature
  • marks timeout/transport uncertainty with the derived signature

Clients must reconcile an ambiguous signature rather than resubmit it blindly.

The server clamps request size, concurrency, and per-minute operation counts. Signed-session claims can impose lower limits per subject. Rate buckets and subject concurrency are process-local in V1.

Keep a transaction-enabled deployment at one replica until admission state is externalized. Scaling replicas multiplies effective per-process limits.

Optional transaction usage delivery sends operation, result, identity, byte count, and latency metadata. It excludes transaction bytes, account lists, program logs, credentials, and full signatures.

V1 uses a bounded in-memory queue with bounded retries. It is not a durable billing queue. Alert on queue saturation and failed delivery logs.

With the otel feature, monitor:

  • arete.transaction.requests.total
  • arete.transaction.request.duration
  • arete.transaction.request.bytes
  • arete.transaction.upstream.total
  • arete.transaction.inflight
  • arete.transaction.denials.total
  1. Use a dedicated HTTPS transaction RPC.
  2. Configure signed-session HTTP auth and exact scopes.
  3. Configure trusted proxy CIDRs correctly.
  4. Keep one replica in V1.
  5. Alert on upstream timeouts and ambiguous submissions.
  6. Alert on usage queue drops when usage tracking is enabled.
  7. Exercise a signed test transaction before enabling browser traffic.
  8. Preserve routes during rollback until issued transaction tokens expire.

The most valuable follow-up work is:

  1. Durable, batched usage delivery with a dead-letter path.
  2. Shared rate/admission state for horizontal scaling.
  3. Configurable circuit breaking and provider failover for inspect/status calls only.
  4. A reusable transport conformance test suite.
  5. Validator-backed release tests and hosted browser canaries.
  6. Grafana dashboards and alerts for transaction-specific metrics.

See Using transactions for application integration and error handling.