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

@stratosphere-network/dividends

v1.0.0

Published

Dividend management functionality for Strato SDK

Readme

@strato-sdk/dividends

This package provides dividend management functionality for the Strato SDK, allowing you to distribute ERC20 token dividends to holders either manually or via a smart contract.

Installation

npm install @strato-sdk/dividends

Usage

For backend (pass private key to class)

import { Dividends } from "@strato-sdk/dividends";
import type {
  Chain,
  DistributeDividendIn,
  DistributeDividendOut,
  DistibuteDividendOutOnChain,
  DistributeDivdendOnchainConfig,
} from "@strato-sdk/dividends";

// Example chain object
const chain: Chain = {
  id: 1,
  rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_KEY",
};

// Initialize with private key (for automatic signing)
const privateKey = "0x..." as `0x${string}`;
const dividends = new Dividends(chain, privateKey);

// Or initialize with address only (for unsigned transactions)
const address = "0x..." as `0x${string}`;
const dividendsUnsigned = new Dividends(chain, address);

// Define your indexer and business logic
const indexer = async (tokenAddress: string, chain: Chain) => [
  { address: "0x...", amount: "100" },
  // ... more holders, you can put your indexer logic here
];

const businessLogic = async ({
  holders,
  totalSupply,
  dividendAmount,
  tokenAddress,
  chain,
}) => {
  // Your logic to determine dividend portions
  return holders.map((holder) => ({
    address: holder.address,
    amount: (
      (BigInt(holder.amount) * BigInt(dividendAmount)) /
      BigInt(totalSupply)
    ).toString(),
  }));
};

// 1. Manual dividend distribution (direct ERC20 transfers)
const distributeDividendParams: DistributeDividendIn = {
  tokenAddress: "0x..." as `0x${string}`, // ERC20 token address
  stableCoinAddress: "0x..." as `0x${string}`, // ERC20 stablecoin address
  amount: 1000, // Amount to distribute (number)
  indexer,
  businessLogic,
};

const resultManual: DistributeDividendOut =
  await dividends.distributeDividendManual(distributeDividendParams);
console.log(resultManual);

// 2. On-chain dividend distribution via smart contract
const dividendContractAddress = "0x..." as `0x${string}`;
const config: DistributeDivdendOnchainConfig = {
  title: "Q4 2024 Dividend",
  directTransfer: true, // Automatically transfer stablecoin to contract, if you choose false, you should transfer amount of dividend to dividend smart contract first
};

const resultOnChain: DistibuteDividendOutOnChain =
  await dividends.distributeDividend(
    distributeDividendParams,
    dividendContractAddress,
    config
  );
console.log(resultOnChain);

// 3. Get dividend contract address from factory
const factoryAddress = "0x..." as `0x${string}`;
const tokenAddress = "0x..." as `0x${string}`;

const contractAddress: string = await dividends.getDividendContractAddress(
  factoryAddress,
  tokenAddress
);
console.log("Dividend contract address:", contractAddress);

For frontend (with wallet integration)

When integrating with frontend wallets like MetaMask, Rainbow, or Rabby, you should initialize the Dividends class with the wallet address only and handle transaction signing through the wallet interface.

import { Dividends } from "@strato-sdk/dividends";
import type {
  Chain,
  DistributeDividendIn,
  DistributeDividendOut,
  DistibuteDividendOutOnChain,
  DistributeDivdendOnchainConfig,
} from "@strato-sdk/dividends";
import { createWalletClient, custom, createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

// Frontend wallet integration
declare global {
  interface Window {
    ethereum?: any;
  }
}

// Connect to user's wallet
const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
});

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
});

// Get connected wallet address
const [account] = await walletClient.getAddresses();

// Initialize Dividends with wallet address (no private key)
const chain: Chain = { id: 1 };
const dividends = new Dividends(chain, account);

