Rand Protocol
Documentación
Guías

Puente

El puente de guardianes tal como está construido: un bloqueo en la cadena de origen, una atestación de los guardianes con una cofirma Dilithium2, un BridgeAttest que deposita una nota y un BridgeBurn de un solo paquete que retira; con el formato de transmisión, los siete respaldos de zUSD, las comisiones de zUSD en la acuñación y la quema, los límites de acuñación y la pausa, las rotaciones de rules-v2 con su prueba de posesión y su retraso, y lo que está activo en la chain 20.

Usa esta página para integrarte con el puente: construir un depósito en una cadena de origen, enviar una atestación en Rand, quemar de vuelta un activo puenteado y leer el estado público del puente. La cadena 20, la testnet actual (activa desde el 2026-10-01 06:17 UTC, fullnode v0.6.8), se creó con una sección bridge: ocho guardianes a partir del índice de conjunto de guardianes 1, ocho guardianes poscuánticos, una clave de pausa, emisores para Ethereum, BSC, Tron y Solana (los tres primeros, los endpoints redesplegados el 2026-09-30), el grupo rules-v2, cuyo límite global de acuñación es de 4,000 zUSD por cada 24 horas móviles, un umbral mínimo de repetición entrante, y dos grupos nuevos en v0.6.8: comisiones de 10 bps en cada acuñación y en cada quema, cobradas en zUSD, y reglas de rotación que obligan a que la rotación del conjunto poscuántico o de la clave de pausa demuestre la posesión de las nuevas claves y espere un día. A diferencia del génesis de la cadena 14, el de esta lista zUSD con los siete respaldos y arrastra la custodia que ha ido pasando desde la cadena 14 en cada corte posterior (Carried over from chain 14). rand_getAssets y el /bridge/assets del explorador dan el locked actual de cada respaldo.

El flujo

source chain (Ethereum / BSC / Tron / Solana)                 Rand
┌────────────────────────────────┐                           ┌────────────────────────────────┐
│ lock tokens, emit              │  guardians observe,       │ BridgeAttest transaction        │
│ (amount, token, to, fee)       │  sign mu = keccak²(body)  │   admission → one deposit note  │
└────────────────────────────────┘  threshold n·2/3 + 1      │   computed by the chain          │
              ▲                     + Dilithium2 co-signers  │                                  │
   release after guardian         guardian committee         │ BridgeBurn transaction           │
   signatures on the burn  ◄───── secp256k1 keys ◄─────────  │   one bundle: slots 0–1 burn the │
   message                        read the burn log          │   asset, 2–3 pay the RAND fee;   │
                                                             │   emits a sequenced burn message │
                                                             └────────────────────────────────┘

Entrante: el valor se bloquea en la cadena de origen, los guardianes lo atestan, cualquiera envía la atestación a Rand y el libro mayor añade una nota de depósito para el destinatario: una nota cuya palabra asset es el índice del token puenteado en el registro. Saliente: una transacción de Rand quema notas de ese activo y emite un mensaje; los guardianes atestan ese mensaje; el contrato de la cadena de origen libera los fondos. Rand nunca verifica una cabecera de la cadena de origen ni una prueba de estado: las firmas del comité de guardianes son todo el modelo de confianza.

Modelo de confianza e identificadores de cadena

Identificadores de cadena locales del puente, compartidos con los contratos (bridge-codec):

cadenaid
Rand1
Ethereum2
BSC3
Tron4
Solana5

Los guardianes son un comité de claves secp256k1; la identidad de un guardián son los últimos 20 bytes de keccak256(uncompressed_pubkey[1..]), la derivación de direcciones de Ethereum, de modo que una misma clave funciona en los contratos EVM, en el programa de Solana y en Rand. El quórum para un conjunto de tamaño n es n·2/3 + 1. Toda firma debe ser low-s (s ≤ n/2), lo que descarta la maleabilidad. El digest que firman los guardianes es mu = keccak256(keccak256(body_bytes)). La rotación del conjunto de guardianes es una atestación del emisor de gobernanza (chain 1, keccak256("rand-bridge-governance")), debe llevar new_index == current + 1 y debe estar firmada por el conjunto actual, no por uno reemplazado; el conjunto saliente conserva una ventana de gracia de 86,400 segundos para las transferencias en curso. (Ese es el conjunto ECDSA. El conjunto poscuántico y la clave de pausa rotan mediante sus propias acciones, que en la cadena 20 esperan un retraso de un día: más abajo.) Si n·2/3 + 1 guardianes se confabulan, pueden acuñar valor arbitrario; Bridge hardening acota cuánto y con qué rapidez, pero no elimina ese riesgo.

Vinculación de la transacción. Una transacción de Rand no lleva firma: una quema se autoriza mediante la prueba STARK de su paquete. Desde la cadena 14, toda prueba de paquete se genera sobre Transaction::binding y se verifica contra ella: un hash de la transacción completa (id de cadena, el único paquete con sus cuatro sobres, la acción) con solo los bytes de la prueba en blanco. Desde la cadena 17, un paquete lleva dos pruebas, la del paquete y la de autorización de gasto: la vinculación deja ambas en blanco, conserva el auth_commit del paquete, y ambas se verifican contra ella. Una copia de una quema pendiente con to, relayer_fee o (to_chain, token) modificados, o de una atestación con su time o su sobre modificados, no supera la prueba (TxError::InvalidBundleProof). En la cadena 20, la vinculación también incluye el hash del génesis (genesis binding_domain: 1: rand-tx-bind-2 sobre el hash del génesis, el id de cadena, el paquete y la acción), de modo que tampoco se verifica una prueba generada para otra cadena que comparta el id de cadena. Las carteras y los relayers deben ejecutar un binario compilado para la cadena a la que envían: v0.6.8 en la cadena 20 (v0.6.7 en las cadenas 18 y 19); un rand más antiguo genera pruebas y firmas que la cadena 20 rechaza.

Los demonios del guardián y del relayer no forman parte del full node; el full node incluye el códec compartido y la mitad receptora.

Formato de transmisión

Todo entero es big-endian; la versión del formato es 1.

Sobre de atestación

offsetlencampo
01version = 1
14guardian_set_index
51n_sigs
666·n_sigsfirmas: index (1), r (32), s (32), v (1)
6 + 66·n_sigs…body

Cabecera del cuerpo: 51 bytes, luego la carga útil

offsetlencampo
04timestamp
44nonce
82emitter_chain
1032emitter_address
428sequence
501consistency_level
51…payload

Carga útil Transfer: id 1, 133 bytes

