Skip to main content
The Wallet API is in Beta. Endpoints and response formats may change.

Overview

The Wallet Funding Source endpoint identifies who originally funded a Solana wallet by analyzing its first incoming SOL transfer. It is valuable for attribution, compliance, understanding wallet relationships, and identifying exchange-funded wallets. The funder’s name and category come from the same identity system used by the Identity endpoint, so when the funder is a known entity you get a human-readable label and category directly in the response.

When to use this

Use the Wallet Funding Source API for:
  • Wallet attribution: track where new wallets are being funded from.
  • Exchange detection: identify wallets funded directly from centralized exchanges.
  • Compliance and AML: flag wallets funded by known entities for compliance checks.
  • Bot detection: identify bot farms funded from the same source.
  • Airdrop analysis: track which wallets received initial funding from a project.
  • Sybil detection: find clusters of wallets funded by the same address.

Quickstart

Basic funding lookup

Find out who funded a wallet:
const getWalletFundingSource = async (address) => {
  const url = `https://api.helius.xyz/v1/wallet/${address}/funded-by?api-key=YOUR_API_KEY`;

  const response = await fetch(url);

  if (response.status === 404) {
    console.log('No funding transaction found for this wallet');
    return null;
  }

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const funding = await response.json();

  console.log(`Funding Source: ${funding.funderName || funding.funder}`);
  console.log(`Funder Type: ${funding.funderType || 'Unknown'}`);
  console.log(`Initial Amount: ${funding.amount} SOL`);
  console.log(`Date: ${new Date(funding.timestamp * 1000).toLocaleString()}`);
  console.log(`Transaction: ${funding.explorerUrl}`);

  return funding;
};

getWalletFundingSource("86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY");

Response format

A successful response describes the wallet’s first incoming SOL transfer:
{
  "funder": "26MAyPNpK4At8LgRECMMbgiKQuJyg3oACtw1Q9FRyuba",
  "funderName": null,
  "funderType": null,
  "mint": "So11111111111111111111111111111111111111111",
  "symbol": "SOL",
  "amount": 0.09811972,
  "amountRaw": "98119720",
  "decimals": 9,
  "date": "2022-01-19T20:46:34.000Z",
  "signature": "5WX9C5kCQNULGGrSHJBR1WDFyetVyekbUpe1KQ45p3zEBe6jVgSsJuMqLWijjTDcnaAK2518ZriktRMCNycnsNAG",
  "timestamp": 1642625194,
  "slot": 116984883,
  "explorerUrl": "https://orbmarkets.io/tx/5WX9C5kCQNULGGrSHJBR1WDFyetVyekbUpe1KQ45p3zEBe6jVgSsJuMqLWijjTDcnaAK2518ZriktRMCNycnsNAG?tab=summary"
}
If a wallet has never received SOL, the API returns a 404:
{
  "error": "No funding transaction found",
  "code": 404
}

Field notes

  • funder: the address that sent the first SOL transfer to this wallet.
  • funderName: human-readable name if the funder is a known entity (e.g., exchange, protocol); null otherwise.
  • funderType: category of the funder (e.g., exchange, defi-protocol); null if not in the identity database.
  • mint: token mint address (So11111111111111111111111111111111111111111 for SOL).
  • symbol: token symbol (always SOL for funding transactions).
  • amount: initial SOL amount received (human-readable, e.g., 0.05 SOL).
  • amountRaw: raw amount in lamports as a string (e.g., "50000000" for 0.05 SOL).
  • decimals: number of decimal places for the token (9 for SOL).
  • date: ISO 8601 formatted date string (e.g., "2024-01-01T00:00:00.000Z").
  • signature: transaction signature of the funding transfer.
  • timestamp: Unix timestamp (in seconds) when the wallet was funded.
  • slot: Solana slot number when the funding transaction was confirmed.
  • explorerUrl: direct link to view the transaction on Orb.

Use cases

Detect exchange-funded wallets

Identify wallets funded directly from centralized exchanges:
const isExchangeFunded = async (address) => {
  try {
    const funding = await getWalletFundingSource(address);

    if (!funding) {
      console.log('Wallet has no funding transaction');
      return false;
    }

    if (funding.funderType === 'exchange') {
      console.log(`Wallet was funded by ${funding.funderName}`);
      console.log(`This is likely a retail user withdrawing from an exchange`);
      return true;
    }

    console.log(`Wallet was not funded by an exchange`);
    return false;

  } catch (error) {
    console.error('Error checking funding source:', error);
    return false;
  }
};

