Carteras y exchanges

Esta página es para quien integra Velum: carteras, exchanges, pasarelas de pago y aplicaciones. Velum no es compatible con EVM: tiene su propio formato de direcciones, de transacciones y de firma. Todo lo necesario está aquí.

Resumen técnico

Parámetro Valor
Cadena {{chain}} (campo chain de cada transacción)
Moneda nativa VEL, 6 decimales (1 VEL = 1.000.000 µVEL)
Importes Enteros en la unidad mínima del token
Tiempo de bloque Objetivo 2 s (ritmo real medido en el explorador) · época: 1.800 bloques (≈ 1 h)30 bloques (≈ 60 s)
Curva y firma ed25519 sobre JSON canónico
Dirección vel + base58(clave pública ed25519 de 32 B)
Derivación de cartera BIP-39 (24 palabras) + SLIP-10 ed25519 en m/44'/7420'/cuenta'/0'/índice'
Comisión de pago 0 por debajo de 1.000 € de valor (con prueba de trabajo); 0,01 % desde 1.000 €
Precio Oráculo propio: TWAP de 1 h de {{pair}}
Orden de transacciones nonce por cartera, empieza en 0
Id de transacción 48 primeros hex de SHA-256(JSON canónico con sig)
Confirmaciones recomendadas 6 (pagos), 30 (depósitos en exchanges)
Nodo RPC JSON sobre WebSocket
Explorador API REST de solo lectura en /api

Direcciones {#direcciones}

Una dirección es el prefijo vel seguido de la clave pública ed25519 (32 bytes) en base58 (alfabeto de Bitcoin). Mide entre 46 y 47 caracteres.

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; }
  })();
  • No lleva suma de control. Valida siempre decodificando y comprobando que salen exactamente 32 bytes. Para direcciones introducidas a mano, recomienda copiar y pegar o escanear un QR.
  • Las direcciones que empiezan por vel_ son cuentas especiales sin clave (vel_pool, vel_reserve, vel_escrow). No aceptes retiradas hacia ellas.
  • Los usuarios pueden compartir un alias (@lucia) en lugar de la dirección. Resuélvelo siempre contra la cadena y enseña la dirección resultante antes de enviar.

Derivación de claves {#claves}

Una frase BIP-39 de 24 palabras genera las carteras con SLIP-10 ed25519, todos los niveles endurecidos:

Dato Valor
Ruta m/44'/7420'/cuenta'/0'/índice'
Cartera de la app oficial m/44'/7420'/0'/0'/0'
Moneda SLIP-44 7420 (pendiente de registrar)

Las claves de identidad y buzón de la mensajería salen de la misma semilla con HKDF-SHA512 (velum/v2/identity, velum/v2/mailbox) y no son de cartera: un exchange o una cartera no las necesita.

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);

Con el SDK:

import { walletFromPhrase, isAddress } from "@velum/sdk";

const { address, secret } = walletFromPhrase(phrase, 0, 0);
isAddress(address);                                // true: "vel" + base58(32-byte ed25519 public key)

Es la misma ruta que usan el SDK @velum/sdk, las carteras físicas (Trezor, Ledger) y el Snap de MetaMask. Ver Ecosistema.