offsetlencampo
01id = 1
132amount (u256; Rand usa los 128 bits inferiores, y un depósito debe caber además en el u64 de una nota)
3332token_address
652token_chain
6732to: el hash del destinatario, véase más abajo
992to_chain
10132fee (u256): la parte de amount que corresponde al relayer

Carga útil GuardianSetUpgrade: id 2: id (1), new_index (4), n (1), y luego n claves de 20 bytes.

Las direcciones de Ethereum, BSC y Tron son de 20 bytes, rellenadas a la izquierda con 12 bytes cero en las ranuras de 32 bytes; una dirección de Solana ocupa toda la ranura. Un Transfer debe provenir del emisor registrado de la cadena para emitter_chain, y token_chain debe ser igual a la cadena emisora. Una atestación pesa como máximo 16 KiB. Ninguna parte del endurecimiento del puente modificó el formato de transmisión, el códec ni los endpoints desplegados en las cadenas de origen: las cofirmas poscuánticas y los mensajes de gobernanza viajan en la transacción de Rand, no en la atestación.

El destinatario de 32 bytes

Una dirección blindada ocupa 1,216 bytes y el formato solo tiene espacio para 32. El campo to lleva un hash de ella:

to = blake3("rand-shielded-recipient" || pk_bytes || kem_ek)     // ShieldedAddress::recipient_hash

El depositante indica el hash en la cadena de origen, los guardianes lo firman, quien envía coloca la dirección completa en la acción BridgeAttest, y el libro mayor recalcula el hash y rechaza cualquier discrepancia; de lo contrario, quien envía podría elegir quién recibe el depósito de otra persona. Las interfaces de las cadenas de origen deben aceptar esta forma; la cartera la imprime. La cadena 11 convirtió brevemente la ranura directamente en el id del receptor; esa forma de dirección se revirtió con la cadena 12 (Accounts and addresses).

Un token, muchos respaldos: zUSD

El registro de activos ya no pertenece al puente. Pasó al registro de tokens RPL a nivel del libro mayor, compartido por los tokens nativos y los puenteados, y la raíz de estado de la cadena se compromete con él como tokens_root. Un token puenteado es una entrada del registro cuya autoridad de acuñación es Bridge { backings }, y ya no corresponde a un único par (chain, token). El primero, zUSD, está respaldado por USDT y USDC bloqueados en cuatro cadenas: Ethereum, Binance Smart Chain y Solana admiten ambas monedas, y Tron solo USDT (Circle descontinuó USDC en Tron):

chain  token (32-byte wire form)                                                  coin  decimals
2      000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7           USDT  6
2      000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48           USDC  6
3      00000000000000000000000055d398326f99059ff775485246999027b3197955           USDT  18
3      0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d           USDC  18
4      000000000000000000000000a614f803b6fd780986a42c78ec9c7f77e6ded13c           USDT  6
5      ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264           USDT  6
5      c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61           USDC  6

En la cadena 20, zUSD es el índice 1, rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx, con el nombre “Shielded USD”, 8 decimales, y figura en el archivo de génesis, como en las cadenas 15 a 19, con el nombre, el símbolo y la sal que tenía en la cadena 14, por lo que su id de activo (32e5ab28c782c663e14da2650a3feb12f16a12db85599f4f62dc169d26f37b1f) y su id rpl1… se mantuvieron sin cambios (explorador: GET /tokens/1). En la cadena 14 se registró en la altura 256 mediante una transacción register_bridged_token.

Backing { chain: u16, token: [u8; 32], decimals: u8, locked: u64, minted_today: u64, mint_day: u32 }

Un par (chain, token) respalda como máximo un token en todo el registro (TokenError::BackingTaken); un token tiene de 1 a 32 respaldos; decimals es el número de decimales propio de la moneda de origen, de 0 a 18, nunca los 8 del token puenteado. El respaldo agrupado es un riesgo declarado: las siete monedas son fungibles hacia y desde zUSD a la par, de modo que la pérdida de paridad de cualquiera de ellas diluye a todos los titulares de zUSD, y no existe un límite en Rand para la proporción de un respaldo.

zUSD es una cesta. Quien lo posee asume el riesgo de cada respaldo listado, no el de la moneda que depositó: un zUSD es un derecho sobre el fondo, contra cualquier respaldo con locked suficiente. Nada fija el precio de un respaldo. Un bloqueo de 1 unidad de cualquier moneda listada acuña 1 zUSD, y una quema puede nombrar cualquier respaldo cuyo locked la cubra, de modo que una moneda que ha perdido su paridad puede depositarse a la par y quemarse el zUSD hacia una moneda que la mantuvo, hasta agotar los respaldos sanos. La pérdida está acotada por las ventanas móviles de acuñación y por la custodia realmente bloqueada: en chain 20 pueden acuñarse como máximo 4,000 zUSD entre todos los respaldos, y 100,000 por respaldo, en cualquier periodo de 24 horas (reglas v2), y no puede salir por un respaldo sano más de lo que ese respaldo contiene. La ventana limita la tasa, no el total: compra el tiempo para pausar las acuñaciones, lo que detiene los nuevos depósitos de la moneda débil mientras las quemas siguen abiertas. No hay límite de reembolso por respaldo ni pausa por respaldo. En chain 14, antes de las reglas v2, el pauser propio de los endpoints de origen y los límites de tasa por token eran el único freno.

El invariante: total_supply == Σ backings.locked. TokenRegistry::lock (un BridgeAttest) suma el monto atestado al locked del respaldo y al total_supply del token en una sola llamada; TokenRegistry::release (un BridgeBurn) resta el monto liberado de ambos. Nada más escribe ninguno de los dos campos, y la hoja del token compromete el locked de cada respaldo en tokens_root, de modo que dos nodos que discrepen sobre uno divergen en la raíz de estado. Los endpoints de origen no cobran comisión al bloquear y deducen el monto atestado completo al liberar, así que el locked de cada respaldo es igual a la custodia de ese endpoint, salvo los mensajes en tránsito. Las comisiones de chain 20 no rompen esto: una comisión es una nota zUSD que sigue emitida y respaldada, de modo que el depósito sigue bloqueando el monto bruto y la quema libera solo amount − fee. En chain 20, el descuento propio de 10 bps de USDT que aplicaban los endpoints al liberar se fijó en 0 en el corte; ahora la comisión se cobra en Rand.

Los montos son unidades de 8 decimales en el protocolo, sean cuales sean los decimales propios de la moneda de origen: un depósito de 1 USDT desde Ethereum (6 decimales) se bloquea como 100_000_000 unidades. Solo el índice 0, RAND, tiene los nueve decimales de esta cadena.

