Skip to content

API reference

@morsel-wallet/adapter 0.2.0

Everything the package exports, with types and defaults. Every prop is optional unless marked otherwise.

Entry points

@morsel-wallet/adapter

Everything: the core, the React provider and hooks, and the connect widget. Needs React 18 or 19 with react-dom.

@morsel-wallet/adapter/core

The adapters, errors and constants with no React at all. For Svelte, Vue, plain JavaScript or your own UI.

ESM only, with types. @solana/web3.js 1.x is a peer dependency. Nothing touches window during server rendering; the widget mounts on the client through a portal.

WalletProvider

Holds the wallet state for everything below it. Required, once, near the root.

PropTypeDefaultDescription
childrenReactNodeYour app.
adaptersCookieWalletAdapter[][new MorselCookieWalletAdapter()]The wallets you list yourself. Every installed Wallet Standard wallet (Phantom, Backpack, Solflare, ...) is discovered and added after them, without duplicates.
adapterCookieWalletAdapterA single wallet instead of a list. Kept for older code; prefer adapters.
autoConnectbooleanfalseReconnect on load when the user connected before (remembered in localStorage under morsel.wallet.autoConnect).
onError(error: Error) => voidCalled with every adapter error.

Wrap the part of your app that opens the widget in WalletModalProvider (no props), and render <WalletModal /> once inside it.

WalletModal

