Afficher les tokens crypto d'un utilisateur dans Laravel Filament
Créez une page 'My Wallet' dans Filament pour afficher le solde ETH et les tokens ERC-20
Apprenez à créer une page FilamentPHP qui affiche le solde crypto et les tokens ERC-20 d'un utilisateur connecté via son wallet Ethereum. Ce guide utilise ethers.js pour interroger la blockchain directement depuis le navigateur, sans backend ni clé API.
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Une application Laravel avec FilamentPHP v4
- L'authentification wallet déjà configurée (voir article précédent)
- Un champ
eth_addresssur le modèle User
Création de la Page Filament
Créez app/Filament/Pages/WalletTokens.php :
namespace App\Filament\Pages;
use BackedEnum;
use Filament\Pages\Page;
use Illuminate\Contracts\Support\Htmlable;
class WalletTokens extends Page
{
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-wallet';
protected string $view = 'filament.pages.wallet-tokens';
protected static ?int $navigationSort = 100;
public static function canAccess(): bool
{
if (! config('metamask.enabled', true)) {
return false;
}
$user = auth()->user();
return $user && ! empty($user->eth_address);
}
public static function shouldRegisterNavigation(): bool
{
return static::canAccess();
}
public static function getNavigationLabel(): string
{
return __('wallet.navigation_label');
}
public function getTitle(): string|Htmlable
{
return __('wallet.page_title');
}
public function getEthAddress(): ?string
{
return auth()->user()?->eth_address;
}
}
La page n'apparaît dans le menu que si :
- La feature wallet est activée (
METAMASK_AUTH_ENABLED=true) - L'utilisateur a un wallet lié à son compte
Configuration des réseaux
Dans la vue, définissez les réseaux supportés avec leurs RPC publics :
const networks = {
mainnet: {
rpc: 'https://eth.llamarpc.com',
symbol: 'ETH',
explorer: 'https://etherscan.io',
coingeckoId: 'ethereum'
},
polygon: {
rpc: 'https://polygon-rpc.com',
symbol: 'MATIC',
explorer: 'https://polygonscan.com',
coingeckoId: 'matic-network'
},
arbitrum: {
rpc: 'https://arb1.arbitrum.io/rpc',
symbol: 'ETH',
explorer: 'https://arbiscan.io',
coingeckoId: 'ethereum'
},
base: {
rpc: 'https://mainnet.base.org',
symbol: 'ETH',
explorer: 'https://basescan.org',
coingeckoId: 'ethereum'
},
sepolia: {
rpc: 'https://rpc.sepolia.org',
symbol: 'SEP',
explorer: 'https://sepolia.etherscan.io',
coingeckoId: null // testnet
}
};
Récupération du solde natif
Utilisez ethers.js pour lire le solde ETH/MATIC :
async function loadNativeBalance() {
const provider = new ethers.JsonRpcProvider(networks[currentNetwork].rpc);
const balance = await provider.getBalance(address);
const formattedBalance = ethers.formatEther(balance);
document.getElementById('native-balance').textContent =
parseFloat(formattedBalance).toFixed(6);
// Conversion USD via CoinGecko
const coingeckoId = networks[currentNetwork].coingeckoId;
if (coingeckoId) {
const res = await fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${coingeckoId}&vs_currencies=usd`
);
const data = await res.json();
const usdValue = (parseFloat(formattedBalance) * data[coingeckoId].usd).toFixed(2);
document.getElementById('native-balance-usd').textContent = `≈ $${usdValue} USD`;
}
}
Lecture des tokens ERC-20
Définissez les tokens populaires à vérifier :
const popularTokens = {
mainnet: [
{ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', decimals: 6 },
{ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', decimals: 6 },
{ address: '0x6B175474E89094C44Da98b954EesC37d7B35C823', symbol: 'DAI', decimals: 18 },
],
polygon: [
{ address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', decimals: 6 },
{ address: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', symbol: 'USDC', decimals: 6 },
],
// ...
};
const ERC20_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)'
];
Pour lire le solde d'un token :
async function loadTokens() {
const provider = new ethers.JsonRpcProvider(networks[currentNetwork].rpc);
const tokens = popularTokens[currentNetwork] || [];
const tokenBalances = [];
for (const token of tokens) {
const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
const balance = await contract.balanceOf(address);
if (balance > 0n) {
const formattedBalance = ethers.formatUnits(balance, token.decimals);
tokenBalances.push({
symbol: token.symbol,
balance: parseFloat(formattedBalance).toFixed(4),
});
}
}
// Afficher les tokens...
}
Vue Blade complète
Créez resources/views/filament/pages/wallet-tokens.blade.php :
<x-filament-panels::page>
<div class="space-y-6">
{{-- Adresse du wallet --}}
<x-filament::section heading="Your Wallet">
<p class="font-mono">{{ $this->getEthAddress() }}</p>
</x-filament::section>
{{-- Sélecteur de réseau --}}
<x-filament::section heading="Network">
<div class="flex gap-2">
<button data-network="mainnet" class="network-btn ...">Ethereum</button>
<button data-network="polygon" class="network-btn ...">Polygon</button>
<button data-network="arbitrum" class="network-btn ...">Arbitrum</button>
</div>
</x-filament::section>
{{-- Solde natif --}}
<x-filament::section>
<x-slot name="heading">
<span id="native-token-name">ETH</span> Balance
</x-slot>
<div id="balance-loading">Loading...</div>
<div id="balance-content" class="hidden">
<span id="native-balance" class="text-3xl font-bold">0.00</span>
<span id="native-symbol">ETH</span>
<p id="native-balance-usd" class="text-sm text-gray-500"></p>
</div>
</x-filament::section>
{{-- Tokens ERC-20 --}}
<x-filament::section heading="Tokens">
<div id="tokens-loading">Loading...</div>
<div id="tokens-content" class="hidden space-y-2"></div>
<div id="tokens-empty" class="hidden text-gray-500">No tokens found.</div>
</x-filament::section>
{{-- Lien explorateur --}}
<a id="explorer-link" href="#" target="_blank" class="fi-btn ...">
View on Block Explorer
</a>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.umd.min.js"></script>
<script>
// Code JavaScript ici...
</script>
</x-filament-panels::page>
Configuration optionnelle
Ajoutez dans config/metamask.php :
'default_network' => env('METAMASK_DEFAULT_NETWORK', 'mainnet'),
Et dans .env :
METAMASK_DEFAULT_NETWORK=mainnet
Avantages de cette approche
Pas de backend nécessaire : ethers.js interroge directement les nœuds RPC publics
Multi-chain : Support facile de plusieurs réseaux (Ethereum, Polygon, Arbitrum, Base...)
Temps réel : Les données sont toujours à jour depuis la blockchain
Gratuit : Utilise des RPC publics, pas besoin de clé API Alchemy/Infura
Privacy : Aucune donnée n'est envoyée à votre serveur
Limitations
- Seuls les tokens populaires prédéfinis sont affichés
- Les RPC publics peuvent avoir des limites de rate
- Pour un usage intensif, envisagez Alchemy ou Infura
Conclusion
En quelques étapes, vous avez créé une page wallet complète dans Filament :
- Affichage du solde natif (ETH, MATIC, etc.)
- Liste des tokens ERC-20 détenus
- Switch entre plusieurs réseaux blockchain
- Conversion en USD via CoinGecko
- Lien direct vers l'explorateur de blocs
Cette page offre à vos utilisateurs une vue d'ensemble de leurs actifs crypto directement dans votre application, sans quitter l'interface Filament.
Obtenez le Code Source Complet
Gagnez du temps et accédez à l'intégralité du projet.
Je veux le code source