Un token debe estar listado antes de su primer depósito. Ya no existe el registro en la primera aparición: una atestación que nombra un (chain, token) que nadie ha listado se rechaza con BridgeError::UnlistedToken (token … of chain … is not listed on this chain). Los fondos permanecen en la custodia del endpoint de origen hasta que un quórum de guardianes poscuánticos liste la moneda (Listado después del génesis).

asset_id = blake3("rand-bridge-asset" || token_chain (2 bytes BE) || token_address (32 bytes))

rand_bridgeAssetId(token_chain, token_address) sigue calculando esto en cualquier cadena, y es lo que resuelve el (token_chain, token_address) de la carga útil de un Transfer, pero ya no es una clave del registro. Lo que una nota y una billetera llevan es el índice del token, un u32 denso: el índice 0 es RAND y nunca está en el registro. rand_getAssets tiene una fila por respaldo, y todas las filas de un mismo token bajo el mismo índice:

[{ "index": 1, "chain": 2, "token": "000000000000000000000000dac17f…", "asset_id": "…", "decimals": 6,
   "locked": "0", "mint_cap_per_day": "10000000000000", "minted_today": "0", "mint_day": 20716 }]

locked, mint_cap_per_day y minted_today son cadenas decimales en unidades de 8 decimales, como todo monto u64 que sirve el RPC desde chain 14; index, chain, decimals y mint_day siguen siendo números. Los métodos a nivel de token — rand_getTokens, rand_getToken, rand_getTokenSupply — devuelven un token puenteado como una sola fila que lleva todos sus respaldos (tokens RPL). El GET /bridge/tokens del explorador es la lista de permitidos estática de las monedas que acepta el puente.

Entrante: BridgeAttest

Action::BridgeAttest { attestation: Vec<u8>, recipient: ShieldedAddress, r: Word8, time: u32,
                       asset: u32, envelope: Envelope, pq_signatures: Vec<PqSignature> }

PqSignature { index: u8, signature: Vec<u8> }        // Dilithium2, 2,420 bytes

Lo lleva una transacción blindada ordinaria cuyo único paquete de activo oculto paga la base de 0.001 RAND y nada más: su relayer paga por un depositante que aún no tiene RAND. La cadena, y no quien envía, calcula la nota de depósito { pk: recipient.pk, from: 0, amount, asset: <index>, time, r } a partir del monto que firmaron los guardianes, de modo que un relayer no puede inflar ni redirigir una acuñación; su compromiso es la única hoja que una transacción no lleva en el protocolo. En chain 20 el amount de la nota es el bruto menos la comisión de acuñación, y la cadena añade una segunda nota, la comisión, para la tesorería (Comisiones de acuñación y quema).

El blinding se deriva, no se elige. r debe ser

r = blake3("rand-deposit-r-1" || mu)        // read as eight little-endian words; mu = keccak256(keccak256(body))

el digest que firmó el quórum de guardianes; cualquier otra cosa es BridgeError::WrongDepositBlinding, rechazada antes de cualquier trabajo de firmas. El campo permanece en el protocolo —la reconstrucción de campos públicos de la billetera lo lee allí—; solo está restringido, y la regla cubre también el attest de una rotación. Antes de ella, cualquiera que viera la transacción pendiente de un relayer podía enviar la misma atestación con un blinding propio: el destinatario seguía recibiendo una nota, pero la transacción del relayer moría después de haber pagado una prueba. Con r fijado, dos remitentes de una misma atestación en un mismo time nombran una sola nota y el pool los hace entrar en conflicto sobre el compromiso.

time y asset son las dos predicciones del depositante, hechas para que el sobre pueda sellarse antes de que exista la transacción. time se mantiene dentro de la misma ventana que el de un paquete (1,024 bloques en chain 20, según el proof_window_blocks del génesis; 256 antes), y deliberadamente no se deriva: ninguna regla da un time en el que todos los remitentes coincidirían, por lo que un front-runner aún puede elegir otro time dentro de la ventana —una nota distinta del mismo destinatario, monto y activo, que la billetera del destinatario encuentra de la misma manera—, y las dos presentaciones colisionan en cambio sobre el digest de la atestación. asset debe ser igual al índice que el registro ya ha dado a (chain, token), o TxError::AttestAssetMismatch.

El fee de la carga útil de Transfer se transporta y no se paga a nadie: no hay identidad de remitente a quien pagar, así que el depósito es el monto bruto, menos solo la comisión de acuñación propia de la cadena donde el génesis la fija (chain 20). La protección contra repetición es por digest: cada mu consumido entra en el conjunto spent del puente, y el mempool rechaza a un segundo relayer que dispute la misma atestación. Un depósito también está sujeto al límite de acuñación del respaldo, en chain 20 también al límite global, y se rechaza mientras las acuñaciones estén en pausa (B1, reglas v2). Ambos límites cuentan el bruto. En una cadena cuyo génesis fija un piso de repetición, chain 20 incluida, una transferencia cuya sequence esté por debajo del piso de su cadena de origen se rechaza con BelowReplayFloor antes de verificar ninguna firma (Heredado de chain 14).

rand bridge-mint <attestation hex or @file> --pq <JSON or @file> [--to rand1…] [--fee <RAND>] [--no-wait]
rand bridge-rotate <rotation hex or @file> --pq <JSON or @file> [--fee <RAND>] [--no-wait]

--pq es obligatorio: el quórum de cofirmas Dilithium2 de los guardianes como [{"index":0,"signature":"<4840 hex>"},…]. Antes de cualquier prueba, la billetera decodifica la atestación, comprueba el quórum contra los pq_guardians del nodo y el id de cadena, comprueba que el destinatario produce el hash atestado y lee el índice listado; luego deriva r, sella el sobre del destinatario (por el monto neto en una cadena con comisiones) y paga con un paquete propio de RAND. bridge-rotate envía una rotación del conjunto de guardianes (carga útil 2), que no deposita nada, y rechaza cualquier cosa que no avance en uno el conjunto de Rand. Como cada campo de una nota de depósito es público en esa transacción, la billetera de un destinatario reconstruye las notas de depósito dirigidas a él recorriendo las transacciones bridge_attest confirmadas, sin abrir ningún sobre: un relayer hostil puede sellar basura, pero no puede ocultar la nota.

Saliente: BridgeBurn

