home/blog/helius api wallet history

Helius API wallet history tutorial: get every trade from any Solana wallet in 10 minutes

By · Founder, DegenJournal
August 10, 2026 · updated August 11, 2026 · 11 min read

This is a Helius API wallet history tutorial with real, working TypeScript — the kind you paste into a Node script and watch spit out every trade a Solana wallet has ever made. By the end you'll pull complete solana wallet history for any public address, page backward through thousands of transactions, and parse raw swap events into a clean list of buys and sells. No private keys, no shady approvals, no "connect wallet" popups. Just a public address and an API key. Read-only, the way it should be.

Fair warning up front: this is not financial advice, and copy-pasting code that touches money-adjacent data means you own the bugs. We'll be precise about the endpoints and the response shapes, but APIs drift — so trust the code, verify against the live docs, and test on a wallet you don't care about first.

What Helius actually is

Helius is two things bolted together, and the distinction matters for this tutorial:

  • A Solana RPC provider. Standard JSON-RPC — getSignaturesForAddress, getTransaction, getAccountInfo, and the rest of the node interface. This is the raw firehose. You get transactions, but you decode the instructions yourself.
  • A data / enhanced API layer. This is the good stuff for building a journal. Helius runs the raw transactions through its own parsers and hands you back structured objects with a human-readable type (SWAP, TRANSFER, NFT_SALE, ...) and an events object that already decoded the token movements for you.

For pulling a memecoin trader's history, the enhanced layer — specifically the Enhanced Transactions API — turns a week of instruction-decoding into an afternoon. That's what we're using.

Why wallet history matters

If you trade Solana memecoins, your entire track record already exists, permanently, on a public ledger. Every ape, every paper-hand, every "I'll just check the chart one more time" 3 a.m. entry. The problem isn't that the data is missing — it's that it's unreadable in its raw form. A single Jupiter swap can touch a dozen accounts across several inner instructions. Your brain sees a green candle; the chain sees lamports and mints.

Pulling and structuring that history is the foundation of any honest trading journal. It's how you find out that your "strategy" is actually just buying tops on tokens with the word "cat" in them. This is exactly what powers our read-only wallet analysis — you paste a public address and get your real numbers back, no self-reported nonsense. If you want the human side of turning this data into discipline, we wrote about journaling your Solana trades separately. And if you want to feel seen, the data usually confirms the patterns that wreck memecoin traders.

read-only — we literally cannot touch your funds. History is public data; reading it needs a public address and nothing else.

Two ways to get history (and why we pick one)

You have two roads to the same destination.

Road 1: the low-level JSON-RPC pair

Call getSignaturesForAddress to list transaction signatures for the wallet, then call getTransaction on each signature to fetch the full transaction. You'll get the account keys, the top-level instructions, the inner instructions, pre/post token balances — everything. And then you have to reconstruct the swap yourself: figure out which program was the DEX, diff the token balances, handle wrapped SOL, deal with route-splitting aggregators. It's doable. It's also a lot of code and a lot of edge cases.

Road 2: the Enhanced Transactions API

One endpoint. It returns an array of already-parsed transactions, each with a type, a timestamp, a signature, a source (the venue, like JUPITER or PUMP_FUN), and an events object. For swaps, events.swap hands you tokenInputs, tokenOutputs, nativeInput, and nativeOutput — the actual movement, decoded.

For a trade journal, Road 2 wins by a mile. We'll use it, and mention Road 1 again at the end for the masochists.

Step 0: get a key and store it correctly

Sign up at Helius, create a project, copy your API key. Then — and this is the part people fumble — do not hardcode it. Put it in your environment. In a Node project, a .env file (git-ignored) plus something like dotenv, or just export it in your shell. The code reads process.env.HELIUS_API_KEY and nothing else. A leaked RPC key isn't as catastrophic as a leaked wallet key, but it's still your quota someone else gets to burn.

Step 1: fetch a single page of history

The address history endpoint is:

https://api.helius.xyz/v0/addresses/<ADDRESS>/transactions?api-key=<API_KEY>

It returns a JSON array of parsed transactions, newest first. Here's a typed fetch using the built-in fetch that ships with modern Node and TypeScript — no axios, no node-fetch:

