API des soldes de tokens de wallet : actifs ERC-20 et historique des transferts
Créez une page des actifs de wallet avec les soldes de tokens ERC-20 non nuls, l'historique des transferts et les métadonnées par lots. Vérifiez la couverture de la chaîne, paginez les résultats et ajustez les montants entiers selon les décimales.
Créez une page des actifs de wallet avec l'API de données de wallet blockchain : utilisez l'API des soldes de tokens pour les avoirs ERC-20 non nuls et l'API des transferts de tokens pour l'historique du wallet. Les développeurs et les agents IA utilisent les mêmes requêtes authentifiées. Avant d'interroger, lisez GET /v1/status et vérifiez les champs data_features et data_status de la chaîne sélectionnée ; la couverture des soldes varie selon la chaîne. Les paramètres de requête et les schémas de réponse se trouvent dans la référence de la Data API.
Tâches que ce guide vous aide à accomplir
- Lire les soldes de tokens du wallet avec une clé et paginer les avoirs ERC-20 non nuls.
- Lire l'historique des transferts du wallet dans une fenêtre de blocs fixe et suivre les curseurs pour l'adresse sélectionnée.
- Compléter les métadonnées des tokens pour afficher les noms et les symboles aux côtés des soldes entiers bruts, en préservant les champs manquants.
Les trois types de données nécessaires à une page des actifs de wallet
Une page des actifs de wallet peut afficher les soldes de tokens ERC-20 d'une adresse, l'historique des transferts de tokens et les métadonnées des tokens. La Data API fournit un point de terminaison pour chacun d'eux :
- Soldes :
GET /{chain}/addresses/{address}/balancesrenvoie les soldes ERC-20 non nuls de l'adresse, triés par adresse detokencroissante, avec lesymbolet lesdecimalsdu token inclus lorsqu'ils sont disponibles. Une adresse sans solde renvoie200avecdata: []. - Transferts :
GET /{chain}/addresses/{address}/transfersrenvoie les transferts de tokens impliquant l'adresse dans une fenêtre de blocs requise, triés par(block_number, log_index)décroissants. - Métadonnées de tokens :
GET /{chain}/tokens/{token}lit le nom, le symbole, les décimales et l'offre totale d'un token par son adresse de contrat ;POST /{chain}/tokens:batchlit les mêmes métadonnées pour un maximum de 100 adresses en une seule requête.
Tous trois utilisent https://api.blockvectra.com/v1/data comme URL de base et l'en-tête de requête x-api-key, avec robinhood_mainnet comme chaîne d'exemple. Ils appartiennent respectivement aux fonctionnalités balances, transfers et token_metadata ; pour connaître les chaînes proposant chaque fonctionnalité, consultez la page des chaînes prises en charge. Sur une chaîne ne disposant pas de la fonctionnalité, le point de terminaison renvoie 422 no_coverage.
Requête 1 : soldes d'adresses
Ce point de terminaison prend moins de paramètres, ce qui en fait une bonne première requête pour une page :
{chain}(paramètre de chemin, obligatoire) : identifiant de chaîne, la valeurchaind'une entrée dansGET /chains(par exemplerobinhood_mainnet). La correspondance est exacte et sensible à la casse ; les alias et les identifiants numériques de chaîne ne sont pas acceptés.{address}(paramètre de chemin, obligatoire) : adresse de 20 octets ; le préfixe0xest facultatif et la casse majuscule ou minuscule est acceptée.limit(paramètre de requête, facultatif) : taille de la page. Vaut 50 par défaut ; les valeurs supérieures à 500 sont ramenées à 500 ;0ou un non-entier renvoie400 bad_request.cursor(paramètre de requête, facultatif) : lenext_cursorde la réponse précédente, renvoyé tel quel pour récupérer la page suivante. Un curseur n'est valide que pour la chaîne, le point de terminaison et les paramètres de requête qui l'ont émis ; le réutiliser ailleurs renvoie400 bad_request.
Il utilise une pagination par jeu de clés (keyset pagination) : next_cursor n'apparaît que lorsqu'il y a une page suivante. Sur la dernière page, la clé est entièrement absente, jamais null.
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances?limit=50" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"L'enveloppe de réponse est AddressBalanceListEnvelope, contenant data et meta. Chaque élément de data est un AddressBalance :
| Champ | Type | Description |
|---|---|---|
token | string (adresse) | Adresse du contrat de token ; la forme canonique est 0x suivi de 40 chiffres hexadécimaux en minuscules. |
balance | string (décimal) | Solde entier brut, pouvant dépasser 2^53, renvoyé sous la forme d'une chaîne décimale brute — jamais un nombre JSON, une notation scientifique ou de l'hexadécimal. |
symbol | string ou null | Symbole du token, ou null lorsqu'il est indisponible. |
decimals | integer ou null | Décimales du token, de 0 à 255, ou null lorsqu'elles sont indisponibles. |
Requête 2 : transferts d'adresses
Le point de terminaison des transferts nécessite une fenêtre de blocs explicite : from_block et to_block sont tous deux obligatoires et doivent satisfaire from_block <= to_block. Il prend quelques paramètres supplémentaires :
standard(paramètre de requête, obligatoire) :erc20ouerc721. Les requêtes restreintes à une adresse ne couvrent paserc1155; le transmettre renvoie422 no_coverage.direction(paramètre de requête, facultatif) :in,outouany; vautanypar défaut et filtre par direction relative à l'adresse.token(paramètre de requête, facultatif) : restreint les résultats à un contrat de token.clamp(paramètre de requête, facultatif) : seule la chaîne littéraletruel'active ; toute autre valeur est traitée commefalse.
Bornes de la fenêtre et finalité : un to_block explicite supérieur à as_of_block renvoie 409 not_indexed_yet à moins que clamp=true ne le tronque à as_of_block ; une fenêtre plus large que la limite de la chaîne (limits.max_window_blocks issu de GET /chains) renvoie 409 window_too_large à moins que clamp=true ne la tronque du côté le plus ancien (en augmentant from_block et en maintenant to_block fixe). Si from_block dépasse déjà lui-même as_of_block, l'erreur 409 persiste même avec clamp=true. Lorsque la fenêtre est tronquée ou partiellement couverte, le champ meta.coverage de la réponse vaut "partial" ; sinon, il vaut "full".
Dans les enregistrements de transferts, les éléments ERC-20 ajoutent amount ; les éléments ERC-721 ajoutent token_id. Les deux incluent token, standard, from, to, block_number, block_timestamp, tx_hash, tx_index et log_index.
export BLOCKVECTRA_API_KEY=rgw_your_api_key
# 1) Lire as_of_block depuis la réponse des soldes.
AS_OF_BLOCK=$(curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" | grep -o '"as_of_block":[0-9]*' | cut -d: -f2)
# 2) clamp=true tronque une fenêtre trop large ou un to_block supérieur à as_of_block, au lieu de renvoyer 409.
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/transfers?standard=erc20&from_block=0&to_block=$AS_OF_BLOCK&direction=any&clamp=true" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"Parcourir tous les transferts avec la pagination
Le champ next_cursor du point de terminaison des transferts d'adresses est optimiste : il n'apparaît que lorsque la page a renvoyé exactement limit lignes, de sorte qu'une page peut comporter un next_cursor tout en s'avérant être la dernière page. Ne vous arrêtez pas lorsqu'une page est vide ; suivez next_cursor jusqu'à ce que la clé soit absente.
Le code ci-dessous récupère chaque transfert dans la fenêtre :
const address = "0x1111111111111111111111111111111111111111";
const head = await fetch(
`https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
{ headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());
const asOfBlock = head.meta.as_of_block;
const transfers: unknown[] = [];
let cursor: string | undefined;
do {
const url = new URL(
`https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/transfers`,
);
url.searchParams.set("standard", "erc20");
url.searchParams.set("from_block", "0");
url.searchParams.set("to_block", String(asOfBlock));
url.searchParams.set("limit", "500");
// clamp truncates from the older end
url.searchParams.set("clamp", "true");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const page = await res.json();
transfers.push(...page.data);
cursor = page.next_cursor; // absent on the last page
} while (cursor);Requête 3 : métadonnées de tokens et tokens:batch
Lisez un seul token avec GET /{chain}/tokens/{token} ; le chemin ne prend que {chain} et {token}, sans pagination. L'enveloppe de réponse est TokenEnvelope, et data est un Token :
| Champ | Type | Description |
|---|---|---|
address | string (adresse) | Adresse du contrat de token. |
standard | string | erc20, erc721 ou unknown. |
name | string ou null | Nom du token, ou null lorsqu'il est indisponible. |
symbol | string ou null | Symbole du token, ou null lorsqu'il est indisponible. |
decimals | integer ou null | Décimales du token, de 0 à 255, ou null lorsqu'elles sont indisponibles. |
total_supply | string ou null | Offre totale brute ; l'API n'applique pas d'ajustement selon les decimals. null lorsqu'elle est indisponible. |
first_seen_block | integer (int64) | Hauteur de bloc où le token a été vu pour la première fois. |
metadata_updated_at | string (horodatage) | Heure UTC de la dernière mise à jour des métadonnées. |
metadata_block | integer (int64) | Hauteur de bloc à laquelle les métadonnées ont été lues. |
metadata_status | string | ok, partial ou unavailable. |
metadata_issues | object | Enregistrements d'anomalies par champ indexés par name, symbol, decimals, total_supply, avec les valeurs reverted, no_data, invalid_encoding ou temporarily_unavailable. |
Un {token} qui n'est pas une adresse valide de 20 octets renvoie 400 bad_request ; un {token} inconnu renvoie 404 not_found ; une {chain} inconnue renvoie 404 unknown_chain.
Le point de terminaison des soldes inclut déjà symbol et decimals lorsqu'ils sont disponibles, mais les deux peuvent valoir null. Pour compléter le nom et les décimales de chaque token dans un wallet, utilisez POST /{chain}/tokens:batch :
- Le corps de la requête est
{"addresses": [...]}avec au maximum 100 adresses par requête ; plus de 100 entrées, ou une entrée qui n'est pas une adresse valide de 20 octets, renvoie400 bad_request(la requête échoue sur la première adresse invalide rencontrée). - Les adresses introuvables ne déclenchent pas d'erreur ; elles sont répertoriées dans
data.missing, tandis quedata.tokenscontient uniquement les tokens dont les métadonnées ont été trouvées. - Les adresses dupliquées sont dédupliquées dans
tokensetmissing, chacune selon l'ordre de première apparition dans la requête.
export BLOCKVECTRA_API_KEY=rgw_your_api_key
# Token unique
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
# Lot : jusqu'à 100 adresses par requête
curl -s -X POST "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"addresses":["0x1111111111111111111111111111111111111111","0x2222222222222222222222222222222222222222"]}'Ajuster les montants selon les décimales
Le champ de solde balance et le champ de transfert ERC-20 amount sont des entiers bruts restitués sous forme de chaînes décimales (UInt256String) ; l'offre totale total_supply d'un token est également un entier brut on-chain sans ajustement de decimals. Pour afficher une quantité lisible par un humain, divisez par les decimals de ce token.
decimalsprovient du champsymbol/decimalspropre à l'élément de solde, ou deGET /{chain}/tokens/{token}etPOST /{chain}/tokens:batch; il peut valoirnull.- Ces valeurs pouvant dépasser
2^53, n'effectuez pas l'opération arithmétique avec un nombre JSON : utilisezBigInten TypeScript etDecimalen Python, en analysant la chaîne décimale telle quelle pour éviter toute perte de précision.
function toDisplayAmount(raw: string, decimals: number | null): string {
if (decimals === null) return raw; // pas de métadonnées de décimales : conserver l'entier brut
const value = BigInt(raw);
const base = 10n ** BigInt(decimals);
const whole = value / base;
const fraction = (value % base)
.toString()
.padStart(decimals, "0")
.replace(/0+$/, "");
return fraction ? `${whole}.${fraction}` : whole.toString();
}
// balance.balance est une chaîne décimale brute ; decimals provient du même élément ou de tokens:batch.
const display = toDisplayAmount(balance.balance, balance.decimals);Fraîcheur des données
Chaque réponse réussie ciblée sur une chaîne comporte meta :
as_of_block: le bloc le plus récent entièrement écrit de la chaîne. Les points de terminaison délimités par des blocs fournissent des données jusqu'à cette hauteur.safe_block: un marqueur indiquant l'étiquette de bloc de consensussafedu nœud (nulltant qu'il est inconnu). Jamais inférieur àfinalized_block, et ne tronque, ne rejette ni ne retarde les réponses.finalized_block: un marqueur indiquant l'étiquette de bloc de consensusfinalizeddu nœud (nulltant qu'il est inconnu). Il ne tronque, ne rejette ni ne retarde les réponses ; les clients décident du niveau de sécurité requis à partir du marqueur (comme le statut de confirmation).coverage:"full"ou"partial". Les transferts d'adresses et points de terminaison similaires indiquent"partial"lorsqueclampa restreint la fenêtre servie, ou lorsque la fenêtre commence avant le premier bloc indexé de la chaîne.refreshed_at: moment où les données sous-jacentes à la réponse ont été mises à jour pour la dernière fois (UTC). Peut valoirnull:nullsignifie que l'heure de mise à jour des données est inconnue et qu'elles doivent être traitées comme périmées ; les points de terminaison basés sur des blocs renvoient toujours une valeur.- Il répète également
chain,chain_slugetchain_external_id.
Un schéma courant : lisez meta.as_of_block dès la première réponse pour lire jusqu'au bloc indexé le plus récent, et vérifiez meta.safe_block / meta.finalized_block si vous souhaitez afficher le statut confirmé.
Estimation en CU pour le chargement d'une page
Chaque méthode est facturée selon son poids en CU, lu depuis l'API des forfaits de la plateforme :
Poids en CU par appel
| Méthode | CU par appel |
|---|---|
data.address_balances | 25 |
data.address_transfers | 25 |
data.tokens_batch | 10 |
Un chargement de page (estimé)
1 requête balances + 3 pages de transferts + 1 requête(s) tokens:batch, 5 appels au total, environ 110 CU. L'utilisation réelle dépend du nombre de pages et de tokens.
Pour les décisions de facturation et les réponses d'erreur non facturées, consultez les règles de facturation. Si vous n'avez pas besoin d'un historique de transferts indexé mais des logs des blocs les plus récents, lisez d'abord Données récentes de nœud vs historique indexé avant de décider de basculer vers eth_getLogs.
Prochaines étapes
- Parcourir le catalogue des jeux de données pour découvrir chaque jeu de données indexé par BlockVectra.
- Consulter le forfait gratuit et les tarifs pour vérifier ce que comprend votre compte.
- Se connecter à la console pour créer une API key.
Dernière mise à jour :
Traces de transaction
Reconstruisez les arbres d'appels d'exécution d'une transaction : la méthode JSON-RPC debug_traceTransaction avec ses traceurs autorisés et ses garde-fous, et les points de terminaison getTransactionTrace et getBlockTraces de la Data API avec leurs limites de couverture.
RPC personnalisé du wallet
Ajoutez une URL RPC BlockVectra à MetaMask ou Rabby. Trouvez les Chain ID et symboles natifs, configurez une API key dans le chemin et gérez une clé dédiée au wallet.