Transaction {
  chain_id,
  bundle: <hidden-asset bundle: slots 0–1 spend the token, slots 2–3 pay the RAND fee;
           burn_asset = asset, burn_a = amount, burn_r = 0, fee >= BRIDGE_BURN_FEE>,
  action: BridgeBurn { asset, amount, relayer_fee, to_chain, token, to }
}

Desde el paquete de activo oculto de chain 14, una quema es un solo paquete (antes eran dos): las ranuras 0–1 prueban que quien quema poseía notas del activo que sumaban al menos amount, con burn_a como el valor que sale del pool, y las ranuras 2–3 pagan la comisión en RAND. La comisión es BRIDGE_BURN_FEE, 0.01 RAND, que cubre la base del paquete. relayer_fee es una porción de amount (fee ≤ amount) que el contrato de liberación separa en la cadena de destino, no un cargo adicional, de modo que burn_a es exactamente amount. En chain 20 la cadena también retiene una comisión de quema de 10 bps de amount como nota zUSD para la tesorería, y la liberación es amount − fee; la porción del relayer debe caber dentro de esa liberación (Comisiones de acuñación y quema).

Una quema nombra su moneda. (to_chain, token) debe ser un respaldo del activo quemado (TokenError::NotABacking en caso contrario), y se comprueban dos reglas en el paso de acción, antes de la prueba del paquete:

  • La unidad de liberación. Un respaldo con d < 8 decimales de origen libera en unidades de 10^(8−d) (100 para una moneda de 6 decimales; 1 para una de 18). El monto liberado y relayer_fee deben ser cada uno un múltiplo entero, o TokenError::NotReleasable (… is not a multiple of the release unit …): una fracción por debajo de la unidad mínima de la moneda de origen quedaría varada en la custodia.
  • La custodia propia del respaldo. El monto liberado ≤ locked de ese respaldo, o TokenError::InsufficientBacking (only … is locked in that backing, not the … asked for). El suministro total del token puede cubrir la liberación; lo que tiene que cubrirla es la custodia de este respaldo.

El monto liberado es amount en una cadena sin comisiones y amount − fee en chain 20.

to debe tener la forma correcta (20 bytes rellenados con ceros en las cadenas 2, 3 y 4; nunca todo ceros). El libro mayor registra un BridgeBurnRecord bajo un burn_sequence estrictamente creciente, con el hash de la transacción en la ranura del remitente, y los guardianes leen el registro de quemas:

→ {"method":"rand_getBridgeBurn","params":[13]}
← { "sequence": 13, "amount": "99900000", "fee": "99900", "release_amount": "99800100",
    "body_hex": "…", "digest": "a4bc1c7d…63f4", "tx": "ceb9e3c2…0be7", "height": 2899 }

body_hex es el mensaje saliente exactamente como los guardianes deben calcular su hash y firmarlo; su importe Transfer es release_amount. El registro anterior es la primera quema de la chain 20: 0.999 zUSD quemados hacia USDT en BSC en la altura 2,899, de los cuales 0.000999 fueron al tesoro y 0.998001 se liberaron.

rand bridge-burn <ASSET> <AMOUNT> <TO_CHAIN> <TOKEN> <TO> [--relayer-fee N] [--fee <RAND>] [--no-wait] [--yes]
rand bridge-message <SEQUENCE>       # one outbound message, verbatim, for a guardian to sign

La wallet comprueba primero —a partir de una sola lectura del estado del bridge y del registro de tokens, antes de generar cualquier prueba— que la chain tiene un bridge, que ASSET está en el registro, que TOKEN en TO_CHAIN lo respalda y tiene al menos AMOUNT bloqueado, y que AMOUNT y la comisión del relayer son unidades de liberación enteras. En una chain con comisiones, a continuación muestra la comisión del bridge y el importe que se liberará, y pide confirmación antes de generar la prueba (--yes omite la pregunta). Una quema genera entonces un solo bundle: unos 100 s en un portátil y unos 1.5 MB de prueba y, en la chain 20, como en todos los bundles desde la chain 17, una prueba de autorización de gasto a su lado (unos segundos, unos 1.37 MB).

Comisiones en el mint y la quema

Novedad de fullnode v0.6.8, fijada en la chain 20 por el grupo de génesis

"bridge": { …, "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" } }

la chain cobra una comisión en cada mint y en cada quema a través del bridge, en el token puenteado, en Rand, como una nota blindada a la dirección del tesoro recipient. Cada tasa es como máximo de 100 bps (1 %); en la chain 20 ambas son de 10 bps, 0.1 %. La comisión es ⌊amount · bps / 10⁴⌋ redondeada a la baja a una unidad de liberación entera del respaldo (10^(8 − source decimals): 100 unidades para un USDT de 6 decimales, 1 para una moneda de 18 decimales), de modo que la comisión y el resto son ambos unidades enteras, y una comisión que se redondea a cero no genera nota (ledger/bridge_notes.rs, bridge_fee).

depósito (BridgeAttest) de bruto gquema (BridgeBurn) de amount
comisión⌊g · mint_bps / 10⁴⌋, a la baja hasta una unidad de liberación⌊amount · burn_bps / 10⁴⌋, a la baja hasta una unidad de liberación
el lado del usuariouna nota de depósito de g − feeamount quemado (burn_a == amount, sin cambio del guest); el cuerpo saliente atestigua release_amount = amount − fee, y el endpoint libera exactamente esa cantidad
la nota del tesorofee, blinding blake3("rand-bridge-fee-r-1" ‖ mu), en el time y el activo del depósitofee, blinding blake3("rand-bridge-burn-fee-r-1" ‖ txid), en el time del bundle
locked y total_supplysuben en gbajan en release_amount
se comprueba contralos límites de mint por respaldo y global, sobre gInsufficientBacking y NotReleasable sobre release_amount; la parte del relayer debe caber dentro de ella (FeeExceedsAmount)

Ambas notas se emiten y están respaldadas, de modo que total_supply == Σ locked == custody sigue cumpliéndose: la nota de comisión es zUSD como cualquier otra, y su valor permanece bloqueado en la chain de origen hasta que alguien la queme. BRIDGE_BURN_FEE, los 0.01 RAND que una quema paga en sus ranuras de RAND, no cambia y es independiente.

El primer viaje de ida y vuelta de la chain 20, leído del nodo: un depósito de 1 zUSD desde BSC en la altura 1,664 (4f6e6c54…1a64) dio al depositante 0.999 zUSD y al tesoro 0.001; una quema de 0.999 zUSD de vuelta a USDT en BSC en la altura 2,899 (secuencia 13) retuvo 0.000999 zUSD y liberó 0.998001. Después de ella (leído el 2026-10-01) el respaldo de USDT en BSC contenía 22.001999 zUSD, y el suministro del token, 99.001999 zUSD, era igual a la suma de sus respaldos.

