> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Construire un Suivi de Portefeuille sur Solana

> Créez un suivi de portefeuille Solana qui affiche l'ensemble des avoirs de tout portefeuille ainsi que son activité sur la chaîne en direct et historique.

export const PortfolioTrackerDemo = () => {
  const [apiKey, setApiKey] = useState('');
  const [ownerAddress, setOwnerAddress] = useState('86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [portfolio, setPortfolio] = useState(null);
  const [history, setHistory] = useState(null);
  const [live, setLive] = useState([]);
  const [liveStatus, setLiveStatus] = useState('idle');
  const wsRef = useRef(null);
  const rpc = async (method, params) => {
    const res = await fetch(`https://mainnet.helius-rpc.com/?api-key=${apiKey}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'quickstart',
        method,
        params
      })
    });
    const json = await res.json();
    if (json.error) throw new Error(json.error.message || 'RPC error');
    return json.result;
  };
  const stopLive = () => {
    if (wsRef.current) {
      try {
        wsRef.current.close();
      } catch (e) {}
      wsRef.current = null;
    }
    setLiveStatus('idle');
  };
  useEffect(() => stopLive, []);
  const handleLoad = async e => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    setPortfolio(null);
    setHistory(null);
    stopLive();
    setLive([]);
    try {
      const assets = await rpc('getAssetsByOwner', {
        ownerAddress,
        page: 1,
        limit: 20,
        displayOptions: {
          showFungible: true,
          showNativeBalance: true
        }
      });
      const txns = await rpc('getTransactionsForAddress', [ownerAddress, {
        transactionDetails: 'signatures',
        sortOrder: 'desc',
        limit: 10,
        filters: {
          tokenAccounts: 'balanceChanged'
        }
      }]);
      const items = assets.items || [];
      const tokens = items.filter(i => (i.interface || '').includes('Fungible'));
      const nfts = items.filter(i => !(i.interface || '').includes('Fungible'));
      setPortfolio({
        sol: (assets.nativeBalance && assets.nativeBalance.lamports ? assets.nativeBalance.lamports : 0) / 1e9,
        tokenCount: tokens.length,
        nftCount: nfts.length,
        tokens: tokens.slice(0, 6),
        nfts: nfts.slice(0, 4)
      });
      setHistory(txns.data || []);
    } catch (err) {
      const msg = err.message || '';
      setError((/too big|too large/i).test(msg) ? 'This wallet holds too many assets to load in the demo. Try a wallet with fewer tokens and NFTs, or lower the limit in getAssetsByOwner.' : msg || 'Failed to load wallet');
    } finally {
      setLoading(false);
    }
  };
  const startLive = () => {
    if (!apiKey) {
      setError('Enter your API key first');
      return;
    }
    setLiveStatus('connecting');
    const ws = new WebSocket(`wss://mainnet.helius-rpc.com/?api-key=${apiKey}`);
    wsRef.current = ws;
    ws.onopen = () => {
      setLiveStatus('live');
      ws.send(JSON.stringify({
        jsonrpc: '2.0',
        id: 1,
        method: 'transactionSubscribe',
        params: [{
          accountInclude: [ownerAddress],
          tokenAccounts: 'balanceChanged'
        }, {
          commitment: 'confirmed',
          encoding: 'jsonParsed',
          transactionDetails: 'signatures',
          maxSupportedTransactionVersion: 1
        }]
      }));
    };
    ws.onmessage = event => {
      const msg = JSON.parse(event.data);
      const sig = msg && msg.params && msg.params.result && msg.params.result.signature;
      if (sig) setLive(prev => [{
        sig,
        at: new Date().toLocaleTimeString()
      }, ...prev].slice(0, 8));
    };
    ws.onerror = () => setLiveStatus('error');
    ws.onclose = () => {
      if (wsRef.current === ws) setLiveStatus('idle');
    };
  };
  const short = s => s ? `${s.slice(0, 6)}...${s.slice(-6)}` : '';
  const btnClass = loading ? 'px-4 py-2 font-medium rounded-full bg-gray-300 dark:bg-gray-700 cursor-not-allowed' : 'px-4 py-2 font-medium rounded-full bg-primary hover:bg-primary/80 text-white';
  const dotClass = liveStatus === 'live' ? 'inline-block w-2 h-2 rounded-full bg-green-500 animate-pulse' : liveStatus === 'error' ? 'inline-block w-2 h-2 rounded-full bg-red-500' : 'inline-block w-2 h-2 rounded-full bg-yellow-500';
  return <div className="p-4 border dark:border-zinc-950/80 rounded-xl bg-white dark:bg-zinc-950/80 shadow-sm">
      <form onSubmit={handleLoad} className="space-y-4">
        <div>
          <label className="block text-sm text-zinc-950/70 dark:text-white/70 mb-1">Your API Key</label>
          <input type="text" value={apiKey} onChange={e => setApiKey(e.target.value)} placeholder="Enter your API key" className="w-full p-2 border rounded dark:bg-zinc-900 dark:border-zinc-700" required />
        </div>
        <div>
          <label className="block text-sm text-zinc-950/70 dark:text-white/70 mb-1">Wallet Address</label>
          <input type="text" value={ownerAddress} onChange={e => setOwnerAddress(e.target.value)} placeholder="Solana wallet address" className="w-full p-2 border rounded dark:bg-zinc-900 dark:border-zinc-700" required />
        </div>
        <div className="flex gap-2">
          <button type="submit" disabled={loading} className={btnClass}>
            {loading ? 'Loading...' : 'Load Wallet'}
          </button>
          {liveStatus === 'live' || liveStatus === 'connecting' ? <button type="button" onClick={stopLive} className="px-4 py-2 font-medium rounded-full border border-zinc-300 dark:border-zinc-700">Stop live feed</button> : <button type="button" onClick={startLive} className="px-4 py-2 font-medium rounded-full border border-zinc-300 dark:border-zinc-700">Go live</button>}
        </div>
      </form>
      <p className="mt-3 text-xs text-zinc-500 dark:text-zinc-400">
        Your API key is used only in your browser to call Helius directly. It is never sent anywhere else.
      </p>
      {error ? <div className="mt-4 p-3 bg-red-100 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded text-red-700 dark:text-red-300">
          <strong>Error:</strong> {error}
        </div> : null}
      {portfolio ? <div className="mt-6 space-y-4">
          <div className="grid grid-cols-2 gap-3 text-center">
            <div className="p-3 rounded-lg border border-zinc-200 dark:border-zinc-800">
              <div className="text-xl font-semibold text-zinc-950 dark:text-white">{portfolio.sol.toFixed(3)}</div>
              <div className="text-xs text-zinc-500 dark:text-zinc-400">SOL</div>
            </div>
            <div className="p-3 rounded-lg border border-zinc-200 dark:border-zinc-800">
              <div className="text-xl font-semibold text-zinc-950 dark:text-white">{portfolio.nftCount}</div>
              <div className="text-xs text-zinc-500 dark:text-zinc-400">NFTs</div>
            </div>
          </div>
          {portfolio.tokens.length > 0 ? <div>
              <h4 className="text-sm font-semibold mb-2 text-zinc-950 dark:text-white">Tokens</h4>
              <ul className="space-y-1 list-none pl-0 m-0">
                {portfolio.tokens.map(t => {
    const info = t.token_info || ({});
    const amount = (info.balance || 0) / 10 ** (info.decimals || 0);
    const name = t.content && t.content.metadata && t.content.metadata.name || info.symbol || 'Unknown token';
    const img = t.content && t.content.links ? t.content.links.image : '';
    return <li key={t.id} className="flex items-center gap-3 text-sm my-0">
                      <img src={img || ''} alt="" className="w-7 h-7 rounded-full bg-zinc-200 dark:bg-zinc-800 my-0 shrink-0" />
                      <span className="flex-1 truncate text-zinc-800 dark:text-zinc-200">{name}</span>
                      <span className="font-mono text-zinc-600 dark:text-zinc-400">{amount.toLocaleString(undefined, {
      maximumFractionDigits: 4
    })}</span>
                    </li>;
  })}
              </ul>
            </div> : null}
          {portfolio.nfts.length > 0 ? <div className="grid grid-cols-4 gap-2">
              {portfolio.nfts.map(nft => <img key={nft.id} src={nft.content && nft.content.files && nft.content.files[0] ? nft.content.files[0].uri : ''} alt={nft.content && nft.content.metadata ? nft.content.metadata.name : 'NFT'} className="rounded-lg border border-zinc-200 dark:border-zinc-800 aspect-square object-cover my-0" />)}
            </div> : null}
        </div> : null}
      {history && history.length > 0 ? <div className="mt-6">
          <h4 className="text-sm font-semibold mb-2 text-zinc-950 dark:text-white">Recent activity</h4>
          <ul className="space-y-1 text-sm font-mono list-none pl-0 m-0">
            {history.map(tx => <li key={tx.signature} className="flex items-center justify-between text-zinc-600 dark:text-zinc-400 my-0">
                <span className="flex items-center gap-2">
                  <span className={tx.err ? 'inline-block w-2 h-2 rounded-full bg-red-500' : 'inline-block w-2 h-2 rounded-full bg-green-500'} />
                  <a href={'https://orbmarkets.io/tx/' + tx.signature} target="_blank" rel="noreferrer" className="hover:underline">{short(tx.signature)}</a>
                </span>
                <span className="text-xs">{tx.blockTime ? new Date(tx.blockTime * 1000).toLocaleDateString() : ''}</span>
              </li>)}
          </ul>
        </div> : null}
      {liveStatus !== 'idle' || live.length > 0 ? <div className="mt-6">
          <h4 className="text-sm font-semibold mb-2 text-zinc-950 dark:text-white flex items-center gap-2">
            Live feed
            <span className={dotClass} />
            <span className="text-xs font-normal text-zinc-500 dark:text-zinc-400">{liveStatus}</span>
          </h4>
          {live.length === 0 ? <p className="text-sm text-zinc-500 dark:text-zinc-400">Waiting for the next transaction that touches this wallet.</p> : <ul className="space-y-1 text-sm font-mono list-none pl-0 m-0">
              {live.map((item, i) => <li key={i} className="flex items-center justify-between text-zinc-600 dark:text-zinc-400 my-0">
                  <span className="flex items-center gap-2">
                    <span className="inline-block w-2 h-2 rounded-full bg-green-500" />
                    <a href={'https://orbmarkets.io/tx/' + item.sig} target="_blank" rel="noreferrer" className="hover:underline">{short(item.sig)}</a>
                  </span>
                  <span className="text-xs">{item.at}</span>
                </li>)}
            </ul>}
        </div> : null}
    </div>;
};

Dans ce guide, vous allez créer un tableau de bord qui montre ce que détient une adresse Solana et ce qu'elle fait en ce moment. Vous allez récupérer son portefeuille complet de tokens, NFT et SOL en un seul appel [DAS](/docs/fr/api-reference/das/getassetsbyowner), charger l'historique des transactions avec l'API exclusive à Helius [`getTransactionsForAddress`](/docs/fr/rpc/gettransactionsforaddress), et diffuser en direct la nouvelle activité via [`transactionSubscribe`](/docs/fr/rpc/websocket/transaction-subscribe). Vous l'essaierez d'abord sous forme de démo intégrée, puis le construirez comme une application complète Next.js.

## Essayez-le en direct

Collez votre clé API et une adresse de portefeuille pour voir les trois éléments fonctionner ensemble : portefeuille, historique, et flux en direct. Ce sont les mêmes données que votre application affichera.

<PortfolioTrackerDemo />

***

## Construisez-le en tant qu'application Next.js

<Card title="Récupérez l'application complète sur GitHub" icon="github" href="https://github.com/helius-labs/quickstart-portfolio-tracker">
  Clonez le projet de départ complet — l'application Next.js finale issue de ce guide, prête à `npm install && npm run dev`.
</Card>

Le widget ci-dessus appelle Helius directement depuis le navigateur, ce qui expose votre clé API. Dans une application réelle, vous gardez la clé sur le serveur. Ce guide utilise le Router d'application Next.js avec deux gestionnaires de route pour que **votre clé n'atteigne jamais le client** : l'un relaie les appels RPC, l'autre relaie le flux WebSocket comme des événements envoyés par le serveur.

<Info>
  **Prérequis :** Node.js 18+ et une [clé API Helius](https://dashboard.helius.dev/api-keys).
</Info>

<Steps>
  <Step title="Préparer le projet">
    ```bash theme={"system"}
    npx create-next-app@latest portfolio-tracker --ts --app --tailwind --no-src-dir --eslint --yes
    cd portfolio-tracker
    npm install helius-sdk ws
    npm install --save-dev @types/ws
    ```

    Vous utiliserez le [Helius SDK](https://github.com/helius-labs/helius-sdk) pour les appels RPC et `ws` pour le flux WebSocket en direct.

    Ajoutez votre clé à `.env.local` :

    ```bash .env.local theme={"system"}
    HELIUS_API_KEY=YOUR_API_KEY
    ```
  </Step>

  <Step title="Relayer les appels RPC via un gestionnaire de route">
    Cela garde votre clé API côté serveur. Le client publie une méthode et des paramètres ici, et le gestionnaire les exécute via le Helius SDK, qui attache votre clé et construit la requête pour vous.

    ```typescript app/api/helius/route.ts theme={"system"}
    import { NextRequest, NextResponse } from "next/server";
    import { createHelius } from "helius-sdk";

    export const runtime = "nodejs";

    const helius = createHelius({
      apiKey: process.env.HELIUS_API_KEY!,
      network: "mainnet",
    });

    export async function POST(req: NextRequest) {
      const { method, params } = await req.json();
      try {
        // The two Helius calls that power this app: the full portfolio and its history.
        const result =
          method === "getAssetsByOwner"
            ? await helius.getAssetsByOwner(params)
            : method === "getTransactionsForAddress"
            ? await helius.getTransactionsForAddress(params)
            : undefined;

        if (result === undefined) {
          return NextResponse.json(
            { error: { message: `Unsupported method: ${method}` } },
            { status: 400 }
          );
        }
        return NextResponse.json({ result });
      } catch (e) {
        return NextResponse.json({ error: { message: (e as Error).message } });
      }
    }
    ```
  </Step>

  <Step title="Relayer le flux en direct via les événements envoyés par le serveur">
    Le navigateur ne peut pas ouvrir un WebSocket Helius sans la clé. Au lieu de cela, ouvrez le WebSocket sur le serveur et diffusez les signatures au client avec SSE.

    ```typescript app/api/stream/route.ts [expandable] theme={"system"}
    import { NextRequest } from "next/server";
    import WebSocket from "ws";

    export const runtime = "nodejs";

    export async function GET(req: NextRequest) {
      const address = req.nextUrl.searchParams.get("address");
      if (!address) return new Response("Missing address", { status: 400 });

      const stream = new ReadableStream({
        start(controller) {
          const encoder = new TextEncoder();
          const send = (data: unknown) =>
            controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));

          const ws = new WebSocket(
            `wss://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`
          );

          ws.on("open", () => {
            ws.send(
              JSON.stringify({
                jsonrpc: "2.0",
                id: 1,
                method: "transactionSubscribe",
                params: [
                  // tokenAccounts: "balanceChanged" also matches the wallet's token accounts,
                  // so incoming SPL transfers (which touch an ATA, not the wallet pubkey) show up.
                  { accountInclude: [address], tokenAccounts: "balanceChanged" },
                  {
                    commitment: "confirmed",
                    encoding: "jsonParsed",
                    transactionDetails: "signatures",
                    maxSupportedTransactionVersion: 1,
                  },
                ],
              })
            );
          });

          ws.on("message", (raw) => {
            const msg = JSON.parse(raw.toString());
            const sig = msg?.params?.result?.signature;
            if (sig) send({ signature: sig, at: Date.now() });
          });

          // Tear down the upstream socket when the client disconnects.
          req.signal.addEventListener("abort", () => {
            ws.close();
            controller.close();
          });
        },
      });

      return new Response(stream, {
        headers: {
          "Content-Type": "text/event-stream",
          "Cache-Control": "no-cache",
          Connection: "keep-alive",
        },
      });
    }
    ```
  </Step>

  <Step title="Rendre le tableau de bord">
    La page charge le portefeuille et l'historique via le proxy, puis s'abonne au flux SSE pour les mises à jour en direct.

    ```tsx app/page.tsx [expandable] theme={"system"}
    "use client";

    import { useEffect, useState } from "react";

    const rpc = async (method: string, params: unknown) => {
      const res = await fetch("/api/helius", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ jsonrpc: "2.0", id: "1", method, params }),
      });
      const json = await res.json();
      if (json.error) throw new Error(json.error.message);
      return json.result;
    };

    export default function Home() {
      const [address, setAddress] = useState(
        "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY"
      );
      const [portfolio, setPortfolio] = useState<any>(null);
      const [history, setHistory] = useState<any[]>([]);
      const [live, setLive] = useState<any[]>([]);
      const [streamAddress, setStreamAddress] = useState<string | null>(null);
      const [error, setError] = useState<string | null>(null);

      const load = async () => {
        setError(null);
        setLive([]);
        setStreamAddress(address); // point the live feed at the wallet you just loaded
        try {
          const [assets, txns] = await Promise.all([
            rpc("getAssetsByOwner", {
              ownerAddress: address,
              page: 1,
              limit: 20,
              displayOptions: { showFungible: true, showNativeBalance: true },
            }),
            rpc("getTransactionsForAddress", [
              address,
              { transactionDetails: "signatures", sortOrder: "desc", limit: 10, filters: { tokenAccounts: "balanceChanged" } },
            ]),
          ]);
          const items = assets.items ?? [];
          setPortfolio({
            sol: (assets.nativeBalance?.lamports ?? 0) / 1e9,
            tokens: items.filter((i: any) => `${i.interface}`.includes("Fungible")),
            nfts: items.filter((i: any) => !`${i.interface}`.includes("Fungible")),
          });
          setHistory(txns.data ?? []);
        } catch (e: any) {
          setError(e.message);
        }
      };

      // Open the live feed only after Load runs, and reopen only when the loaded wallet changes
      // (not on every keystroke in the input).
      useEffect(() => {
        if (!streamAddress) return;
        const source = new EventSource(`/api/stream?address=${streamAddress}`);
        source.onmessage = (e) =>
          setLive((prev) => [JSON.parse(e.data), ...prev].slice(0, 10));
        return () => source.close();
      }, [streamAddress]);

      return (
        <main className="max-w-2xl mx-auto p-8 space-y-6">
          <div className="flex gap-2">
            <input
              className="flex-1 border rounded p-2"
              value={address}
              onChange={(e) => setAddress(e.target.value)}
            />
            <button className="px-4 rounded bg-black text-white" onClick={load}>
              Load
            </button>
          </div>

          {error && <p className="text-red-600">{error}</p>}

          {portfolio && (
            <section className="space-y-6">
              <Stat label="SOL balance" value={portfolio.sol.toFixed(3)} />

              {portfolio.tokens.length > 0 && (
                <div>
                  <h2 className="font-semibold mb-2">Tokens ({portfolio.tokens.length})</h2>
                  <ul className="space-y-1 list-none pl-0">
                    {portfolio.tokens.map((t: any) => {
                      const info = t.token_info ?? {};
                      const amount = (info.balance ?? 0) / 10 ** (info.decimals ?? 0);
                      return (
                        <li key={t.id} className="flex items-center gap-3">
                          <img
                            src={t.content?.links?.image ?? ""}
                            alt=""
                            className="w-8 h-8 rounded-full bg-zinc-200 shrink-0"
                          />
                          <span className="flex-1 truncate">
                            {t.content?.metadata?.name ?? info.symbol ?? "Unknown token"}
                          </span>
                          <span className="font-mono text-sm">
                            {amount.toLocaleString(undefined, { maximumFractionDigits: 4 })}
                          </span>
                        </li>
                      );
                    })}
                  </ul>
                </div>
              )}

              {portfolio.nfts.length > 0 && (
                <div>
                  <h2 className="font-semibold mb-2">NFTs ({portfolio.nfts.length})</h2>
                  <div className="grid grid-cols-3 gap-3">
                    {portfolio.nfts.map((n: any) => (
                      <img
                        key={n.id}
                        src={n.content?.files?.[0]?.uri ?? n.content?.links?.image ?? ""}
                        alt={n.content?.metadata?.name ?? "NFT"}
                        className="rounded-lg aspect-square object-cover border"
                      />
                    ))}
                  </div>
                </div>
              )}
            </section>
          )}

          {history.length > 0 && (
            <section>
              <h2 className="font-semibold mb-2">History</h2>
              <ul className="font-mono text-sm space-y-1 list-none pl-0">
                {history.map((tx) => (
                  <li key={tx.signature}>
                    {tx.err ? "❌" : "✅"}{" "}
                    <a
                      href={`https://orbmarkets.io/tx/${tx.signature}`}
                      target="_blank"
                      rel="noreferrer"
                      className="underline hover:text-black"
                    >
                      {tx.signature.slice(0, 16)}…
                    </a>
                  </li>
                ))}
              </ul>
            </section>
          )}

          {live.length > 0 && (
            <section>
              <h2 className="font-semibold mb-2">Live ⚡</h2>
              <ul className="font-mono text-sm space-y-1 list-none pl-0">
                {live.map((e, i) => (
                  <li key={i}>
                    <a
                      href={`https://orbmarkets.io/tx/${e.signature}`}
                      target="_blank"
                      rel="noreferrer"
                      className="underline hover:text-black"
                    >
                      {e.signature.slice(0, 16)}…
                    </a>
                  </li>
                ))}
              </ul>
            </section>
          )}
        </main>
      );
    }

    function Stat({ label, value }: { label: string; value: string | number }) {
      return (
        <div className="border rounded-lg p-3">
          <div className="text-xl font-semibold">{value}</div>
          <div className="text-xs text-zinc-500">{label}</div>
        </div>
      );
    }
    ```
  </Step>

  <Step title="Exécutez-le">
    ```bash theme={"system"}
    npm run dev
    ```

    Ouvrez [http://localhost:3000](http://localhost:3000), collez une adresse de portefeuille et cliquez sur **Charger**. Vous verrez le portefeuille et l'historique immédiatement, et le flux en direct se remplira au fur et à mesure que de nouvelles transactions toucheront le portefeuille.
  </Step>
</Steps>

## Ce qui se passe

### Un appel DAS pour l'ensemble du portefeuille

`getAssetsByOwner` avec `showFungible: true` et `showNativeBalance: true` retourne des tokens fongibles, NFT (standard **et** compressé), et le solde SOL natif dans une seule réponse, vous n'avez donc pas besoin d'appels `getBalance` ou `getTokenAccountsByOwner` séparés. Séparez `items` par `interface` pour regrouper les tokens par rapport aux NFT.

Chaque élément possède ses propres métadonnées, donc le même appel alimente les deux listes. Utilisez `content.metadata.name` pour le nom et `content.links.image` pour le logo ou l'œuvre. Les tokens fongibles incluent également un objet `token_info` avec le `balance` brut, `decimals`, et `symbol` — divisez `balance` par `10 ** decimals` pour le montant lisible par l'homme. Tous les tokens n'ont pas de nom ou de logo, donc assurez-vous de prévoir une alternative lorsque ceux-ci manquent.

### Historique complet en une requête

`getTransactionsForAddress` retourne l'historique des transactions du portefeuille, et avec `filters.tokenAccounts: "balanceChanged"`, il inclut également les transferts sur les comptes token associés au portefeuille, l'activité qu'un simple `getSignaturesForAddress` manquerait. Utilisez `transactionDetails: "full"` pour obtenir des données de transaction complètes au lieu de seulement les signatures.

### Mises à jour en temps réel sans sondage

`transactionSubscribe` envoie un message chaque fois qu'une transaction touche le portefeuille, donc vous ne faites jamais de sondage. Ajouter `tokenAccounts: "balanceChanged"` au filtre correspond également aux comptes token du portefeuille, de sorte que les transferts SPL entrants apparaissent dans le flux — la même activité que le comptage de l'historique des appels. Relayer l'abonnement via un gestionnaire de route côté serveur garde votre clé API hors du client.

## Prochaines étapes

<CardGroup cols={2}>
  <Card title="getAssetsByOwner" icon="images" href="/docs/fr/api-reference/das/getassetsbyowner">
    Toutes les options d'affichage, pagination, et la forme complète de la réponse des actifs.
  </Card>

  <Card title="getTransactionsForAddress" icon="clock-rotate-left" href="/docs/fr/rpc/gettransactionsforaddress">
    Filtrage avancé par heure, slot, token, direction, et montant.
  </Card>

  <Card title="transactionSubscribe" icon="bolt" href="/docs/fr/rpc/websocket/transaction-subscribe">
    Options de filtrage et forme du payload pour le flux WebSocket en direct.
  </Card>

  <Card title="Déployez votre propre programme" icon="rocket" href="/docs/fr/quickstart/deploy-program">
    Prochaine étape : déployez un programme Solana sur devnet via Helius.
  </Card>
</CardGroup>
