A Svelte component library for managing multi-step blockchain transactions with a beautiful, user-friendly interface.
Note: This modal is a UI-only component. The parent is responsible for executing transactions and managing their statuses.
npm install web3-transaction-manager
The parent component is responsible for:
The modal:
<script>
import { TransactionModal } from 'web3-transaction-manager';
import { ethers } from 'ethers';
import { writable } from 'svelte/store';
let isOpen = false;
let signer; // Your ethers.js signer
let address; // User's wallet address
// Define your transactions
const transactions = [
{
id: 'approve',
type: 'approval',
params: {
to: '0xTokenAddress...',
data: '0xEncodedData...'
},
metadata: {
title: 'Approve USDC',
buttonLabel: 'Approve'
}
},
{
id: 'borrow',
type: 'contract',
params: {
to: '0xContractAddress...',
data: '0xEncodedData...'
},
metadata: {
title: 'Borrow 1000 USDC',
buttonLabel: 'Borrow'
}
}
];
// Track transaction statuses in a Svelte store
let transactionStatuses = writable([
{ id: 'approve', status: 'pending' },
{ id: 'borrow', status: 'pending' }
]);
// Example: Parent executes transactions and updates statuses
async function executeTransactions() {
for (const tx of transactions) {
transactionStatuses.update(arr => arr.map(t => t.id === tx.id ? { ...t, status: 'processing' } : t));
try {
// Example: send transaction using ethers.js
// await signer.sendTransaction({ to: tx.params.to, data: tx.params.data });
transactionStatuses.update(arr => arr.map(t => t.id === tx.id ? { ...t, status: 'success' } : t));
} catch (e) {
transactionStatuses.update(arr => arr.map(t => t.id === tx.id ? { ...t, status: 'failed' } : t));
}
}
}
</script>
<button on:click={() => { isOpen = true; executeTransactions(); }}>
Start Transaction Flow
</button>
<TransactionModal
{isOpen}
{transactions}
{signer}
{address}
transactionStatuses={$transactionStatuses}
theme="light"
title="Borrow 1000 USDC"
subtitle="Variable Rolling Rate"
redirectUrl="/positions"
socialLinks=[
{ label: 'Twitter', url: 'https://twitter.com/your-handle' },
{ label: 'Discord', url: 'https://discord.gg/your-server' }
]
supportChannelUrl="https://t.me/your-support"
customTheme={{
light: {
primary: '#4F7FFF',
success: '#10B981',
error: '#DC2626',
text: '#111827',
background: '#FFFFFF',
border: '#E5E7EB',
disabled: '#9CA3AF',
hover: '#3B82F6',
card: '#F7F7FA',
buttonPrimary: '#4F7FFF',
buttonPrimaryText: '#FFFFFF',
buttonDisabled: 'rgba(79,127,255,0.1)',
buttonDisabledText: '#4F7FFF',
buttonError: '#DC2626',
buttonErrorText: '#FFFFFF',
buttonSuccess: '#FFFFFF',
buttonSuccessText: '#64748B',
buttonProcessing: '#4F7FFF',
buttonProcessingText: '#FFFFFF',
buttonHover: '#3B82F6'
}
// Add dark theme if needed
}}
on:close={() => isOpen = false}
/>
Prop | Type | Description |
---|---|---|
transactions |
Transaction[] |
Array of transactions to display |
signer |
ethers.Signer |
Ethers.js signer instance |
address |
string |
User's wallet address |
Prop | Type | Default | Description |
---|---|---|---|
isOpen |
boolean |
false |
Controls modal visibility |
theme |
'light' | 'dark' |
'light' |
UI theme |
title |
string |
Modal title | |
subtitle |
string |
Modal subtitle | |
redirectUrl |
string |
'#' |
URL for redirect after success |
socialLinks |
Array<{label, url}> |
[] |
Social media links |
supportChannelUrl |
string |
Support channel URL | |
customTheme |
Partial<ThemeConfig> |
{} |
Custom theme configuration |
transactionStatuses |
Array<{id, status}> |
Status for each transaction | |
closeOnOverlayClick |
boolean |
false |
Whether clicking overlay closes modal |
successMessage |
string |
Success screen message | |
redirectMessage |
string |
Text for redirect link in success message | |
showHelpSection |
boolean |
true |
Show help/feedback section |
helpMessage |
string |
Help section message | |
helpRedirectText |
string |
Help section link text | |
showFinalSuccessScreen |
boolean |
true |
Show success screen after completion |
Event | Description |
---|---|
close |
Dispatched when the modal is closed |
... | (Add any other events your modal emits) |
interface Transaction {
id: string;
type: 'approval' | 'contract' | 'standard';
params: {
to: string;
data: string;
value?: string;
};
metadata: {
title: string;
buttonLabel: string;
};
}
type TransactionStatus = 'pending' | 'processing' | 'success' | 'failed';
customTheme
prop..web3-tx-modal
.customTheme={{
light: {
primary: '#4F7FFF',
// ...see full example above
},
dark: {
// ...your dark theme colors
}
}}
role="button"
.To run the preview and test the TransactionModal component locally:
npm install
npm run dev
This will start a local development server and open the preview page at the root URL (http://localhost:5173 or similar). The preview page allows you to interactively test and customize the TransactionModal component and theme.
If you want to batch multiple contract calls into a single transaction (multicall), you must encode the multicall before passing it to the transaction flow. The modal does not need to know it is a multicall; it simply sends the transaction as specified.
Example:
// Prepare individual calls
const call1 = contract.interface.encodeFunctionData('doSomething', [arg1, arg2]);
const call2 = contract.interface.encodeFunctionData('doAnotherThing', [arg3]);
// Encode multicall
const multicallData = contract.interface.encodeFunctionData('multicall', [[call1, call2]]);
// Pass to the transaction flow
{
to: contract.address,
data: multicallData,
value: 0
}
The modal will submit this as a single transaction. The contract will execute all batched actions.
MIT
You can now customize font sizes, font family, and text colors for all modal elements via the customTheme
prop. All values are optional and will fall back to sensible defaults.
Variable | Default (Light) | Default (Dark) | Description |
---|---|---|---|
fontFamily | inherit | inherit | Font family for all modal text |
titleFontSize | 24px | 24px | Modal title font size |
titleColor | #000000 | #FFFFFF | Modal title color |
subtitleFontSize | 18px | 18px | Modal subtitle font size |
subtitleColor | #555F81 | #AAB8D1 | Modal subtitle color |
metadataTitleFontSize | 15px | 15px | Metadata title font size |
metadataTitleColor | #000000 | #FFFFFF | Metadata title color |
metadataButtonLabelFontSize | 14px | 14px | Button label font size |
metadataButtonLabelColor | #FFFFFF | #FFFFFF | Button label color (default) |
metadataButtonLabelSuccessColor | #000000 | #2B51E8 | Button label color (success) |
helpTextFontSize | 12px | 12px | Help/need help text font size |
helpTextColor | #555F81 | #AAB8D1 | Help/need help text color |
helpRedirectFontSize | 12px | 12px | Help redirect link font size |
helpRedirectColor | #555F81 | #AAB8D1 | Help redirect link color |
helpRedirectHoverColor | #2e54e8 | #6C8CFF | Help redirect link hover color |
socialLinkFontSize | 15px | 15px | Social link text font size |
socialLinkColor | #000000 | #FFFFFF | Social link text color |
socialLinkButtonBackground | #FFFFFF | #232946 | Social link button background color |
successMessageFontSize | 18px | 18px | Success message font size |
successMessageColor | #555F81 | #AAB8D1 | Success message text color |
successRedirectColor | #2e54e8 | #6C8CFF | Success redirect link color |
successRedirectHoverColor | #2e54e8 | #AAB8D1 | Success redirect link hover color |
modalBackground | rgba(62,124,255,0.3) | rgba(43,81,232,0.3) | Modal background color |
primaryActionButtonBackground | #2B51E8 | #6C8CFF | Primary action button background |
primarySuccessButtonBackground | #FFFFFF | #232946 | Success button background |
disabledButtonBackground | #BDC9F8 | #3A4668 | Disabled button background |
<TransactionModal
customTheme={{
light: {
fontFamily: 'Inter, sans-serif',
titleFontSize: '28px',
titleColor: '#222222',
subtitleFontSize: '20px',
subtitleColor: '#888888',
// ...override any other variables as needed
},
dark: {
// ...dark theme overrides
}
}}
/>
You can now include both on-chain (contract/approval) and off-chain (fetch/REST/database) steps in your transaction flow. This allows you to mix web3 contract calls with backend/database requests, all with status tracking and retry support.
approval
: ERC20 approve or similar contract callcontract
: Any smart contract callfetch
: Off-chain HTTP(S) request (e.g., REST API, database)standard
: ETH transfer or other simple transactionexport type TransactionType = 'approval' | 'contract' | 'fetch' | 'standard';
export interface Transaction {
id: string;
type: TransactionType;
params: {
// For contract/approval
to?: string;
data?: string;
value?: string;
// For fetch
url?: string;
method?: string;
body?: any;
headers?: Record<string, string>;
};
metadata: {
title: string;
buttonLabel: string;
description?: string;
};
}
const transactions = [
{
id: 'approve',
type: 'approval',
params: { /* ... */ },
metadata: { title: 'Approve', buttonLabel: 'Approve' }
},
{
id: 'deposit',
type: 'contract',
params: { /* ... */ },
metadata: { title: 'Deposit', buttonLabel: 'Deposit' }
},
{
id: 'notifyBE',
type: 'fetch',
params: {
url: 'https://api.example.com/notify',
method: 'POST',
body: { user: '0x...', action: 'deposit' }
},
metadata: { title: 'Notify Backend', buttonLabel: 'Notify' }
},
{
id: 'borrow',
type: 'contract',
params: { /* ... */ },
metadata: { title: 'Borrow', buttonLabel: 'Borrow' }
}
];
In your parent execution logic, check the type
and execute accordingly (see previous instructions for code sample).