Cómo encuentra el tesoro sus notas. Una nota de comisión no tiene envoltorio: cada una de sus palabras es pública (el importe, a partir de la transacción y de la tasa de génesis; el activo y time, de la transacción; el blinding, derivado; el propietario, el recipient de génesis). La wallet cuya dirección es recipient reconstruye las notas de comisión en la misma pasada pública que reconstruye las notas de depósito, leyendo rand_getBridgeState.fees y los decimales de los respaldos una vez por sincronización. Un nodo que mienta sobre las comisiones solo puede hacer que una nota no se encuentre, nunca acreditar una. La wallet de un depositante valora su propio depósito de la misma manera, bruto menos comisión. Solo una wallet v0.6.8 hace esto; una más antigua no puede enviar en la chain 20 en ningún caso.

Sin el grupo, chains 14 a 19, no hay comisión: un depósito acredita el bruto y una quema libera amount. Hasta la chain 20, la única comisión del bridge era el recorte de 10 bps de USDT que aplicaban los propios endpoints de origen al liberar, en la chain anfitriona; se fijó en 0 en el corte de la chain 20. El grupo está comprometido en el hash de génesis e integrado en la raíz del bridge bajo rand-bridge-fees-1. Fullnode docs/bridge.md §25 contiene la codificación.

Endurecimiento del bridge

Los locked por respaldo y la guarda InsufficientBacking protegen el rescate. No acotan el mint: una atestación con un quórum válido de guardianes acuña lo que dice. Tres capas cierran esa brecha y una cuarta permite listar un token sin un corte de chain. Ninguna modifica los endpoints de la chain de origen, el formato de cable de la atestación ni bridge-codec.

B1: un límite de mint por respaldo y una pausa del mint

El tokens.mint_cap_per_day de génesis se aplica por respaldo: la chain 20 fija 10000000000000, 100,000 zUSD por respaldo y día, como toda chain desde la 14. Sin rules v2 (chain 14), un día es timestamp_ms / 86_400_000 de la marca de tiempo del bloque; con rules v2 (chains 15 a 20) el mismo límite se mide sobre una ventana móvil de cap_window_secs de tiempo de bloque, y el límite global de todo el registro —4,000 zUSD por 86,400 s en la chain 20— es la cota más estricta (rules v2). Un depósito que supera el límite se rechaza con TokenError::MintCapExceeded antes del trabajo de firmas de los guardianes, y es admisible de nuevo cuando la ventana vuelve a tener espacio.

El bridge.pause_key de génesis es una única clave Dilithium2, distinta de toda clave de guardián poscuántica. Dos acciones sin bundle y sin comisión:

  • PauseMints { nonce, signature } — firmada solo por la clave de pausa. Solo puede pausar.
  • UnpauseMints { nonce, pq_signatures } — requiere un quórum de guardianes poscuánticos, nunca la clave de pausa.

Ambas llevan e incrementan pause_nonce, de modo que ninguna se puede reproducir. Mientras está en pausa, todo BridgeAttest de transferencia se rechaza con BridgeError::MintsPaused; las quemas y las rotaciones del conjunto de guardianes siguen abiertas, de modo que una pausa nunca atrapa el rescate. Con rules v2 (chains 15 a 20), el listado también se rechaza mientras está en pausa. La clave de pausa debe custodiarse en una máquina distinta de las claves de los guardianes.

rand bridge-pause --sig <HEX or @file> [--no-wait]        # no key file and no RAND needed
rand bridge-unpause --pq <JSON or @file> [--no-wait]

Qué cubre la pausa: daemons de guardianes engañados o comprometidos para cofirmar un mint falsificado mientras quienes poseen el quórum poscuántico no lo están. No es una defensa contra el robo de ese quórum, que puede reanudar, listar hasta 32 respaldos en un token, cada uno con su propio límite diario, y con rules v2 reemplazar el conjunto poscuántico y la clave de pausa.

B2: marcas de tiempo de bloque acotadas

La marca de tiempo de bloque de una chain con bridge decide la expiración del conjunto de guardianes y el día del límite de mint, por lo que un líder no debe poder saltarla. En una chain con bridge:

  • Una regla de validez, comprobada también en la reproducción: timestamp_ms ≤ parent.timestamp_ms + 60_000, o bien BlockError::TimestampLeap. Junto con la regla de retroceso existente (BlockError::TimestampRewind), el tiempo de bloque solo avanza, como máximo un minuto por bloque.
  • Una regla de voto, nunca una regla de reproducción: un validador retiene su voto a un bloque que esté más de 15,000 ms por delante de su propio reloj.

Por tanto, todo validador de una chain con bridge necesita NTP: uno con más de 15 segundos de desfase es a la vez un líder defectuoso y un votante defectuoso. Mientras una chain se pone al día tras una parada, el tiempo de bloque puede avanzar hasta 60 s por bloque, de modo que un día de límite de mint puede rodar más rápido que un día real: como mucho el límite de un día extra por cada día completo de retraso acumulado.

B3: una cofirma Dilithium2 en cada mint

Todo BridgeAttest, tanto un depósito como una rotación, debe llevar junto a su quórum ECDSA un quórum de firmas Dilithium2 sobre

M = "rand-bridge-pq-cosign-1" || rand_chain_id (u64 BE) || mu        // 63 bytes

mu es el digest que firma el quórum ECDSA; el id de chain evita que una cofirma de testnet se verifique en otra chain de Rand. Las firmas viajan en pq_signatures de la acción, dentro del binding de la transacción, de modo que no se pueden eliminar ni intercambiar en una copia. Se comprueba lo barato antes que lo caro: quorum(n) ≤ len ≤ n (PqNoQuorum), índices estrictamente crecientes (PqIndexOrder), todo índice < n (PqIndexOutOfRange), toda firma de exactamente 2,420 bytes (PqBadSignatureLength) y, por último, que cada una se verifique con pq_guardians[index] (PqBadSignature). Los dos quórums se cuentan de forma independiente. El bridge.pq_guardians de génesis está alineado por índice con el conjunto ECDSA de génesis (el conjunto 1 en la chain 20). Una rotación de payload 2 mueve solo el conjunto ECDSA; sin rules v2 el conjunto poscuántico es fijo durante toda la vida de la chain, y con rules v2 solo RotatePqGuardians lo reemplaza (en la chain 20, su forma V2, con una prueba de posesión y un retraso de un día). La chain 20 tiene ocho, las mismas ocho claves que las chains 15 a 19; un mint necesita seis de ellas (la chain 14 tenía seis y necesitaba cinco).