// src/helius.ts
// Enhanced Transactions API — parsed Solana wallet history.
// Never hardcode the key. Read it from the environment.

type HeliusTx = {
  signature: string;
  timestamp: number;      // unix SECONDS, not milliseconds
  type: string;           // "SWAP", "TRANSFER", "UNKNOWN", ...
  source: string;         // "JUPITER", "RAYDIUM", "PUMP_FUN", ...
  events?: {
    swap?: SwapEvent;
  };
};

async function fetchWalletPage(
  address: string,
  before?: string
): Promise<HeliusTx[]> {
  const key = process.env.HELIUS_API_KEY;
  if (!key) throw new Error("Set HELIUS_API_KEY in your environment");

  let url =
    "https://api.helius.xyz/v0/addresses/" +
    address +
    "/transactions?api-key=" +
    key +
    "&limit=100";

  if (before) url += "&before=" + before;

  const res = await fetch(url);
  if (!res.ok) {
    throw new Error("Helius returned HTTP " + res.status);
  }
  return (await res.json()) as HeliusTx[];
}

Notice the string concatenation instead of template literals — partly house style, partly a reminder that the URL is just a string you're assembling. The limit caps how many transactions come back per call (100 is a sane page size). before is the pagination cursor, which we wire up next.

Step 2: paginate through the entire history

One page is never enough for an active degen. To walk the full solana wallet history, you page backward in time: take the last signature from the page you just got, pass it as before, and ask for the next batch. Keep going until Helius returns fewer transactions than your limit — that's the signal you've hit the wallet's first-ever transaction.

async function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetchAllHistory(address: string): Promise<HeliusTx[]> {
  const key = process.env.HELIUS_API_KEY;
  if (!key) throw new Error("Set HELIUS_API_KEY in your environment");

  const all: HeliusTx[] = [];
  let before: string | undefined = undefined;
  const LIMIT = 100;

  while (true) {
    let url =
      "https://api.helius.xyz/v0/addresses/" +
      address +
      "/transactions?api-key=" +
      key +
      "&limit=" +
      LIMIT;
    if (before) url += "&before=" + before;

    const res = await fetch(url);

    // Rate limited — back off and retry the SAME page.
    if (res.status === 429) {
      await sleep(1000);
      continue;
    }
    if (!res.ok) {
      throw new Error("Helius returned HTTP " + res.status);
    }

    const page = (await res.json()) as HeliusTx[];
    all.push(...page);

    // Fewer than a full page means we reached the end of history.
    if (page.length < LIMIT) break;

    // Page backward: last signature becomes the next cursor.
    before = page[page.length - 1].signature;
  }

  return all;
}

That 429 branch is not optional. Free-tier keys and busy wallets will get you throttled, and the correct move is to wait and retry the exact same page — not to skip it, or you'll silently lose transactions. A flat one-second backoff is fine to start; if you're hammering the API hard, use exponential backoff and cap your concurrency. For a full-history pull you generally want to fetch pages sequentially anyway, because each page depends on the cursor from the previous one.

Step 3: filter for swaps and parse the events

Now the payoff. Every swap carries an events.swap object. The important fields:

  • nativeInput / nativeOutput — SOL moving in or out, in lamports as a string. 1 SOL = 1,000,000,000 lamports.
  • tokenInputs / tokenOutputs — SPL tokens moving, each with a mint and a rawTokenAmount ({ tokenAmount, decimals }). Raw amounts are integers; divide by 10^decimals to get a human number.

A buy typically looks like SOL in (nativeInput) and a token out (tokenOutputs). A sell is the mirror: token in, SOL out. Here's the parser:

type SwapEvent = {
  nativeInput?: { account: string; amount: string };   // lamports (string)
  nativeOutput?: { account: string; amount: string };
  tokenInputs?: TokenBalanceChange[];
  tokenOutputs?: TokenBalanceChange[];
};

type TokenBalanceChange = {
  userAccount: string;
  tokenAccount: string;
  mint: string;
  rawTokenAmount: { tokenAmount: string; decimals: number };
};

const LAMPORTS_PER_SOL = 1_000_000_000;

function toUiAmount(raw: string, decimals: number): number {
  return Number(raw) / Math.pow(10, decimals);
}