isExchangeFunded("86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY");

Find wallet clusters (Sybil detection)

Identify groups of wallets funded by the same source:
const findWalletClusters = async (walletAddresses) => {
  const fundingData = await Promise.all(
    walletAddresses.map(async address => {
      try {
        const funding = await getWalletFundingSource(address);
        return { address, funder: funding?.funder };
      } catch {
        return { address, funder: null };
      }
    })
  );

  // Group by funder
  const clusters = {};

  fundingData.forEach(({ address, funder }) => {
    if (funder) {
      if (!clusters[funder]) {
        clusters[funder] = [];
      }
      clusters[funder].push(address);
    }
  });

  // Report clusters
  Object.entries(clusters).forEach(([funder, wallets]) => {
    if (wallets.length > 1) {
      console.log(`\nFound cluster: ${wallets.length} wallets funded by ${funder.slice(0, 8)}...`);
      wallets.forEach(wallet => console.log(`  - ${wallet}`));
    }
  });

  return clusters;
};

// Example: Check list of wallets for clusters
const suspiciousWallets = [
  "Wallet1...",
  "Wallet2...",
  "Wallet3..."
];

findWalletClusters(suspiciousWallets);

Track airdrop recipients

Analyze where airdrop recipients came from:
const analyzeAirdropRecipients = async (airdropWallets) => {
  const fundingSources = await Promise.all(
    airdropWallets.map(async address => {
      try {
        return await getWalletFundingSource(address);
      } catch {
        return null;
      }
    })
  );

  const stats = {
    total: airdropWallets.length,
    exchangeFunded: 0,
    unknown: 0,
    byExchange: {}
  };

  fundingSources.forEach(funding => {
    if (!funding) {
      stats.unknown++;
      return;
    }

    if (funding.funderType === 'exchange') {
      stats.exchangeFunded++;
      const exchange = funding.funderName || 'Unknown Exchange';
      stats.byExchange[exchange] = (stats.byExchange[exchange] || 0) + 1;
    }
  });

  console.log('Airdrop Recipient Analysis:');
  console.log(`Total Recipients: ${stats.total}`);
  console.log(`Exchange-Funded: ${stats.exchangeFunded} (${(stats.exchangeFunded / stats.total * 100).toFixed(1)}%)`);
  console.log(`Unknown Source: ${stats.unknown}`);
  console.log('\nBy Exchange:');
  Object.entries(stats.byExchange).forEach(([exchange, count]) => {
    console.log(`  ${exchange}: ${count}`);
  });

  return stats;
};

Build a wallet timeline

Create a timeline starting from the wallet’s creation:
const buildWalletTimeline = async (address) => {
  const funding = await getWalletFundingSource(address);

  if (!funding) {
    console.log('No funding data available');
    return null;
  }

  const creationDate = new Date(funding.timestamp * 1000);
  const ageInDays = Math.floor((Date.now() - creationDate.getTime()) / (1000 * 60 * 60 * 24));

  console.log('Wallet Timeline:');
  console.log(`Created: ${creationDate.toLocaleString()} (${ageInDays} days ago)`);
  console.log(`Initial Funding: ${funding.amount} SOL`);
  console.log(`Funded By: ${funding.funderName || funding.funder.slice(0, 8) + '...'}`);

  if (funding.funderType === 'exchange') {
    console.log(`This wallet was likely created by withdrawing from ${funding.funderName}`);
  }

  return {
    creationDate,
    ageInDays,
    initialFunding: funding.amount,
    fundedBy: funding.funderName || funding.funder
  };
};

Compliance risk scoring

Assign risk scores based on funding source:
const assessWalletRisk = async (address) => {
  const funding = await getWalletFundingSource(address);

  if (!funding) {
    return { riskLevel: 'UNKNOWN', score: 50, reasons: ['No funding data available'] };
  }

  let score = 0;
  let reasons = [];

  // Low risk: Funded by known exchange
  if (funding.funderType === 'exchange') {
    score = 20;
    reasons.push(`Funded by known exchange (${funding.funderName})`);
  }
  // Medium risk: Unknown funder
  else if (!funding.funderName) {
    score = 50;
    reasons.push('Funded by unknown wallet');
  }
  // High risk: Funded by flagged address
  else if (funding.funderType === 'flagged') {
    score = 90;
    reasons.push('Funded by flagged address');
  }

  // Age factor: New wallets are higher risk
  const ageInDays = (Date.now() / 1000 - funding.timestamp) / (60 * 60 * 24);
  if (ageInDays < 7) {
    score += 20;
    reasons.push('Wallet is less than 7 days old');
  }

  // Amount factor: Very small initial funding is suspicious
  if (funding.amount < 0.01) {
    score += 10;
    reasons.push('Very small initial funding amount');
  }

  const riskLevel = score < 30 ? 'LOW' : score < 60 ? 'MEDIUM' : 'HIGH';

  console.log(`Risk Assessment for ${address}:`);
  console.log(`Risk Level: ${riskLevel} (Score: ${score}/100)`);
  reasons.forEach(reason => console.log(`  - ${reason}`));

  return { riskLevel, score, reasons };
};