Esta cosignatura sigue vinculando únicamente el id de la cadena, también en la chain 20: a diferencia de los mensajes de gobernanza que se describen más abajo, no se trasladó detrás del hash de génesis, porque los daemons de guardianes que la producen viven en otro repositorio y no han cambiado. Una copia de una cadena con el mismo id que además copiara el conjunto de guardianes aceptaría las cosignaturas de un depósito hechas para la original; el paquete de comisión de la propia transacción de depósito está vinculado al génesis en la chain 20, pero la atestación que contiene no lo está.

B4: listar un token puenteado después del génesis

Una dirección ordinaria nunca puede crear un token con autoridad Bridge. Dos acciones, cada una con un paquete de comisión ordinario en RAND y cada una bajo el quórum poscuántico:

  • RegisterBridgedToken { name, symbol, salt, chain, token, decimals, nonce, pq_signatures } — un nuevo token puenteado en el siguiente índice, con 8 decimales en Rand, bajo el límite de emisión del génesis, con su primer respaldo. Comisión: la base del paquete más la registration_fee del registro (1 RAND en la chain 20, es decir, 1,001 RAND; la chain 20 quema la comisión de registro en lugar de pagarla al proponente).
  • ListBacking { token_index, chain, token, decimals, nonce, pq_signatures } — un respaldo adicional en un token puenteado ya registrado, que parte de locked = 0. Comisión: la base del paquete.

Ambas llevan e incrementan list_nonce (BadListNonce { expected, got } en caso contrario) y ejecutan las mismas comprobaciones: la cadena tiene un emisor registrado (BridgeError::NoEmitter), (chain, token) todavía no respalda nada, menos de 32 respaldos, decimals ≤ 18.

rand token register-bridged --name … --symbol … --salt <hex32> --chain N --token <hex32> --decimals N --pq <JSON or @file>
rand token list-backing --asset <index> --chain N --token <hex32> --decimals N --pq <JSON or @file>

Dos reglas de orden. Los guardianes firman primero el registro, esperan a que se confirme, leen el índice asignado y solo entonces firman los listados que lo nombran: registrar un token nativo no requiere permisos, de modo que un listado firmado por adelantado podría encontrar su índice ya ocupado. Y una moneda se lista en Rand antes de que setToken la habilite en el endpoint de origen: a la inversa, un depósito podría bloquearse antes de que Rand tenga un respaldo al que acreditar, y su atestación sería rechazada con UnlistedToken hasta que Rand se pusiera al día.

Mensajes de gobernanza

Bytes fijos, nunca bincode; todos los enteros en big-endian:

M_pause    = "rand-bridge-pause-1"        || chain_id u64 || nonce u64
M_unpause  = "rand-bridge-pq-unpause-1"   || chain_id u64 || nonce u64
M_list     = "rand-bridge-pq-list-1"      || chain_id u64 || nonce u64 || token_index u32 || chain u16 || token [32] || decimals u8
M_register = "rand-bridge-pq-register-1"  || chain_id u64 || nonce u64 || u8 len || name || u8 len || symbol || salt [32] || chain u16 || token [32] || decimals u8
M_rotate_pq    = "rand-bridge-pq-rotate-pq-1"    || chain_id u64 || rotation_nonce u64 || count u32 || key [1312] …   // rules v2
M_rotate_pause = "rand-bridge-pq-rotate-pause-1" || chain_id u64 || rotation_nonce u64 || key [1312]            // rules v2

M_pause/M_unpause llevan pause_nonce, M_list/M_register llevan list_nonce, y M_rotate_pq/M_rotate_pause llevan rotation_nonce. count es el número de claves, no una longitud en bytes; cada clave son los 1.312 bytes en bruto de una clave pública Dilithium2, en el orden que tendrá el nuevo conjunto.

En la chain 20 cada mensaje está vinculado al hash de génesis (binding_domain: 1 del génesis): el dígito de versión de la etiqueta pasa a 2 y el hash de génesis de 32 bytes se sitúa entre la etiqueta y el id de la cadena, con el resto sin cambios — M'_pause = "rand-bridge-pause-2" || genesis [32] || chain_id u64 || nonce u64, y lo mismo para rand-bridge-pq-unpause-2, rand-bridge-pq-list-2, rand-bridge-pq-register-2, rand-bridge-pq-rotate-pq-2 y rand-bridge-pq-rotate-pause-2 (bridge/gov.rs). Una clave de pausa o un quórum que firma para una cadena ya no firma para ninguna otra cadena que comparta su id y sus claves. Un archivo firmado en la forma …-1 es rechazado en la chain 20 (BadPauseSignature, PqBadSignature). Las chains 14 a 19 usan los formatos …-1 anteriores, byte por byte.

Reglas v2: rotaciones, límites en ventana móvil, sin listados durante la pausa

Un grupo opcional dentro de la sección bridge del génesis. La chain 14 no lo tenía; todas las cadenas desde la 15 sí, la chain 20 con los mismos valores:

"bridge": { …, "rules_v2": { "global_mint_cap_per_window": 400000000000, "cap_window_secs": 86400 } }
  • Dos rotaciones bajo el quórum poscuántico. RotatePqGuardians { new_pq_guardians, nonce, pq_signatures } reemplaza todo el conjunto poscuántico; RotatePauseKey { new_pause_key, nonce, pq_signatures } reemplaza la clave de pausa. Ambas carecen de paquete y de comisión, como PauseMints, ambas están firmadas por el conjunto poscuántico actual, y ambas llevan e incrementan rotation_nonce, de modo que un quórum firmado autoriza exactamente una rotación. El nuevo conjunto debe tener la misma longitud que el conjunto ECDSA actual, claves de 1.312 bytes, sin repeticiones y sin incluir la clave de pausa. No hay período de gracia: el conjunto o la clave sustituidos no firman nada más. En la chain 20 estas acciones v1 son rechazadas, y una rotación debe demostrar la posesión de las nuevas claves y esperar un día (más abajo).
  • Límites en ventana móvil. El mint_cap_per_day por respaldo se mide sobre una ventana móvil de cap_window_secs en lugar del día natural, y un segundo límite, global para todo el registro, global_mint_cap_per_window, cubre conjuntamente todos los respaldos de todos los tokens (TokenError::GlobalMintCapExceeded). En la chain 20 son 400.000.000.000 unidades, 4.000 zUSD por 86.400 s. Con la regla del día, el límite a las 23:59:59 y de nuevo a las 00:00:01 equivalía al doble del límite en dos segundos; con la ventana, el segundo se rechaza.
  • Sin listados durante la pausa. RegisterBridgedToken y ListBacking son rechazados con MintsPaused mientras mint_paused. Las quemas y ambas rotaciones permanecen abiertas.