// Example: Manual dividend distribution with wallet signing
const distributeDividendParams: DistributeDividendIn = {
  tokenAddress: "0x..." as `0x${string}`,
  stableCoinAddress: "0x..." as `0x${string}`,
  amount: 1000,
  indexer: async (tokenAddress: string, chain: Chain) => {
    // Your indexer logic here
    return [
      { address: "0x...", amount: "100" },
      { address: "0x...", amount: "200" },
    ];
  },
  businessLogic: async ({ holders, totalSupply, dividendAmount }) => {
    return holders.map((holder) => ({
      address: holder.address,
      amount: (
        (BigInt(holder.amount) * BigInt(dividendAmount)) /
        BigInt(totalSupply)
      ).toString(),
    }));
  },
};

// Get unsigned transactions
const resultManual: DistributeDividendOut =
  await dividends.distributeDividendManual(distributeDividendParams);

// Sign each transaction with wallet
for (const transfer of resultManual) {
  if (transfer.type === "unsigned") {
    try {
      // Request user to sign transaction via wallet
      const txHash = await walletClient.writeContract({
        ...transfer.txUnsigned,
        account,
      });
      console.log(`Transfer to ${transfer.address} successful: ${txHash}`);
    } catch (error) {
      console.error(`Failed to transfer to ${transfer.address}:`, error);
    }
  }
}

// Example: On-chain dividend distribution with wallet signing
const dividendContractAddress = "0x..." as `0x${string}`;

// First, ensure the dividend contract has sufficient stablecoin balance
// You may need to transfer stablecoin to the contract first
const transferToContract = await walletClient.writeContract({
  address: distributeDividendParams.stableCoinAddress as `0x${string}`,
  abi: [
    {
      name: "transfer",
      type: "function",
      inputs: [
        { name: "to", type: "address" },
        { name: "amount", type: "uint256" },
      ],
      outputs: [{ name: "", type: "bool" }],
      stateMutability: "nonpayable",
    },
  ],
  functionName: "transfer",
  args: [dividendContractAddress, BigInt(1000 * 10 ** 6)], // Assuming 6 decimals
  account,
});

console.log("Transferred stablecoin to contract:", transferToContract);

// Get unsigned transaction for batch distribution
const resultOnChain: DistibuteDividendOutOnChain =
  await dividends.distributeDividend(
    distributeDividendParams,
    dividendContractAddress,
    {
      title: "Q4 2024 Dividend",
      directTransfer: false, // We manually transferred above
    }
  );

// Sign the batch transaction with wallet
if (resultOnChain.type === "unsigned") {
  try {
    const batchTxHash = await walletClient.writeContract({
      ...resultOnChain.txUnsigned,
      account,
    });
    console.log(`Batch dividend distributed: ${batchTxHash}`);
    console.log(`Recipients: ${resultOnChain.dividendPortion.length}`);
  } catch (error) {
    console.error("Failed to distribute dividend:", error);
  }
}

Indexer Integration with Ponder (GraphQL)

Ponder is a popular indexing framework for Ethereum data. Here's how to integrate it as an indexer function:

import { Dividends } from "@strato-sdk/dividends";
import type { Chain, AddressAmount } from "@strato-sdk/dividends";

// Ponder GraphQL indexer function
const ponderIndexer = async (
  tokenAddress: string,
  chain: Chain
): Promise<AddressAmount[]> => {
  // Your Ponder GraphQL endpoint
  const PONDER_GRAPHQL_URL = "https://your-ponder-instance.com/graphql";

  const query = `
    query GetTokenBalances($tokenAddress: String!) {
      tokenBalances(where: { tokenAddress: $tokenAddress }) {
        totalCount
        items {
          account {
            address
          }
          balance
        }
      }
    }
  `;

  try {
    const response = await fetch(PONDER_GRAPHQL_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        query,
        variables: {
          tokenAddress: tokenAddress.toLowerCase(),
        },
      }),
    });

    const result = await response.json();

    if (result.errors) {
      throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
    }

    // Transform Ponder response to expected format
    const holders: AddressAmount[] = result.data.tokenBalances.items
      .filter((item: any) => BigInt(item.balance) > 0n) // Only include holders with balance > 0
      .map((item: any) => ({
        address: item.account.address,
        amount: item.balance, // Keep as string
      }));

    console.log(`Found ${holders.length} token holders from Ponder`);
    return holders;
  } catch (error) {
    console.error("Error fetching from Ponder:", error);
    throw new Error(`Failed to fetch token holders: ${error.message}`);
  }
};