function summarizeSwaps(txs: HeliusTx[]) {
  return txs
    .filter((tx) => tx.type === "SWAP" && tx.events?.swap)
    .map((tx) => {
      const s = tx.events!.swap!;

      const solIn = s.nativeInput
        ? Number(s.nativeInput.amount) / LAMPORTS_PER_SOL
        : 0;
      const solOut = s.nativeOutput
        ? Number(s.nativeOutput.amount) / LAMPORTS_PER_SOL
        : 0;

      const tokensIn = (s.tokenInputs ?? []).map((t) => ({
        mint: t.mint,
        amount: toUiAmount(
          t.rawTokenAmount.tokenAmount,
          t.rawTokenAmount.decimals
        ),
      }));

      const tokensOut = (s.tokenOutputs ?? []).map((t) => ({
        mint: t.mint,
        amount: toUiAmount(
          t.rawTokenAmount.tokenAmount,
          t.rawTokenAmount.decimals
        ),
      }));

      return {
        signature: tx.signature,
        when: new Date(tx.timestamp * 1000).toISOString(),
        venue: tx.source,
        solIn,
        solOut,
        tokensIn,
        tokensOut,
      };
    });
}

Two easy-to-miss details are baked in here. First, timestamp is in seconds, so multiply by 1000 before feeding it to Date, or every trade will look like it happened in 1970. Second, always coerce the string amounts with Number(...) and divide by the right scale — the API gives you raw integers as strings on purpose, so you don't lose precision in transit.

A real (redacted) example

Here's a trimmed, redacted response for a wallet we'll call 7xKX...gAsU. Never publish full addresses or signatures from someone's wallet in a tutorial — the ledger is public, but you don't need to hand people a map to a specific stranger's bags. This one is a Pump.fun buy: half a SOL in, a pile of some memecoin out.

[
  {
    "signature": "5xR...redacted-88-char-base58-signature...Qp",
    "timestamp": 1723406912,
    "type": "SWAP",
    "source": "PUMP_FUN",
    "feePayer": "7xKX...gAsU",
    "events": {
      "swap": {
        "nativeInput": {
          "account": "7xKX...gAsU",
          "amount": "500000000"
        },
        "nativeOutput": null,
        "tokenInputs": [],
        "tokenOutputs": [
          {
            "userAccount": "7xKX...gAsU",
            "tokenAccount": "9aB...redacted...",
            "mint": "So1...redacted-mint...pump",
            "rawTokenAmount": {
              "tokenAmount": "184920000000",
              "decimals": 6
            }
          }
        ]
      }
    }
  }
]

Run that through summarizeSwaps and you get: at that timestamp, on PUMP_FUN, solIn = 0.5, tokensOut = one entry of 184,920 tokens (184920000000 divided by 10^6). Clean, journalable, human. Do this across the whole history and you've got a full ledger of entries and exits — the raw material for actual PnL and behavior analysis.

Parsing swaps: the nuances that bite

The happy path above covers most trades. The chain, being the chain, has opinions:

  • Wrapped SOL vs native SOL. Some routes wrap SOL into an SPL token (the So111...11112 mint) mid-swap. You may see it as a token movement instead of nativeInput/nativeOutput. If your PnL looks off, check whether SOL is showing up as a wrapped mint.
  • Aggregators split routes. Jupiter and friends may fill one swap across multiple pools, so a single logical trade can have several token legs. The events.swap aggregation usually nets this out for you — but verify on multi-hop trades before you trust the totals.
  • Spam and dust. Airdropped scam tokens and dust transfers clutter history. For a trade journal, filter to mints the wallet actually swapped, or apply a minimum-value threshold. Otherwise your "portfolio" includes 4 billion units of RUGCOIN nobody bought.
  • Type isn't always SWAP. Some venues or edge transactions get tagged UNKNOWN even though economically they were a swap. If you need every trade, fall back to diffing token balances for the UNKNOWN ones — or accept a small miss rate for simplicity.

