React component library for accepting crypto payments via Web3Settle. Drop in a provider and a pay button to take on-chain payments on Ethereum, Base, and TRON directly from your users' wallets — no custody, no card data, no server-side signing.
Chain cut (2026-09-24). Polygon was dropped permanently (decision D1) and the
./solanasubpath was archived (decision D2).@web3settle/merchant-sdk/solanano longer exists, and chainId137is gone from the default chain catalog. Seearchive/solana/README.mdand the CHANGELOG's Unreleased entry for the full list of removed exports.Which chains are supported is stated in exactly one place: the platform's
docs/CHAIN_MATRIX.md(workspace root of theweb3settle-monoreporepo), mirrored contractually bydocs/SOFTWARE_PRODUCT_DESCRIPTION.md§3.1. If this README and that table ever disagree, the table is right.
Distribution: each chain family lives behind a separate subpath export so the bundler only pulls in what you actually use.
| Subpath | What it brings | Peer deps you must install |
|---|---|---|
@web3settle/merchant-sdk |
EVM (Ethereum / Base) provider + button + modal + hooks | wagmi, viem, @wagmi/core, @tanstack/react-query |
@web3settle/merchant-sdk/tron |
TRON provider + button + modal + hooks (TronLink-backed) | TronLink browser extension at runtime. The tronweb package is a peer for TypeScript types only — the SDK uses the extension's injected window.tronWeb |
@web3settle/merchant-sdk/headless |
Framework-agnostic controllers (createPayButtonController, createWalletConnectController, createGasEstimateController) — V0.5.0 — for Vue / Svelte / vanilla JS callers |
None beyond your chain-stack peers above |
@web3settle/merchant-sdk/wc |
<web3settle-pay-button> native HTMLElement built on top of the headless layer — V0.5.0 |
None beyond your chain-stack peers above |
EVM-only consumers never pay the bundle cost of the TRON stack; TRON-only consumers never pull wagmi. Import only the subpaths you need.
The gateway can only tell you who paid if the pay-in carries a payment reference. The flow is four steps, and only the second happens in the browser:
your server ──POST /api/payment/create (X-Api-Key)──▶ gateway → { requestId, paymentReference, status: "pending" }
browser ──SDK: payInNativeWithReference / payInTokenWithReference(paymentReference)──▶ merchant contract (MerchantPayIn ≥ V3.2.3)
gateway ──detects the deposit, links it to the request──▶ status: pending → processing → confirmed
gateway ──POST payment.confirmed (X-Webhook-Signature: v1=…)──▶ your webhook → credit data.metadata.userId
-
Create the payment on your server — never in the browser; it needs your storefront API key. Put what you need back on the webhook into
metadata(the webhook has nouserIdfield of its own).amountis in whole token units of the asset you will be paid in (e.g.0.01ETH or9.99USDC), not USD: the gateway only links a deposit of at least that amount, in that asset, on that network, into that storefront's contract.// server-side (Node) const res = await fetch(`${GATEWAY}/api/payment/create`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': process.env.WEB3SETTLE_API_KEY!, 'idempotency-key': orderId }, body: JSON.stringify({ network: 1, // BlockchainNetwork ordinal: Ethereum 0, Base 1, Tron 2 toAddress: contractAddress, // GET /api/payment/deposit-address?network=Base amount: 0.01, // whole units of the token below (here: ETH) tokenAddress: undefined, // ERC-20 address, or omit for the native coin metadata: { userId, orderId }, }), }); const { requestId, paymentReference } = await res.json(); // paymentReference: bytes32 hex
-
Pay with the reference in the browser. Either hand it to the hook yourself…
const { startPayment } = usePayment(); await startPayment(amountUsd, chain, token, { atomicAmount, paymentReference });
…or let the modal ask your backend at click time through
createPayment(it passes the exact token, chain and amount about to be sent, plususerId):<Web3SettleProvider config={{ apiBaseUrl, storefrontId, createPayment: async (ctx) => (await fetch('/api/checkout', { method: 'POST', body: JSON.stringify(ctx) })).json(), }}>
Attribution is the default and is enforced: without a valid reference the SDK refuses before the wallet is prompted (
MissingPaymentReferenceError). The plainpayInNative()/payInToken()still exist behind the explicitmode: 'unattributed'opt-in — such a deposit is detected and settled, but reaches your webhook with nopaymentRequestIdand nometadata. -
Poll or wait.
GET /api/payment/status/{requestId}(server-side, same API key) goespending→processing(deposit seen, withtxHashand liveconfirmations) →confirmed(required depth reached) — orfailedif a reorg removed the deposit. -
Credit on the webhook, not on the browser's success callback. Verify
X-Webhook-Signature: v1=hex(HMAC-SHA256(secret, "{X-Webhook-Timestamp}.{rawBody}"))over the raw body, reject stale timestamps, ignoretest: true, dedupe ondata.txHash, and creditdata.metadata.userId.data.amountPaidis what arrived (gross);data.amountInvoicedis what the request asked for — creditmin(amountPaid, amountInvoiced)unless you handle overpayment.
Needs a merchant contract at MerchantPayIn V3.2.3 or later; a V3.2.2 contract has no
…WithReference functions and the wallet's gas estimation fails. The TRON subpath (0.7.0) follows
the same contract: useTronPayment().startPayment(…, { paymentReference }) sends
payInNativeWithReference / payInTokenWithReference, TronTopUpModal asks your
Web3SettleConfig.createPayment for the reference at Pay time, and attribution is the default there too.
Known limitation (0.6.0).
WEB3SETTLE_PAYMENT_CONFIG_PUBKEY_PRIMARYis still the all-zero development placeholder, so the signedGET /payment-confignever verifies against a real gateway andWeb3SettlePayButton/Web3SettleTopUpModalcannot load their chain list. Until the real key ships in an SDK release, drive payments withusePaymentand your ownChainConfig(whatmerchant-integration-demodoes). Tracked in the workspacehumanpending.md.
- Three chains across two stacks: Ethereum, Base (wagmi + viem) and TRON (TronLink)
- Unified
PaymentPipelineinterface so both stacks present the samequoteAmount → needsApproval → approve → execute → waitForReceiptsurface - Native currency and fungible-token payments on every chain
- EVM: ERC-20 approval flow with exact-amount allowance (never unlimited). EIP-712 permit (V0.5.0 / segment 14.6) — when the token implements EIP-2612 the SDK signs the typed-data permit and submits
permit(...)directly, saving the user one popup and ~$0.50 of gas. - TRON: TRC-20 approve + pay,
SafeTRC20-aware for non-return-value tokens like USDT-TRON - Built-in wallet connection per chain (injected + WalletConnect on EVM; TronLink on TRON)
- Real-time transaction status tracking with reorg-aware confirmation counts
- Gas estimator (V0.5.0 / segment 14.1) —
estimateEvmGas,estimateTronGas— singleGasEstimateshape across both stacks; the modal renders a≈ $Xnetwork-fee badge - Telemetry breadcrumbs (V0.5.0 / segment 14.2) — opt-in
onTelemetrycallback emits a privacy-redacted event per failed pay-in (no plain addresses, no amounts; PII-redacted message ≤240 chars) - Headless layer + Web Components (V0.5.0 / segment 14.5) —
@web3settle/merchant-sdk/headlessand@web3settle/merchant-sdk/wc(<web3settle-pay-button>native HTMLElement) for Vue / Svelte / vanilla JS callers - CoinGecko price feeds with in-memory caching + stale-while-revalidate fallback
- Dark theme glassmorphism UI with CSS-variable hooks for theming
- Zod-validated API responses at every boundary
- Tree-shakeable ES modules + CommonJS output, one subpath per chain family
- Full TypeScript type exports
- i18n via i18next — English and Brazilian Portuguese shipped; straightforward extension to any locale; separate
tron.*key namespace - Accessibility: role-dialog modal with focus trap, ESC close, screen-reader labels
npm install @web3settle/merchant-sdk wagmi viem @tanstack/react-query @wagmi/coreAll four are peer dependencies — they must live in your app's own node_modules tree. The SDK itself adds only zod at runtime.
| Package | Range |
|---|---|
react |
^18.0.0 || ^19.0.0 |
react-dom |
^18.0.0 || ^19.0.0 |
wagmi |
^3.6.16 |
viem |
^2.52.2 |
@wagmi/core |
^3.5.0 (optional) |
@tanstack/react-query |
^5.101.0 |
import { Web3SettleProvider } from '@web3settle/merchant-sdk';
import '@web3settle/merchant-sdk/styles.css';
function App() {
return (
<Web3SettleProvider
config={{
apiBaseUrl: 'https://api.yoursite.com',
storefrontId: 'your-storefront-uuid',
// Your backend creates the gateway payment and returns { paymentReference, requestId }.
createPayment: (ctx) => fetch('/api/checkout', { method: 'POST', body: JSON.stringify(ctx) }).then((r) => r.json()),
// Browser-side "tx mined" signal only — credit the user from the signed webhook.
onSuccess: (session) => console.log('Payment sent:', session.id, session.txHash),
onError: (error) => console.error('Payment failed:', error),
}}
walletConnectProjectId="your-wc-project-id" // optional
>
<YourApp />
</Web3SettleProvider>
);
}import { Web3SettlePayButton } from '@web3settle/merchant-sdk';
export function Checkout() {
return (
<Web3SettlePayButton
amount={29.99}
label="Pay with Crypto"
variant="primary"
size="lg"
/>
);
}import { useState } from 'react';
import { Web3SettleTopUpModal } from '@web3settle/merchant-sdk';
export function TopUp() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Top Up</button>
<Web3SettleTopUpModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
amount={50}
userId="user-123"
/>
</>
);
}The TRON subpath uses the user's TronLink extension at runtime. No build-time TronWeb dependency is required.
npm install @web3settle/merchant-sdk @tanstack/react-query
# tronweb is a peer for TypeScript types only; no runtime install required if you don't need the types in your own code.import { TronWeb3SettleProvider, TronPayButton } from '@web3settle/merchant-sdk/tron';
import '@web3settle/merchant-sdk/styles.css';
export function App() {
return (
<TronWeb3SettleProvider
config={{ apiBaseUrl: 'https://api.yoursite.com', storefrontId: 'uuid' }}
>
<TronPayButton amount={29.99} label="Pay with TRX or USDT-TRON" />
</TronWeb3SettleProvider>
);
}Give the provider the same createPayment callback as the EVM one: the modal calls it with the
exact chain / token / amount about to be sent, your server creates the gateway payment
(POST /api/payment/create) and returns its paymentReference, and only then is TronLink prompted —
with payInNativeWithReference / payInTokenWithReference (MerchantPayIn V3.2.3). Without a
createPayment the modal refuses to pay; payInMode: 'unattributed' is the explicit opt-out.
For a custom flow, useTronPayment() mirrors usePayment():
const { startPayment } = useTronPayment();
await startPayment(0, chain, 'native', {
paymentReference: instruction.paymentReference, // from your backend's POST /api/payment/create
atomicAmount: instruction.amountAtomic, // sun / raw TRC-20 units; skips the SDK's own quote
});useTronPipeline() gives you the TronPaymentPipeline for custom flows (execute(chain, token, amount, { paymentReference })). The provider surfaces { address, connected, connecting, error } via useTronWeb3SettleContext().wallet, and exposes connect() / disconnect() for manual control.
EVM and TRON each need their own provider — they use different wallet stacks and can't share a single Web3SettleProvider. Nest them, or pick at render time:
import { Web3SettleProvider, Web3SettlePayButton } from '@web3settle/merchant-sdk';
import { TronWeb3SettleProvider, TronPayButton } from '@web3settle/merchant-sdk/tron';
export function MultiChainCheckout({ chain, amount }) {
if (chain === 'tron') {
return (
<TronWeb3SettleProvider config={…}>
<TronPayButton amount={amount} />
</TronWeb3SettleProvider>
);
}
return (
<Web3SettleProvider config={…}>
<Web3SettlePayButton amount={amount} />
</Web3SettleProvider>
);
}Both pipelines implement the same PaymentPipeline interface exported from the root:
import type { PaymentPipeline, PaymentReceipt, PaymentErrorKind } from '@web3settle/merchant-sdk';so you can build your own chain-agnostic UI if the default modals don't suit.
| Component | Description |
|---|---|
Web3SettleProvider |
Context provider wrapping WagmiProvider, QueryClientProvider, and SDK config. Required root. |
Web3SettlePayButton |
Styled button that opens the payment modal. Three variants × three sizes. |
Web3SettleTopUpModal |
Full multi-step payment flow modal (amount → wallet → token → review → status). |
ChainSelector |
Network selection with icons and token tooltips. role="radiogroup" + keyboard-accessible. |
TokenSelector |
Token selection with live balance and USD display. Falls back gracefully if RPC is unreachable. |
TransactionStatus |
Animated transaction progress indicator (sending → confirming → success) + explorer link. |
WalletConnect |
Wallet connection UI listing every configured wagmi connector. |
| Hook | Description |
|---|---|
useWeb3Settle() |
Access SDK config + cached payment configuration loaded from the API. Exposes refetch(). |
usePayment() |
Full payment lifecycle: chain switch, USD→token conversion, approval, pay-in, receipt wait. Returns { status, txHash, error, startPayment, reset }. startPayment(amount, chain, token, { paymentReference }) pays with payIn*WithReference; without a reference it refuses unless mode: 'unattributed'. |
useWallet() |
Wagmi-wrapped convenience hook: { address, isConnected, chainId, balance, balanceSymbol, displayAddress, connectors, connect, disconnect, error }. |
Useful if you want to drive payments without the UI, or integrate with an existing checkout.
import {
// Contract interaction
executePayInNativeWithReference, // attributed (V3.2.3) — use these for orders
executePayInTokenWithReference,
buildPayInCall, // pure: { data, value } for either form
executePayInNative, // unattributed — no paymentRequestId/metadata on the webhook
executePayInToken,
// Payment references
isPaymentReference,
resolveAttribution,
MissingPaymentReferenceError,
InvalidPaymentReferenceError,
approveToken,
checkAllowance,
getTokenBalance,
getTokenDecimals,
waitForReceipt,
parseTokenAmount,
// Price feeds
getNativeTokenPrice,
getTokenPrice,
usdToNativeAmount,
usdToTokenAmount,
clearPriceCache,
// API client
Web3SettleApiClient,
Web3SettleApiError,
} from '@web3settle/merchant-sdk';import {
// Constants
PAYMENT_CONTRACT_ABI,
ERC20_ABI,
DEFAULT_CHAINS,
CHAIN_ICONS,
COINGECKO_CHAIN_IDS,
PRICE_CACHE_TTL_MS,
NATIVE_TOKEN_SENTINEL,
// Zod schemas (validate at runtime)
TokenConfigSchema,
ChainConfigSchema,
PaymentConfigSchema,
PaymentSessionSchema,
Web3SettleConfigSchema,
// Enums
PaymentStatus,
} from '@web3settle/merchant-sdk';
// TypeScript types (erased at build time)
import type {
TokenConfig,
ChainConfig,
PaymentConfig,
PaymentSession,
CreatePaymentContext,
CreatedPayment,
StartPaymentOptions,
PaymentReference,
PayInMode,
Web3SettleConfig,
PayButtonProps,
TopUpModalProps,
ChainSelectorProps,
TokenSelectorProps,
TransactionStatusProps,
WalletConnectProps,
ButtonVariant,
ButtonSize,
TokenSelection,
} from '@web3settle/merchant-sdk';| Prop | Type | Required | Description |
|---|---|---|---|
config |
Web3SettleConfig |
Yes | See table below. |
children |
ReactNode |
Yes | Your app. |
walletConnectProjectId |
string |
No | WalletConnect v2 project ID from cloud.walletconnect.com. Omit to use injected connectors only. |
queryClient |
QueryClient |
No | Reuse your existing React Query client. The SDK creates one if omitted. |
| Property | Type | Required | Description |
|---|---|---|---|
apiBaseUrl |
string |
Yes | HTTPS URL of the Web3Settle backend (MerchantPaymentApi). |
storefrontId |
string |
Yes | Your storefront UUID from the back office. |
theme |
'dark' | 'light' |
No | UI theme. Default 'dark'. (Only dark ships today; light is reserved.) |
createPayment |
(ctx: CreatePaymentContext) => Promise<{ paymentReference, requestId? }> |
Yes, unless payInMode: 'unattributed' |
Called on Pay, before the wallet prompt. Your backend creates the gateway payment for exactly ctx.amount of ctx.token and returns its paymentReference. |
payInMode |
'attributed' | 'unattributed' |
No | Default 'attributed'. 'unattributed' sends the plain payIn* (no order linkage). |
onSuccess |
(session: PaymentSession) => void |
No | Fires after the tx is mined at the SDK's receipt depth. session.id is the requestId from createPayment. Not a credit signal — credit from the webhook. |
onError |
(error: Error) => void |
No | Fires on any unrecoverable failure (revert, user rejection, network error). |
| Property | Type | Default | Description |
|---|---|---|---|
amount |
number |
— | Payment amount in USD. |
label |
string |
"Pay $X.XX" |
Button label. |
variant |
'primary' | 'outline' | 'ghost' |
'primary' |
Visual style. |
size |
'sm' | 'md' | 'lg' |
'md' |
Button size. |
disabled |
boolean |
false |
Disable the button. |
className |
string |
— | Additional CSS classes (merged after the variant/size classes). |
The SDK ships with translations in English (en) and Brazilian Portuguese (pt-BR) and uses i18next + react-i18next under the hood. Strings are namespaced under web3settle-sdk so they never collide with your application's own translations.
There are two modes, pick whichever matches how your app already handles i18n.
Mode A — you already use i18next. Merge the SDK's resource bundles into your existing instance:
// src/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { addSdkResourcesTo } from '@web3settle/merchant-sdk';
void i18n
.use(initReactI18next)
.init({
lng: 'pt-BR',
fallbackLng: 'en',
interpolation: { escapeValue: false },
resources: {
en: { translation: { /* your app strings */ } },
'pt-BR': { translation: { /* your app strings */ } },
},
})
.then(() => {
// Adds `web3settle-sdk` namespace bundles for every locale the SDK ships.
addSdkResourcesTo(i18n);
});
export default i18n;The SDK components pick the language from your i18n.language. Changing it with i18n.changeLanguage('pt-BR') updates the SDK UI in the same frame.
Mode B — you don't use i18next in your app. Let the SDK create a standalone instance:
import { ensureSdkI18n } from '@web3settle/merchant-sdk';
const sdkI18n = ensureSdkI18n();
await sdkI18n.changeLanguage('pt-BR');The SDK components will use this instance automatically.
import { SUPPORTED_LOCALES, LOCALE_LABELS, SDK_NAMESPACE } from '@web3settle/merchant-sdk';
// SUPPORTED_LOCALES = ['en', 'pt-BR'] as const
// LOCALE_LABELS = { en: 'English', 'pt-BR': 'Português (Brasil)' }
// SDK_NAMESPACE = 'web3settle-sdk'The SDK will happily translate into any language you register with it. The work is just a JSON file and three lines of glue code in your app.
Step 1 — copy one of the shipped locale files as a starting point. The shape is stable across versions; pick en.json (authoritative) or pt-BR.json (reference for gendered / length-sensitive translations).
# Grab the canonical English pack from the installed package:
cp node_modules/@web3settle/merchant-sdk/src/i18n/locales/en.json \
src/locales/web3settle-sdk.es-ES.jsonOpen the file and translate every leaf string, leaving the keys unchanged. A complete pack looks like this:
Step 2 — register the pack with the SDK's i18n instance.
If you use Mode A (your own i18next):
import i18n from './i18n';
import { SDK_NAMESPACE } from '@web3settle/merchant-sdk';
import esES from './locales/web3settle-sdk.es-ES.json';
// Tell i18next that 'es-ES' exists and what its SDK pack looks like.
i18n.addResourceBundle('es-ES', SDK_NAMESPACE, esES, /* deep */ true, /* overwrite */ false);
// Let users switch to it:
await i18n.changeLanguage('es-ES');If you use Mode B (SDK-owned instance):
import { ensureSdkI18n, SDK_NAMESPACE } from '@web3settle/merchant-sdk';
import esES from './locales/web3settle-sdk.es-ES.json';
const sdkI18n = ensureSdkI18n();
sdkI18n.addResourceBundle('es-ES', SDK_NAMESPACE, esES, true, false);
await sdkI18n.changeLanguage('es-ES');Step 3 — (optional) advertise the locale in your language switcher.
SUPPORTED_LOCALES and LOCALE_LABELS exported by the SDK are not mutable — they describe the bundles the SDK ships with. If you want your own language switcher to list Spanish as well, maintain your own array in your app and include whatever languages you've added via addResourceBundle.
const ALL_LOCALES = [
{ code: 'en', label: 'English' },
{ code: 'pt-BR', label: 'Português (Brasil)' },
{ code: 'es-ES', label: 'Español (España)' },
];When we add a new string in a minor version, the English entry is authoritative. Missing keys in custom locale files fall back to English automatically — nothing breaks, but the un-translated string will appear in English until you copy the new key over. We recommend running a diff against node_modules/@web3settle/merchant-sdk/src/i18n/locales/en.json after every version bump.
Translations use i18next interpolation syntax for substitution and the standard _one / _other suffixes for plurals:
"chain": {
"tokensCount_one": "{{count}} token",
"tokensCount_other": "{{count}} tokens"
}Follow CLDR plural categories for the languages you add (some languages need _few, _many, _zero, etc.).
Visual RTL support (dir="rtl") is not applied automatically — your host app's root element should set dir based on the active locale. The SDK's layout uses logical properties (ms-, me-, start, end) wherever Tailwind supports them, so it adapts correctly when the host sets dir="rtl".
| Chain | Stack | Native | Default whitelist | SDK subpath |
|---|---|---|---|---|
| Ethereum | EVM / wagmi | ETH | USDC, USDT | root |
| Base | EVM / wagmi | ETH | USDC | root |
| TRON mainnet | TronLink | TRX | TRC-20 (incl. USDT-TRON via SafeTRC20) |
/tron |
All three use the same MerchantPayIn V3.0 contract model — immutable commission set at initialize, token whitelist managed by the admin role, no renegotiation path.
Removed 2026-09-24 (chain cut): Polygon (decision D1) and Solana (decision D2). The product's GTM target is four mainnets; the fourth is an open founder decision (D4a) tracked in the workspace-root humanpending.md. Adding a fourth EVM chain here is a config change only — one entry in DEFAULT_CHAINS, CHAIN_ICONS, COINGECKO_CHAIN_IDS, KNOWN_CONTRACT_ADDRESSES, DEFAULT_CONFIRMATION_THRESHOLDS, CHAIN_FAMILY_REGISTRY, DEFAULT_SECONDS_TO_FINALITY, plus the wagmi chain list in Web3SettleProvider.
- All API responses are validated with Zod schemas before use.
- Signed PaymentConfig (V0.5+).
GET /payment-configreturns{ data, signedAt, signature }; the SDK refuses to build calldata unless the Ed25519 signature verifies againstWEB3SETTLE_PAYMENT_CONFIG_PUBKEY_PRIMARY(or_SECONDARYduring a rotation overlap) oversignedAt + canonical_json(data). The constants are baked into the SDK at release time so a poisoned-DNS or CDN-edge MITM that swaps the contract address cannot be silently honoured. The.well-known/web3settle-config-pubkeyendpoint mirrors the constants for out-of-band drift checks. - Contract allowlist. The SDK refuses to call
payIn*against any address that is neither in the baked-inKNOWN_CONTRACT_ADDRESSESnor explicitly elevated by the signedallowedContractAddressesmap for the storefront. New canonical addresses require an SDK release. - ABI version handshake. The signed payload carries
contractAbiVersion. The SDK fails closed when it sees a revision not inSUPPORTED_ABI_VERSIONS. - Permit allowlist.
permit: 'auto'falls back toapprove()for any token whose(name, version, chainId, verifyingContract)quadruple is not in the SDK's baked-inKNOWN_PERMIT_TOKENS(0.6.0: USDC Ethereum, DAI Ethereum, USDC Base — verified on-chain, seedocs/PERMIT_ALLOWLIST.md). A token whosePERMIT_TYPEHASHis not EIP-2612's (DAI) is treated as permit-unsupported and also usesapprove().permit: 'require'raisesUnknownPermitTokenErroron unknown tokens. To add a token, computepermitDomainKey(name, version, chainId, verifyingContract)and PR the resulting hex digest intocore/config.ts; the addition ships in the next SDK release. - Salted telemetry digests.
walletDigestis salted by(storefrontId, dayUtc)so two storefronts of the same wallet cannot be cross-joined and the same shop's digests rotate every UTC day. - ERC-20 approvals request only the exact amount needed — never unlimited.
- Transaction receipts are verified for
status === 'success'; reverts surface as anError. - Wallet connections use standard EIP-1193 providers via wagmi; the SDK does not read or persist private keys.
Web3SettleApiClientvalidatesstorefrontIdas a UUID at construction time and builds URLs via theURLconstructor (no string concat). It has no payment-creation call: that needs your API key and belongs on your server.- Payment references are public. Anyone can pay any contract with any reference; the gateway only links a deposit that could settle the request (same merchant, storefront, network, asset; at least the amount; request still pending). Never treat "a tx with my reference exists" as payment — wait for
confirmed/ the signed webhook. - Modal:
role="dialog"+aria-modal+ focus restoration + ESC-key close. Click-outside closes on mouse but the Escape handler is always present for keyboard-only users.
The permit: 'auto' path silently signs EIP-2612 permits only for tokens whose
EIP-712 domain quadruple is on the SDK's allowlist. To add one:
import { permitDomainKey } from '@web3settle/merchant-sdk';
console.log(permitDomainKey('USD Coin', '2', 1, '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'));
// → "<64-char hex>"Read name() / version() from the token itself and check its DOMAIN_SEPARATOR() against the
quadruple first — docs/PERMIT_ALLOWLIST.md has the script. Open a PR against
merchant-sdk/src/core/config.ts adding the digest to KNOWN_PERMIT_TOKENS. Include in the PR body the source you used to verify the
contract address (Etherscan label, official docs link). The merge ships in the
next SDK release; backend deploys alone cannot expand the set.
Modern evergreen browsers with ES2020 support (Chrome 94+, Firefox 93+, Safari 15+, Edge 94+).
npm install
npm run dev # Build with watch
npm run build # Production build (tsc + Vite lib)
npm run test # Run Vitest (12 test files, 150 tests)
npm run typecheck # tsc --noEmit
npm run lint # ESLint (flat config, strict type-checked)
npm run audit:ci # Fail on high/critical vulnsBuilt artefacts in dist/:
| File | Purpose |
|---|---|
index.js |
ESM entry point. |
index.cjs |
CommonJS entry point. |
index.d.ts |
Rolled-up TypeScript declarations. |
styles.css |
Tailwind-compiled stylesheet. Import separately via @web3settle/merchant-sdk/styles.css. |
styles.js / styles.cjs |
40-byte side-effect entry that imports styles.css (used by tooling). |
Source maps accompany every .js / .cjs output.
Proprietary — Copyright © 2026 Web3Settle.com. See LICENSE for the full terms. Redistribution, sublicensing, and embedding this SDK into third-party products requires written permission from Web3Settle.com.