Skip to content
For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending .md to the URL or sending Accept: text/markdown.

Transactions

For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /using-stacks/transactions.md.

Arete’s transaction transport lets an application use the same authenticated stack endpoint for transaction inspection, submission, and confirmation. The wallet still compiles and signs locally. Arete never receives a private key and never signs on the user’s behalf.

The transaction transport is separate from the WebSocket transport used for streamed views:

  • ConnectOptions.transport selects WebSocket or HTTP-only stack data access.
  • A wallet adapter’s transport selects Arete, direct Solana RPC, or a custom transaction transport.
Terminal window
npm install @usearete/sdk @usearete/adapter-web3js @solana/web3.js

Use auto for normal hosted and self-hosted applications. When the wallet is invoked by a connected Arete client, the adapter receives that client’s authenticated transaction transport. A standalone adapter can still use a configured direct connection for backward compatibility.

import { createSession } from '@usearete/sdk';
import { createWalletAdapter } from '@usearete/adapter-web3js';
import { MY_STACK } from './generated/my-stack';
const wallet = createWalletAdapter({
signer: {
publicKey,
signTransaction,
supportedTransactionVersions,
},
transport: 'auto',
});
const session = await createSession(
{ stacks: { app: MY_STACK } },
{
wallet,
auth: { publishableKey: import.meta.env.VITE_ARETE_PUBLISHABLE_KEY },
},
);

The connected client supplies its transport per invocation. This means one wallet can safely serve multiple clients connected to different stacks without mutable global transport state.

There is no automatic Arete-to-direct fallback after an operation starts. In particular, a failed or ambiguous Arete submission is never retried through a direct RPC connection.

Prepare a generated semantic operation, then inspect it without signing or prompting the wallet:

const prepared = await session.programs.myProgram.transactions.deposit.prepare({
owner: publicKey.toBase58(),
amount: 1_000_000n,
});
const inspection = await session.stacks.app.inspectOperation(prepared, {
inspect: {
commitment: 'confirmed',
minContextSlot,
innerInstructions: true,
},
});
console.log(inspection.transaction.feeLamports);
console.log(inspection.transaction.logs);
console.log(inspection.programError);

Inspection compiles an unsigned v0 transaction locally and calls the Arete fee and simulation routes. It does not sign or submit. Core enriches simulation failures with generated program error metadata when available.

const receipt = await session.execute(prepared, {
send: {
confirmationLevel: 'confirmed',
confirmationTimeoutMs: 60_000,
statusPollIntervalMs: 500,
},
});
console.log(receipt.transaction.signature);
console.log(receipt.transaction.slot);

The adapter performs these steps:

  1. Fetch a recent blockhash through Arete.
  2. Compile a static-key v0 message locally.
  3. Sign locally once.
  4. Submit the fully signed wire transaction once.
  5. Poll signature status and block height through Arete.
  6. Return the landed slot when the requested commitment is reached.

The Web3.js adapter currently requires v0 wallet support and does not accept address lookup table accounts.

Generated program operations exposed by useArete have .useMutation() hooks. A semantic mutation prepares the operation before wallet approval, so protocol-specific preparation can re-read authoritative accounts and reject stale input.

const arete = useArete(MY_STACK);
const deposit = arete.programs.myProgram.transactions.deposit.useMutation();
await deposit.submit(args, {
reconcile: {
refresh: [position, quote],
},
});

In WebSocket mode, generated program mutations reconcile by default after chain confirmation. The hook waits for the stack’s processed-slot watermark to reach the highest completed receipt slot, then refreshes the supplied hook results or runs supplied callbacks. This ordering prevents a confirmed mutation from settling before the stream has processed its slot.

The boundary is intentionally narrow:

  • A processed-slot watermark does not guarantee that a particular entity changed.
  • One-shot quotes and reads do not refresh unless included in reconcile.refresh or refreshed manually.
  • A pre-submit quote is a preview, not a reservation. Preparation must revalidate authoritative accounts where staleness matters.
  • A reconciliation timeout produces confirmed-unreconciled; it does not undo, retry, or reject the confirmed chain result.
  • onConfirmed runs after chain confirmation. onSuccess runs after successful reconciliation, or immediately when reconciliation is disabled or skipped.