The connect widget: a 360 px card on desktop and a bottom sheet you can drag down on phones. Which path it takes (QR, extension, phone deep link, Morsel's browser) is decided by the device; see how it connects.

PropTypeDefaultDescription
variant'compact' | 'premium' | 'headless''compact'premium renders the same widget (kept for 0.1 code). headless is a bare, unstyled list.
theme'light' | 'dark' | 'system' | 'auto''system'system (or auto) follows the OS setting live.
accentColorCSS colourMorsel blueSame as setting --mw-accent.
placement'center' | 'anchor''center'anchor drops the widget down from the element that opened it (desktop). Phones always get the bottom sheet.
titlestring'Connect a wallet'Title of the wallet list.
subtitlestringA line under the title.
logostring | ReactNodeYour dApp's logo, next to the title of the wallet list.
networks{ id, name, icon? }[]With more than one entry, a small network switch shows on the list.
selectedNetworkIdstringThe selected entry of networks.
onNetworkChange(id: string) => voidCalled when the user switches network.
links{ install, ios, android, extension, website }Morsel install pageWhere the Get Morsel surfaces point. Each link falls back to install.
autoClosebooleantrueClose a moment after a successful connection.
autoConnectInMorselbooleantrueInside Morsel's browser, connect without showing the list.
mobileDeepLinkstringuniversal browse linkOverride the Open in Morsel link on phones.
connectLabelstring'Connect'Label of the Morsel action.
installLabelstring'Get'Label for wallets that are not installed.
closeLabelstring'Close'Accessible name of the close button.
showPoweredBybooleanfalseA small Powered by Morsel line under the list.
onConnectError(error: Error) => voidCalled when a connection attempt fails or is declined.
className / overlayClassNamestringExtra classes on the card and the backdrop.
zIndexnumber2147483000Stacking order of the widget layer.
noncestringCSP nonce for the injected stylesheet.

ConnectButton

Without className or children, an accent pill that opens the widget, then an account chip (avatar, wallet badge, short address and, with a connection, the native balance) with a menu to copy the address, switch wallet or disconnect.

Header.tsx
<ConnectButton connection={connection} balanceSymbol="COOK" theme="dark" />
PropTypeDefaultDescription
theme / accentColoras on WalletModal'system'Theme and accent of the button and the account chip.
connectionConnectionA web3.js Connection. When given, the chip shows the native balance.
balanceSymbolstring'COOK'Symbol after the balance.
disconnectedLabelstring'Connect wallet'Label while disconnected.
connectingLabelstring'Connecting…'Label while connecting.
showLogobooleantrueThe Morsel mark on the button.
size'sm' | 'md''md'sm for tight headers.
useModalbooleanfalse connects the active adapter directly instead of opening the widget.
disabledbooleanfalse
onConnectError / onDisconnectError(error: Error) => void
className / childrenstring / ReactNodeEither one turns it into the original unstyled button that you style yourself; clicking it while connected disconnects (showAddress and connectedLabel apply).

useWalletModal

Open and close the widget from anywhere under WalletModalProvider.

Anywhere.tsx
const { open, close, visible } = useWalletModal();

open();                              // the wallet list
open({ view: 'get-morsel' });        // the Get Morsel card
open({ anchor: buttonRef.current }); // with placement="anchor"
MemberTypeDescription
open(options?){ view?, anchor? }Open the widget. view is 'wallets' (default) or 'get-morsel'; anchor is the element to drop from with placement="anchor". Safe to pass straight to onClick.
close()Close it.
visiblebooleanWhether it is open.
setVisible(visible)booleanOpen or close without options.
optionsWalletModalOpenOptionsOptions of the current or last open() call.

Theming

Every class is prefixed mw- and scoped under .mw-scope, so nothing leaks into your page and your global button or image rules do not leak in. The stylesheet is injected once into <head>; pass nonce for a strict CSP, or ship getMorselConnectCss() yourself. Override any token:

app.css
.mw-scope {
  --mw-accent: #ff6b00;
  --mw-radius: 20px;
  --mw-font: 'DM Sans', system-ui, sans-serif;
}
.mw-scope[data-mw-theme='dark'] {
  --mw-bg: #0b0b0f;
}
  • --mw-accent
  • --mw-accent-fg
  • --mw-bg
  • --mw-bg-2
  • --mw-bg-3
  • --mw-fg
  • --mw-fg-2
  • --mw-fg-3
  • --mw-line
  • --mw-overlay
  • --mw-success
  • --mw-danger
  • --mw-warn
  • --mw-shadow
  • --mw-radius
  • --mw-font
  • --mw-z

Hooks

All hooks read from the nearest WalletProvider and are fully typed.

useWallet() WalletContextState

Everything: publicKey, connected, connecting, readyState, error, adapters, activeAdapter, connect(), disconnect(), selectAdapter(i), signTransaction(), signAllTransactions(), signMessage(), sendTransaction(), on(), off().

useWalletAddress() string | null

The connected address in base58.

useWalletStatus() { publicKey, connected, connecting, readyState, wallet }

Connection state only.

useWalletConnection() { connect, disconnect, toggleConnection, connected, connecting, disconnecting, pending, error, clearError }

Connect and disconnect with their own pending and error state.

useWalletBalance(connection, { autoFetch?, commitment? }) { balance, lamports, loading, error, refetch, clearError }

Native balance (COOK on Cookie Chain, SOL on Solana). balance is in whole units, lamports in base units.

useSignMessage() { signMessage, signing, error, clearError }

signMessage(bytes) resolves to the signature (Uint8Array).

useSignTransaction() { signTransaction, signAllTransactions, signing, error, clearError }

Legacy and versioned transactions.

useSendTransaction() { sendTransaction, sending, error, clearError }

sendTransaction(tx, connection, options?) signs, sends and resolves to the signature.

useWalletError() { error, clearError }

The last adapter error.

useAutoConnect() { enableAutoConnect, disableAutoConnect, isAutoConnectEnabled }

Control reconnect-on-load at runtime.

useWalletEvent(event, listener) void

Subscribe to any adapter event; unsubscribes on unmount.

useOnConnect(listener) void

listener(publicKey) on every connection.

useOnDisconnect(listener) void

listener() on disconnect.

useOnWalletError(listener) void

listener(error) on adapter errors.

useWalletModal() WalletModalContextState

Open and close the widget from anywhere under WalletModalProvider.

useOptionalWalletModal() WalletModalContextState | null

The same, or null outside a WalletModalProvider.

Sending a transaction with the connected wallet:

Send.tsx
const { publicKey, sendTransaction } = useWallet();

const tx = new Transaction().add(
  SystemProgram.transfer({ fromPubkey: publicKey, toPubkey: recipient, lamports: 1_000_000 }),
);
tx.feePayer = publicKey;
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;

const signature = await sendTransaction(tx, connection);

Events

React to connection changes without polling, with the event hooks or the adapter's own emitter.

Analytics.tsx
useOnConnect((publicKey) => track('wallet_connected', publicKey.toBase58()));
useOnDisconnect(() => track('wallet_disconnected'));
useOnWalletError((error) => toast(error.message));

// Or any adapter event by name
useWalletEvent('readyStateChange', (state) => console.log(state));
EventTypeDescription
connect(publicKey: PublicKey)A wallet connected, by any path.
disconnect()
error(error: WalletAdapterError)
readyStateChange(state: WalletReadyState)The extension appeared or went away.
wcUriChange(uri: string)A new pairing QR.
relayStatusChange(status: MorselRelayStatus)The QR pairing moved on.

MorselCookieWalletAdapter

The Morsel adapter, from either entry point. It looks for the injected provider (the extension or Morsel's browser) and keeps a QR pairing ready over the relay. WalletProvider creates one for you.

morsel.ts
import { MorselCookieWalletAdapter } from '@morsel-wallet/adapter/core';

const morsel = new MorselCookieWalletAdapter();

morsel.on('connect', (publicKey) => console.log('connected', publicKey.toBase58()));
morsel.on('relayStatusChange', (status) => console.log(status)); // 'waiting', 'scanned', 'connected'...

if (morsel.readyState === 'Installed') {
  // The Morsel extension, or the page is open inside Morsel's browser
  await morsel.connect();
} else {
  // Desktop without the extension: draw this as a QR and the Morsel app scans it.
  // On a phone, morsel.connect() opens your page inside Morsel instead.
  showQr(morsel.wcUri);
  morsel.on('wcUriChange', showQr); // a fresh code after it expires
}

// Then sign like any Solana wallet adapter
const signed = await morsel.signTransaction(transaction);

Properties

PropertyTypeDescription
namestring'Morsel Cookie Wallet' (shown as Morsel).
iconstringThe Morsel mark as a data URI.
urlstringMorsel's install page.
publicKeyPublicKey | nullThe connected account.
connected / connectingboolean
readyState'Installed' | 'NotDetected' | 'Unsupported'Installed when the extension or Morsel's browser is on the page, or after a QR pairing. Unsupported on the server.
wcUristringThe live morsel://connect pairing URI. Draw it as a QR.
wcUriCreatedAtnumberWhen it was minted (ms). A pairing lives for 5 minutes.
relayStatusMorselRelayStatusWhere the QR pairing stands; see below.
supportedTransactionVersionsSet<'legacy' | 0>Legacy and v0 transactions.

Methods

MethodTypeDescription
connect()Promise<void>Connect through the extension or Morsel's browser. On a phone without either, it opens your page inside Morsel instead. On a desktop without the extension it throws WalletNotFoundError: pair with the QR.
disconnect()Promise<void>Ends the connection. After a QR pairing, a fresh QR is minted for the next one.
signTransaction(tx)Promise<Transaction | VersionedTransaction>Over the relay, a request times out after 60 seconds.
signAllTransactions(txs)Promise<(Transaction | VersionedTransaction)[]>
signMessage(message)Promise<{ signature: Uint8Array }>
sendTransaction(tx, connection, options?)Promise<string>Signs, then sends with connection.sendRawTransaction.
restartRelaySession()voidMint a fresh QR (new session and keys). A live relay connection is left alone.
refreshProvider()voidLook for the injected provider again. Runs every second on its own.
destroy()voidStop timers, close the relay and drop listeners.

Relay status

relayStatus tracks the QR pairing with the Morsel phone app. The relay is end-to-end encrypted (NaCl box) between the page and the app; the relay server only forwards ciphertext. A QR lives for five minutes; restartRelaySession() mints a fresh one.

ValueDescription
'idle'No relay session (on the server, or after destroy()).
'waiting'The QR is live and nobody has scanned it yet.
'scanned'The phone scanned it and shows the approval prompt.
'rejected'The user declined in the app; a fresh session follows shortly.
'connected'Approved; the encrypted channel is live.

StandardWalletAdapter

Wraps any Wallet Standard wallet that supports a Solana chain, with the same interface as the Morsel adapter. WalletProvider does this for every installed wallet; use it directly when you build your own list.

wallets.ts
import { getWallets } from '@wallet-standard/app';
import { StandardWalletAdapter } from '@morsel-wallet/adapter/core';

const adapters = getWallets().get().map((wallet) => new StandardWalletAdapter(wallet));

Errors

Every error is an instance of WalletAdapterError, so one instanceof check catches them all.

ClassDescription
WalletAdapterErrorThe base class of every error below.
WalletNotFoundErrorNo Morsel provider on this page (see connect()).
WalletNotConnectedErrorSigning before connecting.
WalletConnectionErrorThe wallet refused or failed to connect.
WalletDisconnectionError
WalletSignTransactionErrorDeclined, timed out or failed to sign.
WalletSignMessageError

Constants and helpers

NameTypeDescription
COOKIE_CHAIN{ name, chainId, symbol, rpcUrl, explorerUrl }Cookie Chain: 'cookie-mainnet', COOK, https://rpc.cookiescan.io, https://cookiescan.io.
morselBrowseLink(url, ref?)stringA universal link that opens url inside Morsel's browser.
MORSEL_RELAY_PAIRING_TTL_MSnumberHow long a QR pairing lives: 5 minutes.
MORSEL_COOKIE_WALLET_NAME / _ICON / _URLstringName, mark and install page of the Morsel adapter.
detectMorselCookieProvider()MorselCookieProvider | nullThe injected Morsel provider, if any.
getMorselConnectCss()stringThe widget's stylesheet, to ship yourself under a strict CSP. Main entry only.