@droplinked_inc/web3-ui
v0.3.0
Published
Hardened React UI primitives for droplinked web3 flows (payment & NFT claim).
Readme
@droplinked_inc/web3-ui
Hardened React UI primitives for droplinked web3 flows.
This is the first-pass rewrite of the original [email protected]
(hostile-published by an external actor and now deprecated). The public API
surface intentionally matches the .d.ts shape of the upstream package so
existing call sites continue to compile.
This package is a pure renderer. All wallet-protocol logic
(chain selection, RPC, signing, broadcast, receipts) lives in
@droplinked_inc/wallet-connection and is injected at runtime through a
WalletAdapter interface. See THREAT_MODEL.md for
the security delta vs. the upstream.
Install
pnpm add @droplinked_inc/web3-uiQuick start
import {
DroplinkedWeb3Ui,
DropWeb3Provider,
Actions,
type WalletAdapter,
} from '@droplinked_inc/web3-ui';
// Bridge to @droplinked_inc/wallet-connection (or a stub in tests).
const adapter: WalletAdapter = {
async payWithCrypto({ cartId, shopId }) {
/* ... */
return orderId;
},
async claimNft({ orderId, skuId }) {
/* ... */
return { transactionHash, transactionUrl };
},
};
function App() {
return (
<DropWeb3Provider adapter={adapter}>
<Checkout />
</DropWeb3Provider>
);
}Component gallery
DroplinkedWeb3Ui — Actions.Payment
Renders a "Pay with crypto" button. Initiates the payment flow via the
adapter, surfaces pending state, and invokes the caller-supplied
onSuccess(orderId) / onError(message) callbacks.
<DroplinkedWeb3Ui
action={Actions.Payment}
inputs={{
cartId: 'cart-abc',
shopId: 'shop-xyz',
onError: (msg) => console.error(msg),
onSuccess: (orderId) => console.log('paid:', orderId),
}}
/>| Prop | Type | Required | Notes |
| ------------------ | --------------------------------- | -------- | -------------------------------------- |
| cartId | string (≤128 printable ASCII) | yes | Zod-validated at the component boundary |
| shopId | string (≤128 printable ASCII) | yes | " |
| onError(message) | (string) => void | yes | Errors from adapter rejections |
| onSuccess(id?) | (string \| undefined) => void | yes | Empty / whitespace-only ids → undefined |
DroplinkedWeb3Ui — Actions.ClaimNFT
Renders a customizable claim button. On click, calls
adapter.claimNft({ orderId, skuId }), renders the resulting tx hash, and
emits a "view tx" link only if the adapter returned an https URL.
<DroplinkedWeb3Ui
action={Actions.ClaimNFT}
inputs={{
orderId: 'order-1',
skuId: 'sku-2',
buttonText: 'Claim NFT',
isButtonDisabled: false,
onError: (msg) => console.error(msg),
onSuccess: (txHash, txUrl) => console.log('claimed', txHash, txUrl),
}}
/>| Prop | Type | Required | Notes |
| -------------------- | ----------------------------------- | -------- | ----------------------------------------------------- |
| orderId | string (≤128 printable ASCII) | yes | |
| skuId | string (≤128 printable ASCII) | yes | |
| buttonText | string (1–64 chars) | yes | Auto-escaped on render; control bytes stripped |
| isButtonDisabled | boolean | yes | |
| onError(message) | (string) => void | yes | |
| onSuccess(hash,url)| (string, string) => void | yes | url is '' when adapter returned a non-https value |
DropWeb3Provider
Supplies the WalletAdapter to the subtree.
| Prop | Type | Default | Notes |
| --------- | -------------------------- | ------- | --------------------------------------------------------------------- |
| adapter | WalletAdapter \| null | null | When null, all buttons render disabled and emit a config error toast |
| children| ReactNode | — | |
Utility exports
parseHttpUrl(raw, opts)— strict https URL parser, rejectsjavascript:/data:/vbscript:/ etc. Allowshttp:only with{ allowHttp: true }.safeHref(raw, opts)— returns the URL string orundefinedfor use in JSXhrefprops.safeText(value, fallback, maxLength)— render-safe string coercion; strips C0 / DEL, trims, length-caps.PaymentInputsSchema,ClaimNFTInputsSchema— re-exported zod schemas for consumer-side validation.
Design constraints
- Pure renderer — never talks to a wallet directly. Every wallet
call goes through the injected
WalletAdapter. Move that logic into@droplinked_inc/wallet-connection. - TypeScript strict — no
any, noascasts on user inputs (only on the zod schema declaration to satisfy zod'sz.function()type-inference limitation). - No
dangerouslySetInnerHTML— anywhere. React's auto-escape is sufficient and oursafeTextadds defense in depth. - No global state — phase/error state lives in
useState. - No embedded URLs / API keys / dev endpoints — the upstream
bundle had
apiv3dev.droplinked.combaked in (see PR body). All network calls are delegated to the consumer's adapter.
Scripts
pnpm --filter @droplinked_inc/web3-ui build # tsc -p
pnpm --filter @droplinked_inc/web3-ui typecheck # tsc --noEmit
pnpm --filter @droplinked_inc/web3-ui lint # eslint src
pnpm --filter @droplinked_inc/web3-ui test:coverage # jest --coverage