Rotación: posesión y un retraso de un día

Las reglas v2 dejaban abiertas tres cuestiones: nada demostraba que alguien poseyera las nuevas claves (un error tipográfico, o una clave que nadie posee, inutilizaría para siempre el quórum poscuántico), una rotación surtía efecto en el bloque en que se incluía, y nada podía detener una rotación hostil. Un segundo grupo opcional junto a rules_v2 (que lo requiere), nuevo en la v0.6.8 (auditoría v6, BRG-14), las resuelve. La chain 20 lo activa:

"bridge": { …, "rotation": { "delay_secs": 86400, "needs_possession": true } }
  • Posesión. Una rotación debe ser RotatePqGuardiansV2 { new_pq_guardians, possession, nonce, pq_signatures } o RotatePauseKeyV2 { new_pause_key, possession, nonce, pq_signatures }: possession[i] es la propia firma Dilithium2 de la nueva clave i sobre exactamente el mismo mensaje que firma el quórum (M_rotate_pq o M_rotate_pause, en su forma …-2 en la chain 20), una por clave y en el orden de las claves, de 2.420 bytes cada una. Las acciones v1 son rechazadas con PossessionRequired. Un recuento o una longitud erróneos se rechazan antes de verificar el quórum; una firma de posesión incorrecta (BadPossession { index }), después.
  • Un retraso. Una rotación aceptada se registra como pendiente, con effective_at_secs = block time + delay_secs, y surte efecto al final del primer bloque cuya marca de tiempo lo alcance. En la chain 20 son 86.400 s, un día. Hasta entonces el conjunto antiguo cosigna cada emisión y la clave de pausa antigua pausa. Aceptar una rotación consume un rotation_nonce y activarla consume otro, de modo que una atestación de emisión agrupada bajo el conjunto antiguo sale del grupo cuando el conjunto cambia. Puede estar pendiente una rotación de cada tipo a la vez (RotationPending { effective_at_secs }).
  • La cancelación. CancelRotation { kind, nonce, signature } (sin paquete y sin comisión) descarta la rotación pendiente de kind (0 = el conjunto poscuántico, 1 = la clave de pausa). La firma la clave de pausa actual, la única clave que se mantiene separada del quórum, sobre "rand-bridge-cancel-rotation-2" || genesis [32] || chain_id u64 || rotation_nonce u64 || kind u8 en la chain 20, y consume el nonce. Por tanto, un quórum capturado no puede rotar el conjunto en menos de un día, y quien tiene la clave de pausa puede detenerlo durante ese día; la clave de pausa entrante no puede cancelar su propia llegada.

El firmante es rand-node bridge-gov (rotate-pq, rotate-pause, cancel-rotation, cada uno con los pasos message, sign y submit; submit vuelve a verificar cada línea de posesión antes de enviar). rand_getBridgeState sirve rotation_rules y pending_rotations; ambos son null en una cadena sin el grupo (chains 14 a 19). La rotación del conjunto de guardianes ECDSA (payload 2) no se ve afectada: conserva sus propias reglas y su período de gracia. No se ha ejecutado ninguna rotación en la chain 20.

Carried over from chain 14

Chain 15 was cut from chain 14’s bridge position (the same emitters and source endpoints), and chains 16, 17 and 18 were each cut from their predecessor’s the same way. Chain 19 was cut from chain 18’s position with one change: its Ethereum, BSC and Tron emitters are the endpoints redeployed on 2026-09-30 (0x7af6b17047c1db6cb54347fdea45cf9179075bfa for chains 2 and 3, 0x6410797df959987a5baf65b5fab97edeb34d5163 for chain 4). Chain 18 never trusted them, so nothing from them was minted there. Chain 20 was cut from a snapshot of chain 19 at height 7,258, with the bridge stopped first, and keeps chain 19’s emitters, guardians, post-quantum keys and pause key unchanged; what it adds is the fees and rotation groups. Solana’s emitter and the Rand-side emitter have not changed since chain 14. What chain 20’s genesis carries:

  • Custodia. 99 zUSD, como dos notas zUSD de génesis (10 y 89 zUSD), respaldados por 22 USDT bloqueados en Ethereum, 22 USDT en BSC, 30 USDT en Tron y 25 USDT en Solana, y 0 en cada respaldo en USDC: el locked de la chain 19 en la instantánea, que coincidía con la custodia de los endpoints. total_supply == Σ locked es igual a la custodia de los endpoints en el bloque 0. Historia: los 10 zUSD que quedaron en la chain 14 fueron una sola nota de génesis en las chains 15 a 19; el génesis de la chain 18 los tenía como 9 USDT bloqueados en Tron y 1 en Solana, y el de la chain 19 como 10 en Solana tras un reequilibrio en la chain 18. Los otros 89 son los depósitos netos de la chain 19, menos sus quemas.
  • Índice de conjunto de guardianes 1, donde ya están los endpoints de origen; la próxima rotación ECDSA debe llevar el índice 2. Las ocho claves poscuánticas y la clave de pausa son las de la chain 15, sin cambios.
  • burn_sequence 13: las quemas de la chain 14 terminaron en 6, el génesis de las chains 15 a 18 empezó en 7, la chain 18 hizo una quema (7) y la chain 19 empezó en 8. La chain 19 hizo cinco quemas, con secuencias de 8 a 12, así que la chain 20 empieza en 13, y ningún endpoint ni daemon indexado por secuencia ve una dos veces. La primera quema de la chain 20 tomó la secuencia 13.
  • Repetición. La acuñación de una chain anterior aún pasaría el quórum ECDSA, pero su cofirma poscuántica vincula el id de esa chain, por lo que la chain 20 la rechaza. El génesis de la chain 15 no tenía un umbral mínimo de repetición entrante (min_inbound_sequence era null), de modo que allí esto descansaba en la cofirma y en que los guardianes nunca cofirmaran un bloqueo antiguo para el nuevo id de chain. Desde la chain 16 el génesis fija uno: por cada chain de origen, la sequence más baja que puede llevar una atestación de transferencia del emisor de esa chain. El de la chain 20 es { "2": 3, "3": 3, "4": 3, "5": 7 }: la siguiente secuencia saliente de cada endpoint cuando se tomó la instantánea de la chain 19, y todo bloqueo anterior ya había acuñado en la chain 19. (El de la chain 18 era { "2": 2, "3": 2, "4": 2, "5": 2 }; el de la chain 19 era { "2": 1, "3": 1, "4": 1, "5": 4 }, donde la secuencia 0 de cada endpoint redesplegado era el bloqueo propio del operador, que nunca debe acuñar.) Una transferencia por debajo del umbral se rechaza con BelowReplayFloor, sea cual sea el quórum y la cofirma que lleve. Las rotaciones del conjunto de guardianes no se juzgan con él. El umbral se fija en el génesis, queda comprometido en el hash del génesis y se incorpora a la raíz del bridge.

