npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@web3cloud-io/vesting-widget

v1.0.3

Published

Embeddable token vesting widget SDK for Web3 applications

Downloads

584

Readme

@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