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)
  1. Construis l’objet sans sig. Omets les champs facultatifs inutilisés (ne les mets pas à null).
  2. Sérialise en JSON canonique : clés triées, sans espaces.
  3. Signe les octets UTF-8 avec la clé du portefeuille. sig = signature en hex.
  4. 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)"
}
  • amount en unités minimales du token (µVEL en l’absence de token). Supérieur à 0.
  • fee suit 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 avec fee à 0 incluent un sceau de travail pow (SHA-512, environ 50 ms sur un téléphone) qui fait partie de ce qui est signé ; buildTransfer de @velum/sdk le calcule. Le format exact est dans PROTOCOL.md et dans spec/vectors/.
  • token facultatif : symbole du token. Sans lui, c’est du VEL qui est envoyé.
  • memo facultatif, 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 nonce d’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 nonce dé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 }
| `faucet` | `{ address }` | `{ id, amount }` (réseau de test uniquement) |

| 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 memo comme 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.
  1. Suis la chaîne bloc par bloc avec chain.head et chain.block sur ton propre nœud.
  2. Ne traite que les transactions transfer vers tes adresses. L’actif est token ou, à défaut, le VEL.
  3. Crédite quand le bloc a 30 confirmations.
  4. 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.
  5. 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 nonce avec account.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 nonce avant 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 :

  1. Dériver la clé ed25519. Avec snap_getBip32Entropy et curve: "ed25519" (SLIP-10) sur m/44'/7420'/0'/0'/0', le même chemin que le SDK. Le 7420 est en attente d’enregistrement dans SLIP-44.
  2. Former l’adresse vel + base58 de la clé publique.
  3. Signer le JSON canonique de la transaction en ed25519 après confirmation de l’utilisateur via snap_dialog.
  4. 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.

  1. 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 le memo.
  2. Affiche un QR code avec l’adresse, le montant et le token (par exemple {{quote}}).
  3. Écoute les blocs via hub?role=watch ou ton nœud et marque la commande comme payée après 6 confirmations.
  4. 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).