Sidecars

Dispatch

Records a bounded attempt before sending and retains the signature when a reply is lost.

0.2.0-alpha.3Alpha capabilityDeclared targets: Windows, Androiduk.capp.sidecar.solana-dispatch

What it does

Dispatch delivers one recorded attempt and keeps enough local identity to recover a missing reply. Use it when you already have the authorized signed transaction, or when rehearsing delivery against Arena.

It stores the attempt before invoking the provider. Live mode derives the first signature and message/transaction digests from the supplied bytes. It does not choose a wallet, create an approval, or interpret arbitrary instructions as safe. Use Edge for the integrated reviewed-payment path.

Use independently

Select uk.capp.sidecar.solana-dispatch, then choose either a simulation provider or an intentional live provider. Retain a stable operationId and attemptId before sending. Use window.capp.solana.dispatch or the portable client.

For a first recovery test, choose arena-peer and select Arena with failureMode: drop-response. No Circuit, Latch or network is required for this rehearsal. The result must remain explicitly simulated.

Provider dependencies

ModeSend dependencyReconciliation dependency
arena-peerArena in deterministic-local modeReads the same Arena operation.
conduit-peerMatching writable Conduit; allowSubmission enabledMatching Witness, which observes the retained signature.
external-adapterConfigured HTTPS adapter; allowSubmission enabledAdapter action reconcile, not an implicit Witness fallback.

Live mainnet transport requires both Dispatch's and Conduit's relevant permission flags. That does not enable mainnet Edge or the built-in Latch signing recipe. Keep your application's authorization boundary separate from transport admission.

A lost-reply rehearsal

Copy the client module alongside this module. The helper checks for simulation before doing anything. Invoke it once from a labelled rehearsal action with saved IDs and a 64-character SHA-256 messageDigest for your synthetic scenario.

js
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);

export async function dispatchDemoOnce({operationId, attemptId, messageDigest}) {
  const status = await client.call('dispatch', 'status');
  if (!status.ready || status.providerMode !== 'arena-peer') {
    throw new Error('This example requires Dispatch backed by Arena');
  }
  const input = {operationId, attemptId, messageDigest, network: 'demo'};
  try {
    return await client.call('dispatch', 'submit', input);
  } catch (error) {
    if (error.detail?.state !== 'unknown') throw error;
    const retained = await client.call('dispatch', 'read', {attemptId});
    if (retained.operationId !== operationId || retained.messageDigest !== messageDigest) {
      throw new Error('Stored attempt differs; preserve it for investigation');
    }
    return retained; // Unknown is a result to reconcile, not permission to resend.
  }
}

export async function reconcileDispatchAttempt(attemptId) {
  return client.call('dispatch', 'reconcile', {attemptId});
}

With drop-response, expect the first helper to return state: unknown, then call reconcileDispatchAttempt() with the same attempt ID. Arena's recorded result can recover to simulated finality without another execution. Do not put a fresh call to submit() in a generic catch or retry loop.

Live request and result boundaries

Supply operationId, attemptId, network, messageDigest and signedTransactionBase64; include transactionDigest when already known. Dispatch verifies byte/digest agreement and non-empty signature slots, but its wire inspection is not a cryptographic signature verification or a transaction-policy evaluator. Your signer/review boundary must establish those properties first.

The returned attempt contains requestDigest, transactionDigest, signature, state, responseKnown, provider/network and simulation flags. It does not retain the raw signed transaction in its attempt record. Keep the reviewed operation and its signed bytes at their existing protected owner for recovery—not in a public diagnostic log.

An exact existing attempt is returned without another provider invocation. Changing bound arguments under the same attempt ID is refused. Another attempt for an operation with an unresolved or successful attempt is refused too. maxRetries is passed to the RPC for the same transaction; it is not an application-level permission to rebuild or re-sign.

Scope and limitations

Once the provider was invoked, an error cannot generally prove that nothing happened. Dispatch records unknown, retains any signature computed before sending, and requires observation of the existing attempt. responseKnown: true is not synonymous with success: a known rejection or failure is still a failure.

Reconciliation is a single observation and can remain unknown, submitted or confirmed. Terminal results are finalised, failed, rejected and expired; elapsed client time alone does not produce one. A local store can survive reload without being a distributed queue or cross-device exactly-once guarantee.

Do not clear attempt storage to get past OPERATION_PENDING. If persistence fails after provider invocation, preserve every known signature/operation binding and reconcile outside the failed write path before considering further effects.

Application API

The owning interface is window.capp.solana.dispatch. 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.

js
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);
const status = await client.call('dispatch', 'status');

status

client.call('dispatch', 'status', input)

Main input: No arguments required.

Read submission-provider readiness, network and configured gates.

submit

client.call('dispatch', 'submit', input)

Main input: operationId, messageDigest, network; signedTransactionBase64 for live submission; optional attemptId, transactionDigest.

Record an attempt before the provider can have an effect, then submit already signed bytes. Dispatch never signs.

read

client.call('dispatch', 'read', input)

Main input: attemptId or other supported recorded-attempt identity.

Read the retained submission attempt and its response-known/unknown state.

reconcile

client.call('dispatch', 'reconcile', input)

Main input: Recorded attempt identity.

Observe the same attempt through the configured observation provider. Do not create a replacement payment from an uncertain result.

Troubleshooting

Symptom or errorNext step
DISABLED / MAINNET_DISABLEDReview the intended provider and build permissions; never silently enable writes.
MESSAGE_MISMATCH / TRANSACTION_MISMATCHUse the exact approved signed bytes. Do not recompute identifiers around changed bytes.
ID_CONFLICTRecover the original attempt and compare all request fields.
OPERATION_PENDINGRead/reconcile the reported attempt before considering a replacement.
unknown after an exceptionPreserve the attempt and signature; observation, not resending, is the next step.
Missing Witness in Conduit modeSupply the matching observation provider before expecting live reconciliation.

Verify your integration

Test a dropped reply after acceptance, identical submission replay, a changed byte binding and reload before reconciliation. Count actual provider sends: recovery must not increment them. Also test a failure to save the pre-send record; no provider effect should occur before that record succeeds.

Integration recipes

Composition · Devnet payment · Recovery · Typed client · Schemas

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.

FieldTypeMeaning and constraints
providerModeselectSelect the implementation this build will use. Configuring an endpoint does not verify its connection or behavior.. Choices: arena-peer, conduit-peer, external-adapter
networkselectMust match the live provider and requests. Arena and demo confirmation always use simulation.. Choices: devnet, testnet, mainnet-beta, demo
allowSubmissioncheckboxMaster gate for live submission. Keep off for Arena-only or read-only builds.
allowMainnetcheckboxSecond explicit gate before Dispatch may submit to mainnet-beta.
skipPreflightcheckboxAsk the selected live RPC provider to skip its own preflight. Sentinel remains a separate policy layer.
maxRetriesnumberMaximum provider-level send retries for one attempt; operation recovery still belongs to Circuit/Witness.. Bounds: 0 to 20
maxAttemptsnumberMaximum 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
externalAdapterUrltextAbsolute 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.

ToolDeclared riskApproval requirement
solana.dispatch.statuslowmode-policy

Component changelog

baseline0.2.0-alpha.3Permalink

Dispatch

Records a bounded attempt before sending and retains the signature when a reply is lost.

Documentation snapshot, not a historical release date.
View component notes →
↑ ↓ to navigate · Enter to open · Esc to close. Search stays in your browser.