@deloraprotocol/widget
v1.0.23
Published
A React widget component library with light/dark themes and CSS variable overrides
Readme
@deloraprotocol/widget
A React widget component library for crypto trading UI, migrated from delora-exchange (Angular). Includes Trade widget, token selection, network selection, and slippage panel. Consumers do not need Tailwind — the library ships compiled CSS.
Note: By default the widget manages wallet connection and transaction execution itself. If your app already owns wallet connection, pass it explicitly via TradeWidgetWalletProvider.
Installation
npm install @deloraprotocol/widget react react-domImport styles
You must import the library CSS once in your app (e.g. in your root entry file):
import "@deloraprotocol/widget/styles.css";Usage
Trade Widget
import { TradeWidget } from "@deloraprotocol/widget";
<TradeWidget
theme="dark"
config={{}}
initialBuyToken={{ chainId: 1, address: "0x..." }}
lockBuyToken
onQuote={(quote) => console.log("quote", quote)}
onApprove={(payload) => console.log("approve", payload)}
onSwap={(payload) => console.log("swap", payload)}
onError={(error) => console.error(error)}
/>apiUrl is used for:
/v1/chains/v1/tokens/v1/tools/v1/quotes
If omitted, the widget defaults to https://api.delora.build.
apiKey is optional. For browser embeds, prefer keeping the key on your backend and pointing
config.apiUrl to your own proxy instead of exposing the key in the client.
If you need to resolve relative image URLs from a custom source, pass assetBaseUrl.
External Wallet Management
Explicit Host-Managed Wallets
If your app already owns wallet connection, wrap the widget in TradeWidgetWalletProvider.
The widget will use your external connect/disconnect flow and your connected providers for
approve/swap execution.
import {
TradeWidget,
TradeWidgetWalletProvider,
} from "@deloraprotocol/widget";
<TradeWidgetWalletProvider
value={{
origin: {
namespace: connected ? "EVM" : null,
address: connectedAddress ?? null,
walletName: "My App Wallet",
evmProvider,
connect: async ({ namespace }) => {
await openMyWalletModal(namespace);
return true;
},
disconnect: async () => {
await disconnectWallet();
},
},
}}
>
<TradeWidget
theme="dark"
config={{
apiUrl: "https://api.delora.build",
}}
/>
</TradeWidgetWalletProvider>Notes:
- Provide
originto control the sell-side wallet. Providedestinationtoo if your app also manages a separate receiver wallet. - Keep
status,namespace,address,walletName, provider refs, and error fields current. The widget treats this object as the source of truth in external mode. - You can pass raw providers via
evmProvider/solanaProvider, or getter functions if your wallet layer resolves them lazily. - If
walletOptionsis empty, the widget connect button callsconnectPreferred/connecton your host wallet directly. - If
walletOptionsis provided, the widget renders its wallet picker, then delegates the selected option to yourconnectWalletOption. - For custom wallet branding in the widget UI, pass
walletOptionsandconnectedWalletId, or setconnectedWalletVisualId/connectedWalletIconUrldirectly on the managed wallet. - If a side is omitted from
TradeWidgetWalletProvider, that side uses the widget's built-in wallet flow.
Widget Picker + Host Connect
Use this mode when you want Delora's wallet selection UI but still want all connection state owned by the host app.
import {
TradeWidget,
TradeWidgetWalletProvider,
type TradeWidgetManagedWallet,
} from "@deloraprotocol/widget";
const originWallet: TradeWidgetManagedWallet = {
status,
namespace,
address,
walletName,
evmProvider,
walletOptions: [
{
id: "metamask",
label: "MetaMask",
type: "EVM",
availableNamespaces: ["EVM"],
detected: Boolean(window.ethereum),
visualId: "metamask",
},
],
connectWalletOption: async (walletId, namespace) => {
await connectInHostWalletLayer({ walletId, namespace });
return true;
},
disconnect: disconnectInHostWalletLayer,
};
<TradeWidgetWalletProvider value={{ origin: originWallet }}>
<TradeWidget config={{ apiUrl: "https://api.delora.build" }} />
</TradeWidgetWalletProvider>;Optional Adapter Package
@deloraprotocol/widget-wallet-management provides LI.FI-style adapters around
the low-level contract above. It is separate from @deloraprotocol/widget, so
Wagmi, RainbowKit, Reown, Dynamic, and Solana Wallet Adapter dependencies do not
enter the core widget bundle.
npm install @deloraprotocol/widget-wallet-managementInstall the peer wallet libraries your app already uses. For example, a Wagmi
host installs wagmi; a Solana host installs @solana/wallet-adapter-react.
If you do not install both wallet stacks, prefer subpath imports such as
@deloraprotocol/widget-wallet-management/wagmi and
@deloraprotocol/widget-wallet-management/solana.
Exports:
DeloraAutoWalletManagementProvideruseWagmiManagedWalletuseSolanaWalletAdapterManagedWalletuseCompositeManagedWalletDeloraWalletManagementProvider- subpaths:
/auto,/wagmi,/solana,/provider,/composite
Auto-detect External Contexts
Use DeloraAutoWalletManagementProvider when your app already renders
WagmiProvider and/or Solana WalletProvider above the widget. It detects the
available contexts and wires them into Delora without requiring manual adapter
hooks in your app component.
import { TradeWidget } from "@deloraprotocol/widget";
import {
DeloraAutoWalletManagementProvider,
} from "@deloraprotocol/widget-wallet-management/auto";
function DeloraWithAutoWallets() {
return (
<DeloraAutoWalletManagementProvider
evm={{
walletOptions: [],
openConnectModal: () => openRainbowKitModal(),
}}
solana={{
walletOptions: [],
openConnectModal: () => openSolanaWalletModal(),
}}
usePartialWalletManagement
>
<TradeWidget config={{ apiUrl: "https://api.delora.build" }} />
</DeloraAutoWalletManagementProvider>
);
}Partial wallet management is enabled by default. If only Wagmi is detected,
Delora uses the external Wagmi wallet for EVM and keeps the built-in Solana flow.
If only Solana Wallet Adapter is detected, Delora keeps the built-in EVM flow.
Set usePartialWalletManagement={false} when the host wants to own all wallet
namespaces and fail explicitly instead of falling back to built-in wallets.
Set forceInternalWalletManagement to ignore detected contexts completely.
Wagmi / RainbowKit
import { TradeWidget } from "@deloraprotocol/widget";
import {
DeloraWalletManagementProvider,
} from "@deloraprotocol/widget-wallet-management/provider";
import {
useWagmiManagedWallet,
} from "@deloraprotocol/widget-wallet-management/wagmi";
import { useConnectModal } from "@rainbow-me/rainbowkit";
function DeloraWithRainbowKit() {
const { openConnectModal } = useConnectModal();
const evmWallet = useWagmiManagedWallet({
walletOptions: [],
openConnectModal: async () => {
if (!openConnectModal) {
throw new Error("RainbowKit connect modal is not available");
}
openConnectModal();
},
});
return (
<DeloraWalletManagementProvider origin={evmWallet}>
<TradeWidget config={{ apiUrl: "https://api.delora.build" }} />
</DeloraWalletManagementProvider>
);
}When openConnectModal is used, the adapter waits until Wagmi account state is
actually connected before resolving the widget connect action.
For a Delora-rendered wallet picker, omit walletOptions: []. The Wagmi adapter
only exposes recognized wallet rows by default, so service connectors such as
Safe or Base Account do not show up unless you explicitly opt into unknown
connectors with includeUnknownWalletOptions.
Solana Wallet Adapter
import { TradeWidget } from "@deloraprotocol/widget";
import {
DeloraWalletManagementProvider,
} from "@deloraprotocol/widget-wallet-management/provider";
import {
useSolanaWalletAdapterManagedWallet,
} from "@deloraprotocol/widget-wallet-management/solana";
import { useWalletModal } from "@solana/wallet-adapter-react-ui";
function DeloraWithSolanaWalletAdapter() {
const { setVisible } = useWalletModal();
const solanaWallet = useSolanaWalletAdapterManagedWallet({
walletOptions: [],
openConnectModal: () => setVisible(true),
});
return (
<DeloraWalletManagementProvider destination={solanaWallet}>
<TradeWidget config={{ apiUrl: "https://api.delora.build" }} />
</DeloraWalletManagementProvider>
);
}The Solana adapter maps Wallet Adapter state to Delora's SolanaProvider
contract. v1 execution requires a wallet adapter with signTransaction.
Undetected Solana adapters are hidden from the widget picker by default; pass
includeUndetectedWallets: true to show installable rows.
Composite EVM + Solana
const evm = useWagmiManagedWallet();
const solana = useSolanaWalletAdapterManagedWallet();
const wallet = useCompositeManagedWallet({ evm, solana });
<DeloraWalletManagementProvider wallet={wallet}>
<TradeWidget config={{ apiUrl: "https://api.delora.build" }} />
</DeloraWalletManagementProvider>;For cross-chain flows where origin and destination should prefer different namespaces, pass separate composites:
const origin = useCompositeManagedWallet({
evm,
solana,
preferredNamespace: "EVM",
});
const destination = useCompositeManagedWallet({
evm,
solana,
preferredNamespace: "SVM",
});
<DeloraWalletManagementProvider origin={origin} destination={destination}>
<TradeWidget config={{ apiUrl: "https://api.delora.build" }} />
</DeloraWalletManagementProvider>;Scope for the adapter package v1 is EVM + SVM. Sui and Bitcoin are not included because the current Delora execution flow does not support them.
Simple Widget (MyWidget)
import { MyWidget, TradeWidget } from "@deloraprotocol/widget";
<MyWidget theme="light" />
<MyWidget theme="dark" />
<MyWidget vars={{ accent: "#22c55e", radius: "24px" }} />
<TradeWidget
config={{ apiUrl: "https://api.delora.build" }}
vars={{ radius: "24px", actionButtonRadius: "999px" }}
/>
<MyWidget className="max-w-md" style={{ marginLeft: "2rem" }} />radius controls the widget surface corners, while actionButtonRadius lets you round the main CTA independently.
API
Exports
Additional wallet-management exports:
TradeWidgetWalletProviderTradeWidgetManagedWalletTradeWidgetWalletManagementMyWidget— the widget componentTradeWidget— trade widget with token/network selection and quote flowMyWidgetTheme—"light" | "dark"MyWidgetVars— typed object for token overridesMyWidgetProps— component propsTradeWidgetProps,TradeWidgetConfigTradeWidgetTokenSelectionTradeWidgetQuotePayloadTradeWidgetActionPayloadTradeWidgetErrorPayloadToken,Network
MyWidgetProps
| Prop | Type | Description |
|----------|-------------------|--------------------------------------------------|
| theme | "light" \| "dark" | Built-in theme (default: "light") |
| vars | Partial<MyWidgetVars> | Override theme tokens via CSS variables |
| className | string | Applied to root (for host layout/styling) |
| style | React.CSSProperties | Applied last for advanced overrides |
Extends React.HTMLAttributes<HTMLDivElement> for other div props.
TradeWidgetProps
TradeWidgetProps extends normal root div props except DOM onError, which is reserved for the widget error callback below.
| Prop | Type | Description |
|----------|-------------------|--------------------------------------------------|
| config | TradeWidgetConfig | Widget runtime config |
| theme | "light" \| "dark" | Built-in theme (default: "dark") |
| vars | Partial<MyWidgetVars> | Override theme tokens via CSS variables |
| initialSellToken | { chainId: number; address: string } | Preselect sell token |
| initialBuyToken | { chainId: number; address: string } | Preselect buy token |
| initialSellNetworkId | number | Preselect sell network |
| initialBuyNetworkId | number | Preselect buy network |
| lockSellToken | boolean | Prevent changing sell token |
| lockBuyToken | boolean | Prevent changing buy token |
| lockSellNetwork | boolean | Prevent changing sell network |
| lockBuyNetwork | boolean | Prevent changing buy network |
| onQuote | (payload) => void | Called when a new quote is resolved |
| onApprove | (payload) => void | Called when the user presses Approve |
| onSwap | (payload) => void | Called when the user presses Swap |
| onError | (payload) => void | Called for metadata, selection, and quote errors |
TradeWidgetConfig
| Prop | Type | Description |
|----------|-------------------|--------------------------------------------------|
| apiUrl | string | Optional Delora API base for /v1/chains, /v1/tokens, /v1/tools, /v1/quotes |
| rpcUrls | Record<number, string[]> | Optional RPC override map by chain id |
| integrator | string | Optional integrator id passed to quote requests |
| apiKey | string | Optional Delora API key sent as x-api-key; for browser embeds, prefer using a backend proxy |
| fee | number | Optional Delora fee value for quote requests, in the range 0..0.1 |
| slippage | number | Optional initial slippage override |
| excludeBridges | string[] | Optional bridge denylist for quotes |
| excludeExchanges | string[] | Optional exchange denylist for quotes |
| assetBaseUrl | string | Optional base for resolving relative image URLs |
| termsUrl | string | Optional Terms of Use URL shown in the connect modal |
| privacyPolicyUrl | string | Optional Privacy Policy URL shown in the connect modal |
| walletConnectProjectId | string | Optional WalletConnect Cloud project id |
Callback payloads
onQuote(payload)returns selected sell/buy network, selected sell/buy token, current sell/buy amounts, slippage, and the raw Deloraquote.onApprove(payload)andonSwap(payload)return the current selection plusquote,txData,price, andgasCostUSD.onError(payload)returns{ source, message, status?, statusCode?, error? }, wheresourceis one ofmetadata,selection, orquote.
Locking behavior
lockBuyToken/lockSellTokendisable token picking and also freeze the related network, because the token already implies a chain.lockBuyNetwork/lockSellNetworkdisable network changes but keep token selection available within the locked chain.- Any lock also disables the center "swap sides" button to avoid silently breaking host-provided constraints.
Why no Tailwind required in host
The library uses Tailwind only at build time to author styles. The output is plain compiled CSS shipped in dist/styles.css. Your host app imports this CSS file and does not need Tailwind, PostCSS, or any build-time Tailwind configuration.
Style isolation
The library provides style isolation without Shadow DOM:
Our styles don't affect the host: We disable Tailwind preflight and do not emit global element selectors (e.g. no
button {}orh1 {}). All styling is class-based and scoped within the widget markup.Host styles have limited impact on us: We add a minimal scoped reset under
[data-delora-widget-root]:box-sizing: border-boxon root and descendants- Explicit
font-family,font-size,color,line-heighton the root
Limitations: Host global styles (e.g.
* { box-sizing: content-box; }orbutton { font-size: 30px; }) can still affect the widget in extreme cases. We mitigate by setting explicit values on the widget root and avoiding reliance on inherited defaults.
Build
npm run buildOutputs:
dist/index.js(ESM)dist/index.d.tsdist/styles.css
Publishing to npm
Prerequisites
- npm account
- For scoped packages like
@deloraprotocol/widget: create the org at npmjs.com/org/create (or change the scope inpackage.jsonto your username)
First-time publish
Build the package:
npm run buildLog in to npm:
npm loginEnter your username, password, and email. If 2FA is enabled, enter the one-time code.
Publish (scoped packages require
--access public):npm publish --access public
Publishing updates
Bump the version:
npm version patch # 1.0.0 → 1.0.1 (bug fixes) npm version minor # 1.0.0 → 1.1.0 (new features) npm version major # 1.0.0 → 2.0.0 (breaking changes)Build and publish:
npm run build npm publish --access public
Org members
- Add members under Organization → Members on npmjs.com
- Any member with publish rights can publish and update the package
- Only one person needs to create the org; others can be invited
Running the widget locally
One-time setup
npm install
npm run build
cd playground && npm installDevelopment
npm run playground:devThis runs the library watcher and the playground dev server together. The playground uses the built output from dist/.
When errors occur or the UI shows stale/broken code: run npm run build separately to rebuild the library, then restart or refresh the playground. The playground does not auto-rebuild the library.
Alternative: build then run
npm run build
cd playground && npm run devBuild the library first, then run only the playground dev server.
