@d0fi/checkout-react
v0.3.1
Published
React bindings for @d0fi/checkout-core
Readme
@d0fi/checkout-react
Hosted checkout UI (D0fiCheckoutExperience), modal/provider helpers, and embed protocol wiring on top of @d0fi/checkout-core.
For order-funded on-chain withdrawals, use D0fiWithdrawalExperience with a
browser-safe D0fiWithdrawalClient implemented against the merchant's own
authenticated backend. See Withdrawal SDK integration.
Connect Wallet payments (no D0 merchant account)
D0fiConnectWalletPayment is the sessionless wallet-to-wallet interface. The customer
application owns the payment intent, receiving address, transaction adapter and
on-chain confirmation. The payer's funds never pass through D0.
import {
D0fiConnectWalletPayment,
type D0fiConnectWalletPaymentAdapter,
type D0fiConnectWalletPaymentIntent,
} from "@d0fi/checkout-react";
import "@d0fi/checkout-react/styles.css";
export function WalletPayment({
intent,
adapter,
registerSubmittedHash,
}: {
intent: D0fiConnectWalletPaymentIntent;
adapter: D0fiConnectWalletPaymentAdapter;
registerSubmittedHash: (payment: {
reference: string;
transactionHash: string;
}) => void;
}) {
return (
<D0fiConnectWalletPayment
intent={intent}
adapter={adapter}
onSubmitted={registerSubmittedHash}
/>
);
}No D0 Checkout Session or browser-side D0 credential is used. onSubmitted
means the wallet adapter returned a transaction hash; the customer backend must
still verify the network, asset, recipient, amount and finality before crediting
the order. Use D0fiCheckoutExperience when D0 should own Session policy,
merchant records, status and webhooks.
The active intent owns one stable idempotency key for uncertain retries. An
adapter must deduplicate that key before broadcasting. Register the returned
hash from onSubmitted; a registration outage must not be presented as if the
wallet transfer failed. See the runnable EVM setup and the Solana/TRON adapter
requirements at https://docs.d0fi.com/connect-wallet.
Wallet connection
@d0fi/checkout-react includes a namespaced EVM and Solana wallet connection layer for merchant applications. It discovers installed EIP-6963 wallets, Solana Wallet Standard wallets (Phantom, Solflare, Fordefi, Backpack, OKX, Coinbase, Trust, Magic Eden, Jupiter, and others), using browser wallet interfaces. Phantom, Jupiter, Fordefi, Ledger, and Solflare are always shown in the Solana list: installed Wallet Standard wallets connect directly and carry an INSTALLED badge, while uninstalled fallbacks open their official websites. Ledger is presented as a hardware-wallet setup option and normally connects to Solana DApps through a compatible software wallet such as Phantom or Solflare.
import {
D0fiCheckoutExperience,
D0fiConnectButton,
D0fiWalletProvider,
createD0fiWalletConfig,
useD0fiAccount,
useD0fiSignMessage,
} from "@d0fi/checkout-react";
import "@d0fi/checkout-react/styles.css";
import "@d0fi/checkout-react/wallet.css";
const walletConfig = createD0fiWalletConfig({
appName: "Website A",
// Opt in so existing merchant UIs do not change after an SDK upgrade.
solana: { enabled: true },
});
function Profile() {
const account = useD0fiAccount();
const { signMessageAsync, isPending, error } = useD0fiSignMessage();
return account.isConnected ? (
<div>
<button
disabled={isPending}
onClick={() => void signMessageAsync("Sign in to Website A").catch(() => undefined)}
>
Sign test message for {account.address}
</button>
{error ? <p role="alert">{error.message}</p> : null}
</div>
) : null;
}
export function App() {
return (
<D0fiWalletProvider config={walletConfig}>
<D0fiConnectButton />
<Profile />
<D0fiCheckoutExperience
sessionId="YOUR_SESSION_ID"
walletHttpBase="https://api.d0.fi"
embedded
/>
</D0fiWalletProvider>
);
}Public hooks: useD0fiAccount, useD0fiNetwork, useD0fiSignMessage, useD0fiSolanaTransaction, useD0fiSwitchChain, useD0fiDisconnect, useD0fiWalletClient, and useD0fiConnectModal.
Use D0fiConnectWalletButton when the host wants one combined EVM/Solana
picker. Its default form is a trigger plus modal; pass inline to render the
same picker directly in the page without an overlay, close control, focus trap,
or body scroll lock:
<D0fiConnectWalletButton inline />For an EVM write that must run on a specific chain, use
useD0fiWalletClient().getClientForChainAsync(chainId). It switches the wallet
when required and returns a newly-created client pinned to that chain, so code
does not reuse chain metadata captured before the switch completed.
EVM remains the default namespace, so existing calls keep their original viem-compatible types. Pass namespace: "solana" to read and sign with the independent Solana connection:
function SolanaProfile() {
const account = useD0fiAccount({ namespace: "solana" });
const { signMessageAsync, isPending, error } = useD0fiSignMessage({
namespace: "solana",
});
if (!account.isConnected) {
return <D0fiConnectButton namespace="solana" label="Connect Solana" />;
}
return (
<>
<p>{account.address}</p>
<button
disabled={isPending}
onClick={() => {
void signMessageAsync(`Website A nonce: ${nonce}`)
.then(({ signedMessage, signatureBase58 }) => {
// Send signedMessage, signatureBase58, address, and the nonce to
// Website A's backend. Do not create a login session in-browser.
submitProof({ signedMessage, signatureBase58, address: account.address! });
})
.catch(() => undefined);
}}
>
Sign Solana message
</button>
{error ? <p role="alert">{error.message}</p> : null}
</>
);
}The Solana result contains signedMessage, raw signature bytes, and signatureBase58. Verify signedMessage rather than reconstructing it: Wallet Standard allows a wallet to prefix or otherwise transform the requested bytes. A backend can decode the public key and signature with bs58 and verify the Ed25519 signature with its preferred audited crypto library. Nonce issuance, expiry, domain binding, replay protection, SIWS, and the authenticated merchant session remain the merchant's responsibility.
useD0fiProvider("solana") exposes serialized transaction signing in addition to message signing. It supports signTransaction, batched signAllTransactions, and wallet-side signAndSendTransaction when the selected wallet advertises that feature. useD0fiSolanaTransaction() exposes the same operations with shared pending/error state. useD0fiDisconnect().disconnectAsync({ namespace: "solana" }) disconnects only Solana; omitting the namespace disconnects all D0fi namespaces. EVM and Solana connections can coexist in one provider tree.
Solana DeFi and Neutral Trade-style integrations
Transaction signing is available through two optional compatibility entry points so an existing DeFi frontend can keep its transaction stack:
# Install the one already used by the application.
pnpm add @solana/web3.js
# or
pnpm add @solana/kitFor a Reown/AppKit Solana frontend that already passes walletProvider into web3.js or Anchor helpers, replace the provider hook and keep the downstream calls:
import { useD0fiAccount } from "@d0fi/checkout-react";
import { useD0fiSolanaWeb3Provider } from "@d0fi/checkout-react/solana-web3";
function DepositAction() {
const { address, isConnected } = useD0fiAccount({ namespace: "solana" });
const { walletProvider, error } = useD0fiSolanaWeb3Provider();
async function deposit() {
if (!isConnected || !address || !walletProvider) return;
// Existing web3.js / Anchor code can continue to use:
// walletProvider.publicKey
// walletProvider.signMessage(bytes)
// walletProvider.signTransaction(transaction)
// walletProvider.signAllTransactions(transactions)
// walletProvider.sendTransaction(transaction, connection, options)
await existingDepositFlow({ walletProvider, userAddress: address });
}
return <button onClick={() => void deposit()}>{error?.message ?? "Deposit"}</button>;
}For @neutral-trade/sdk and other @solana/kit instruction builders, pass the D0fi adapter as their TransactionSigner:
import { buildDepositInstructions } from "@neutral-trade/sdk";
import {
useD0fiSolanaKitTransactionSigner,
} from "@d0fi/checkout-react/solana-kit";
function NeutralDeposit() {
const { data: user, error } = useD0fiSolanaKitTransactionSigner();
async function buildDeposit() {
if (!user) return;
const instructions = await buildDepositInstructions(rpc, {
user,
// Existing vault and amount arguments stay unchanged.
vault,
amount,
});
await existingKitTransactionPipeline(instructions);
}
return <button onClick={() => void buildDeposit()}>{error?.message ?? "Deposit"}</button>;
}The Kit bridge intentionally implements a partial signer and rejects a wallet response that changes the compiled transaction message. This prevents a modified transaction from being merged under the wrong Kit signer contract. Both compatibility packages are optional peers and separate bundle entry points; applications that only use Checkout do not load web3.js or Kit through D0fi.
Browser wallet connections do not require a project ID or a remote wallet directory.
- Connecting never signs a message or sends a transaction automatically.
signMessageproves wallet control only. The merchant remains responsible for nonce issuance, signature verification, replay protection, and its authenticated session.- Solana can be configured with the exported
d0fiSolanaMainnet,d0fiSolanaDevnet, andd0fiSolanaTestnetconstants. D0fi supplies cluster-aware connection and signing, while the merchant keeps its RPC, transaction construction, simulation, submission/confirmation policy, SIWS, and backend login session. - D0fi does not automatically sign, submit, or retry a transaction. Every signing call must originate from an explicit merchant-controlled user action.
- Wallet prompts can be rejected or fail. Catch returned promises and render the hook's
error; do not treat a connection, signature, or submitted transaction as successful before its promise resolves. - The defaults are Ethereum Mainnet and BNB Chain. Pass
chainsandtransportsto use production RPC endpoints or more EVM networks. - For SSR, create the config with
ssr: truein a client-safe module and renderD0fiWalletProviderfrom a client component. Browser wallet discovery runs only on the client. - Shared state works when merchant UI and
D0fiCheckoutExperienceare in the same React tree. Cross-origin Hosted iframes intentionally do not receive the parent provider.
RainbowKit and Reown compatibility
RainbowKit 2.2 migrations use @d0fi/checkout-react/rainbowkit and keep the website's existing WagmiProvider and QueryClientProvider. Reown AppKit 1.8 migrations use @d0fi/checkout-react/appkit, keep the explicit createAppKit result, and wrap the root with <AppKitProvider instance={appKit}>. See the migration guide for complete examples, supported APIs, codemod usage, and blockers such as Para or embedded login.
For a purpose="deposit" experience, when enabledMethods.connectWallet is true and D0fiCheckoutExperience receives a partnerContractDeposit adapter, Checkout shows a separate direct-contract entry. Its confirmation calls only the host adapter; it never falls back to the D0 session depositAddress. The existing Checkout wallet transfer method remains EVM-only and sends to the D0 session depositAddress. See docs/deposit-integration.md for the two trust boundaries and end-to-end partner gateway contract.
Pay vs deposit
purpose="pay"(default): samePOST /v1/checkout/sessions(omit or setpurpose: "pay"); bootstrap and polling useGET /v1/checkout/sessions/:idviagetSession/refreshSession.purpose="deposit": create withPOST /v1/checkout/sessionsandpurpose: "deposit"(orcreateDepositSessionin core); bootstrap and polling still useGET /v1/checkout/sessions/:id. Recovery retry usescreateDepositSession.
The merchant backend only needs /v1/checkout/sessions for both flows; distinguish with the purpose field on create.
Embed events keep the same type names (payment.completed, etc.); payload.purpose is "pay" | "deposit" (see docs/checkout-embed-protocol.md).
Glide-inspired flows (Embed Pay, Embed Deposit)
| Glide | D0fi widget |
|-------|-------------|
| Backend createWidgetSession (mode, amount) | Merchant backend POST /v1/checkout/sessions (purpose, amount, …); returns sessionId. |
| useGlidePay({ app, sessionId, onSuccess }) | D0fiCheckoutExperience with sessionId, walletHttpBase, onSuccess / onFailure (terminal states). |
| useGlideDeposit({ recipient, onSuccess, mode? }) | purpose="deposit" + same checkout API; the generated session depositAddress is the destination for manual or connected-wallet funding. |
| openGlidePay() iframe/modal | useD0fiHostedIframe() (alias of useD0fiCheckout) + D0fiCheckoutModal / openEmbedded toward Hosted origin configured on D0fiCheckoutProvider. |
| Optional skip first screen | initialFlow="crypto" on D0fiCheckoutExperience lands on the manual QR/address flow without the payment-method tiles (pay flow only). |
CSS
Import @d0fi/checkout-react/styles.css once (e.g. in app entry). Tailwind utilities used by the UI require this stylesheet.
Appearance / theming (appearance prop)
Merchants customize the widget at integration time through the appearance prop on D0fiCheckoutExperience. It is resolved into --d0fi-* CSS variables injected on the checkout root; any token you omit keeps its built-in dark/light default.
import { D0fiCheckoutExperience, type D0fiAppearance } from "@d0fi/checkout-react";
const appearance: D0fiAppearance = {
mode: "auto", // "dark" | "light" | "auto" (system)
accent: "#8B5CF6", // brand accent (icons, brand surfaces, legacy --brand-color)
accentText: "#FFFFFF",
font: "'Inter', sans-serif",
colors: {
dark: { bg: "#0B0B0F", card: "#15151B", textPrimary: "#FFFFFF" },
light: { bg: "#F4F4F7", card: "#FFFFFF" },
},
radius: { card: 20, list: 14, modal: 20 }, // px
border: { cardWidth: 1, cardColor: "#26262B" },
components: {
card: { title: "#FFFFFF", subtitle: "#9AA0AB", background: "#15151B" },
list: { rowBackground: "#1C1C22", sectionTitle: "#7A8090" },
},
general: { modalTitle: "Pay Order #A1234" },
};
<D0fiCheckoutExperience sessionId={id} walletHttpBase="https://api.d0.fi" appearance={appearance} embedded />;| Field | Effect |
|-------|--------|
| mode | dark / light / auto. auto tracks prefers-color-scheme and syncs the shared theme store. |
| accent / accentText | Brand accent color + text on accent. |
| font | Any CSS font-family string. |
| colors.{dark,light} | Per-mode base palette overrides (keys are ThemeTokenKey; only the active mode is emitted). |
| radius | Corner radius per surface: card / list / modal (px). |
| border | cardWidth (px) + cardColor. |
| components | Per-component overrides layered on the base palette (e.g. card.title, list.rowBackground). |
| general.modalTitle | Header title text on the checkout card. |
Helpers buildAppearanceVars, resolveAppearanceMode, THEME_DEFAULTS, tokenVarName are exported for SSR pre-rendering or building your own editor. Payment-method visibility remains controlled by CheckoutFeaturesProvider (enabledMethods), not appearance.
Recovery defaults
recoveryDefaults on D0fiCheckoutExperience retains the deposit destination needed by the “expired → try again” experience. Session recreation remains a merchant-backend responsibility; the browser never selects a tenant principal:
depositAddress— required when recovery is enabled.depositReferencePrefix— optional; used when buildingreferenceIdfor deposit retries iforderIdis empty.
Re-exported types: WalletCheckoutPurpose and the Merchant-scoped Checkout experience types from @d0fi/checkout-core.
Deposit with Wallet
The wallet payment-method row connects directly to installed EVM browser extensions (MetaMask, Coinbase Wallet, OKX, Phantom EVM, Rabby, Trust Wallet, and Brave Wallet). After connection, the payer selects an enabled EVM asset/network pair, receives the session payment address, enters an amount, reviews the full transfer, and confirms it in the wallet.
Each widget-config.crypto.pairs[] row used by this flow must include chain-neutral wallet metadata:
| Field | Role |
|-------|------|
| walletFamily | Transfer adapter family. evm uses the EVM browser-wallet path; solana uses Wallet Standard and requires mainnet mint, decimals, and a browser-safe RPC URL. |
| networkReference | Wallet network identifier, e.g. eip155:1 or eip155:56. |
| assetReference | Token contract/mint identifier; omitted for native assets. |
| decimals | Amount precision used to build the wallet transaction. |
| isNative | Whether the transfer sends the network's native asset rather than a token contract call. |
The client-returned transaction hash is a non-authoritative submitted-state hint. Checkout status only advances after backend chain scanning verifies the actual deposit.
