Skip to content

web3cloud-io/vesting-widget

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@web3cloud-io/vesting-widget

Embeddable token vesting widget SDK for Web3 applications. Easily integrate token vesting functionality into any web application with support for EVM (Ethereum, Polygon, etc.) and Solana ecosystems.

Installation

npm install @web3cloud-io/vesting-widget

Prerequisites

  • For EVM: A wallet provider with EIP-1193 interface (wagmi WalletClient, window.ethereum, ethers.js BrowserProvider, etc.)
  • For Solana: @solana/web3.js package and a wallet adapter (e.g., @solana/wallet-adapter-react)

Note: The SDK automatically imports @solana/web3.js Transaction classes when needed. You don't need to pass them manually.

Quick Integration Guide

Step 1: Install dependencies

# For EVM
npm install wagmi viem @rainbow-me/rainbowkit

# For Solana
npm install @solana/web3.js @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets

Step 2: Set up wallet providers

EVM (wagmi):

import { WagmiProvider } from 'wagmi';
import { RainbowKitProvider } from '@rainbow-me/rainbowkit';

// Configure your chains and providers
function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <RainbowKitProvider>
        <YourApp />
      </RainbowKitProvider>
    </WagmiProvider>
  );
}

Solana (wallet-adapter):

import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets';

const wallets = [new PhantomWalletAdapter(), new SolflareWalletAdapter()];

