@usearete/adapter-kit
v0.46.0
Published
@solana/kit wallet adapter for the Arete SDK instruction boundary
Readme
@usearete/adapter-kit
A WalletAdapter for the Arete SDK backed by @solana/kit.
The adapter owns legacy, v0 and transaction-V1 (SIMD-0385) construction, signing, submission, confirmation, and unsigned RPC inspection. It supports @solana/kit 8 — the first release that constructs V1 messages and carries the resource budget as a typed message config — and requires Node.js 20.18 or newer for Node consumers.
transport: 'auto' uses the connected Arete client's authenticated HTTP transaction transport per invocation. Standalone auto mode uses configured rpc and rpcSubscriptions; transport: 'direct' requires both. A custom TransactionTransport can be supplied instead. The Arete path performs no subscription calls and never falls back or resubmits.
Install
npm install @usearete/adapter-kit @solana/kit @usearete/sdkPeer dependencies
| Package | Range | Required |
| --------------- | ----------------- | ------------------------------------------------------------------ |
| @usearete/sdk | same release line | Yes. Install exactly one copy, on the same release as this adapter |
| @solana/kit | ^8.3 | Yes |
Runtime: Node.js 20.18 or newer (the floor Kit 8 declares), or a modern browser. Transactions: legacy, 0 (the default) and 1, with the full resource budget. Address lookup tables are refused for every version.
Browser wallets connect through Wallet Standard with createWalletStandardSigner; no wallet-specific adapter packages are needed.
Usage
import { Arete } from '@usearete/sdk';
import { createWalletAdapter } from '@usearete/adapter-kit';
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
createKeyPairSignerFromBytes,
} from '@solana/kit';
import { MY_STACK } from './generated/my-stack';
const rpc = createSolanaRpc('https://api.devnet.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.devnet.solana.com');
const signer = await createKeyPairSignerFromBytes(secretKeyBytes);
const wallet = createWalletAdapter({ rpc, rpcSubscriptions, signer });
const client = await Arete.connect(MY_STACK, { wallet });
const prepared = await client.programs.myProgram.instructions.buy.prepare({
amount: 1_000_000n,
maxSolCost: 100_000_000n,
mint: 'So11111111111111111111111111111111111111112',
});
const inspection = await client.inspectOperation(prepared);
console.log(inspection.transaction.feeLamports, inspection.transaction.logs);
const receipt = await client.execute(prepared);
console.log(receipt.signature, receipt.slot);Signers
signer and additionalSigners must be @solana/kit TransactionSigner implementations. The primary signer is the default fee payer. Configured additional signers are published through wallet.signerAddresses so Arete can validate prepared operations before execution.
Per-send signers, additionalSigners, and feePayer values can satisfy transaction-specific signer requirements. A fee-payer override is itself a TransactionSigner. The adapter invokes signers only from signAndSend; inspectTransaction compiles an unsigned transaction and never calls a signer or submits to the network.
Transaction versions and resources
An omitted transactionVersion still compiles v0. 'legacy', 0 and 1 are all built; anything else is refused before a signer is reached, never downgraded.
One typed resources contract covers every version: computeUnitLimit, loadedAccountsDataSizeLimit, heapSize, priorityFeeLamports (V1 only, total lamports) and computeUnitPriceMicroLamports (legacy/v0 only, per compute unit). Using a fee against the wrong version is an error, not a conversion. V1 carries the budget in the message config; legacy/v0 carry the same budget as the ComputeBudget instructions kit prepends — so a hand-built ComputeBudget instruction is rejected with a pointer to the option that replaces it. Address lookup tables are refused for every version.
const receipt = await client.execute(prepared, {
send: {
transactionVersion: 1,
resources: { priorityFeeLamports: 5_000n },
},
});V1's computeUnitLimit and loadedAccountsDataSizeLimit are always resolved before signing, because an omitted V1 budget requests the minimum rather than a default (SIMD-0385) — a message without them could only fail on chain. An explicit value is used verbatim and never raised. An omitted one is measured: the adapter simulates a provisional unsigned message declaring the protocol maxima (1,400,000 CU and 64 MiB) with signature verification off, then derives the budget with 20% compute headroom and one 32 KiB page of loaded-data headroom, bounded by those same maxima. Only a metric the simulation never reports is refused, naming the option to pass instead.
inspectTransaction builds that same provisional message, never signs or submits, and returns its transactionVersion and applied resources alongside the fee and simulation metrics — so its output is what you pin the budgets with.
Inspect-only wallets
createInspectOnlyWalletAdapter({ publicKey }) holds only an address, which it uses as the fee payer. Operations prepare against it and client.inspectOperation() estimates fees and simulates, but every sign or send throws KitTransactionExecutionError with a not-submitted (wallet phase) outcome whose cause is named InspectOnlyWalletError, before anything is built or dispatched. Use it for agents and CI that verify transactions without keys; hosted inspection needs the transaction:inspect scope.
import { createInspectOnlyWalletAdapter } from '@usearete/adapter-kit';
const wallet = createInspectOnlyWalletAdapter({ publicKey: owner, transport: 'auto' });
const client = await Arete.connect(MY_STACK, { wallet });
const inspection = await client.inspectOperation(prepared);Final wire bytes are checked against 1232 bytes for legacy/v0 and 4096 for V1. The V1 structural caps (12 signatures, 64 accounts, 64 top-level instructions) are enforced by kit's own compiler, one step earlier and equally before signing.
External wallets (Wallet Standard)
createWalletStandardSigner bridges a Wallet Standard account into the kit signer boundary as a byte-oriented handoff: the app selects the format, the wallet decodes the version out of the bytes, and Arete relays what comes back without converting versions.
import { createWalletStandardSigner } from '@usearete/adapter-kit';
const signer = createWalletStandardSigner({ wallet, account, chain: 'solana:devnet' });
const adapter = createWalletAdapter({ transport: 'auto', signer });The account must advertise solana:signTransaction, and that feature must advertise the version being signed. solana:signAndSendTransaction is never read as evidence of sign-only support: it cannot hand signed bytes back for Arete to relay. A wallet that advertises only legacy/0 refuses a V1 transaction before the user is prompted. Returned bytes are decoded with kit's V1-capable codec and rejected if the wallet changed the message or omitted the account's signature.
External-wallet V1 is unverified against a released wallet. As of Kit 8, the upstream Wallet Standard signTransaction feature still types supportedTransactionVersions as legacy | 0, and Anza's Phantom adapter advertises only legacy/0. The bridge is covered by controlled wallet fixtures and real codecs; a smoke test against an actual released wallet is tracked separately. Local keypair signers are verified end to end.
Outcomes
Submission failures throw KitTransactionExecutionError with an Arete-compatible outcome:
not-submittedfor build, signer, and known preflight failures.submitted-unknownwhen confirmation fails and one signature-status query cannot prove the result.chain-failedwhen that status query reports an on-chain error.
The adapter never rebuilds, retries, or resubmits a transaction. On the Arete path, confirmationTimeoutMs is one deadline covering submission and confirmation, including whatever request is in flight, so a relay that stops answering yields submitted-unknown rather than hanging. The signature derived from the signed bytes is authoritative throughout: a relay reporting a different one is logged as a diagnostic, never polled for and never returned as the transaction submitted.
