A modern, embeddable cryptocurrency donation widget with cross-chain support powered by LiFi. Accept donations in any token on any supported chain, and receive them in your preferred token on your preferred chain.
- π Cross-chain support - Accept donations from Ethereum, Arbitrum, Polygon, BSC, Optimism, Base, and more
- π± Any token to any token - Automatic conversion via LiFi aggregation
- π¨ Fully customizable themes - Light, dark, auto, or completely custom color schemes
- π± Responsive design - Works perfectly on mobile and desktop
- π Easy integration - Simple HTML tag, no complex setup
- π― Event-driven API - Track donations, wallet connections, and errors
- π Web Components - Built with Lit, works with any framework or vanilla JavaScript
- π Secure - Uses WalletConnect/Reown for wallet connections
The easiest way to integrate the widget is to use the configurator:
- Visit the configurator (deployed from
www/) - Configure your widget (recipient address, chain, token, theme)
- Select a specific version or use "latest"
- Copy the generated embed code with the correct integrity hash
- Paste it into your website
The configurator automatically generates the complete embed code including:
- Versioned script URL with SRI integrity hash
- Widget configuration HTML
- All necessary security attributes (
crossorigin="anonymous")
If you prefer manual integration, you can embed a specific version directly:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<!-- Your donation widget -->
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>
<!-- Load specific version with SRI (recommended for production) -->
<script
src="https://cdn.donations.kalatori.org/donation-widget.v0.1.1.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
</body>
</html>Important:
- Replace
your-cdn-domain.comwith your actual CDN domain where the widget is hosted - Always include the
integrityattribute for security (get the hash fromversions.json) - The
crossorigin="anonymous"attribute is required for SRI to work
You can also download widget files directly from GitHub Releases:
- Go to the Releases page
- Download
donation-widget.v{version}.jsfrom the latest release - Download
donation-widget.v{version}.js.integrity.txtfor the SRI hash - Host the file on your own server or CDN
- Use the integrity hash in your embed code
# Clone the repository
git clone https://github.com/Kalapaja/donato.git
cd donation-widget
# Install dependencies with Deno
deno install
# Build the widget
deno task buildThe compiled widget will be in the dist/ directory.
The donation widget supports versioned deployments with cryptographic integrity verification through Subresource Integrity (SRI). This ensures both stability and security for your integration.
The widget uses a GitHub Release-based versioning system with automatic build and distribution:
How it works:
-
Widget Build & Release (Automated via GitHub Actions)
- When a Git tag is created (e.g.,
v0.0.0), GitHub Actions automatically triggers - The widget is built with Vite in production mode
- The bundle is renamed to
donation-widget.v{version}.js - A SHA-384 integrity hash is calculated and saved to a
.integrity.txtfile - Both files are uploaded as GitHub Release assets
- Release notes are automatically generated with embed code examples
- When a Git tag is created (e.g.,
-
WWW Build Process (Automated during deployment)
- The configurator website (
www/) fetches all releases from the GitHub API during build - Widget scripts and integrity files are downloaded from GitHub Release assets
- All versions are placed in the
www/public/directory for serving - A
versions.jsonmanifest is generated containing metadata for all versions - The manifest includes version numbers, file paths, integrity hashes, sizes, and release dates
- The configurator website (
-
Static File Serving (CDN/Static Host)
- All widget versions are served from the
www/public/directory - Versioned files get long-term cache headers (1 year, immutable)
- The
versions.jsonmanifest gets short-term cache (5 minutes) - CORS headers allow the widget to be embedded on any domain
- All widget versions are served from the
-
User Integration
- Users visit the configurator to select a version and generate embed code
- The configurator reads
versions.jsonto show available versions - Generated embed code includes the versioned URL and integrity hash
- Browsers verify the integrity hash before executing the script
Benefits of this architecture:
- β Immutable versions - Once published, versions never change
- β Automatic distribution - No manual deployment needed
- β Version discovery - Configurator always shows all available versions
- β Security - SRI ensures scripts haven't been tampered with
- β Performance - Long-term caching for optimal load times
Widget versioning allows you to:
- Lock to a specific version - Your integration remains stable even when new versions are released
- Control updates - Choose when to upgrade to new features or bug fixes
- Ensure compatibility - Test new versions before deploying to production
- Roll back easily - Return to a previous version if issues arise
All widget builds follow semantic versioning (MAJOR.MINOR.PATCH):
- MAJOR - Breaking changes that may require code updates
- MINOR - New features, backward compatible
- PATCH - Bug fixes, backward compatible
Subresource Integrity is a security feature that enables browsers to verify that files they fetch (such as the widget script) are delivered without unexpected manipulation.
How it works:
- A cryptographic hash (SHA-384) is calculated from the widget file during build
- This hash is included in the
integrityattribute of the script tag - When the browser loads the script, it recalculates the hash
- If the hashes match, the script is executed; if not, the browser blocks it
Benefits of SRI:
- π‘οΈ Security - Protects against compromised CDNs or man-in-the-middle attacks
- β Integrity - Guarantees the exact code you tested is what runs on user browsers
- π Trust - Users can verify the script hasn't been tampered with
- π Compliance - Meets security requirements for many organizations
The widget is available through two types of URLs:
<!-- Specific version with SRI -->
<script
src="https://your-cdn.com/donation-widget.v0.0.0.js"
integrity="sha384-7VZDmiHh/FiieJH3qmVUcQ+fXKmNcEUd1+LV7evvqlk9EJnENaN4C64/Asu2LXBB"
crossorigin="anonymous"
></script>Characteristics:
- β Immutable - Never changes once published
- β Long cache (1 year) - Excellent performance
- β SRI protection - Maximum security
- β Stable - No unexpected updates
- β Manual updates required
Best for:
- Production websites
- Critical integrations
- Applications requiring stability
- Security-conscious deployments
<!-- Always serves the latest version -->
<script
src="https://your-cdn.com/donation-widget.js"
integrity="sha384-7VZDmiHh/FiieJH3qmVUcQ+fXKmNcEUd1+LV7evvqlk9EJnENaN4C64/Asu2LXBB"
crossorigin="anonymous"
></script>Characteristics:
- β Auto-updates - Always get the latest features and fixes
- β SRI protection - Still secure
β οΈ Short cache (5 minutes) - Checks for updates frequentlyβ οΈ May introduce changes - Update integrity hash when version changes- β Less stable - Behavior may change
Best for:
- Development and testing
- Personal projects
- Getting started quickly
- Non-critical integrations
The versions.json manifest is automatically generated during the www build and contains metadata for all published widget versions.
Access the manifest:
- From the configurator domain:
https://your-cdn-domain.com/versions.json - This file is generated by the www build process from GitHub Releases
- Updated automatically when new versions are released and the www site is deployed
Example manifest structure:
{
"latest": "0.0.0",
"versions": {
"0.0.0": {
"file": "donation-widget.v0.0.0.js",
"integrity": "sha384-7VZDmiHh/FiieJH3qmVUcQ+fXKmNcEUd1+LV7evvqlk9EJnENaN4C64/Asu2LXBB",
"size": 2781344,
"date": "2025-11-07T13:36:05.372Z",
"releaseUrl": "https://github.com/Kalapaja/donato/releases/tag/v0.0.0"
}
}
}The manifest includes:
latest- The recommended stable versionfile- The filename in the public directoryintegrity- The SHA-384 hash for SRIsize- File size in bytesdate- Release publication datereleaseUrl- Link to the GitHub Release with full release notes
Select the version you want to use based on:
- latest field - The recommended stable version
- date - When the version was published
- size - Bundle size for performance considerations
Use the script tag with the correct version and integrity hash:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<!-- Your donation widget -->
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>
<!-- Load specific version with SRI -->
<script
src="https://your-cdn.com/donation-widget.v0.0.0.js"
integrity="sha384-7VZDmiHh/FiieJH3qmVUcQ+fXKmNcEUd1+LV7evvqlk9EJnENaN4C64/Asu2LXBB"
crossorigin="anonymous"
></script>
</body>
</html>Important: Always include both the integrity and crossorigin="anonymous" attributes for SRI to work properly.
If you're a maintainer creating a new widget version:
-
Update the version in
deno.json:{ "version": "0.2.0" } -
Commit your changes:
git add . git commit -m "Release v0.2.0"
-
Create and push a Git tag:
git tag v0.2.0 git push origin v0.2.0
-
GitHub Actions automatically:
- Builds the widget
- Calculates the integrity hash
- Creates a GitHub Release
- Uploads the versioned bundle and integrity file
- Generates release notes with embed code
-
The www site automatically:
- Fetches the new release during next deployment
- Downloads the widget files to
www/public/ - Updates the
versions.jsonmanifest - Makes the new version available in the configurator
Note: Git tags must follow the format vX.Y.Z (e.g., v0.0.0, v1.2.3).
The easiest way to generate embed code with the correct version and integrity hash is to use the interactive configurator (deployed from the www/ directory).
The configurator will:
- Display all available widget versions from
versions.json - Let you select a specific version or use "latest"
- Generate the complete embed code with the correct integrity hash
- Include all your configuration options (recipient, theme, etc.)
- Show version metadata (size, release date, release notes link)
When you're ready to update to a new version:
- Check the changelog - Review what changed in the new version
- Test in development - Use the new version in a test environment
- Update your embed code - Replace both the
srcURL andintegrityhash - Deploy - Push the updated code to production
Example update from v0.0.0 to v0.2.0:
<!-- Before: Version 0.0.0 -->
<script
src="https://your-cdn.com/donation-widget.v0.0.0.js"
integrity="sha384-OLD_HASH_HERE"
crossorigin="anonymous"
></script>
<!-- After: Version 0.2.0 -->
<script
src="https://your-cdn.com/donation-widget.v0.2.0.js"
integrity="sha384-NEW_HASH_HERE"
crossorigin="anonymous"
></script>Important: You must update BOTH the URL and the integrity hash together, or the script will fail to load.
Error: "Failed to find a valid digest in the 'integrity' attribute"
Causes:
- Mismatched integrity hash and script content
- Incorrect version URL
- Script modified in transit
Solutions:
- Verify you're using the correct integrity hash from versions.json
- Make sure the version in the URL matches the hash you're using
- Check that your CDN/server hasn't modified the file (compression, minification)
- Ensure CORS headers are properly configured with
Access-Control-Allow-Origin: *
Error: "Cross-Origin Request Blocked"
Solution: The script must be served with proper CORS headers. Ensure your server includes:
Access-Control-Allow-Origin: *
The crossorigin="anonymous" attribute in the script tag is required for SRI to work with CDN-hosted files.
- Always use SRI in production - Include the
integrityattribute - Use versioned URLs for stability - Pin to specific versions
- Verify hashes manually - When updating, check versions.json for the correct hash
- Serve over HTTPS - SRI only works with secure connections
- Monitor for updates - Subscribe to release notifications
- Test before deploying - Always test new versions in a staging environment
For maintainers and contributors creating new versions:
Development build (local testing):
# Regular development build (not versioned)
deno task build
# This creates:
# - dist/donation-widget.js (for local development)
# - dist/donation-widget.js.map (source map)Production release (versioned):
# 1. Update version in deno.json
# 2. Commit and create a Git tag
git tag v0.2.0
git push origin v0.2.0
# GitHub Actions will automatically:
# - Run: deno task build:release
# - Create dist/donation-widget.v0.2.0.js (versioned bundle)
# - Calculate SHA-384 hash and save to .integrity.txt
# - Upload both files to GitHub Release
# - Generate comprehensive release notesFor self-hosting (advanced):
If you want to host the widget on your own infrastructure:
- Download widget files from GitHub Releases
- Set up the www build process to fetch from your releases
- Configure your CDN/static host with proper cache headers
- Update the configurator to point to your CDN domain
See the www/scripts/fetch-releases.ts script for reference configuration.
The minimal setup requires these attributes:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>Important:
- Get your free Reown Project ID at https://reown.com
- Get your LiFi API key at https://li.fi
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
recipient-token-symbol="USDC"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="dark"
locale="en"
default-amount="25"
header-title="Support Us"
success-message="Thank you for your support!"
donate-again-text="Donate Again"
confetti-enabled="true"
confetti-colors="#ff6b6b,#4ecdc4,#45b7d1"
>
</donation-widget>| Attribute | Type | Description |
|---|---|---|
recipient |
string |
Ethereum address that will receive donations (must start with 0x) |
recipient-chain-id |
number |
Chain ID where you want to receive donations (e.g., 42161 = Arbitrum) |
recipient-token-address |
string |
Token address you want to receive (e.g., USDC on Arbitrum: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) |
reown-project-id |
string |
Your Reown project ID (Get one here) |
lifi-api-key |
string |
Your LiFi API key (Get one here) |
| Attribute | Type | Default | Description |
|---|---|---|---|
recipient-token-symbol |
string |
Auto-detected | Token symbol that recipient will receive (e.g., "USDC"). If not provided, will be looked up from chain data |
theme |
string |
auto |
Theme mode: light, dark, auto, or custom |
locale |
string |
Auto-detected | Language/locale for the widget (e.g., "en", "ru"). If not set, auto-detects from browser |
default-amount |
string |
"" |
Default donation amount to pre-fill (e.g., "25") |
header-title |
string |
"Donate" |
Header title text displayed next to the heart icon |
success-message |
string |
"Thank you for your donation!" |
Custom success message displayed after donation |
donate-again-text |
string |
"Donate Again" |
Custom text for the "donate again" button |
confetti-enabled |
boolean |
true |
Whether confetti animation is enabled |
confetti-colors |
string |
Theme-appropriate colors | Comma-separated list of hex colors for confetti (e.g., "#ff0000,#00ff00,#0000ff") |
| Chain | Chain ID |
|---|---|
| Ethereum | 1 |
| Arbitrum | 42161 |
| Polygon | 137 |
| BSC (Binance Smart Chain) | 56 |
| Optimism | 10 |
| Base | 8453 |
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="56"
recipient-token-address="0x55d398326f99059fF775485246999027B3197955"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>The widget supports four theme modes and full customization through CSS variables.
Automatically matches the user's system preference (light/dark mode):
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="auto"
>
</donation-widget><donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="light"
>
</donation-widget><donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="dark"
>
</donation-widget>Set theme="custom" and use CSS variables to create your own color scheme.
Note: Custom theme mode hides the theme toggle button.
| Variable | Description | Light Default | Dark Default |
|---|---|---|---|
--color-background |
Main background color | oklch(100% 0 0) |
oklch(16% 0 0) |
--color-foreground |
Main text color | oklch(14% 0 0) |
oklch(99% 0 0) |
--color-primary |
Primary accent color | oklch(17% 0 0) |
oklch(99% 0 0) |
--color-secondary |
Secondary background | oklch(96% 0 0) |
oklch(26% 0 0) |
--color-accent |
Accent elements | oklch(32% 0 0) |
oklch(68% 0 0) |
--color-border |
Border color | oklch(91% 0 0) |
oklch(30% 0 0) |
--color-muted |
Muted backgrounds | oklch(96% 0 0) |
oklch(22% 0 0) |
--color-muted-foreground |
Muted text | oklch(52% 0 0) |
oklch(68% 0 0) |
--radius |
Border radius | 1rem |
1rem |
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="custom"
style="--color-background: oklch(15% 0.05 280); --color-foreground: oklch(95% 0.15 320); --color-primary: oklch(75% 0.25 320); --color-secondary: oklch(70% 0.25 190); --color-accent: oklch(80% 0.25 340); --color-border: oklch(45% 0.15 280); --color-muted: oklch(25% 0.08 280); --color-muted-foreground: oklch(65% 0.12 190); --radius: 0.25rem"
>
</donation-widget><donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="custom"
style="--color-background: oklch(18% 0.08 230); --color-foreground: oklch(95% 0.08 200); --color-primary: oklch(65% 0.18 220); --color-secondary: oklch(35% 0.12 230); --color-accent: oklch(75% 0.2 200); --color-border: oklch(35% 0.1 230); --color-muted: oklch(25% 0.08 230); --color-muted-foreground: oklch(70% 0.1 210); --radius: 0.75rem"
>
</donation-widget><donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="custom"
style="--color-background: oklch(98% 0.02 150); --color-foreground: oklch(25% 0.08 145); --color-primary: oklch(50% 0.15 145); --color-secondary: oklch(88% 0.05 150); --color-accent: oklch(45% 0.18 140); --color-border: oklch(80% 0.04 150); --color-muted: oklch(93% 0.03 150); --color-muted-foreground: oklch(45% 0.08 145); --radius: 1rem"
>
</donation-widget><donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="custom"
style="--color-background: oklch(100% 0 0); --color-foreground: oklch(0% 0 0); --color-primary: oklch(20% 0 0); --color-secondary: oklch(95% 0 0); --color-accent: oklch(40% 0 0); --color-border: oklch(85% 0 0); --color-muted: oklch(97% 0 0); --color-muted-foreground: oklch(50% 0 0); --radius: 0rem"
>
</donation-widget>You can pre-fill the donation amount using the default-amount attribute. This is useful for donation links with suggested amounts.
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
default-amount="25"
>
</donation-widget>Create different pages with different suggested amounts:
<!-- Small donation page -->
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
default-amount="10"
header-title="Buy us a coffee β"
>
</donation-widget>
<!-- Large donation page -->
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
default-amount="100"
header-title="Become a Sponsor π"
>
</donation-widget>The widget supports multiple languages and automatically detects the user's browser language. You can also set the language explicitly using the locale attribute.
| Code | Language |
|---|---|
en |
English |
ru |
Russian |
By default, the widget automatically detects the user's preferred language from the browser:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>Force a specific language regardless of browser settings:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
locale="ru"
>
</donation-widget>You can customize the header title using the header-title attribute:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
header-title="Support Our Project"
>
</donation-widget>After a successful donation, the widget displays a celebratory success state with confetti animation and transaction details. You can customize all aspects of the success state to match your brand.
- Confetti Animation - Animated confetti particles celebrate the successful donation
- Transaction Summary - Shows amount, token, chain, and timestamp
- Donate Again Button - Quick way for users to make another donation
By default, the widget automatically shows a success state after a successful donation:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>The success state includes:
- Default success message: "Thank you for your donation!"
- Confetti animation (enabled by default)
- Transaction details with amount, token, chain, and timestamp
- "Donate Again" button
Customize the success message to match your brand voice:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
success-message="Thank you for your generous support! π"
>
</donation-widget>Change the button text to encourage repeat donations:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
donate-again-text="Make Another Donation"
>
</donation-widget>For a more subtle celebration, disable the confetti animation:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
confetti-enabled="false"
>
</donation-widget>Use your brand colors for the confetti animation. Provide a comma-separated list of hex colors:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
confetti-colors="#ff0000,#00ff00,#0000ff,#ffff00"
>
</donation-widget>Confetti Color Examples:
- Brand Colors:
"#ff6b6b,#4ecdc4,#45b7d1,#f7b731" - Rainbow:
"#ff0000,#ff7f00,#ffff00,#00ff00,#0000ff,#4b0082,#9400d3" - Monochrome:
"#000000,#333333,#666666,#999999" - Pastel:
"#ffb3ba,#ffdfba,#ffffba,#baffc9,#bae1ff"
Note: If no custom colors are provided, the widget uses theme-appropriate default colors that match your selected theme (light or dark).
Combine all customization options for a fully branded experience:
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="dark"
success-message="Thank you for supporting our mission! π"
donate-again-text="Support Us Again"
confetti-enabled="true"
confetti-colors="#ff6b6b,#4ecdc4,#45b7d1"
>
</donation-widget>- Automatic Display: The success state appears automatically after a successful transaction
- Transaction Details: Shows all relevant transaction information including amount, token, and chain
- Accessibility: Respects
prefers-reduced-motionmedia query for users who prefer reduced animations - Performance: Confetti animation automatically scales particle count based on viewport size for optimal performance
The widget emits custom events that you can listen to for tracking donations, wallet connections, and errors.
// Get widget element
const widget = document.getElementById("myWidget");
// Or using querySelector
const widget = document.querySelector("donation-widget");| Event Name | Description | Event Detail Properties |
|---|---|---|
donation-initiated |
Fired when user initiates a donation | { amount, token } |
donation-completed |
Fired when donation is successful | { amount, token, recipient } |
donation-failed |
Fired when donation fails | { error, code } |
wallet-connected |
Fired when wallet is connected | { address, chainId } |
wallet-disconnected |
Fired when wallet is disconnected | - |
network-switched |
Fired when user switches networks | { chainId } |
<donation-widget
id="myWidget"
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>
<script>
const widget = document.getElementById("myWidget");
// Listen for successful donations
widget.addEventListener("donation-completed", (event) => {
console.log("Donation completed!");
console.log("Amount:", event.detail.amount);
console.log("Token:", event.detail.token.symbol);
console.log("Recipient:", event.detail.recipient);
// Send to analytics
gtag("event", "donation", {
value: event.detail.amount,
currency: event.detail.token.symbol,
});
});
// Listen for failed donations
widget.addEventListener("donation-failed", (event) => {
console.error("Donation failed:", event.detail.error);
console.error("Error code:", event.detail.code);
// Show error to user
alert(`Donation failed: ${event.detail.error}`);
});
// Listen for wallet connections
widget.addEventListener("wallet-connected", (event) => {
console.log("Wallet connected:", event.detail.address);
console.log("Chain ID:", event.detail.chainId);
});
// Listen for wallet disconnections
widget.addEventListener("wallet-disconnected", () => {
console.log("Wallet disconnected");
});
// Listen for network changes
widget.addEventListener("network-switched", (event) => {
console.log("Network switched to chain:", event.detail.chainId);
});
</script><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Donation Widget with Event Tracking</title>
</head>
<body>
<donation-widget
id="donationWidget"
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>
<div id="eventLog"></div>
<script src="https://your-cdn.com/donation-widget.js"></script>
<script>
const widget = document.getElementById("donationWidget");
const eventLog = document.getElementById("eventLog");
function logEvent(eventName, data) {
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement("div");
entry.textContent = `[${timestamp}] ${eventName}: ${
JSON.stringify(data)
}`;
eventLog.appendChild(entry);
}
// Track all events
widget.addEventListener("donation-initiated", (e) => {
logEvent("donation-initiated", {
amount: e.detail.amount,
token: e.detail.token?.symbol,
});
});
widget.addEventListener("donation-completed", (e) => {
logEvent("donation-completed", {
amount: e.detail.amount,
token: e.detail.token?.symbol,
recipient: e.detail.recipient,
});
// Success notification
alert("Thank you for your donation! π");
});
widget.addEventListener("donation-failed", (e) => {
logEvent("donation-failed", {
error: e.detail.error,
code: e.detail.code,
});
});
widget.addEventListener("wallet-connected", (e) => {
logEvent("wallet-connected", {
address: e.detail.address,
chainId: e.detail.chainId,
});
});
widget.addEventListener("wallet-disconnected", () => {
logEvent("wallet-disconnected", {});
});
widget.addEventListener("network-switched", (e) => {
logEvent("network-switched", {
chainId: e.detail.chainId,
});
});
</script>
</body>
</html>You can control the widget programmatically using JavaScript.
Change the recipient address dynamically:
const widget = document.querySelector("donation-widget");
widget.setRecipient("0x1234567890123456789012345678901234567890");Change the theme programmatically:
const widget = document.querySelector("donation-widget");
widget.setTheme("dark");Get the current widget state:
const widget = document.querySelector("donation-widget");
const state = widget.getState();
console.log(state);
// {
// recipient: '0x...',
// theme: 'auto',
// selectedToken: { symbol: 'ETH', ... },
// recipientAmount: '10',
// isInitialized: true,
// isDonating: false,
// error: null
// }Reset the widget to its initial state:
const widget = document.querySelector("donation-widget");
widget.reset(); // Clears amount, selected token, and errorsYou can also control the widget by changing attributes:
const widget = document.querySelector("donation-widget");
// Change recipient
widget.setAttribute("recipient", "0x1234567890123456789012345678901234567890");
// Change theme
widget.setAttribute("theme", "dark");
// Change default token
// Remove attribute<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Widget Control Panel</title>
</head>
<body>
<div>
<h2>Control Panel</h2>
<label>
Recipient Address:
<input type="text" id="recipientInput" placeholder="0x...">
</label>
<label>
Theme:
<select id="themeSelect">
<option value="auto">Auto</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<button onclick="applyChanges()">Apply Changes</button>
<button onclick="resetWidget()">Reset Widget</button>
<button onclick="getWidgetState()">Get State</button>
</div>
<donation-widget
id="widget"
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
>
</donation-widget>
<script src="https://your-cdn.com/donation-widget.js"></script>
<script>
const widget = document.getElementById("widget");
function applyChanges() {
const recipient =
document.getElementById("recipientInput").value;
const theme = document.getElementById("themeSelect").value;
if (recipient) {
widget.setRecipient(recipient);
}
widget.setTheme(theme);
alert("Changes applied!");
}
function resetWidget() {
widget.reset();
alert("Widget reset!");
}
function getWidgetState() {
const state = widget.getState();
console.log("Widget State:", state);
alert(
`Widget State (check console):\n${
JSON.stringify(state, null, 2)
}`,
);
}
</script>
</body>
</html>import { useEffect, useRef } from "react";
import "donation-widget";
function DonationPage() {
const widgetRef = useRef(null);
useEffect(() => {
const widget = widgetRef.current;
const handleDonationCompleted = (event) => {
console.log("Donation completed!", event.detail);
// Track with your analytics
trackDonation(event.detail);
};
widget?.addEventListener("donation-completed", handleDonationCompleted);
return () => {
widget?.removeEventListener(
"donation-completed",
handleDonationCompleted,
);
};
}, []);
return (
<div>
<h1>Support Our Project</h1>
<donation-widget
ref={widgetRef}
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="dark"
/>
</div>
);
}
export default DonationPage;<template>
<div>
<h1>Support Our Project</h1>
<donation-widget
ref="widgetRef"
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain="42161"
recipient-token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
project-id="YOUR_PROJECT_ID"
api-key="YOUR_API_KEY"
:theme="theme"
@donation-completed="handleDonationCompleted"
/>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import 'donation-widget';
const widgetRef = ref(null);
const theme = ref('auto');
const handleDonationCompleted = (event) => {
console.log('Donation completed!', event.detail);
};
onMounted(() => {
const widget = widgetRef.value;
widget?.addEventListener('donation-completed', handleDonationCompleted);
});
onUnmounted(() => {
const widget = widgetRef.value;
widget?.removeEventListener('donation-completed', handleDonationCompleted);
});
</script><script>
import { onMount } from 'svelte';
import 'donation-widget';
let widgetElement;
onMount(() => {
widgetElement.addEventListener('donation-completed', (event) => {
console.log('Donation completed!', event.detail);
});
});
</script>
<h1>Support Our Project</h1>
<donation-widget
bind:this={widgetElement}
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="dark"
/>"use client";
import { useEffect, useRef } from "react";
export default function DonationPage() {
const widgetRef = useRef(null);
useEffect(() => {
// Import widget only on client side
import("donation-widget");
const widget = widgetRef.current;
const handleDonationCompleted = (event) => {
console.log("Donation completed!", event.detail);
};
widget?.addEventListener("donation-completed", handleDonationCompleted);
return () => {
widget?.removeEventListener(
"donation-completed",
handleDonationCompleted,
);
};
}, []);
return (
<div>
<h1>Support Our Project</h1>
<donation-widget
ref={widgetRef}
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="dark"
/>
</div>
);
}Add to your theme or use a custom HTML block:
<!-- Add to header.php or in a custom HTML block -->
<script src="https://your-cdn.com/donation-widget.js"></script>
<!-- Add widget anywhere in your content -->
<donation-widget
recipient="0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
recipient-chain-id="42161"
recipient-token-address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
reown-project-id="YOUR_REOWN_PROJECT_ID"
lifi-api-key="YOUR_LIFI_API_KEY"
theme="auto"
>
</donation-widget>- Deno 2.5.4
# Clone the repository
git clone https://github.com/Kalapaja/donato.git
cd donato
# Install dependencies
deno install
# Start development server
deno task dev# Build for production
deno task build
# Preview production build
deno task previewdonation-widget/
βββ src/
β βββ components/ # Web components
β β βββ donation-widget.ts
β β βββ wallet-section.ts
β β βββ donation-form.ts
β β βββ ...
β βββ services/ # Business logic
β β βββ WalletService.ts
β β βββ LiFiService.ts
β β βββ ...
β βββ index.ts # Main entry point
βββ examples/ # Example usage
β βββ javascript-api.html
β βββ themes.html
βββ dist/ # Built files (generated)
βββ vite.config.ts # Vite configuration
βββ deno.json # Deno configuration
The main widget component.
Tag name: <donation-widget>
Attributes:
Required:
recipient- Recipient wallet addressrecipient-chain-id- Chain ID for receivingrecipient-token-address- Token address to receivereown-project-id- Reown project IDlifi-api-key- LiFi API key
Optional:
recipient-token-symbol- Token symbol that recipient will receive (e.g., "USDC"). If not provided, will be looked up from chain datatheme- Theme mode: 'light', 'dark', 'auto', or 'custom' (default: 'auto')locale- Language/locale for the widget (e.g., "en", "ru"). If not set, auto-detects from browserdefault-amount- Default donation amount to pre-fill (e.g., "25")header-title- Header title text displayed next to the heart icon (default: uses i18n)success-message- Custom success message displayed after donation (default: "Thank you for your donation!")donate-again-text- Custom text for the "donate again" button (default: "Donate Again")confetti-enabled- Whether confetti animation is enabled (default: true)confetti-colors- Comma-separated list of hex colors for confetti (e.g., "#ff0000,#00ff00,#0000ff")
Events:
donation-initiated- User started donation processdonation-completed- Donation successfuldonation-failed- Donation failedwallet-connected- Wallet connectedwallet-disconnected- Wallet disconnectednetwork-switched- Network changed
Methods:
setRecipient(address: string)- Change recipientsetTheme(theme: ThemeMode)- Change themegetState()- Get current statereset()- Reset widget
- The widget never has access to user private keys
- All wallet interactions use WalletConnect/Reown protocol
- Cross-chain swaps are executed through LiFi's audited smart contracts
- No personal data is collected or stored
Contributions are welcome! Please read our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- π§ Email: [email protected]
Made with β€οΈ