Portefeuilles et plateformes d’échange
Cette page s’adresse à ceux qui intègrent Velum : portefeuilles, plateformes d’échange, passerelles de paiement et applications. Velum n’est pas compatible EVM : il a son propre format d’adresses, de transactions et de signature. Tout ce qu’il faut savoir est ici.
Résumé technique
| Paramètre | Valeur |
|---|---|
| Chaîne | {{chain}} (champ chain de chaque transaction) |
| Monnaie native | VEL, 6 décimales (1 VEL = 1 000 000 µVEL) |
| Montants | Entiers dans l’unité minimale du token |
| Temps de bloc | Objectif de 2 s (rythme réel mesuré dans l’explorateur) · époque : 1 800 blocs (≈ 1 h)30 blocs (≈ 60 s) |
| Courbe et signature | ed25519 sur JSON canonique |
| Adresse | vel + base58(clé publique ed25519 de 32 o) |
| Dérivation du portefeuille | BIP-39 (24 mots) + SLIP-10 ed25519 sur m/44'/7420'/compte'/0'/index' |
| Frais de paiement | 0 en dessous d’une valeur de 1 000 € (avec preuve de travail) ; 0,01 % à partir de 1 000 € |
| Prix | Oracle du réseau : TWAP sur 1 h de {{pair}} |
| Ordre des transactions | nonce par portefeuille, à partir de 0 |
| Identifiant de transaction | 48 premiers caractères hex de SHA-256(JSON canonique avec sig) |
| Confirmations recommandées | 6 (paiements), 30 (dépôts sur une plateforme d’échange) |
| Nœud | RPC JSON sur WebSocket |
| Explorateur | API REST en lecture seule sous /api |
Adresses {#direcciones}
Une adresse est le préfixe vel suivi de la clé publique ed25519 (32 octets) en base58 (alphabet de Bitcoin). Elle compte 46 ou 47 caractères.
import { ed25519 } from "@noble/curves/ed25519.js";
import { base58 } from "@scure/base";
// A Velum address is "vel" + base58(32-byte ed25519 public key). No checksum byte:
// validate by decoding and checking the length is exactly 32.
const address = (pub: Uint8Array) => "vel" + base58.encode(pub);
const isAddress = (a: string) =>
a.startsWith("vel") && !a.startsWith("vel_") && (() => {
try { return base58.decode(a.slice(3)).length === 32; } catch { return false; }
})();
- Elle ne contient pas de somme de contrôle. Valide toujours en décodant et en vérifiant qu’on obtient exactement 32 octets. Pour les adresses saisies à la main, recommande le copier-coller ou la lecture d’un QR code.
- Les adresses qui commencent par
vel_sont des comptes spéciaux sans clé (vel_pool,vel_reserve,vel_escrow). N’accepte pas de retraits vers ces adresses. - Les utilisateurs peuvent partager un pseudo (
@lucia) au lieu de l’adresse. Résous-le toujours auprès de la chaîne et affiche l’adresse obtenue avant d’envoyer.
Dérivation des clés {#claves}
Une phrase BIP-39 de 24 mots génère les portefeuilles avec SLIP-10 ed25519, tous les niveaux renforcés :
| Donnée | Valeur |
|---|---|
| Chemin | m/44'/7420'/compte'/0'/index' |
| Portefeuille de l’app officielle | m/44'/7420'/0'/0'/0' |
| Monnaie SLIP-44 | 7420 (enregistrement en attente) |
Les clés d’identité et de boîte aux lettres de la messagerie proviennent de la même graine via HKDF-SHA512 (velum/v2/identity, velum/v2/mailbox) et ne sont pas des clés de portefeuille : une plateforme d’échange ou un portefeuille n’en a pas besoin.
import { mnemonicToSeedSync } from "@scure/bip39";
import { hkdf } from "@noble/hashes/hkdf.js";
import { sha512 } from "@noble/hashes/sha2.js";
import { walletFromPhrase, walletPath } from "@velum/sdk";
// Wallet keys: SLIP-10 ed25519, every level hardened: m/44'/7420'/account'/0'/index'
const hot = walletFromPhrase(phrase, 0, 0); // m/44'/7420'/0'/0'/0' (the official app's wallet)
const user42 = walletFromPhrase(phrase, 0, 42); // one deposit address per customer
walletPath(0, 42); // "m/44'/7420'/0'/0'/42'"
// Messaging keys are NOT wallet keys: they come from the same seed with HKDF-SHA512.
const seed = mnemonicToSeedSync(phrase);
const identity = hkdf(sha512, seed, undefined, new TextEncoder().encode("velum/v2/identity"), 32);
Avec le SDK :
import { walletFromPhrase, isAddress } from "@velum/sdk";
const { address, secret } = walletFromPhrase(phrase, 0, 0);
isAddress(address); // true: "vel" + base58(32-byte ed25519 public key)
C’est le même chemin qu’utilisent le SDK
@velum/sdk, les portefeuilles matériels (Trezor, Ledger) et le Snap MetaMask. Voir Écosystème.
Transactions {#transacciones}
Toutes les transactions partagent ces champs :
| Champ | Type | Signification |
|---|---|---|
type |
string | Type de transaction |
chain |
string | {{chain}}. Si ce n’est pas le cas, elle est rejetée. |
from |
adresse | Portefeuille signataire |
nonce |
entier | Nombre de transactions précédentes de from (0, 1, 2…) |
ts |
entier (ms) | Heure du client. Indicative : la chaîne utilise l’heure du bloc. |
sig |
hex (128) | Signature ed25519 du portefeuille |
Signature et JSON canonique
// Canonical JSON: keys sorted, no whitespace, undefined fields dropped. This is what gets signed and hashed.
export function canonical(v: unknown): string {
if (v === null || typeof v !== "object") return JSON.stringify(v);
if (Array.isArray(v)) return "[" + v.map(canonical).join(",") + "]";
const o = v as Record<string, unknown>;
return "{" + Object.keys(o).filter((k) => o[k] !== undefined).sort()
.map((k) => JSON.stringify(k) + ":" + canonical(o[k])).join(",") + "}";
}
// sig = hex(ed25519.sign(utf8(canonical(tx without "sig")), walletSecret))
// txId = hex(sha256(utf8(canonical(tx with "sig")))).slice(0, 48)
- Construis l’objet sans
sig. Omets les champs facultatifs inutilisés (ne les mets pas ànull). - Sérialise en JSON canonique : clés triées, sans espaces.
- Signe les octets UTF-8 avec la clé du portefeuille.
sig= signature en hex. - L’identifiant est
sha256(canonique avec sig)en hex, tronqué à 48 caractères.
Paiement (transfer)
{
"type": "transfer",
"chain": "{{chain}}",
"from": "vel8Kx3vQ…",
"to": "vel4Jt9mR…",
"amount": 12500000,
"fee": 0,
"token": "tEUR",
"memo": "inv-20481",
"nonce": 17,
"ts": 1793577600000,
"sig": "9f2c…(128 hex)"
}
amounten unités minimales du token (µVEL en l’absence detoken). Supérieur à 0.feesuit la règle v3 : 0 si la valeur du paiement, au prix de l’oracle du réseau, est inférieure à 1 000 € ; sinon, 0,01 % du montant. Les paiements avecfeeà 0 incluent un sceau de travailpow(SHA-512, environ 50 ms sur un téléphone) qui fait partie de ce qui est signé ;buildTransferde@velum/sdkle calcule. Le format exact est dansPROTOCOL.mdet dansspec/vectors/.tokenfacultatif : symbole du token. Sans lui, c’est du VEL qui est envoyé.memofacultatif, jusqu’à 128 caractères. Il est public : utilise-le pour des références de paiement, jamais pour des données personnelles.
Inscription de nœud (register)
{
"type": "register", "chain": "{{chain}}", "from": "vel…node", "nonce": 0, "ts": 1793577600000,
"name": "atico-madrid", "payout": "vel…wallet", "version": "2.0.0",
"kind": "full", "endpoint": "wss://node.example.org:7420/ws",
"sig": "…"
}
Gratuite. kind vaut full (ordinateur, produit des blocs) ou light (téléphone). payout est le portefeuille qui encaisse.
Tokens (token.create, token.mint, pool.fund)
{ "type": "token.create", "chain": "{{chain}}", "from": "vel…", "nonce": 3, "ts": 1793577600000,
"symbol": "CAFE", "name": "Café de barrio", "decimals": 2,
"maxSupply": 100000000, "initial": 2500000, "mintable": true, "fee": 100000000, "sig": "…" }
{ "type": "token.mint", "chain": "{{chain}}", "from": "vel…issuer", "nonce": 4, "ts": 1793577660000,
"symbol": "CAFE", "to": "vel…", "amount": 50000, "fee": 0, "sig": "…" }
{ "type": "pool.fund", "chain": "{{chain}}", "from": "vel…issuer", "nonce": 5, "ts": 1793577720000,
"token": "CAFE", "amount": 1440000, "epochs": 1440, "fee": 0, "sig": "…" }
Voir Tokens personnalisés pour les règles de chaque champ.
Marché (order.place, order.cancel)
{ "type": "order.place", "chain": "{{chain}}", "from": "vel…", "nonce": 9, "ts": 1793577600000,
"pair": "VEL/tEUR", "side": "buy", "price": 12500, "amount": 250000000, "ttl": 1440, "sig": "…" }
{ "type": "order.cancel", "chain": "{{chain}}", "from": "vel…", "nonce": 10, "ts": 1793577660000,
"id": "4c1f0a…", "sig": "…" }
price en unités minimales du token pour 1 VEL ; amount en µVEL (minimum 1 VEL). Voir Marché.
Attestation d’appareil (device.attest)
{ "type": "device.attest", "chain": "{{chain}}", "from": "vel…", "nonce": 0, "ts": 1793577600000,
"device": "e3b0c442…(64 hex = sha256 of the device public key)",
"keyId": "base64…", "attestation": "base64 CBOR…", "sig": "…" }
Envoyée par l’app iOS pour enregistrer qu’il s’agit de l’app d’origine sur un vrai iPhone. Les intégrateurs n’ont pas à l’envoyer.
Nonces
- Le premier
nonced’un portefeuille vaut 0 ; chaque transaction acceptée l’incrémente de 1. - Pour le suivant, utilise
account.get→nonce + pending(pending= transactions de ce portefeuille en attente dans le mempool du nœud). - Une transaction avec un
noncedéjà utilisé est rejetée. Renvoyer la même transaction signée ne présente aucun risque : elle n’est pas débitée deux fois.
Erreurs fréquentes
| Erreur | Cause |
|---|---|
Cadena incorrecta |
chain n’est pas {{chain}} |
Firma no válida |
Le JSON signé n’était pas canonique, ou contenait sig |
Comisión incorrecta |
fee ne respecte pas la règle des 1 000 € et le prix de l’oracle |
Nonce no válido |
Négatif, non entier ou déjà utilisé |
Nota demasiado larga |
memo de plus de 128 caractères |
Les nœuds renvoient ces messages d’erreur en espagnol, tels quels.
Finalité et confirmations {#finalidad}
Pour l’instant, la finalité est probabiliste (voir Consensus). Les réorganisations sont courtes, en général un ou deux blocs.
| Cas | Confirmations | Durée |
|---|---|---|
| Messagerie, pourboires | 1 | Un bloc |
| Commerce, passerelle de paiement | 6 | Quelques secondes |
| Dépôt sur une plateforme d’échange | 30 | ≈ 1 min au rythme visé |
| Gros dépôt | Une époque | ≈ 1 h≈ 1 min |
Une transaction a n confirmations quand son bloc est à la hauteur h et que la tête de la chaîne est à h + n − 1.
RPC du nœud (WebSocket) {#rpc}
Les apps et les services communiquent avec les nœuds par trames JSON sur WebSocket :
- Via la passerelle publique :
wss://velum.consultorabim.com/hub?role=client(de secours :wss://velum.consultora-bim.workers.dev/hub). - Directement avec un nœud :
ws://<host>:7420/ws?role=client. Pour une plateforme d’échange, le mieux est d’avoir son propre nœud.
// Client → gateway (wss://velum.consultorabim.com/hub?role=client) or a node (ws://host:7420/ws?role=client)
type ClientToHub =
| { t: "rpc"; node?: string; id: string; method: RpcMethod; params: any }
| { t: "ping" };
type HubToClient =
| { t: "welcome"; client: string; nodes: PeerInfo[] }
| { t: "nodes"; nodes: PeerInfo[] }
| { t: "rpc_res"; id: string; result?: any; error?: string; node?: string }
| { t: "push"; node: string; event: "shard" | "head"; data: any }
| { t: "pong" };
// Read-only live feed for explorers (wss://…/hub?role=watch)
type HubToWatch =
| { t: "snapshot"; head: { height: number; hash: string } | null; nodes: PeerInfo[]; stats: Record<string, number> }
| { t: "block"; block: Block }
| { t: "nodes"; nodes: PeerInfo[] }
| { t: "flow"; kind: "shard" | "receipt" | "tx"; id: string; from?: string; to?: string; ts: number };
Via la passerelle, node est facultatif : s’il est absent, la passerelle choisit un nœud et l’indique dans la réponse.
Méthodes
| Méthode | Paramètres | Résultat |
|---|---|---|
chain.head |
{} |
{ height, hash, ts, node } |
chain.block |
{ height? , hash? } |
Block ou null |
chain.blocks |
{ from, to } |
Block[] |
account.get |
{ address } |
{ balance, nonce, pending } (VEL en µVEL) |
account.history |
{ address, limit? } |
[{ height, ts, tx }] |
tx.submit |
{ tx } |
{ id } |
| shard.put | { shard } | { ok, carrier } |
| mailbox.open | { mailboxes: MailboxOpen[] } | { shards, courier } puis des événements push |
| receipt.submit | { receipts } | { accepted } |
| node.info | {} | Données du nœud |
| node.list | {} | Nœuds connus |
| stats | {} | Compteurs du nœud |
Exemple sans SDK :
const ws = new WebSocket("wss://velum.consultorabim.com/hub?role=client");
let seq = 0;
const pending = new Map<string, (r: any) => void>();
const rpc = (method: string, params: object) =>
new Promise((resolve, reject) => {
const id = String(++seq);
pending.set(id, (m) => (m.error ? reject(new Error(m.error)) : resolve(m.result)));
ws.send(JSON.stringify({ t: "rpc", id, method, params }));
});
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (m.t === "rpc_res") pending.get(m.id)?.(m), pending.delete(m.id);
};
ws.onopen = async () => {
const head = await rpc("chain.head", {}); // { height, hash, ts, node }
const acc = await rpc("account.get", { address: "vel…" }); // { balance, nonce, pending }
};
API REST de l’explorateur {#rest}
L’explorateur publie une API en lecture seule, avec CORS ouvert, sur https://velum.consultorabim.com/api. Les réponses sont en JSON ; les erreurs, { "error": "…" } avec leur code HTTP.
| Route | Renvoie |
|---|---|
GET /api/stats |
Hauteur, hash, émission, Réserve, offre, nœuds, niveaux et paramètres |
GET /api/oracle |
Prix du réseau : { eur, usd, usdt, ts, source } (sans prix : valeurs null) |
GET /api/blocks?limit=&before= |
Blocs canoniques, du plus récent au plus ancien (100 max.) |
GET /api/block/:hauteur-ou-hash |
Détail du bloc : paiements, crédits, récompenses |
GET /api/block/:id?raw=1 |
Le bloc signé complet (5 000 derniers) |
GET /api/tx/:id |
Transaction, confirmations et JSON signé |
GET /api/txs?type=transfer|register|all&limit=&before= |
Dernières transactions |
GET /api/fragments?limit=&before= |
Reçus de livraison par bloc |
GET /api/nodes |
Nœuds avec niveau, livraisons, gains et état |
GET /api/address/:adresse |
Solde, nonce, mouvements et récompenses |
GET /api/node/:adresse |
Identique à la précédente, pour les nœuds |
GET /api/search?q= |
Cherche une hauteur, un hash, une transaction, une adresse ou un nom de nœud |
curl -s https://velum.consultorabim.com/api/stats
curl -s "https://velum.consultorabim.com/api/blocks?limit=25&before=120000"
curl -s https://velum.consultorabim.com/api/block/118204
curl -s "https://velum.consultorabim.com/api/block/118204?raw=1" # signed block JSON
curl -s https://velum.consultorabim.com/api/tx/4c1f0a9e…
curl -s "https://velum.consultorabim.com/api/txs?type=all&limit=50"
curl -s https://velum.consultorabim.com/api/address/vel8Kx3vQ…
curl -s "https://velum.consultorabim.com/api/search?q=118204"
Pour un flux en direct, connecte-toi à wss://velum.consultorabim.com/hub?role=watch : tu recevras snapshot, chaque nouveau block et la liste des nodes.
L’API de l’explorateur est pratique pour consulter, mais ce n’est pas une source de vérité pour déplacer des fonds. Une plateforme d’échange doit vérifier avec son propre nœud.
Dépôts et retraits sur une plateforme d’échange {#exchanges}
Modèle de comptes
Deux options, comme sur les autres chaînes à comptes :
- Une adresse par utilisateur (recommandé). Dérive
m/44'/7420'/0'/0'/N'par client à partir d’une phrase conservée à froid. Rien ne dépend d’un mémo correctement saisi par l’utilisateur. - Adresse partagée + mémo. Une seule adresse de dépôt, et le
memocomme référence client. Si le mémo manque ou est erroné, le dépôt part en vérification manuelle.
Détecter les dépôts
import { CHAIN_ID } from "@velum/core";
const CONFIRMATIONS = 6; // ≈ 12 s; see "Finality" above
let next = loadCursor(); // last processed height + 1
async function poll() {
const head = (await rpc("chain.head", {})) as { height: number };
while (next <= head.height - CONFIRMATIONS) {
const block = (await rpc("chain.block", { height: next })) as Block;
if (block.header.chain !== CHAIN_ID) throw new Error("wrong chain");
for (const tx of block.txs) {
if (tx.type !== "transfer" || !isOurDepositAddress(tx.to)) continue;
const asset = tx.token ?? "VEL";
credit({ txid: txId(tx), to: tx.to, asset, amount: tx.amount, memo: tx.memo, height: next });
}
saveCursor(++next);
}
}
// Re-check the hash of the last credited blocks on every poll: if it changed, a short reorg happened.
- Suis la chaîne bloc par bloc avec
chain.headetchain.blocksur ton propre nœud. - Ne traite que les transactions
transfervers tes adresses. L’actif esttokenou, à défaut, le VEL. - Crédite quand le bloc a 30 confirmations.
- Conserve le hash de chaque bloc traité. S’il a changé au passage suivant, il y a eu une réorganisation : annule et retraite à partir de ce bloc.
- Transfère les fonds vers le portefeuille froid quand c’est opportun. Les mouvements de moins de 1 000 € ne paient aucuns frais ; les plus importants, 0,01 %.
Retraits
import { makeTransfer, txId } from "@velum/core";
const acc = (await rpc("account.get", { address: hot.address })) as { nonce: number; pending: number };
const tx = makeTransfer(hot.secret, hot.address, userAddress, amountMicro, acc.nonce + acc.pending, { memo: withdrawalId });
const { id } = (await rpc("tx.submit", { tx })) as { id: string }; // id === txId(tx)
// Track id until it is CONFIRMATIONS blocks deep. Re-broadcasting the same signed tx is safe (same nonce).
- Signe dans un service isolé ; le nœud ne reçoit que des transactions déjà signées.
- Demande le
nonceavecaccount.get(nonce + pending) et tiens ton propre compteur si tu en envoies beaucoup à la suite. - Vérifie que l’adresse de destination est valide et n’est pas un compte spécial
vel_…. - Si un retrait n’est pas inclus au bout de quelques blocs, renvoie la même transaction signée. N’en signe pas une autre avec un autre
nonceavant d’être sûr que la première n’est pas passée.
MetaMask Snaps {#snaps}
MetaMask parle Ethereum, mais les Snaps permettent d’ajouter des chaînes non EVM. Un Snap Velum doit :
- Dériver la clé ed25519. Avec
snap_getBip32Entropyetcurve: "ed25519"(SLIP-10) surm/44'/7420'/0'/0'/0', le même chemin que le SDK. Le 7420 est en attente d’enregistrement dans SLIP-44. - Former l’adresse
vel+ base58 de la clé publique. - Signer le JSON canonique de la transaction en ed25519 après confirmation de l’utilisateur via
snap_dialog. - Envoyer la transaction à un nœud par WebSocket (
endowment:network-access) ou la renvoyer signée à la dapp.
{
"version": "0.1.0",
"proposedName": "Velum",
"source": { "shasum": "…", "location": { "npm": { "filePath": "dist/bundle.js", "packageName": "velum-snap", "registry": "https://registry.npmjs.org/" } } },
"initialPermissions": {
"snap_getBip32Entropy": [{ "path": ["m", "44'", "7420'"], "curve": "ed25519" }],
"endowment:rpc": { "dapps": true, "snaps": false },
"snap_dialog": {},
"endowment:network-access": {}
},
"manifestVersion": "0.1"
}
import type { OnRpcRequestHandler } from "@metamask/snaps-sdk";
import { confirmUi } from "./ui"; // renders the transfer for the user
import { ed25519 } from "@noble/curves/ed25519.js";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
import { base58 } from "@scure/base";
import { canonical, CHAIN_ID } from "@velum/core";
// SLIP-10 ed25519 at m/44'/7420'/0'/0'/0' — the same path the Velum SDK and hardware wallets use.
async function walletKey() {
const node = await snap.request({
method: "snap_getBip32Entropy",
params: { path: ["m", "44'", "7420'", "0'", "0'", "0'"], curve: "ed25519" },
});
const secret = hexToBytes(node.privateKey!.slice(2)); // SLIP-10 ed25519 key, "0x…"
const pub = ed25519.getPublicKey(secret);
return { secret, address: "vel" + base58.encode(pub) };
}
export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
const k = await walletKey();
if (request.method === "velum_getAddress") return k.address;
if (request.method === "velum_signTransfer") {
const { to, amount, nonce, token, memo, fee } = request.params as any;
// fee: 0 below 1,000 € at the network oracle price (plus a small proof of work, see PROTOCOL.md), else 0.01 %
const body = { type: "transfer", chain: CHAIN_ID, from: k.address, to, amount, fee, nonce, ts: Date.now(), token, memo };
const ok = await snap.request({ method: "snap_dialog", params: { type: "confirmation", content: confirmUi(body) } });
if (!ok) throw new Error("rejected");
const sig = bytesToHex(ed25519.sign(new TextEncoder().encode(canonical(body)), k.secret));
return { ...body, sig };
}
throw new Error("method not found");
};
À noter :
- Le chemin est le même, mais la phrase est celle de MetaMask : le compte du Snap ne correspond pas à celui de ton app Velum, sauf si tu utilises la même phrase aux deux endroits.
- Avant publication, le Snap doit passer l’audit et le processus d’admission de MetaMask.
Liste de contrôle pour lister le VEL sur une plateforme d’échange {#listado}
Ce que demande généralement une grande plateforme (Binance, Kraken, Coinbase…) pour intégrer une nouvelle chaîne, et où trouver chaque élément :
| Exigence | État |
|---|---|
| Un nœud que la plateforme peut exécuter | Monter un nœud : binaire sans dépendances, Linux x64/arm64 |
| RPC stable et documenté | RPC du nœud |
| Explorateur public | Explorateur et API REST |
| Spécification du format de transaction et de signature | Transactions |
| Règle de finalité | Finalité |
| Code source ouvert du nœud | Dépôt public velum-node (en préparation) |
| Audit de sécurité externe | Prévu, avant le réseau principal |
| Réseau principal | velum-mainnet-1, sans robinet ni tokens de test |
| Cadre juridique (MiCA dans l’UE) | À l’étude avec un conseil juridique |
Passerelles de paiement {#pasarelas}
Velum permet d’encaisser en temps réel : des blocs en quelques secondes et des frais nuls pour les paiements de moins de 1 000 €. Affiche toujours la valeur en EUR, USD ou USDT au prix de l’oracle du réseau, ainsi que ce que reçoit le commerçant.
- Génère une adresse par commande (en dérivant
m/44'/7420'/0'/0'/N'), ou utilise une adresse fixe avec l’identifiant de commande dans lememo. - Affiche un QR code avec l’adresse, le montant et le token (par exemple
{{quote}}). - Écoute les blocs via
hub?role=watchou ton nœud et marque la commande comme payée après 6 confirmations. - Si tu encaisses dans un token et veux régler en VEL (ou l’inverse), utilise le marché.
SDK @velum/core {#sdk}
Le même noyau que celui de l’app, du nœud et de ce site. TypeScript, sans dépendances natives : il fonctionne sous Bun, Node, dans les navigateurs, les Workers et React Native.
Se connecter
import { VelumClient, keysFromPhrase, generatePhrase, createDevice, createAccount } from "@velum/core";
const phrase = generatePhrase("en"); // 24 words — store it offline
const keys = keysFromPhrase(phrase);
const account = createAccount(keys, createDevice("server-1"));
const client = new VelumClient({ account }); // defaults to the public gateways
client.on("status", (s) => console.log("network:", s));
client.connect();
await client.faucet(); // testnet only: 25 VEL
Envoyer un message
client.on("message", (m) => console.log(m.payload.from, m.payload.body));
client.setContact(theirDeviceList); // signed DeviceList received out of band / via QR
const progress = await client.send(theirIdentity, { kind: "text", body: "Hola" });
client.on("progress", (p) => console.log(`${p.accepted}/${p.total} shards accepted`));
Payer
import { parseAmount } from "@velum/core";
const { id } = await client.pay("vel4Jt9mR…", parseAmount("12,5")!); // VEL (µVEL integer)
await client.pay("vel4Jt9mR…", 2_000_000, { token: "tEUR", memo: "order-981" }); // 2 tEUR, fee in VEL
const { balance, nonce } = await client.balance();
Passer un ordre
// Buy 250 VEL at 0.0125 tEUR each. price = smallest token units per 1 VEL (tEUR has 6 decimals).
await client.placeOrder({ token: "tEUR", side: "buy", price: 12_500, amount: 250_000_000, ttl: 1_440 });
Créer un token
await client.createToken({ symbol: "CAFE", name: "Café de barrio", decimals: 2, maxSupply: 100_000_000, initial: 2_500_000, mintable: true });
// Costs 100 VEL. Then reward nodes in CAFE for a day:
// makePoolFund(wallet.secret, wallet.address, nonce, "CAFE", 1_440_000, 1_440)
Le SDK exporte aussi les constructeurs de transactions (makeTransfer, makeRegister, makeTokenCreate, makeTokenMint, makePoolFund, makeOrder, makeCancel), les lectures sur l’état (supplyInfo, difficulty, bookOf, tradesOf) et les formats de montant (formatAmount, parseAmount).