Gotchas checklist

  1. Rate limits (429). Back off and retry the same page. Don't skip. Don't hammer.
  2. Timestamps are seconds. Multiply by 1000 for JavaScript Date.
  3. Amounts are scaled integers. Divide by 10^decimals. Lamports use 9; most SPL memecoins use 6.
  4. Pagination ends on a short page. Fewer than limit results = you're done. Don't loop forever chasing an empty cursor.
  5. Keys live in env vars. process.env.HELIUS_API_KEY. If it's in your source, assume it's compromised.
  6. The API evolves. Helius endpoints, field names, and type enums change over time. If a field in this article isn't there anymore, it's not you — go read the current official Helius docs and adjust the types.

The lower-level alternative (Road 1, briefly)

If you outgrow the Enhanced API — you need exotic instruction data, or you want zero dependence on Helius's parser — drop to raw JSON-RPC. Call getSignaturesForAddress for the wallet to page through signatures (it has its own before/until/limit cursors), then getTransaction per signature with maxSupportedTransactionVersion set so you don't choke on versioned transactions. From there you diff preTokenBalances against postTokenBalances, walk innerInstructions, and reconstruct each swap by hand. It's the same answer at the end — you just did the parser's job. Great for control and cost tuning; a bad first project when you mainly want swaps out the door.

Ship it, or steal ours

You now have the whole pipeline: fetch a page, paginate backward with before, respect 429s, filter for SWAP, and decode events.swap into real numbers. That's a genuine helius api wallet history reader in well under a hundred lines — the exact backbone DegenJournal uses to auto-import trades read-only via Helius on Solana (and Hyperliquid for perps). We never touch funds and never ask for keys, because reading public history doesn't require either.

If you'd rather not babysit edge cases, wrapped SOL, and retry logic yourself, the production version of this reader — hardened, typed, and battle-tested on real memecoin wallets — ships in our Crypto SaaS Starter Kit. It's the actual read-only Helius wallet-reader code, ready to drop in. Or just paste a public address and watch your own history populate in the live demo dashboard — no signup, no keys, no risk. Not financial advice. Trade responsibly (lol).

Key takeaways

  • The Enhanced Transactions API returns parsed, labelled transactions — you don't have to decode raw Solana instructions yourself.
  • Pagination is cursor-based: pass the last signature you received as `before` and repeat until you get an empty array.
  • Filter on transaction `type` of SWAP to separate real trades from transfers, mints, and airdrop spam.
  • Only a public address is needed. Reading history never requires a private key or a wallet signature.
  • Rate limits apply per API key, so back off and retry rather than hammering the endpoint on large wallets.

FAQ

Is Helius free?

Yes, Helius has a free tier that's plenty for learning and small tools. You get an API key and a monthly credit allotment. Enhanced Transactions calls and RPC calls draw from that quota, and heavier workloads push you onto a paid plan. Check current limits on the Helius pricing page, since tiers change.

How far back does wallet history go?

All the way to the wallet's first on-chain transaction. Solana keeps full ledger history and Helius indexes it, so you can page backward with the before cursor until you run out of signatures. Very old or extremely active wallets just mean more pages.

How do I get only swaps?

The Enhanced Transactions API tags each transaction with a type. Filter for type equal to SWAP and check that events.swap exists. If you only ever want swaps, you can also pass a type filter to the endpoint, but filtering client-side is simpler and future-proof.

Do I need the wallet's private key?

No. Never. Wallet history is public on-chain data. You only need the public address. Anything that asks for your seed phrase or private key to read your history is a scam — reading is 100% read-only.

JSON-RPC or the Enhanced Transactions API?

For swaps, use the Enhanced API — it hands you parsed token and native inputs and outputs. The raw JSON-RPC pair getSignaturesForAddress plus getTransaction gives you more control but forces you to decode instructions yourself. Way more work for the same answer.

Why are some tokens showing zero or weird amounts?

Raw amounts are integers scaled by the mint's decimals. You must divide by 10 to the power of decimals to get a human amount. Also, spam and airdrop tokens and dust show up in history — filter by mint allowlists or minimum value for a clean trade log.

The response shape doesn't match this article — did something break?

Possibly the API evolved. Helius endpoints, field names, and enums change over time. Treat any tutorial (including this one) as a starting point and confirm the current shape against the official Helius docs before shipping.

Keep reading

See your own trades, read-only.

Paste a public wallet and DegenJournal auto-imports your history — no signing, no keys, never touches your funds.