Orden de lanzamiento en la chain 14 (historia)

Cómo se puso en marcha el bridge en la chain 14, una testnet anterior, en septiembre de 2026:

  1. Crear la chain con la sección bridge (incluidos pq_guardians y pause_key) y una sección tokens que fije registration_fee y mint_cap_per_day, sin listar ningún token. Hecho el 2026-09-20.
  2. Activar el faucet para que una wallet de despliegue pueda obtener RAND. Hecho.
  3. El desplegador registra zUSD con su primer respaldo bajo la firma del quórum sobre M_register con list_nonce = 0. Hecho en la altura 256.
  4. Los guardianes leen el índice y firman los seis mensajes ListBacking restantes, enviados uno por uno. Hecho: list_nonce marca 7.
  5. Para cada respaldo: listarlo en Rand antes de setToken en el endpoint de origen.
  6. La rotación del conjunto de guardianes (conjunto 0 → conjunto 1, firmada por 5 de 6 del conjunto actual) en los cuatro endpoints de origen, y luego la misma atestación en Rand. Rand marcó el conjunto 0 durante toda la vida de la chain 14; la chain 15 empieza en el conjunto 1.
  7. Un recorrido completo en vivo por cada chain de origen con importes pequeños (entrada por el bridge, acuñación de zUSD, transferencia, quema de vuelta) y una conciliación del locked de cada respaldo (rand_getTokenSupply) con la custodia del endpoint.

Estado público

rand_getBridgeState:

{
  "enabled": true,
  "emitter": "01…",  "emitters": { "2": "02…" },
  "guardian_set_index": 0, "guardians": ["aabb…"],
  "pq_guardians": ["…"],
  "mint_paused": false, "pause_nonce": 0, "list_nonce": 0, "pause_key": "…",
  "registration_fee": "1000000000",
  "burn_sequence": 1,
  "rotation_nonce": 0,
  "rules_v2": { "global_mint_cap_per_window": "400000000000", "cap_window_secs": 86400,
                "global_minted_in_window": "…", "global_mint_headroom": "…" },
  "min_inbound_sequence": null,
  "rotation_rules": null, "pending_rotations": null,
  "fees": null,
  "assets": [ … ]
}

pq_guardians son claves públicas Dilithium2, de 1,312 bytes cada una en hexadecimal. Ya no existe next_index: un token bridged se lista, bajo un índice que fija su registro, antes de poder depositarse, así que no queda ningún índice por predecir. rotation_nonce es lo que debe llevar el próximo M_rotate_pq/M_rotate_pause/M_cancel; rules_v2 es null en una chain sin el grupo (chain 14), y min_inbound_sequence es null en una chain sin umbral de repetición (chain 15 y anteriores); en la chain 20 es { "2": 3, "3": 3, "4": 3, "5": 7 }. rotation_rules, pending_rotations y fees son null en una chain sin esos grupos (chains 14 a 19). El ejemplo de arriba muestra la forma, no los valores de la chain 20: la chain 20 empezó en guardian_set_index 1 y burn_sequence 13, y sirve "rotation_rules": { "delay_secs": 86400, "needs_possession": true }, "pending_rotations": [] y "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" }. Una chain sin sección bridge responde {"enabled": false}.

Sin saldos: el valor bridged son notas, y una wallet lee lo que posee con rand asset-balance [INDEX] a partir de sus propias notas. La raíz del bridge compromete los emisores, los conjuntos de guardianes, los digests gastados, la secuencia de quemas, el conjunto de guardianes poscuántico, y la clave de pausa, el indicador de pausa y los nonces de gobernanza (dos, y rotation_nonce bajo las reglas v2), el umbral de repetición donde el génesis fija uno, y, en la chain 20, las reglas de rotación con cualquier rotación pendiente y el grupo de comisiones; solo se integra en la raíz de estado en una chain que tiene bridge. El registro de tokens, con el locked de cada respaldo, se compromete por separado como tokens_root. Los GET /bridge y GET /bridge/assets del explorador sirven el mismo estado, el segundo con totales de depósitos y quemas por respaldo como cadenas de 8 decimales; GET /tokens y GET /tokens/:id sirven el registro.

Qué es público y qué está oculto

quéquién lo ve
el importe, el índice de activo y la dirección del destinatario de un depósitotodos, en esa única transacción
qué nota gasta después un destinatarionadie
el importe, el activo, la moneda, el destino y la comisión del relayer de una quematodos — los guardianes lo necesitan
el importe, el activo y el propietario de una nota de comisión del bridge, la tesorería (chain 20)todos: se reconstruye a partir de campos públicos
qué notas financiaron una quemanadie
saldos bridgedsolo el titular, o quien tenga una viewing key
conjuntos de guardianes, emisores, el registro con el locked de cada respaldo, el registro de quemastodos

Limitaciones conocidas

De la propia lista del fullnode: no se paga a ningún relayer en Rand; el envelope de un depósito no está vinculado a nada, por lo que un relayer hostil puede sellar basura, y por eso la wallet reconstruye las notas de depósito a partir de campos públicos; los vectores de prueba compartidos a nivel de ledger no se han reconstruido sobre el pool de notas; bridge-burn aún solo conoce la política propia del endpoint de destino después de pagar una prueba; en una chain con bridge se permiten marcas de tiempo de bloque iguales a las del padre, de modo que líderes en connivencia podrían mantener un conjunto de guardianes sustituido dentro de su ventana de gracia; el registro de quemas en memoria no tiene límite; no existe ni está previsto un cliente ligero ni la verificación on-chain del estado de la chain de origen.

Verificado por última vez con el commit c0878c3f del fullnode (cadena 21) el 2026-10-12. Cuando el código y esta página no coinciden, el código tiene razón; avísenos.