// Alternative: More advanced Ponder query with pagination
const ponderIndexerWithPagination = async (
  tokenAddress: string,
  chain: Chain
): Promise<AddressAmount[]> => {
  const PONDER_GRAPHQL_URL = "https://your-ponder-instance.com/graphql";
  let allHolders: AddressAmount[] = [];
  let hasNextPage = true;
  let cursor: string | null = null;
  const pageSize = 1000;

  while (hasNextPage) {
    const query = `
      query GetTokenBalances($tokenAddress: String!, $first: Int!, $after: String) {
        tokenBalances(
          where: { tokenAddress: $tokenAddress }
          first: $first
          after: $after
          orderBy: "balance"
          orderDirection: "desc"
        ) {
          totalCount
          pageInfo {
            hasNextPage
            endCursor
          }
          items {
            account {
              address
            }
            balance
          }
        }
      }
    `;

    try {
      const response = await fetch(PONDER_GRAPHQL_URL, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          query,
          variables: {
            tokenAddress: tokenAddress.toLowerCase(),
            first: pageSize,
            after: cursor,
          },
        }),
      });

      const result = await response.json();

      if (result.errors) {
        throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
      }

      const batchHolders = result.data.tokenBalances.items
        .filter((item: any) => BigInt(item.balance) > 0n)
        .map((item: any) => ({
          address: item.account.address,
          amount: item.balance,
        }));

      allHolders = [...allHolders, ...batchHolders];

      hasNextPage = result.data.tokenBalances.pageInfo.hasNextPage;
      cursor = result.data.tokenBalances.pageInfo.endCursor;

      console.log(
        `Fetched ${batchHolders.length} holders (total: ${allHolders.length})`
      );
    } catch (error) {
      console.error("Error fetching batch from Ponder:", error);
      throw new Error(`Failed to fetch token holders: ${error.message}`);
    }
  }

  return allHolders;
};

// Using the Ponder indexer with Dividends
const chain: Chain = { id: 1 };
const dividends = new Dividends(chain, "0x..." as `0x${string}`);

const distributeDividendParams: DistributeDividendIn = {
  tokenAddress: "0xffad8675234e4c7eacd78fc56b831fbd7fb26d17" as `0x${string}`,
  stableCoinAddress: "0x..." as `0x${string}`,
  amount: 1000,
  indexer: ponderIndexer, // Use Ponder as indexer
  businessLogic: async ({ holders, totalSupply, dividendAmount }) => {
    // Your dividend calculation logic
    return holders.map((holder) => ({
      address: holder.address,
      amount: (
        (BigInt(holder.amount) * BigInt(dividendAmount)) /
        BigInt(totalSupply)
      ).toString(),
    }));
  },
};

// Execute dividend distribution
const result = await dividends.distributeDividendManual(
  distributeDividendParams
);
console.log("Dividend distribution completed:", result);

Error Handling for Frontend Integration

import { DividendsError } from "@strato-sdk/dividends";

try {
  const result = await dividends.distributeDividend(/* ... */);
  // Handle success
} catch (error) {
  if (error instanceof DividendsError) {
    // Handle Dividends-specific errors
    console.error("Dividends error:", error.message);
    // Show user-friendly error message
  } else if (error.name === "UserRejectedRequestError") {
    // User rejected the transaction in wallet
    console.log("User cancelled the transaction");
  } else if (error.name === "InsufficientFundsError") {
    // Not enough ETH for gas or token balance
    console.error("Insufficient funds for transaction");
  } else {
    // Other wallet or network errors
    console.error("Transaction failed:", error);
  }
}