Attribution tracking

Track which sources are creating the most new wallets:
const trackNewWalletSources = async (recentWallets) => {
  const fundingSources = await Promise.all(
    recentWallets.map(async address => {
      try {
        const funding = await getWalletFundingSource(address);
        return {
          address,
          funder: funding?.funder,
          funderName: funding?.funderName,
          funderType: funding?.funderType
        };
      } catch {
        return { address, funder: null };
      }
    })
  );

  // Count by source
  const sourceStats = {};

  fundingSources.forEach(({ funderName, funderType }) => {
    const sourceName = funderName || funderType || 'Unknown';
    sourceStats[sourceName] = (sourceStats[sourceName] || 0) + 1;
  });

  // Sort by count
  const sorted = Object.entries(sourceStats)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 10);

  console.log('Top Wallet Funding Sources:');
  sorted.forEach(([source, count]) => {
    console.log(`${source}: ${count} wallets`);
  });

  return sourceStats;
};

Funder types

The funderType field indicates the category of the wallet that funded the address. All values from the Identity Categories are supported.
Common funder types:
TypeDescriptionExamples
Centralized ExchangeCEX hot walletsBinance, Coinbase, Kraken, OKX
DeFiDeFi protocol addressesJupiter, Raydium, Marinade
Market MakerMarket making firmsJump Trading, Wintermute
Trading FirmProprietary trading firmsInstitutional traders
Cross-chain BridgeBridge protocol addressesWormhole, AllBridge, Portal
ValidatorValidator addressesCoinbase Validator, Jito
Key Opinion LeaderNotable individualsInfluencers, founders
TreasuryProject treasuriesProtocol treasuries
Stake PoolLiquid staking poolsMarinade, Jito
nullUnknown funderRegular wallet, not in identity database
The complete list includes: Airdrop, Authority, Cross-chain Bridge, Casino & Gambling, DAO, DeFi, DePIN, Centralized Exchange, Exploiter/Hackers/Scams, Fees, Fundraise, Game, Genesis Block Distribution, Governance, Hacker, Jito, Key Opinion Leader, Market Maker, Memecoin, Multisig, NFT, Non-Circulating Supply, Oracle, Other, Payments, Proprietary AMM, Restaking, Rugger, Scammer, Spam, Stake Pool, System, Tools, Trading App/Bot, Trading Firm, Transaction Sending, Treasury, Validator, Vault, and X402.See the Identity Categories section for the complete list with descriptions.

Best practices

  • Handle 404 responses. Wallets that have never received SOL return a 404. This is expected for newly created but unfunded wallets.
  • Combine with the Identity API. The response includes funderName and funderType, but you can call the Identity endpoint on the funder address for more detail.
  • Cache funding data. A wallet’s funding source never changes. Cache this data permanently to avoid repeated API calls.
  • Check age for context. The timestamp tells you when the wallet was first funded. Combine age with funding source for better context.

Common errors

Error CodeDescriptionSolution
400Invalid wallet address formatVerify the address is a valid base58 Solana address
401Missing or invalid API keyCheck your API key is included in the request
404No funding transaction foundThis wallet has never received SOL
429Rate limit exceededReduce request frequency or upgrade your plan

Limitations

  • This endpoint only tracks the first SOL transfer to a wallet.
  • If a wallet was created via airdrop or program initialization without a SOL transfer, it won’t have funding data.
  • The funding source represents the immediate funder, not necessarily the ultimate source of funds.
  • Historical data is only available for wallets created after this feature was deployed.

Next steps

Wallet Identity

Resolve the funder address to a full label, category, and tags.

Wallet API Overview

All Wallet API endpoints and shared conventions.

API Reference

Request and response schemas for funding source lookup.