What it does
Arena is an offline execution simulator for payments and position lifecycles. Use it to prove that your interface handles success, rejection, expiry and a lost reply before involving an RPC service or wallet.
It retains outcomes by operation ID and can model opening, increasing, reducing and closing a position. It does not place orders, discover market prices, settle assets or create a real exchange position. Every result is labelled simulated: true and fundsMoved: false.
Use independently
Select uk.capp.sidecar.solana-arena in deterministic-local mode. No peers or network are needed. The owning API is window.capp.solana.arena; the portable client is an optional thin wrapper.
Choose a disposable test workspace and keep each scenario's operation and position IDs. Reusing the exact request returns the retained outcome; changing an existing operation's payload is an identity conflict, not another trade.
Provider dependencies
| Use | Required components | Boundary |
|---|---|---|
| Direct simulation | Arena only | No network transaction occurs. |
| Exercise submission/recovery | Dispatch in arena-peer mode; optionally Witness in arena-peer mode | Dispatch retains the uncertain attempt; Witness observes Arena, not the chain. |
| Exercise a position's allocation | Optional Circuit and Switchyard | Your application links simulation evidence to their local records. Arena does not advance those records automatically. |
| Publish a small devnet evidence transfer | A separately configured devnet Edge composition | Its wallet approval and actual transfer are separate from the simulated outcome. |
First successful scenario
- Start with
failureMode: noneandfinality: simulated-finalised; these are build settings, not arguments toexecute(). - Execute one
transactionor anopenwith a position ID, positive size and entry price. Inspect the returned state and simulation flags. - Repeat the exact request and verify the same outcome, not a second position increase.
- Close the position using a new operation ID but the same position ID. Read
position()to see the retained closed lifecycle. - In a separate fixture, choose
drop-response. Catch the error and callread()for the saved operation instead of inventing another operation.
A repeatable position step
Copy the client module beside this application module. Call the helper from an explicit rehearsal action; it does nothing on module load. Supply the same inputs when resuming the same step.
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);
export async function runArenaStep({operationId, clientPositionId, action, size, price, reduceSize}) {
const status = await client.call('arena', 'status');
if (!status.ready || status.providerMode !== 'deterministic-local') {
throw new Error('A ready local Arena is required');
}
const request = {operationId, clientPositionId, action, network: 'demo'};
if (size !== undefined) request.size = size;
if (price !== undefined) request.price = price;
if (reduceSize !== undefined) request.reduceSize = reduceSize;
const bytes = new TextEncoder().encode(JSON.stringify(request));
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
request.messageDigest = [...hash].map(n => n.toString(16).padStart(2, '0')).join('');
let outcome;
try {
outcome = await client.call('arena', 'execute', request);
} catch (error) {
if (error.code !== 'CAPP_SOLANA_ARENA_RESPONSE_DROPPED') throw error;
outcome = await client.call('arena', 'read', {operationId, messageDigest: request.messageDigest});
}
if (outcome.simulated !== true || outcome.fundsMoved !== false) {
throw new Error('Unexpected simulation boundary');
}
return outcome;
}An opening call can use {operationId: 'docs-open-1', clientPositionId: 'docs-position-1', action: 'open', size: 2, price: 100}. Close it with {operationId: 'docs-close-1', clientPositionId: 'docs-position-1', action: 'close'}. These are synthetic numbers, not a trading recommendation or market observation. Always inspect the returned state: a resolved promise can contain rejected or expired.
Failure and finality choices
| Setting | Observable result | Recovery |
|---|---|---|
none | A retained confirmed/finalised simulation | Read or replay the exact request. |
reject | A retained rejected result, without position execution | Display rejection; a new scenario needs a new ID. |
expire | A retained expired result | Keep the outcome as evidence of this test. |
drop-response | The outcome is saved, then RESPONSE_DROPPED is thrown | Read the same operation; do not execute another identity. |
simulated-confirmed stays a configured simulated result; repeated reads do not turn it into simulated-finalised. A latency setting delays the simulated work, not a real network.
Scope and limitations
Its trades, P&L and settlement are simulations. A 1/10,000 devnet evidence transfer proves only that small transfer and its memo. Arena itself does not calculate a trusted P&L or price feed. Close/sweep flags describe the synthetic lifecycle, not an on-chain fund return.
evidenceTransfer() requires a retained finalised outcome. It uses integer floor division of the original SOL lamports by the divisor, reports the remainder and refuses a result below one lamport. requiresSeparateTransfer: true means exactly that: it neither signs nor sends. Token evidence scaling is not this interface.
Local outcomes and positions are application/origin-scoped. Full stores stop admitting new records rather than evicting unresolved evidence. Keep scenario IDs stable after a reload; clearing storage is not a recovery strategy for a real payment.
Application API
The owning interface is window.capp.solana.arena. The portable client calls the same methods. This table describes main arguments, not a stricter typed guarantee than the current provider implements. Exact transaction/provider bindings must be preserved; unsupported state or arguments are rejected.
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);
const status = await client.call('arena', 'status');status
client.call('arena', 'status', input)
Main input: No arguments required.
Read the deterministic simulator configuration. Network is demo and simulation is explicit.
execute
client.call('arena', 'execute', input)
Main input: operationId, messageDigest; optional attemptId, network: demo, action, clientPositionId, market, side, size, price, reduceSize.
Run the configured simulated outcome, including rejection, expiry or a lost-response scenario. Never performs a network transaction.
read
client.call('arena', 'read', input)
Main input: operationId.
Read the retained simulated outcome instead of executing a new operation.
position
client.call('arena', 'position', input)
Main input: clientPositionId or positionId.
Read the saved simulated position lifecycle.
evidenceTransfer
client.call('arena', 'evidenceTransfer', input)
Main input: operationId, originalAmountLamports; optional attemptId, divisor.
Propose a scaled SOL evidence amount from a successful simulated outcome. Requires a separately authorized transfer; it is not live settlement.
Troubleshooting
| Symptom or error | Next step |
|---|---|
DEMO_ONLY | Pass network: demo; Arena cannot execute devnet or mainnet transactions. |
ID_CONFLICT | Compare the complete request, including action, size and price. Keep the saved request for retries. |
POSITION_EXISTS | Use increase for an existing open position, or a new position ID for a new lifecycle. |
POSITION_NOT_FOUND | Read the saved position; a close/reduction requires an open position. |
RESPONSE_DROPPED | Recover with read() using the same operation and binding. |
SCALING or evidence STATE | Require a finalised simulation and an integer scaled amount of at least one lamport. |
Verify your integration
Exercise all four failure settings, replay each accepted request and verify that a changed request is refused. Test open, partial reduction, full close and a repeated close. Assert simulated, fundsMoved and the absence of real network calls, not just the presence of a signature-shaped string.
Integration recipes
Composition · Devnet payment · Recovery · Typed client · Schemas
Related data contracts
These schemas describe payload shape, not evidence of approval or successful execution. Match the version in your payload; older versions remain listed for compatibility.
- capp.solana-arena.outcome.v2.schema.json
- capp.solana-arena.position.v1.schema.json
- capp.solana-arena.scaled-evidence.v1.schema.json
Configuration reference
These are the selected package's configuration fields. String credential values and installation-specific defaults are never published here. A field's presence does not bypass its runtime policy.
| Field | Type | Meaning and constraints |
|---|---|---|
failureMode | select | Deterministic laboratory outcome used to exercise recovery paths.. Choices: none, drop-response, reject, expire |
latencyMs | number | Artificial delay before Arena returns its deterministic outcome.. Bounds: 0 to 30000 |
feeLamports | number | Synthetic fee exposed to policy/UI tests; never charged on-chain.. Bounds: 0 to 1000000000 |
finality | select | Synthetic finality label returned by Arena; always remains explicitly simulated.. Choices: simulated-confirmed, simulated-finalised |
maxOperations | number | Maximum local records. At the limit, new records stop; existing history is preserved. Increase the limit or archive and reconcile the workspace.. Bounds: 1 to 2048 |
syntheticEntryPrice | number | Synthetic position entry price used only by the local position lifecycle model.. Bounds: 1e-06 to 1000000000 |
maxPositions | number | Maximum local records. At the limit, new records stop; existing history is preserved. Increase the limit or archive and reconcile the workspace.. Bounds: 1 to 1024 |
Agent interfaces
These names are declared by the package for host/Agent integration, not JavaScript methods to invoke directly. The host must register a tool, grant its scope and enforce its approval policy. A manifest declaration alone does not prove availability in every Agent catalogue. Prefer the application API above when integrating your own app.
| Tool | Declared risk | Approval requirement |
|---|---|---|
solana.arena.status | low | mode-policy |