Features

  • Manual Distribution: Direct ERC20 transfers to token holders
  • On-chain Distribution: Batch distribution via smart contract
  • Factory Integration: Get dividend contract addresses from factory
  • Flexible Business Logic: Custom dividend calculation logic
  • Multi-chain Support: Works across multiple EVM-compatible chains
  • Signed/Unsigned Transactions: Support for both wallet integration and direct signing

API Reference

Dividends Class

Constructor

new Dividends(chain: Chain, privateKeyOrAddress: string)

Parameters:

  • chain: Chain - The blockchain network configuration
  • privateKeyOrAddress: string - Either a private key (for signing) or wallet address (for unsigned transactions), if you want to consume this on frontend side, please provide address of connected wallet, otherwise if you want to use it on backend / server please provide private key

Example:

// With private key (auto-signing)
const dividends = new Dividends(
  { id: 1, rpcUrl: "https://mainnet.infura.io/v3/..." },
  "0x1234..." as `0x${string}`
);

// With address only (unsigned transactions)
const dividends = new Dividends(
  { id: 1, rpcUrl: "https://mainnet.infura.io/v3/..." },
  "0xabcd..." as `0x${string}`
);

Methods

distributeDividendManual

Distributes dividends by sending individual ERC20 transfers directly to holders.

async distributeDividendManual(
  params: DistributeDividendIn
): Promise<DistributeDividendOut>

Parameters:

  • params: DistributeDividendIn - Distribution configuration

Returns:

  • Promise<DistributeDividendOut> - Array of transfer results with transaction details

Example:

const result: DistributeDividendOut = await dividends.distributeDividendManual({
  tokenAddress: "0x..." as `0x${string}`,
  stableCoinAddress: "0x..." as `0x${string}`,
  amount: 1000,
  indexer: async (tokenAddr, chain) => {
    // Return array of { address, amount }
    return [
      { address: "0x...", amount: "100" },
      { address: "0x...", amount: "200" },
    ];
  },
  businessLogic: async ({ holders, totalSupply, dividendAmount }) => {
    // Calculate dividend portions
    return holders.map((holder) => ({
      address: holder.address,
      amount: calculateDividend(holder.amount, totalSupply, dividendAmount),
    }));
  },
});

// Handle results
result.forEach((transfer) => {
  if (transfer.type === "signed") {
    console.log(`Transfer to ${transfer.address}: ${transfer.txHash}`);
  } else {
    // Sign the unsigned transaction
    const signedTx = await walletClient.writeContract({
      ...transfer.txUnsigned,
      account: myAccount,
    });
  }
});
distributeDividend

Distributes dividends via a smart contract in a single batch transaction.

async distributeDividend(
  params: DistributeDividendIn,
  dividendContractAddress: string,
  config?: DistributeDivdendOnchainConfig
): Promise<DistibuteDividendOutOnChain>

Parameters:

  • params: DistributeDividendIn - Distribution configuration
  • dividendContractAddress: string - Address of the deployed dividend contract
  • config?: DistributeDivdendOnchainConfig - Optional configuration
    • title?: string, title of dividend, if you skip this it will auto generated using timestamp of executed time
    • directTransfer?: boolean, if you input private key at constructor and give true on params, logic will auto transfer required amount of params.amount from your account (from privatekey) to dividend contract, otherwise you should make sure that dividend contract have sufficient amount of stablecoin to be distributed.

Returns:

  • Promise<DistibuteDividendOutOnChain> - Batch transaction result

Example:

const result: DistibuteDividendOutOnChain = await dividends.distributeDividend(
  {
    tokenAddress: "0x..." as `0x${string}`,
    stableCoinAddress: "0x..." as `0x${string}`,
    amount: 1000,
    indexer,
    businessLogic,
  },
  "0xDividendContract..." as `0x${string}`,
  {
    title: "Q4 2024 Dividend",
    directTransfer: true, // Auto-transfer stablecoin to contract
  }
);

if (result.type === "signed") {
  console.log(`Batch dividend distributed: ${result.txHash}`);
  console.log(`Recipients: ${result.dividendPortion.length}`);
} else {
  // Sign the unsigned transaction
  const signedTx = await walletClient.writeContract({
    ...result.txUnsigned,
    account: myAccount,
  });
}
getDividendContractAddress

