@rpc-bastion/wallet
v0.4.1
Published
Drop-in resilience for Wallet Standard and wallet-adapter dApps.
Readme
@rpc-bastion/wallet
Drop-in resilience for Wallet Standard and @solana/wallet-adapter dApps:
sign with the user's wallet, then submit + rebroadcast + confirm (and optionally
route through Jito) via @rpc-bastion/sender.
Adopt in 3 lines (Wallet Standard)
import { createResilientWalletSender } from '@rpc-bastion/wallet';
const walletSender = createResilientWalletSender({ wallet, sender }); // your Wallet Standard wallet + @rpc-bastion/sender
const result = await walletSender.send(transactionBytes, { lifetime: { lastValidBlockHeight } });transactionBytes is your compiled, unsigned web3.js v2.0 (@solana/kit)
transaction; lifetime is the blockhash lifetime you built it with (it drives
expiry detection during
rebroadcast). result is a SendResult plus a mode flag (see below).
Capability paths (read this — it's honest about the tradeoff)
A wallet advertises Solana features. RPC Bastion prefers the stronger one:
| Wallet exposes | mode | What happens |
|---|---|---|
| solana:signTransaction | resilient | The wallet signs; RPC Bastion submits, rebroadcasts, confirms, and can route via Jito. Full resilience. |
| only solana:signAndSendTransaction | monitor-only | The wallet signs and submits with its own RPC. RPC Bastion can't rebroadcast or route it — it only monitors confirmation (if you pass a monitor RPC) and emits a wallet.capability warning. |
So a signAndSendTransaction-only wallet gets reduced guarantees: no
endpoint rotation, no rebroadcast, no Jito. The warning event tells you when
you're on that path:
bus.on('wallet.capability', (e) => console.warn(e.reason)); // fired at wrap timePass a monitor RPC to still track confirmation on that path:
createResilientWalletSender({ wallet, sender, bus, monitor: { rpc, rpcSubscriptions } });What to do about it: check walletSender.mode (or the returned result.mode)
— if it's monitor-only, you've lost rebroadcast/routing, so for high-value sends
prefer a solana:signTransaction-capable wallet (most modern wallets, including
Phantom, expose it) to get the full resilient path.
@solana/wallet-adapter compat (subpath)
For classic wallet-adapter dApps, override the adapter's sendTransaction to
route through RPC Bastion. It's wallet-agnostic — the same override works for
PhantomWalletAdapter, SolflareWalletAdapter, or any other adapter. This lives
behind the @rpc-bastion/wallet/adapter subpath, which requires the
optional peers @solana/wallet-adapter-base and @solana/compat (web3.js 1.x
interop) — the main entry never pulls them in. See the runnable
Phantom + Solflare recipe.
import { createResilientSendTransaction } from '@rpc-bastion/wallet/adapter';
adapter.sendTransaction = createResilientSendTransaction({
sender,
resolveLifetime: () => ({ lastValidBlockHeight }), // you supply the height you built with
});Install the peers to use it: npm i @solana/wallet-adapter-base @solana/compat.
Only versioned transactions are supported (the modern default); a clear error
is thrown otherwise, or if the peers are absent.
Example
examples/react-phantom is a tiny Vite + React
dApp: connect any Wallet Standard wallet (Phantom, Solflare, …), send a memo with
a route toggle, and watch a live timeline from the event bus.
Notes
- Structural, not coupled. The Wallet Standard surface is defined as our own
minimal interfaces; a real Phantom wallet duck-types into them. A compile-time
conformance test keeps those interfaces aligned with the canonical
@solana/wallet-standard-featurestypes. prepareSendable(in@rpc-bastion/sender) is the lower-level bridge if you already have a signed Kit transaction and want aSendableTransaction.
