What it does
Witness observes a recorded attempt, retains compact evidence and assembles receipts. It also provides an independent payment-verification path that reads transaction bytes and recipient balance changes against trusted saved request terms.
Use it to answer distinct questions: what state did the provider observe; is it the same signed transaction; did this recipient receive the requested payment? Those are different checks. A receipt object or a signature string alone does not answer all three.
Use independently
Select uk.capp.sidecar.solana-witness. Choose arena-peer for a labelled simulation, or conduit-peer for supported live observations. Use window.capp.solana.witness or the portable client.
Keep operation, attempt, signature, network and known digests together. Locally appended evidence and trace() do not need RPC, but they also do not turn caller-supplied claims into independently observed facts.
Provider dependencies
| Operation | Dependency | What it proves |
|---|---|---|
appendEvidence() / trace() / readReceipt() | Local store | A retained local record or supplied evidence digest. |
observe() / receipt() in arena-peer | Arena | A simulated recorded outcome, never a live payment. |
observe() / receipt() in conduit-peer | Matching Conduit | One RPC signature-status observation and a receipt assembled from it. |
| External observation | Configured HTTPS adapter | Its explicitly live, matching observation; qualify that service separately. |
transactionEvidence() | conduit-peer, retained signed bytes and exact operation/attempt/signature | Observed transaction bytes match the recorded transaction. |
verifyPayment() | conduit-peer, trusted saved payment request and signature | Supported transaction/recipient-credit verification, not business fulfilment. |
independentPaymentVerification in status describes an API capability; the selected mode still matters. Independent verification is not available through Arena or an arbitrary observer adapter. A generic receipt() may use supplied digest fields when the observation does not contain them; it is not equivalent to transactionEvidence() or verifyPayment().
First successful observation
- Start with a known saved Arena outcome or a real recorded signature on the intended live network.
- Call
observe()once. Showunknown,submitted,confirmed,failedandfinaliseddistinctly, including simulation status. - Ask for
receipt()when useful and checkcommitmentMet,commitment, finality and exact identity. A receipt can legitimately describe an unmet commitment. - For exact submitted bytes, use
transactionEvidence()with the retained signed transaction. For a merchant payment, useverifyPayment()with the merchant-owned request. - Persist the resulting business decision separately. Witness does not claim an invoice, prevent another device from fulfilling it or deliver an order.
Read a simulated receipt without executing again
Copy the client module beside this module. First create a labelled simulation through Arena or Dispatch. Pass its retained operation and attempt IDs; this helper never calls execution or submission.
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);
export async function readArenaReceipt({operationId, attemptId}) {
const status = await client.call('witness', 'status');
if (!status.ready || status.providerMode !== 'arena-peer') {
throw new Error('This example requires Witness backed by Arena');
}
const receipt = await client.call('witness', 'receipt', {operationId, attemptId, network: 'demo'});
if (receipt.simulated !== true || receipt.operationId !== operationId || receipt.attemptId !== attemptId) {
throw new Error('Receipt does not match this simulated attempt');
}
return receipt; // Inspect commitmentMet; existence is not success.
}
export async function verifySavedPayment(paymentRequest, signature) {
const status = await client.call('witness', 'status');
if (!status.ready || status.providerMode !== 'conduit-peer') {
throw new Error('Independent payment verification requires Conduit');
}
return client.call('witness', 'verifyPayment', {
paymentRequest, signature, commitment: 'confirmed'
});
}The second helper is a separate read-only live verification action, not a continuation of the Arena example. Its paymentRequest must come from your trusted saved request, not customer-supplied recipient/amount claims. A client-side successful result is useful to the UI; a merchant should verify at its trusted service boundary and record fulfilment durably. See independent payment verification.
What each evidence level establishes
| Result | Inspect | Does not establish |
|---|---|---|
observe() | Network, signature, observed state, error, simulation flag | Exact recipient/amount or success just because a signature exists. |
receipt() | Identity/digests, evidence list, commitmentMet, finality, simulation | Authenticity of an imported receipt or truth of arbitrary locally appended metadata. |
transactionEvidence() | wireVerified, expected/observed transaction digests, signature and binding | A merchant invoice/amount/recipient claim beyond the recorded bytes. |
verifyPayment() | paid, exact request digest, credited recipient, asset, atomic amount, reference, genesis, slot and commitment | Delivery, invoice claim uniqueness, or a cross-device business transaction. |
Independent payment verification checks the supported payment's signatures/bytes, reference and optional memo, requested asset/amount and actual credited recipient balance, including token account identity. Sender-to-self movement is not accepted as the requested credit. It enforces the request's validity window against observed block time; request timestamps are seconds.
Token-2022, wrapped SOL, unknown/extra instructions and unsupported lookup-table layouts are rejected rather than accepted as approximate matches. Symbols/logos do not replace mint, program and decimals. paid: true remains accompanied by authorises: false and fulfilmentRecorded: false.
Scope and limitations
Independent payment verification reads RPC bytes and recipient balance changes against a trusted saved request. It does not trust imported receipts, fulfil an order or replace a merchant's durable invoice/signature claim. The RPC/provider remains part of the trust boundary; this is not a self-contained consensus/light-client proof.
Observation is a single query, not a subscription or wait-until-finalised operation. A missing historical signature returns unknown, not proof of failure. Poll deliberately with an application-level interval/budget and stop scheduling new reads when the view closes; do not invent a cancel() or event subscription API.
appendEvidence() keeps selected compact fields only, with a 16 KB payload bound and per-operation limits. A SHA-256 digest provides content identity, not authentication. receiptRetention: false still returns a receipt but does not make it available through a later local readReceipt(). With retention on, use the exact receiptDigest for a specific snapshot; a broad operation lookup can return a newer observation.
Application API
The owning interface is window.capp.solana.witness. 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('witness', 'status');status
client.call('witness', 'status', input)
Main input: No arguments required.
Read observation-provider readiness, commitment and evidence capacity.
observe
client.call('witness', 'observe', input)
Main input: operationId, attemptId and/or signature with matching network and digests.
Observe a recorded attempt through the configured live or simulation provider. Returned evidence remains explicitly labelled.
appendEvidence
client.call('witness', 'appendEvidence', input)
Main input: operationId and bounded evidence payload; optional attempt identity.
Append compact local evidence with a digest. Locally supplied evidence is not automatically chain verification.
receipt
client.call('witness', 'receipt', input)
Main input: Recorded operation/attempt/signature binding.
Observe and assemble a receipt with matching evidence and commitment status.
readReceipt
client.call('witness', 'readReceipt', input)
Main input: operationId, signature or receiptDigest; optional attemptId.
Read a matching retained receipt. Missing identity or receipt is an error.
trace
client.call('witness', 'trace', input)
Main input: operationId.
Read the local evidence trace and count for an operation.
transactionEvidence
client.call('witness', 'transactionEvidence', input)
Main input: operationId, attemptId, network, signature, signedTransactionBase64; optional transactionDigest.
Obtain transaction evidence for the recorded bytes. Use the prepared Edge operation to retain the full binding.
verifyPayment
client.call('witness', 'verifyPayment', input)
Main input: paymentRequest, signature; optional commitment: confirmed or finalized.
Independently check trusted saved request terms against RPC bytes and credited recipient balance. Does not claim or fulfil an order.
Troubleshooting
| Symptom or error | Next step |
|---|---|
unknown observation | Query the same signature later or through your approved history provider. Do not send another payment. |
commitmentMet: false | Keep the result pending at the requested commitment; simulation remains simulation. |
TRANSACTION_PENDING | Exact successful transaction bytes are not available yet; reconcile the same signature. |
BINDING | Preserve the conflicting records and compare operation, attempt, network, signature and digests. |
PAYMENT_PENDING | Inspect whether the transaction is absent/pending or explicitly failed; do not treat the exception as a paid invoice. |
RECEIPT_NOT_FOUND | Check exact identifiers and retention settings; reading is not a new observation. |
| Unsupported payment layout | Use a supported payment request/transaction, not a permissive fallback parser. |
Verify your integration
Test missing, processed, confirmed, finalised and failed observations; a changed signature; wrong recipient/amount/reference; self-transfer; and unsupported token/instruction layouts. Verify exact receipt lookup and disabled retention. Keep fulfilment in a separate durable service transaction that prevents the same payment/request from being claimed twice.
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-witness.observation.v1.schema.json
- capp.solana-witness.receipt.v2.schema.json
- capp.solana-witness.trace.v1.schema.json
- capp.solana-witness.transaction-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 |
|---|---|---|
providerMode | select | Select the implementation this build will use. Configuring an endpoint does not verify its connection or behavior.. Choices: arena-peer, conduit-peer, external-adapter |
network | select | Must match the live provider and requests. Arena and demo confirmation always use simulation.. Choices: devnet, testnet, mainnet-beta, demo |
commitment | select | Target Solana commitment Witness should observe before considering the requested observation satisfied.. Choices: processed, confirmed, finalized |
receiptRetention | checkbox | Whether compact receipts are retained locally after creation. |
maxReceipts | 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 |
maxEvidenceItems | 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 512 |
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.witness.status | low | mode-policy |