Gets the dividend contract address for a specific token from a factory contract.

async getDividendContractAddress(
  factoryAddress: string,
  tokenAddress: string
): Promise<string>

Parameters:

  • factoryAddress: string - Address of the dividend factory contract
  • tokenAddress: string - Address of the ERC20 token

Returns:

  • Promise<string> - Address of the dividend contract for the token

Example:

const factoryAddress = "0xFactory..." as `0x${string}`;
const tokenAddress = "0xToken..." as `0x${string}`;

const dividendContract: string = await dividends.getDividendContractAddress(
  factoryAddress,
  tokenAddress
);

console.log(`Dividend contract for token: ${dividendContract}`);

// Use the contract address for distribution
const result = await dividends.distributeDividend(
  distributionParams,
  dividendContract as `0x${string}`,
  { title: "My Dividend" }
);

Types

Chain

interface Chain {
  id: number; // Chain ID (e.g., 1 for Ethereum mainnet)
  rpcUrl?: string; // Optional RPC URL (required for unsupported chains)
}

DistributeDividendIn

interface DistributeDividendIn {
  tokenAddress: string; // ERC20 token contract address
  stableCoinAddress: string; // ERC20 stablecoin contract address
  amount: number; // Total dividend amount to distribute
  businessLogic: (params: BusinessLogicParams) => Promise<AddressAmount[]>;
  indexer: (tokenAddress: string, chain: Chain) => Promise<AddressAmount[]>;
}

DistributeDividendOut

type DistributeDividendOut = FinishTransferAddressAmount[];

interface FinishTransferAddressAmount {
  address: string; // Recipient address
  amount: string; // Amount transferred
  type: "signed" | "unsigned"; // Transaction status
  txHash?: string; // Transaction hash (if signed)
  txUnsigned?: any; // Unsigned transaction data (if unsigned)
}

DistibuteDividendOutOnChain

interface DistibuteDividendOutOnChain {
  type: "signed" | "unsigned"; // Transaction status
  txHash?: string; // Transaction hash (if signed)
  txUnsigned?: any; // Unsigned transaction data (if unsigned)
  dividendPortion: AddressAmount[]; // Array of recipients and amounts
}

DistributeDivdendOnchainConfig

interface DistributeDivdendOnchainConfig {
  title?: string; // Optional dividend title/description
  directTransfer?: boolean; // Auto-transfer stablecoin to contract (requires private key)
}

Supported Chains

The package supports the following chains out of the box:

  • Ethereum Mainnet (1)
  • Polygon (137)
  • Base (8453)
  • Arbitrum (42161)
  • Optimism (10)
  • Base Sepolia (84532)
  • Berachain (288)
  • Berachain Bepolia (289)
  • Sepolia (1313161554)

For other chains, provide a custom rpcUrl in the chain configuration.

Error Handling

import { DividendsError } from "@strato-sdk/dividends";

try {
  const result = await dividends.distributeDividend(/* ... */);
} catch (error) {
  if (error instanceof DividendsError) {
    console.error("Dividends error:", error.message);
  } else {
    console.error("Unexpected error:", error);
  }
}

Running Unit Tests

To run the unit tests for this package, follow these steps:

  1. Clone the repository:

    git clone <repository-url>
    cd strato-sdk/packages/dividends
  2. Install dependencies:

    npm install
  3. Install and run anvil:

    • Anvil is a local Ethereum node used for testing. Install it if you haven't already:
      npm install -g anvil
    • Run anvil on port 13517:
      anvil --port 13517
  4. Prepare foundry and smart contracts artifacts:

    # Install Foundry
    curl -L https://foundry.paradigm.xyz | bash
    foundryup
    
    # Install OpenZeppelin contracts
    forge install OpenZeppelin/openzeppelin-contracts
    
    # Build contracts
    forge build
  5. Generate contract artifacts:

    ts-node scripts/generate-artifacts.ts
  6. Run the tests:

    npm test

    This will execute the unit tests located in the src/__tests__ directory.