Transacciones {#transacciones}

Todas las transacciones comparten estos campos:

Campo Tipo Significado
type string Tipo de transacción
chain string {{chain}}. Si no coincide, se rechaza.
from dirección Cartera que firma
nonce entero Nº de transacciones previas de from (0, 1, 2…)
ts entero (ms) Hora del cliente. Informativa: la cadena usa la hora del bloque.
sig hex (128) Firma ed25519 de la cartera

Firma y JSON canónico

// 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. Construye el objeto sin sig. Omite los campos opcionales que no uses (no los pongas a null).
  2. Serializa en JSON canónico: claves ordenadas, sin espacios.
  3. Firma los bytes UTF-8 con la clave de cartera. sig = firma en hex.
  4. El id es sha256(canónico con sig) en hex, recortado a 48 caracteres.

Pago (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 unidades mínimas del token (µVEL si no hay token). Mayor que 0.
  • fee sigue la regla de v3: 0 si el valor del pago, al precio del oráculo de la red, es menor de 1.000 €; si no, el 0,01 % del importe. Los pagos con fee 0 llevan un sello de trabajo pow (SHA-512, unos 50 ms en un móvil) que forma parte de lo firmado; buildTransfer de @velum/sdk lo calcula. El formato exacto está en PROTOCOL.md y en spec/vectors/.
  • token opcional: símbolo del token. Sin él, se envía VEL.
  • memo opcional, hasta 128 caracteres. Es público: úsalo para referencias de pago, nunca para datos personales.

Alta de nodo (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": "…"
}

Gratis. kind es full (ordenador, produce bloques) o light (móvil). payout es la cartera que cobra.

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": "…" }

Ver Tokens propios para las reglas de cada campo.

Mercado (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 unidades mínimas del token por 1 VEL; amount en µVEL (mínimo 1 VEL). Ver Mercado.

Atestación de dispositivo (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": "…" }

La envía la app de iOS para registrar que es la app original en un iPhone real. Los integradores no necesitan enviarla.

Nonces

  • El primer nonce de una cartera es 0; cada transacción aceptada lo sube en 1.
  • Para el siguiente, usa account.get → nonce + pending (pending = transacciones de esa cartera que esperan en el mempool del nodo).
  • Una transacción con un nonce ya usado se rechaza. Reenviar la misma transacción firmada es seguro: no se cobra dos veces.

Errores habituales

Error Causa
Cadena incorrecta chain no es {{chain}}
Firma no válida Se firmó un JSON no canónico, o con sig dentro
Comisión incorrecta fee no cuadra con la regla de 1.000 € y el precio del oráculo
Nonce no válido Negativo, no entero o repetido
Nota demasiado larga memo de más de 128 caracteres

Finalidad y confirmaciones {#finalidad}

La finalidad es probabilística por ahora (ver Consenso). Las reorganizaciones son cortas, normalmente de uno o dos bloques.

Caso Confirmaciones Tiempo
Chat, propinas 1 Un bloque
Comercio, pasarela de pago 6 Unos segundos
Depósito en exchange 30 ≈ 1 min con el ritmo objetivo
Depósito grande Una época ≈ 1 h≈ 1 min

Una transacción tiene n confirmaciones cuando su bloque tiene la altura h y la cabeza de la cadena está en h + n − 1.

RPC del nodo (WebSocket) {#rpc}

Las apps y los servicios hablan con los nodos con tramas JSON sobre WebSocket:

  • A través de la puerta pública: wss://velum.consultorabim.com/hub?role=client (de respaldo: wss://velum.consultora-bim.workers.dev/hub).
  • Directamente con un nodo: ws://<host>:7420/ws?role=client. Para un exchange, lo recomendable es tener tu propio nodo.
// 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 };

Por la puerta, node es opcional: si no lo pones, la puerta elige un nodo y te dice cuál en la respuesta.

Métodos

Método Parámetros Resultado
chain.head {} { height, hash, ts, node }
chain.block { height? , hash? } Block o 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 }` (solo red de pruebas) |

| shard.put | { shard } | { ok, carrier } | | mailbox.open | { mailboxes: MailboxOpen[] } | { shards, courier } y después eventos push | | receipt.submit | { receipts } | { accepted } | | node.info | {} | Datos del nodo | | node.list | {} | Nodos conocidos | | stats | {} | Contadores del nodo |

Ejemplo sin 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 del explorador {#rest}

El explorador publica una API de solo lectura, con CORS abierto, en https://velum.consultorabim.com/api. Las respuestas son JSON; los errores, { "error": "…" } con su código HTTP.

Ruta Devuelve
GET /api/stats Altura, hash, emisión, Reserva, oferta, nodos, niveles y parámetros
GET /api/oracle Precio de la red: { eur, usd, usdt, ts, source } (sin precio: valores null)
GET /api/blocks?limit=&before= Bloques canónicos, del más nuevo al más viejo (máx. 100)
GET /api/block/:altura-o-hash Detalle del bloque: pagos, créditos, recompensas
GET /api/block/:id?raw=1 El bloque firmado completo (últimos 5.000)
GET /api/tx/:id Transacción, confirmaciones y JSON firmado
GET /api/txs?type=transfer|register|all&limit=&before= Últimas transacciones
GET /api/fragments?limit=&before= Recibos de entrega por bloque
GET /api/nodes Nodos con nivel, entregas, ganancias y estado
GET /api/address/:dirección Saldo, nonce, movimientos y recompensas
GET /api/node/:dirección Igual que la anterior, para nodos
GET /api/search?q= Busca altura, hash, transacción, dirección o nombre de nodo
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"

Para un feed en vivo, conecta a wss://velum.consultorabim.com/hub?role=watch: recibirás snapshot, cada block nuevo y la lista de nodes.

La API del explorador es cómoda para consultar, pero no es una fuente de verdad para mover fondos. Un exchange debe verificar con su propio nodo.

Depósitos y retiradas en exchanges {#exchanges}

Modelo de cuentas

Dos opciones, como en otras cadenas de cuentas:

  • Una dirección por usuario (recomendado). Deriva m/44'/7420'/0'/0'/N' por cliente desde una frase fría. No depende de que el usuario escriba bien un memo.
  • Dirección compartida + memo. Una sola dirección de depósito y el memo como referencia del cliente. Si el memo falta o está mal, el depósito queda en revisión manual.

Detectar depósitos

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. Sigue la cadena bloque a bloque con chain.head y chain.block en tu propio nodo.
  2. Procesa solo transacciones transfer hacia tus direcciones. El activo es token o, si no hay, VEL.
  3. Acredita cuando el bloque tiene 30 confirmaciones.
  4. Guarda el hash de cada bloque procesado. Si en la siguiente pasada ha cambiado, hubo una reorganización: deshaz y vuelve a procesar desde ese bloque.
  5. Barre los fondos a la cartera fría cuando convenga. Los movimientos de menos de 1.000 € no pagan comisión; los mayores, el 0,01 %.

Retiradas

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).
  • Firma en un servicio aislado; el nodo solo recibe transacciones ya firmadas.
  • Pide el nonce con account.get (nonce + pending) y lleva tu propio contador si envías muchas seguidas.
  • Comprueba que la dirección de destino es válida y no es una cuenta especial vel_….
  • Si una retirada no entra en unos bloques, reenvía la misma transacción firmada. No firmes otra con otro nonce hasta saber que la primera no entró.

MetaMask Snaps {#snaps}

MetaMask habla Ethereum, pero los Snaps permiten añadir cadenas que no son EVM. Un Snap de Velum necesita:

  1. Derivar la clave ed25519. Con snap_getBip32Entropy y curve: "ed25519" (SLIP-10) en m/44'/7420'/0'/0'/0', la misma ruta que el SDK. El 7420 está pendiente de registrar en SLIP-44.
  2. Formar la dirección vel + base58 de la clave pública.
  3. Firmar el JSON canónico de la transacción con ed25519 tras pedir confirmación al usuario con snap_dialog.
  4. Enviar la transacción a un nodo por WebSocket (endowment:network-access) o devolverla firmada a 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");
};

Importante:

  • La ruta es la misma, pero la frase es la de MetaMask: la cuenta del Snap no coincide con la de tu app de Velum salvo que uses la misma frase en los dos sitios.
  • Antes de publicarlo, el Snap debe pasar la auditoría y el proceso de inclusión de MetaMask.

Lista para listar VEL en un exchange {#listado}

Lo que suele pedir un exchange grande (Binance, Kraken, Coinbase…) para integrar una cadena nueva, y dónde está cada cosa:

Requisito Estado
Nodo que el exchange pueda ejecutar Monta un nodo: binario sin dependencias, Linux x64/arm64
RPC estable y documentado RPC del nodo
Explorador público Explorador y API REST
Especificación del formato de transacción y firma Transacciones
Regla de finalidad Finalidad
Código abierto del nodo Repositorio público velum-node (en preparación)
Auditoría externa de seguridad Pendiente, antes de la red principal
Red principal velum-mainnet-1, sin grifo ni tokens de prueba
Marco legal (MiCA en la UE) En estudio con asesoría legal

Pasarelas de pago {#pasarelas}

Velum sirve para cobrar en tiempo real: bloques en segundos y comisión 0 en pagos de menos de 1.000 €. Muestra siempre el valor en EUR, USD o USDT con el precio del oráculo de la red y lo que recibe el comercio.

  1. Genera una dirección por pedido (derivando m/44'/7420'/0'/0'/N'), o usa una dirección fija con el id del pedido en el memo.
  2. Muestra un QR con la dirección, el importe y el token (por ejemplo, {{quote}}).
  3. Escucha los bloques por hub?role=watch o por tu nodo y marca el pedido como pagado con 6 confirmaciones.
  4. Si cobras en un token y quieres liquidar en VEL (o al revés), usa el mercado.

SDK @velum/core {#sdk}

El mismo núcleo que usan la app, el nodo y esta web. TypeScript, sin dependencias nativas: funciona en Bun, Node, navegadores, Workers y React Native.

Conectar

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

Enviar un mensaje

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`));

Pagar

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();

Poner una orden

// 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 });

Crear 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)

El SDK también exporta los constructores de transacciones (makeTransfer, makeRegister, makeTokenCreate, makeTokenMint, makePoolFund, makeOrder, makeCancel), las lecturas sobre el estado (supplyInfo, difficulty, bookOf, tradesOf) y los formatos de importe (formatAmount, parseAmount).