Pass reconcile: false for confirmation-only behavior, { timeoutMs, refresh } to customize the default, or a function to replace processed-slot reconciliation. HTTP-only mutations and results without a receipt slot skip the default.

You can submit manually composed generated instructions through the same transport:

const instruction = session.programs.myProgram.raw.deposit.build({
owner: publicKey.toBase58(),
amount: 1_000_000n,
});
const result = await session.transaction([instruction]);
console.log(result.signature);

Use direct mode when the application intentionally owns its Solana RPC connection:

import { Connection } from '@solana/web3.js';
const wallet = createWalletAdapter({
connection: new Connection(process.env.SOLANA_RPC_URL!, 'confirmed'),
signer,
transport: 'direct',
});

Direct mode ignores the Arete transaction transport. The SDK does not restrict the RPC cluster; cluster checks in the ORE React example are example-specific safety checks.

Implement TransactionTransport when routing through another trusted gateway or test harness:

import type { TransactionTransport } from '@usearete/sdk';
const transactions: TransactionTransport = {
getLatestBlockhash,
getFeeForMessage,
simulateTransaction,
sendTransaction,
getSignatureStatus,
getBlockHeight,
};
const session = await createSession(
{ stacks: { app: MY_STACK } },
{ wallet, transactions },
);

A custom transport can also be passed to createWalletAdapter({ transport: transactions }) or supplied per transaction/operation with transactionTransport.

Custom transports must preserve the safety contract: submit a signed transaction at most once, preserve its signature, and classify uncertainty conservatively.

Hosted clients normally provide auth.publishableKey. The SDK requests short-lived tokens from the configured token endpoint and upgrades the shared token when an operation requires additional scopes.

The scopes are independent:

ScopeAllows
readStack reads and streamed data
transaction:inspectBlockhash, fee, simulation, status, and block-height requests
transaction:sendSubmission of a fully signed wire transaction

Static tokens must already contain the required scopes. Browser publishable-key sessions in Arete Cloud must be origin-bound and requested from the key’s exact allowlisted origin.

Application execution errors have three important outcomes:

OutcomeMeaningSafe response
not-submittedThe transaction definitely did not reach submissionFix the cause; retry only under explicit application policy
submitted-unknownThe signed transaction may have reached SolanaReconcile the included signature; do not blindly resend
chain-failedSolana reported an on-chain errorDisplay the parsed program error when available; do not resend unchanged
import { getTransactionFailureOutcome } from '@usearete/sdk';
try {
await session.execute(prepared);
} catch (error) {
const outcome = getTransactionFailureOutcome(error);
if (outcome?.status === 'submitted-unknown') {
console.error('Check this signature before retrying:', outcome.signature);
}
throw error;
}

HTTP relay failures are exposed as TransactionTransportError with status, code, retryable, requestId, submissionState, signature, and sanitized details. Record the request ID and signature in application diagnostics.

SymptomCheck
AUTH_REQUIREDConfigure publishableKey, tokenEndpoint, getToken, or a static token
insufficient_scopeRequest the exact inspect or send scope
transaction-relay-disabledCheck cloud and deployment rollout gates
transaction-origin-requiredUse an origin-bound publishable key from its allowlisted origin
request_too_largeCompare the JSON body with the relay body limit and token claim
transaction_too_largeCompare decoded wire bytes with the 1232-byte default
rate_limit_exceededCheck session claims, trusted proxy configuration, and per-route limits
submission_unknownQuery the returned signature; never resend blindly
Confirmation timeoutCheck status/block-height RPC health or increase confirmationTimeoutMs
Direct mode has no transportSupply a Web3.js Connection and a v0-capable signer

Before broad production scale, prioritize:

  1. Add a reusable conformance suite for custom TransactionTransport implementations.
  2. Persist or externally queue transaction usage events; V1 uses bounded in-memory retries.
  3. Externalize admission/rate state before running more than one transaction-enabled atom replica.
  4. Add address lookup table support to the Web3.js adapter.
  5. Narrow getSignatureStatus options to fields the route actually uses.
  6. Add release-gated validator and hosted-browser transaction smoke tests.
  7. Add dashboards and alerts for ambiguous submissions, upstream timeouts, rate denials, and usage queue drops.

See Self-hosting the transaction relay for server configuration and security guidance.