What it does
Solana Edge coordinates a reviewed payment across selected Sidecars: prepare exact bytes, evaluate policy, request wallet approval, record submission and collect matching observations/receipts. Your application retains the business request and decides what successful payment permits it to do.
Use Edge for the complete lifecycle rather than assembling every low-level call yourself. Use the individual Sidecars directly when your product needs only a read, a local policy check, a simulator or a ledger. This coordinator is not an RPC proxy/gateway service and is not required by all Solana components.
Use independently
Select uk.capp.sidecar.solana-edge for either an explicit offline demonstration or the supported live devnet flow. Its owner API is window.capp.solana.edge; the portable client adds method discovery and dispatch, not a runtime or extra permissions.
Call status() and composition() first. Check the mode, issues and selected/loaded/ready peers. Method presence is not provider readiness. Sidecars selected but unavailable must not be treated as permission to silently construct a replacement provider.
Provider dependencies
| Devnet component | Required configuration | Role |
|---|---|---|
| Conduit | direct-rpc or external-adapter; devnet writes enabled | Network observation, read/simulation and transport. |
| Circuit | local-store or external-adapter | Operation reservation and attempt state. |
| Sentinel | conduit-peer, devnet, supported exact-transfer policy | Decode, bind and simulate the reviewed payment. |
| Latch | device-wallet, devnet-test-wallet, qualified external-adapter or installed native-mwa; devnet | Exact wallet authority and signed transaction. |
| Dispatch | conduit-peer, devnet, allowSubmission | Retain and send the attempt. |
| Witness | conduit-peer, devnet | Observe the signature and verify matching transaction bytes. |
All six must be selected and available; compositionMode: auto is the intended devnet composition. Compass is optional for intent shaping. Arena remains independent simulation. Accord and Switchyard are not automatically applied by the built-in payment flow.
Demo mode is different: auto uses selected compatible demo peers and internal simulation only where a peer is absent; a selected-but-unavailable peer fails. bundled-arena deliberately bypasses peer composition. Neither path proves live wallet or RPC integration, and devnet never falls back to demo.
First successful devnet payment
- Select/configure the six required peers and a devnet wallet. Keep RPC client credentials out of examples and any backend-only secrets out of the package.
- Save a unique business
operationIdwith the sender, recipient, exact amount and any request/commitment terms before preparation. Do not regenerate it for retries. - Prepare and preflight. Show the actual saved sender, recipient, asset/amount, fees and policy result. Continue only from
preflightedwith matching exact-byte checks. - On a separate explicit approval action, call
authorise(). A declined or expired result must return to an appropriate review state, not auto-submit. - On Send, call
submit()for that saved operation. Keepsubmittedandunknowndistinct from confirmation. - Use
transactionStatus()to reconcile the existing operation. Inspect recovery errors, exact transaction evidence, receipt binding and required commitment before showing completion.
Separate review, approval, send and observation
Copy the client module beside this module. These helpers do nothing until called. Wire each exported action to a distinct user/application control and keep controls disabled until its promise settles. They intentionally do not connect/create a wallet or automatically approve anything.
import {createSolanaClient} from './solana-client.mjs';
const client = createSolanaClient(() => window.capp?.solana);
const active = new Set();
async function withDevnetOperation(operationId, action) {
if (!operationId || active.has(operationId)) throw new Error('Operation is missing or busy');
active.add(operationId);
try {
const status = await client.call('edge', 'status');
if (!status.ready || status.mode !== 'devnet') {
throw new Error('Resolve the devnet composition before continuing');
}
return await action();
} finally { active.delete(operationId); }
}
export async function reviewDevnetPayment({operationId, walletPubkey, recipient, amountLamports}) {
return withDevnetOperation(operationId, async () => {
const saved = await client.call('edge', 'prepare', {
operationId, network: 'devnet', walletPubkey, recipient, amountLamports
});
if (saved.state !== 'prepared') return saved; // Resume its actual saved state.
return client.call('edge', 'preflight', {operationId});
});
}
export async function approveDevnetPayment(operationId) {
return withDevnetOperation(operationId, () => client.call('edge', 'authorise', {operationId}));
}
export async function sendDevnetPayment(operationId) {
return withDevnetOperation(operationId, () => client.call('edge', 'submit', {operationId}));
}
export async function observeDevnetPayment(operationId) {
return withDevnetOperation(operationId, () => client.call('edge', 'transactionStatus', {operationId}));
}Use your actual saved address/amount fields, not a copied demonstration recipient. Render returned data as text and display the returned state even when the promise resolves. For a merchant request use preparePayment() with the complete trusted saved request instead of this native-SOL helper. Signing may produce transferable signed bytes: do not dump an entire operation into analytics or a public console.
Recovery decisions
| Saved state or evidence | Next step | Do not do |
|---|---|---|
prepared / preflighted | Continue the existing review/approval after freshness checks. | Recreate the business operation because the view reloaded. |
authorised | Send the exact saved transaction when explicitly requested. | Modify recipient/amount or re-sign different bytes under the same identity. |
submitting, submitted, unknown | Observe/reconcile the same operation and signature. | Treat timeout or an empty RPC result as proof of no effect. |
confirmed | Inspect receipt commitment and keep observing if stronger confirmation is required. | Call it finalised solely because the send returned. |
| Expired and provably never submitted | Explicit refresh() may create one linked replacement review. | Refresh an unresolved send or reuse the previous approval. |
| Payment request itself expired | Keep its terms for verification and request new merchant terms. | Extend its expiry locally or silently change the invoice. |
refresh() retains the old operation, records a deterministic successor and requires fresh preflight and wallet approval. Save the returned replacement ID and previousOperationId. It is not a universal “retry” button. A recoveryError can accompany an observed state when later evidence or ledger persistence is incomplete; inspect both.
receipt() in devnet reads an already-collected receipt; transactionStatus() performs reconciliation and may update local records. Neither method settles your order or supplies a distributed fulfilment claim. For imported receipts or a merchant decision, use independent payment verification, trusted request terms and your own durable business record.
Assets and commitments
Native prepare() uses safe-integer amountLamports. prepareTokenTransfer() uses amountAtomic as a string and an exact original Token Program/mint/decimals descriptor. Configure matching per-mint limits in Sentinel and Latch, plus enough SOL for fees and any associated account rent.
An optional application commitment contains a lowercase namespace and SHA-256 digest; it becomes a memo-bound commitment, not disclosure or verification of the original document. Scaled Arena evidence and merchant payment requests have their own binding formats and cannot be freely mixed with it.
Scope and limitations
Devnet signing supports native SOL and original SPL Token Program transfers through associated token accounts. Token-2022, wrapped SOL, swaps and mainnet signing are not enabled by this built-in recipe. Exact bytes and retained attempts survive ordinary retries; token amounts are atomic strings.
Local records are scoped to the installed application/origin, not replicated across devices. They do not protect against hostile same-origin scripts or cleared app storage. A successful small evidence transfer proves the transfer and its memo, not simulated trading settlement, full-size payment or real-world delivery.
Application API
The owning interface is window.capp.solana.edge. 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('edge', 'status');status
client.call('edge', 'status', input)
Main input: No arguments required.
Read provider readiness, network and retained-operation status. No approval or submission.
composition
client.call('edge', 'composition', input)
Main input: No arguments required.
Read the selected components, requirements and readiness of this Edge composition.
prepare
client.call('edge', 'prepare', input)
Main input: operationId, network, walletPubkey, recipient, amountLamports; optional legKey and commitment.
Prepare one native SOL operation. Persist the application identity first. Returns the retained operation; it does not authorize it.
preflight
client.call('edge', 'preflight', input)
Main input: operationId.
Evaluate policy and supported exact bytes for the saved operation. Returns the updated operation, not wallet authority.
authorise
client.call('edge', 'authorise', input)
Main input: operationId.
Request explicit wallet approval for the prepared operation. Keep this on a user-approved action path.
submit
client.call('edge', 'submit', input)
Main input: operationId.
Submit the saved authorized operation. A returned submission is not final confirmation.
transactionStatus
client.call('edge', 'transactionStatus', input)
Main input: operationId.
Observe or reconcile the same retained operation, including a possibly lost response.
receipt
client.call('edge', 'receipt', input)
Main input: operationId.
Read the receipt associated with the operation. Inspect its binding, network, simulation flag and commitment.
prepareTokenTransfer
client.call('edge', 'prepareTokenTransfer', input)
Main input: operationId, network: devnet, walletPubkey, recipient, asset, amountAtomic; optional legKey, references, commitment.
Prepare a supported original SPL-token payment. Atomic amount is a string; asset includes mint, tokenProgram and decimals.
preparePayment
client.call('edge', 'preparePayment', input)
Main input: operationId, walletPubkey, paymentRequest; optional legKey.
Prepare from a trusted saved request. It is not safe to substitute caller-supplied merchant terms.
refresh
client.call('edge', 'refresh', input)
Main input: operationId.
Replace only an expired, never-submitted review. Fresh preflight and approval are required; an uncertain send cannot be refreshed.
Troubleshooting
| Symptom or error | Next step |
|---|---|
DEPENDENCY or composition issues | Match all six selected peers, their modes, networks and write/verification settings. |
blocked / policy rejection | Inspect the named Sentinel violations; do not weaken policy merely to pass the demo. |
ID_CONFLICT | Recover the saved operation and compare payer, recipient, amount and request terms. |
| Wallet declined or unavailable | Keep the review; do not substitute another wallet or assume a native MWA provider exists. |
unknown / recoveryError | Reconcile the same identity and retained signature without another send. |
REFRESH_UNSAFE | The runtime cannot establish never-submitted status. Preserve and reconcile. |
RECEIPT_PENDING | Observe the existing transaction and collect matching evidence before reading its receipt. |
Verify your integration
Test no peers, wrong network, unavailable selected peer, rejected policy, declined wallet, changed amount under one ID, response loss after acceptance and reload before observation. Confirm that recovery performs no new signing/send, and that an expired unsent replacement requires fresh approval. A desktop/browser source test does not certify installed Android wallet handoff.
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-edge.composition.v1.schema.json
- capp.solana-edge.composition.v2.schema.json
- capp.solana-edge.operation.v1.schema.json
- capp.solana-edge.operation.v2.schema.json
- capp.solana-edge.operation.v3.schema.json
- capp.solana-edge.operation.v4.schema.json
- capp.solana-edge.receipt.v1.schema.json
- capp.solana-edge.receipt.v2.schema.json
- capp.solana-edge.status.v1.schema.json
- capp.solana-edge.status.v2.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: demo, devnet |
compositionMode | select | Auto uses selected compatible peers for the demo payment stages. Bundled mode explicitly uses only Edge’s own local simulation.. Choices: auto, bundled-arena |
demoMaxLamports | number | Maximum amount accepted by Edge’s no-network demonstration payment flow.. Bounds: 1 to 1000000000000 |
operationRetention | 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 512 |
receiptRetention | checkbox | Whether compact receipts are retained locally after creation. |
showCompositionPanel | checkbox | Expose the declared in-app composition/status panel when the host includes its mount surface. |
devnetMaxLamports | number | Applies to the actual transfer, including a scaled evidence transfer.. Bounds: 1 to 1000000000 |
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.edge.status | low | mode-policy |
solana.edge.composition | low | mode-policy |
solana.payment.prepare | medium | mode-policy |
solana.transaction.preflight | low | mode-policy |
solana.wallet.authorise | high | explicit-owner |
solana.transaction.submit | high | explicit-owner |
solana.transaction.status | low | mode-policy |
evidence.receipt.read | low | mode-policy |