function App() {
  return (
    <ConnectionProvider endpoint="https://api.mainnet-beta.solana.com">
      <WalletProvider wallets={wallets} autoConnect>
        <WalletModalProvider>
          <YourApp />
        </WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
}

Step 3: Integrate the widget

See "React (Complete Example)" section below for full production-ready code.

Minimal setup:

import { createVestingWidget, createHostRpcFromProviders } from '@web3cloud-io/vesting-widget';
import { useWalletClient } from 'wagmi';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';

function MyComponent() {
  const { data: walletClient } = useWalletClient();
  const { connection, sendTransaction, wallet } = useWallet();

  const hostRpc = useMemo(() => createHostRpcFromProviders({
    evm: { walletClient },
    solana: {
      connection,
      sendTransaction,
      getPublicKey: () => wallet?.adapter?.publicKey ?? null,
    },
  }), [walletClient, connection, sendTransaction, wallet]);

  useEffect(() => {
    const widget = createVestingWidget({
      container: document.getElementById('vesting-root'),
      hostRpc,
      ecosystem: 'evm',
    });
    return () => widget.destroy();
  }, [hostRpc]);

  return <div id="vesting-root" />;
}

Quick Start

Embed URL

Production embed URL: https://embed.web3cloud.io

The widget loads from this URL by default. You can override it for development or custom deployments:

const widget = createVestingWidget({
  container: document.getElementById('vesting-root'),
  embedUrl: 'https://embed.web3cloud.io', // Production (default)
  // embedUrl: 'http://localhost:5173',  // Development
  hostRpc,
  ecosystem: 'evm',
});

What is hostRpc?

hostRpc is a function that handles wallet operations (signing transactions, getting accounts, etc.) for the widget. The SDK provides createHostRpcFromProviders() helper that creates this function from your wallet providers.

Minimal Example (EVM)

import { createVestingWidget, createHostRpcFromProviders } from '@web3cloud-io/vesting-widget';

// 1. Get your wallet client (example with window.ethereum)
const walletClient = window.ethereum; // or wagmi useWalletClient(), etc.

// 2. Create hostRpc function
const hostRpc = createHostRpcFromProviders({
  evm: { walletClient },
});

// 3. Create widget
const widget = createVestingWidget({
  container: document.getElementById('vesting-root'),
  embedUrl: 'https://embed.web3cloud.io', // Production embed URL
  hostRpc,
  ecosystem: 'evm',
});

// 4. Listen to events
widget.on('CLOSE', () => console.log('Widget closed'));
widget.on('VESTING_CREATED', (payload) => {
  console.log('Vesting created:', payload);
});

// 5. Cleanup when done
widget.destroy();

Minimal Example (Solana)

import { createVestingWidget, createHostRpcFromProviders } from '@web3cloud-io/vesting-widget';
import { Connection } from '@solana/web3.js';

// 1. Get your Solana wallet and connection
// Example with @solana/wallet-adapter-react:
const { connection } = useConnection();
const { sendTransaction, wallet } = useWallet();

// 2. Create hostRpc function
const hostRpc = createHostRpcFromProviders({
  solana: {
    connection,
    sendTransaction,
    getPublicKey: () => wallet?.adapter?.publicKey ?? null,
  },
});

// 3. Create widget
const widget = createVestingWidget({
  container: document.getElementById('vesting-root'),
  embedUrl: 'https://embed.web3cloud.io', // Production embed URL
  hostRpc,
  ecosystem: 'solana',
});

// 4. Notify widget when wallet changes
widget.notifyWalletChanged({
  ecosystem: 'solana',
  address: wallet?.adapter?.publicKey?.toBase58(),
});

API Reference

createVestingWidget(options)

Creates and embeds the vesting widget into your application.

Options

Option Type Required Description
container HTMLElement Yes DOM element to embed the widget into
hostRpc HostRpcFunction Yes Function to handle wallet RPC requests
ecosystem 'evm' | 'solana' Yes Blockchain ecosystem
embedUrl string No URL of the embed application (default: https://embed.web3cloud.io)
context EmbedContext No Configuration context for the widget
height number No Height of the iframe in pixels (default: 420)
className string No CSS class for the iframe
onClose () => void No Callback when widget closes

Returns VestingWidget

Method Description
on(event, handler) Subscribe to widget events
off(event, handler) Unsubscribe from widget events
notifyWalletChanged(data) Notify widget of wallet changes
destroy() Remove widget and cleanup resources

createHostRpcFromProviders(config)

Helper to create hostRpc from wallet providers. This is the recommended way to set up the SDK.

Config Parameters

EVM (evm):

  • walletClient (required): Any object with request() method that follows EIP-1193 standard
    • Examples: window.ethereum, wagmi WalletClient, ethers.js BrowserProvider wrapped in { request: (args) => provider.send(args.method, args.params) }

Solana (solana):

  • connection (required): Connection instance from @solana/web3.js
    • Example: new Connection('https://api.mainnet-beta.solana.com') or from useConnection() hook
  • sendTransaction (required): Function that sends a transaction to the network
    • Signature: (tx: Transaction | VersionedTransaction, connection: Connection, options?: SendTransactionOptions) => Promise<string>
    • Example: From useWallet().sendTransaction or wallet.sendTransaction
  • getPublicKey (required): Function that returns the current wallet's public key or null if not connected
    • Signature: () => PublicKey | null | undefined
    • Example: () => wallet?.adapter?.publicKey ?? null
  • signMessage (optional): Function for signing arbitrary messages
    • Signature: (message: Uint8Array) => Promise<Uint8Array>
    • Example: From useWallet().signMessage
const hostRpc = createHostRpcFromProviders({
  evm: {
    walletClient, // wagmi WalletClient, window.ethereum, or any EIP-1193 provider
  },
  solana: {
    connection,        // @solana/web3.js Connection
    sendTransaction,   // Function to send transactions
    getPublicKey,      // Function returning PublicKey or null
    signMessage,       // Optional: for signing messages
  },
});

Events

Event Payload Description
CLOSE - Widget requested to close
VESTING_CREATED VestingCreatedPayload Vesting stream(s) created successfully
SOLANA_CONNECT_REQUESTED - User needs to connect Solana wallet
EVM_CONNECT_REQUESTED - User needs to connect EVM wallet
NAVIGATE_BACK - User wants to navigate back in host app

Context Configuration

Initial Page

Open widget on a specific page:

const widget = createVestingWidget({
  // ...
  context: {
    initialPage: 'claim', // 'home', 'createVesting', 'projects', 'streams', etc.
  },
});

Page Parameters

For pages with route params (camelCase for both initialPage and pageParams keys):

const widget = createVestingWidget({
  // ...
  context: {
    initialPage: 'streamDetail',
    pageParams: {
      streamDetail: {
        streamId: '123',
        ecosystem: 'evm',
        network: 'sepolia',
        viewMode: 'view', // 'full' | 'view'
      },
    },
  },
});

// Batch detail example
const widget = createVestingWidget({
  // ...
  context: {
    initialPage: 'batchDetail',
    pageParams: {
      batchDetail: {
        ecosystem: 'evm',
        chainId: 11155111,
        batchId: '5',
      },
    },
  },
});

Custom Styling

Customize widget appearance:

const widget = createVestingWidget({
  // ...
  context: {
    styles: {
      colors: {
        primary: '#6366f1',
        background: '#0a0a0a',
        text: '#ffffff',
      },
      borderRadius: {
        medium: '12px',
      },
    },
  },
});

Framework Examples

React (Complete Example with wagmi + wallet-adapter)

Full production-ready example with both EVM and Solana support:

import { useEffect, useMemo, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
import { useWalletClient, useAccount, useChainId } from 'wagmi';
import {
  createVestingWidget,
  createHostRpcFromProviders,
  type HostRpcFunction,
  type VestingWidget
} from '@web3cloud-io/vesting-widget';

type Ecosystem = 'evm' | 'solana';

export default function VestingPlatform({ ecosystem = 'evm' }: { ecosystem?: Ecosystem }) {
  const navigate = useNavigate();
  const widgetRef = useRef<VestingWidget | null>(null);

  // ========== EVM Setup ==========
  const { data: walletClient } = useWalletClient();
  const { address: evmAddress } = useAccount();
  const chainId = useChainId();

  // ========== Solana Setup ==========
  const { sendTransaction, wallet, signMessage, publicKey: solanaPublicKey } = useWallet();
  const { connection } = useConnection();
  const { setVisible } = useWalletModal();

  // ========== Create hostRpc ==========
  // Recreated when providers change
  const hostRpc = useMemo(() => createHostRpcFromProviders({
    evm: { walletClient },
    solana: {
      connection,
      sendTransaction,
      getPublicKey: () => wallet?.adapter?.publicKey ?? null,
      signMessage: signMessage ? async (message: Uint8Array) => {
        const signature = await signMessage(message);
        return signature;
      } : undefined,
    },
  }), [connection, sendTransaction, wallet, walletClient, signMessage]);

  // ========== Stable reference pattern ==========
  // Prevents widget recreation when hostRpc changes
  const hostRpcRef = useRef(hostRpc);
  hostRpcRef.current = hostRpc;

  const stableHostRpc: HostRpcFunction = useCallback(async (request) => {
    return hostRpcRef.current(request);
  }, []);

  // ========== Create widget ==========
  useEffect(() => {
    const container = document.getElementById('vesting-root');
    if (!container) return;

    const widget = createVestingWidget({
      container,
      embedUrl: 'https://embed.web3cloud.io', // Production embed URL
      ecosystem,
      hostRpc: stableHostRpc,
      context: {
        showHeader: true,
        lockNavigation: false,
      },
      onClose: () => navigate('/'),
    });

    // ========== Event handlers ==========
    const handleClose = () => navigate('/');
    const handleSolanaConnectRequested = () => {
      console.log('Widget requested Solana wallet connection');
      setVisible(true); // Open wallet modal
    };
    const handleNavigateBack = () => {
      console.log('Widget requested navigation back');
      navigate('/');
    };

    widget.on('CLOSE', handleClose);
    widget.on('SOLANA_CONNECT_REQUESTED', handleSolanaConnectRequested);
    widget.on('NAVIGATE_BACK', handleNavigateBack);
    widget.on('VESTING_CREATED', (payload) => {
      console.log('Vesting created:', payload);
      // Track analytics, show notification, etc.
    });

    widgetRef.current = widget;

    return () => {
      widget.off('CLOSE', handleClose);
      widget.off('SOLANA_CONNECT_REQUESTED', handleSolanaConnectRequested);
      widget.off('NAVIGATE_BACK', handleNavigateBack);
      widget.destroy();
      widgetRef.current = null;
    };
  }, [ecosystem, stableHostRpc, setVisible, navigate]);

  // ========== Notify widget about wallet changes ==========
  
  // EVM wallet changes
  useEffect(() => {
    if (widgetRef.current && ecosystem === 'evm') {
      widgetRef.current.notifyWalletChanged({
        ecosystem: 'evm',
        address: evmAddress,
        chainId,
      });
    }
  }, [evmAddress, chainId, ecosystem]);

  // Solana wallet changes
  useEffect(() => {
    if (widgetRef.current && ecosystem === 'solana') {
      widgetRef.current.notifyWalletChanged({
        ecosystem: 'solana',
        address: solanaPublicKey?.toBase58(),
      });
    }
  }, [solanaPublicKey, ecosystem]);

  return <div id="vesting-root" style={{ width: '100%', minHeight: '600px' }} />;
}

Key points:

  • βœ… Stable hostRpc pattern: Uses useRef + useCallback to prevent widget recreation
  • βœ… Both ecosystems: Supports EVM and Solana with proper wallet change notifications
  • βœ… Event handling: Handles CLOSE, SOLANA_CONNECT_REQUESTED, NAVIGATE_BACK, VESTING_CREATED
  • βœ… Proper cleanup: Removes event listeners and destroys widget on unmount
  • βœ… Optional signMessage: Includes signMessage for Solana (optional but recommended)

Vue 3

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { createVestingWidget, createHostRpcFromProviders } from '@web3cloud-io/vesting-widget';

const container = ref<HTMLDivElement | null>(null);
let widget: any = null;

// Your wallet providers (from your Vue wallet setup)
const walletClient = computed(() => /* your EVM wallet client */);
const connection = computed(() => /* your Solana connection */);

const hostRpc = computed(() => createHostRpcFromProviders({
  evm: { walletClient: walletClient.value },
  solana: {
    connection: connection.value,
    sendTransaction: /* your sendTransaction */,
    getPublicKey: () => /* your getPublicKey */,
  },
}));

onMounted(() => {
  if (!container.value) return;
  
  widget = createVestingWidget({
    container: container.value,
    embedUrl: 'https://embed.web3cloud.io', // Production embed URL
    hostRpc: hostRpc.value,
    ecosystem: 'evm',
  });
});

onUnmounted(() => {
  widget?.destroy();
});
</script>

<template>
  <div ref="container" style="width: 100%; height: 600px;" />
</template>

Vanilla JavaScript

<div id="vesting-root"></div>

<script type="module">
import { createVestingWidget, createHostRpcFromProviders } from '@web3cloud-io/vesting-widget';
import { Connection } from '@solana/web3.js';

const hostRpc = createHostRpcFromProviders({
  evm: { walletClient: window.ethereum },
  solana: {
    connection: new Connection('https://api.mainnet-beta.solana.com'),
    sendTransaction: async (tx, conn) => window.solana.signAndSendTransaction(tx),
    getPublicKey: () => window.solana?.publicKey ?? null,
  },
});

const widget = createVestingWidget({
  container: document.getElementById('vesting-root'),
  embedUrl: 'https://embed.web3cloud.io', // Production embed URL
  hostRpc,
  ecosystem: 'evm',
  onClose: () => widget.destroy(),
});

widget.on('VESTING_CREATED', (payload) => {
  console.log('Vesting created:', payload);
});
</script>

TypeScript

All types are exported:

import type {
  VestingWidget,
  EmbedContext,
  VestingWidgetStyles,
  HostRpcFunction,
  RpcUnifiedRequest,
  VestingCreatedPayload,
  PageId,
  PageParams,
  SolanaHostConfig,
  EvmHostConfig,
} from '@web3cloud-io/vesting-widget';

Important Notes

Stable hostRpc Pattern (React)

Always use the stable reference pattern to prevent widget recreation:

// βœ… Correct: Stable reference
const hostRpcRef = useRef(hostRpc);
hostRpcRef.current = hostRpc;

const stableHostRpc = useCallback(async (request) => {
  return hostRpcRef.current(request);
}, []);

// ❌ Wrong: Direct usage causes widget recreation
useEffect(() => {
  const widget = createVestingWidget({
    hostRpc, // ← Widget recreates on every hostRpc change!
  });
}, [hostRpc]);

Wallet Change Notifications

Always notify the widget when wallet changes:

// EVM
useEffect(() => {
  if (widgetRef.current && ecosystem === 'evm') {
    widgetRef.current.notifyWalletChanged({
      ecosystem: 'evm',
      address: evmAddress,
      chainId,
    });
  }
}, [evmAddress, chainId, ecosystem]);

// Solana
useEffect(() => {
  if (widgetRef.current && ecosystem === 'solana') {
    widgetRef.current.notifyWalletChanged({
      ecosystem: 'solana',
      address: solanaPublicKey?.toBase58(),
    });
  }
}, [solanaPublicKey, ecosystem]);

Solana signMessage (Optional but Recommended)

Include signMessage for full functionality:

solana: {
  connection,
  sendTransaction,
  getPublicKey: () => wallet?.adapter?.publicKey ?? null,
  signMessage: signMessage ? async (message: Uint8Array) => {
    const signature = await signMessage(message);
    return signature;
  } : undefined,
}

Next.js / SSR Note

The widget only works on the client side (uses window, document). For Next.js:

'use client';

import { useEffect } from 'react';
import { createVestingWidget } from '@web3cloud-io/vesting-widget';

export default function Page() {
  useEffect(() => {
    // Widget only works in browser
    const widget = createVestingWidget({ ... });
    return () => widget.destroy();
  }, []);
}

Permit Signing: Web3Cloud vs Custom Backend

When creating vesting streams for a project (projectId > 0), the widget needs a cryptographic signature (permit) from the project manager. You have two options:

Option 1: Web3Cloud Backend (Recommended) βœ…

Web3Cloud acts as project manager. Permits are signed automatically.

Feature What you get
Permit signing Automatic β€” widget handles it
Whitelist Use @web3cloud-io/vesting-api SDK
Manager key Stored by Web3Cloud
signPermit in hostRpc Not needed
# Install Backend API SDK for whitelist management
npm install @web3cloud-io/vesting-api
import { ExternalApi, Configuration } from "@web3cloud-io/vesting-api";

const api = new ExternalApi(new Configuration({
  apiKey: "your-api-key",  // Get from project page
}));

// Manage who can create vestings
await api.addWhitelistedAddresses({
  addressToTokenAddresses: [
    { address: "0xUser...", tokenAddress: "0xToken..." },
  ]
});

// Widget handles permit signing automatically!

Option 2: Custom Backend (Enterprise) πŸ”

You are the project manager. Your backend signs permits.

Feature What you do
Permit signing You implement
Authorization Your custom logic
Manager key Your secure servers
signPermit in hostRpc Required

When to use:

  • Custom authorization rules (KYC, token balance checks, etc.)
  • Full audit control
  • Air-gapped security for manager key
  • No dependency on Web3Cloud

Permit Types

Type Ecosystem Description
evm-individual EVM EIP-712 signature for single stream
evm-batch EVM EIP-712 signature for batch of streams
solana-individual Solana Ed25519 signature for single stream
solana-batch Solana Ed25519 signature for batch

Request Structure

// signPermit request via hostRpc
{
  network: 'permit',
  action: 'signPermit',
  payload: {
    type: 'evm-individual' | 'evm-batch' | 'solana-individual' | 'solana-batch',
    projectId: '42',
    data: { /* stream data */ }
  }
}

EVM Individual Data

// data for type: 'evm-individual'
{
  creator: '0x742d35Cc...',           // Stream creator
  token: '0x3Cef0E71...',             // ERC-20 token
  beneficiary: '0xRecipient...',      // Beneficiary
  amount: '1000000000000000000',      // Wei (string)
  startTime: 1704067200,              // Unix timestamp
  endTime: 1735689600,                // Unix timestamp
  curveName: 'Linear',                // Curve type
  curveData: '0x',                    // Curve params (hex)
  beneficiaryName: 'Alice',           // Name
  cancellable: true,
  transferable: false,
  streamOwner: '0x000...000',         // Zero = creator
  nonce: '0',                         // Anti-replay (string)
  deadline: '1704153600',             // Permit deadline (string)
  chainId: '11155111',                // Chain ID (string)
  verifyingContract: '0xVesting...',  // Contract address
}

// Response: EIP-712 signature
return '0x...signature...'  // 65 bytes hex

EVM Batch Data

// data for type: 'evm-batch'
{
  creator: '0x742d35Cc...',
  token: '0x3Cef0E71...',
  beneficiaries: ['0xAddr1...', '0xAddr2...'],  // Array
  amounts: ['1000...', '2000...'],               // Array
  beneficiaryNames: ['Alice', 'Bob'],            // Array
  startTimes: ['1704067200', '1704067200'],      // Array (strings)
  endTimes: ['1735689600', '1735689600'],        // Array (strings)
  curveName: 'Linear',
  curveData: '0x',
  curveDataArray: ['0x', '0x'],                  // Per-beneficiary
  cancellable: [true, true],                     // Array
  transferable: [false, false],                  // Array
  streamOwners: ['0x...', '0x...'],              // Array
  nonce: '1',
  deadline: '1704153600',
  chainId: '11155111',
  verifyingContract: '0xVesting...',
}

// Response: EIP-712 signature
return '0x...signature...'

Solana Individual Data

// data for type: 'solana-individual'
{
  creator: '7xKXtg2CW87d97...',        // Creator pubkey
  tokenMint: '6eSFJze3fdHgiV...',      // SPL Token mint
  beneficiary: 'Bc5qRutWijDNq...',     // Beneficiary pubkey
  amount: '1000000000',                 // Smallest unit
  startTime: 1704067200,
  endTime: 1735689600,
  curveKind: { linear: {} },           // Anchor enum
  curveData: { none: {} },             // Anchor enum
  beneficiaryName: 'Alice',
  cancellable: true,
  transferable: false,
  nonce: '0',
  deadline: 1704153600,
  programId: 'VESTxyz...',             // Vesting program
  managerPublicKey: 'Manager...',      // Manager pubkey
}

// Response: Solana signature object
return {
  signatureHex: '0x...',
  signatureBase64: 'base64...',
  digestHex: '0x...',
  ed25519Instruction: { ... },
  managerPublicKey: 'Pubkey...'
}

Implementation Example

const hostRpc: HostRpcFunction = async (request) => {
  // Handle regular requests...
  if (request.network === 'evm') { /* ... */ }
  if (request.network === 'solana') { /* ... */ }
  
  // Handle signPermit (Enterprise only)
  if (request.network === 'permit' && request.action === 'signPermit') {
    const { type, projectId, data } = request.payload;
    
    // Call your backend
    const response = await fetch('https://your-backend.com/api/sign-permit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type, projectId, data }),
    });
    
    return response.json();  // Return signature
  }
};

Security Notes

  • Never expose manager private key in frontend code
  • Validate all data before signing
  • Use nonce to prevent replay attacks
  • Set short deadlines (1 hour recommended)
  • Authenticate requests on your backend

Troubleshooting

Widget doesn't update when wallet changes

Problem: Widget shows old wallet address after connecting/disconnecting.

Solution: Make sure you're calling notifyWalletChanged() when wallet changes (see "Wallet Change Notifications" above).

Widget recreates on every render

Problem: Widget is destroyed and recreated constantly.

Solution: Use the stable reference pattern (see "Stable hostRpc Pattern" above).

TypeScript error: "Missing Transaction, VersionedTransaction"

Problem: TypeScript complains about missing Transaction classes.

Solution: This is a caching issue. Rebuild the SDK:

cd sdk-vesting && npm run build

Then restart your TypeScript server in your IDE.

Solana wallet not connecting

Problem: Widget shows "Solana wallet not connected" error.

Solution:

  1. Make sure getPublicKey() returns a valid PublicKey when wallet is connected
  2. Listen to SOLANA_CONNECT_REQUESTED event and open wallet modal:
widget.on('SOLANA_CONNECT_REQUESTED', () => {
  setVisible(true); // Open wallet modal
});

License

MIT Β© Web3Cloud

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages