What it does
Switchyard records which off-chain execution bucket belongs to a position and when that resource is eligible for reuse. Use it for a product that needs a visible allocation, open/close lifecycle and explicit evidence of return/sweep.
A bucket is a record, not a wallet key, on-chain vault or custody service. Allocating it does not create/fund a wallet or move assets. Applications or qualified services perform the external work and then record its observed lifecycle.
Use independently
Select uk.capp.sidecar.solana-switchyard in local-allocation mode. No wallet, RPC, Arena or Edge is required. The owning API is window.capp.solana.switchyard; use the portable client for the same methods.
Retain your profile, vault, client-position, market and side identity. The local allocation key is derived from these fields. Keep the returned railId for subsequent reads/transitions instead of guessing which record a position label identifies.
Provider dependencies
| Mode | Dependency | Application responsibility |
|---|---|---|
local-allocation | None | Supply stable position identity and genuine evidence of any external lifecycle step. |
external-adapter | Configured allocation/rail service | Qualify its concurrency, identity, custody and evidence semantics separately. |
| Optional Arena integration | Arena plus your application glue | Keep simulated close/sweep evidence explicitly simulated. |
Edge does not automatically call Switchyard during payment preparation. An application that needs allocation must orchestrate it deliberately. A local executionWallet field is just an identity string; it is not validation that your app controls the address.
First successful allocation
- Define stable
profileId,vaultId,clientPositionId,marketandsidevalues for one position lifecycle. - Allocate with the intended public execution-wallet identity, or omit it when your own workflow still needs funding/setup.
- Retain the returned
railId, allocation key and state. Repeat identical input to confirm idempotence. - Use the transition table only after the corresponding application/provider event has occurred.
- Make a bucket reusable only after closed-position and sweep evidence. A new position gets a new identity and record even if it reuses the same external wallet resource.
Allocate and inspect without moving funds
Copy the client module alongside this module. Supply your actual public execution-wallet identity. The helper is inert until explicitly called and records no funding or trading result.
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);
export async function allocatePositionBucket({profileId, vaultId, clientPositionId, market, side, executionWallet}) {
const status = await client.call('switchyard', 'status');
if (!status.ready || status.providerMode !== 'local-allocation') {
throw new Error('A ready local Switchyard is required');
}
const bucket = await client.call('switchyard', 'allocate', {
profileId, vaultId, clientPositionId, market, side, executionWallet
});
if (bucket.offChainAllocation !== true || bucket.fundsMoved !== false) {
throw new Error('Unexpected allocation boundary');
}
return client.call('switchyard', 'read', {railId: bucket.railId});
}Expect allocated when a wallet identity was supplied, or rail_wallet_needs_funding when it was omitted. Replaying identical input returns the same record. Changing the wallet under the same allocation identity is a conflict, not an update: this API does not provide a method to attach a different wallet to that record later.
Lifecycle and reuse
| Current state | Supported next states |
|---|---|
allocated | rail_wallet_needs_funding, rail_wallet_funded, position_request_created |
rail_wallet_needs_funding | rail_wallet_funded |
rail_wallet_funded | position_request_created, sweep_requested |
position_request_created | position_open_pending |
position_open_pending | position_open, position_closed |
position_open | position_close_pending |
position_close_pending | position_closed |
position_closed | sweep_requested |
sweep_requested | rail_bucket_reusable |
rail_bucket_reusable | Terminal record; preserve it for history |
Transitions into rail_wallet_funded, position_open, position_closed or rail_bucket_reusable require an evidenceDigest. The digest is a caller-supplied reference to evidence, not on-chain verification by Switchyard. Retain the evidence itself at its proper owner, and never substitute an arbitrary hash to release a real resource.
With requireUniqueExecutionWallet enabled, an external wallet identity cannot be assigned to another active record. Reaching rail_bucket_reusable permits its reuse for a new allocation identity; it does not reset/delete the old position. Calling allocate() again with the original identity returns its old terminal record.
Scope and limitations
A bucket is a record, not an on-chain vault. Changing its lifecycle does not move funds. Position identity remains historical when a resource is reused.
The store and wallet-identity guard are local to this app/origin. They are not a distributed allocator or a chain lock. Local rows do not contain private keys or threshold shares, but account/position identity can still be private business information. Inspect manifest() before sharing it.
Each local lifecycle is bounded to 128 entries, alongside configured bucket/store capacity. There is no force-release/reset method in the public API. Keep unresolved records intact when an external operation is uncertain.
Application API
The owning interface is window.capp.solana.switchyard. 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('switchyard', 'status');status
client.call('switchyard', 'status', input)
Main input: No arguments required.
Read allocation-provider readiness and lifecycle constraints.
allocate
client.call('switchyard', 'allocate', input)
Main input: clientPositionId; optional profileId, vaultId, market, side, executionWallet.
Allocate an idempotent off-chain execution bucket. Does not fund a wallet or move assets.
read
client.call('switchyard', 'read', input)
Main input: railId, or a uniquely identifying clientPositionId scope.
Read one execution bucket; ambiguous results are rejected.
transition
client.call('switchyard', 'transition', input)
Main input: railId, state; evidenceDigest for funded, open, closed or reusable transitions.
Record a supported lifecycle transition with supplied evidence. External effects remain the application/provider responsibility.
manifest
client.call('switchyard', 'manifest', input)
Main input: No arguments required.
Read active and reusable buckets. Private keys and raw shares are not included.
Troubleshooting
| Symptom or error | Next step |
|---|---|
WALLET_IN_USE | Read the active rail named in the error and reconcile it before reassigning the resource. |
ID_CONFLICT | Recover the original position allocation; do not change its execution wallet in place. |
NOT_FOUND / ambiguous position read | Use the exact retained railId, or supply a uniquely identifying profile/vault scope. |
STATE | Follow the supported lifecycle; a requested close is not a recorded close/sweep. |
EVIDENCE | Supply the digest of genuine retained lifecycle evidence, preserving its simulation/live classification. |
CAPACITY / storage error | Preserve history and restore storage capacity; do not delete a pending allocation. |
Verify your integration
Test identical allocation, changed wallet, two active records competing for a wallet, a missing evidence digest and reuse after a complete recorded close/sweep. Confirm that no local transition performs or claims an unobserved transfer.
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.
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 |
|---|---|---|
providerMode | select | Select the implementation this build will use. Configuring an endpoint does not verify its connection or behavior.. Choices: local-allocation, external-adapter |
maxBuckets | number | Maximum local records. At the limit, new records stop; existing history is preserved. Increase the limit or archive and reconcile the workspace.. Bounds: 8 to 4096 |
requireUniqueExecutionWallet | checkbox | Reject active allocations that try to reuse the same execution-wallet identity. |
externalAdapterUrl | text | Absolute HTTPS endpoint, or loopback HTTP for development. Keep private credentials on the backend; packaged configuration is visible to users. |
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.switchyard.status | low | mode-policy |