XerisCoin
Especificación técnica

Revisión 2.0.0Octubre de 2026xrs-node 0.1.1Candidata a mainnet · beta federada

XerisCoin es una Layer 1 en la que un único pipeline produce un bloque cada 4 segundos: Proof-of-History ordena localmente, un sorteo ponderado por stake elige al líder y Proof-of-Work con Scrypt produce el bloque. La finalidad es de 64 bloques. Desde el slot 1, cada bloque lleva una firma híbrida del proponente Ed25519 + ML-DSA-65 (Dilithium3). El runtime ejecuta 62 instrucciones tipadas sobre 23 tipos de contrato, 15 de ellos desplegables por usuarios, incluidas la capa de activos del mundo real Alexandria y la capa de agentes Ari. La mainnet se lanzará como beta federada: tres productores del roster, self-staking público cerrado. Esta revisión recoge las revisiones por módulo de CertiK de mayo a octubre de 2026; Xeris ha respondido a cada ronda y ha aplazado dos puntos de consenso, la actualización de dependencias y el modelo de tarifas (§12.3).

1Introducción

XerisCoin es una Layer 1 escrita en Rust (xrs-node 0.1.1). Un slot de 4 s ejecuta tres mecanismos (§2): una cadena de hashes Proof-of-History ordena los eventos localmente y no es una entrada del consenso; un sorteo ponderado por stake entre los validadores con al menos 1,000 XRS elige al líder; Proof-of-Work con Scrypt (N = 4,096, r = 4, p = 1; 2 MiB por hash) produce el bloque.

El runtime ejecuta 62 variantes nativas de instrucción (0–61) bajo un único modelo de tarifas y replay (§3, Apéndice B). Los contratos son 23 plantillas tipadas, sin bytecode; 15 son desplegables por usuarios y 8 son singletons gestionados por el protocolo (§5, Apéndice C). SubDelegate, ZkPrivateTransfer, ZkIdentityProof y PqSignedTransfer se rechazan en el dispatcher (§12.2).

Desde el slot 1, cada bloque lleva una firma híbrida del proponente Ed25519 + ML-DSA-65 (Dilithium3, FIPS 204); ambos componentes deben verificar (§9.1). Las transacciones se firman solo con Ed25519. La finalidad es de 64 bloques, ≈ 4.3 min (§2.4). La mainnet se lanzará como beta federada: tres claves del roster, self-staking público cerrado (§12).

2Consenso

Cada slot de 4 s ejecuta un único pipeline: un registrador de Proof-of-History ejecuta un tick y entrega el número de slot local, un hash y una marca de tiempo de reloj de pared; un sorteo ponderado por stake entre los validadores con al menos 1,000 XRS designa al líder; el productor busca una solución de Proof-of-Work con Scrypt durante como máximo 3.9 s, bajo el objetivo de líder o, como no líder, bajo un objetivo cuatro veces más difícil. El bloque lleva una firma híbrida Ed25519 + ML-DSA-65 del proponente y se difunde. Los validadores recalculan el líder, el objetivo y el trabajo; no recalculan la cadena PoH. La elección de bifurcación se decide por trabajo acumulado y una reorganización sustituye como máximo 64 bloques.

Figura 1. El pipeline del slot
PROOF-OF-HISTORYcadena SHA-256+ subsec_nanosreloj de slot localELECCIÓN DE LÍDERponderada por stakeorden por pubkeysemilla: hash padreMINERÍA SCRYPTN=4096 r=4 p=1plazo de 3.9 s4x tras 2 slotsFIRMA HÍBRIDAEd25519 + ML-DSA-65raíz de Merkledifusión P2PSLOT DE 4 SEGUNDOS

2.1Proof-of-History

El registrador es una cadena SHA-256 sembrada con SHA-256("XERIS_V1_MAINNET_GENESIS_SEED_2026"). El bucle del productor ejecuta un tick por iteración de 4 s, no de forma continua, y el contador de ticks es el reloj de slot local. Cada tick mezcla en la preimagen los nanosegundos transcurridos del reloj monotónico Instant, de modo que dos nodos nunca producen el mismo hash para el mismo slot (XWC-27).

hash_0 = SHA-256("XERIS_V1_MAINNET_GENESIS_SEED_2026") tick: hash_n = SHA-256(hash_{n-1} ‖ "{count}:tick:{nanos}") // nanos = Instant::elapsed().subsec_nanos() count += 1 // count es el slot local time = max(time, wall_clock_ms) // no decreciente bloque: poh_hash = hash_n en la propuesta · poh_timestamp = time (ms)

Los validadores no reproducen la cadena. El consenso comprueba los campos PoH de un bloque según la lista siguiente y nada más. El valor poh_hash queda ligado a la preimagen de PoW y a la firma del proponente; son bytes elegidos por el proponente y comprometidos por trabajo y firma, nunca recalculados por un par.

Tabla 1. Reglas de consenso sobre los campos PoH.
CampoReglaDónde
poh_hashno vacíotoda ruta de admisión
poh_timestampdistinto de cerotoda ruta de admisión
poh_timestamp>= parent.poh_timestamptoda ruta de admisión
poh_timestamp - parent.poh_timestamp>= 2,000 ms; la igualdad fallatoda ruta de admisión
poh_timestamp - reloj de pared<= 2,000 ms (MAX_FUTURE_TIMESTAMP_DRIFT_MS)admisión en vivo e ingesta de bifurcaciones

2.2Proof-of-Work

Todo bloque se mina y se verifica con Scrypt N = 4,096, r = 4, p = 1, salt vacía y salida de 32 bytes (2 MiB por hash). Dos conjuntos de parámetros anteriores permanecen en scrypt_params_for_slot y no pueden activarse: SCRYPT_V2_SLOT y SCRYPT_UPGRADE_SLOT valen ambos 0.

Tabla 2. Conjuntos de parámetros Scrypt en el nodo.
GeneraciónNrpMemoria por hashEstado
Legacy1,02411128 KiBinalcanzable
v1.116,3848116 MiBinalcanzable
v1.24,096412 MiBtodos los bloques

La preimagen lleva etiqueta de dominio y prefijos de longitud desde POW_PREIMAGE_V2_SLOT = 1, de modo que una solución liga un padre, una marca de tiempo y un conjunto de transacciones (XWC-05). El objetivo son 32 bytes de los que solo target[0] es distinto de cero; un byte mayor es un objetivo más fácil. El nonce parte de un u64 aleatorio y se incrementa.

preimage = "XRS_POW_V2" ‖ slot u64 LE ‖ proposer 32 B ‖ len(prev_hash) u32 LE ‖ prev_hash ‖ poh_timestamp u128 LE ‖ len(poh_hash) u32 LE ‖ poh_hash ‖ nonce u64 BE ‖ len(merkle_root) u32 LE ‖ merkle_root hash = scrypt(preimage, salt = "", N = 4096, r = 4, p = 1) → 32 B valid ⇔ hash < target // comparación lexicográfica de 32 bytes

El minado se detiene a los 3.9 s. El productor pierde el slot y las transacciones de la plantilla vuelven al mempool. El objetivo de dificultad se recalcula antes de cada bloque a partir de los valores de poh_timestamp y de la dificultad en curso confirmada tras el padre, en aritmética entera. La reproducción al arranque lo reconstruye desde 0x1f bloque a bloque; la simulación de reorganización lo siembra con el veredicto registrado del ancestro.

Tabla 3. Ajuste de dificultad.
ReglaValor
Byte objetivo de génesis0x1f, también mientras existan menos de 10 bloques
Ventanalos min(len, 20) bloques más recientes
Intervalo objetivo4,000 ms
Pasobase × clamp(avg_ms / 4,000, 0.75, 1.25), punto fijo 1/10,000
Acotaciónde 0x10 (el más difícil) a 0x3f (el más fácil)
Alivio por estancamientoúltimo intervalo (gap) > 12,000 ms: se suma min((gap - 12,000) / 4,000, 0x10), con tope en 0x3f

La continuidad de slots es exacta. En toda ruta de admisión (en vivo, par, reproducción, reorganización) block.slot == parent.slot + 1, o el bloque se rechaza (XWC-10). Un proponente no puede elegir entre slots hijos candidatos ni adelantar estado condicionado por slot, como el desbloqueo de stake.

Un bloque cuyo proponente no es el líder esperado debe cumplir el objetivo de no líder: el objetivo base desplazado dos bits a la derecha (base >> 2, 4× más difícil). Los validadores lo exigen desde LEADER_ENFORCEMENT_ACTIVATION_SLOT = 1: un bloque pasa bajo el objetivo de líder si su proponente es el líder, bajo el objetivo de no líder en otro caso, y se rechaza si no cumple ninguno de los dos (XWC-04). El productor mina como no líder solo tras NON_LEADER_GRACE_SLOTS = 2, es decir, 8,000 ms de silencio de la cadena; es una convención de liveness del productor, no una regla de consenso.

2.3Elección de líder

El líder de un slot es una función determinista de la tabla de stake tras el bloque padre, del hash del padre y del número de slot. Una sola función lo calcula en el productor, en la admisión en vivo y desde pares, en la reproducción al arranque, en la simulación de reorganización y en el evaluador de recuperación.

eligible = { (pk, stake) : stake ≥ 1,000 XRS } // MIN_STAKE_TO_MINE; la única regla si eligible = ∅ → leader = 0x00…00 // sin líder: todo productor mina a base >> 2 ordenar eligible por bytes de pk, ascendente total = Σ stake // u128 seed = SHA-256("XRS_LEADER_V1" ‖ parent.hash ‖ slot u64 LE) raw = u128_be(seed[0..16]) limit = (u128::MAX / total) · total target = raw < limit ? raw mod total : u128_be(SHA-256("XRS_LEADER_V1_RETRY" ‖ seed)[0..16]) mod total leader = primera pk en orden con target < stake acumulado

La elegibilidad es un stake de al menos 1,000 XRS y nada más; no hay ventana de actividad. La semilla usa el hash PoW del padre, no un hash PoH, así que predecir un líder exige minar el padre (C-1). La admisión vuelve a comprobar el stake del proponente contra el mismo umbral desde el slot 1 en todas las rutas. En mainnet solo las tres claves del roster pueden tener stake, así que el sorteo elige entre ellas (§12.1).

2.4Finalidad y elección de bifurcación

No hay finalidad por votación. La elección de bifurcación se decide por trabajo acumulado, y una reorganización sustituye como máximo MAX_REORG_DEPTH = 64 bloques (≈ 4.3 min a 4 s). Un bloque a 64 o más por debajo de la punta es final en la ruta pública; el roster de productores de §12.1 es un control operativo, no un voto de consenso.

Figura 2. Ingesta de bifurcaciones y finalidad. Una rama competidora dentro de 64 bloques se reproduce de forma aislada y se adopta solo si su trabajo sumado es estrictamente mayor.
FINAL (no reversible)puntaCANÓNICARAMA DEL PAR64 bloques · MAX_REORG_DEPTHtrabajo = Σ 2^96 / (target_byte + 1)hijo exacto de la punta → aplicardentro de 64 → retener, reproducir, compararmás allá de 64 → rechazar
Tabla 4. Ingesta de bloques de pares, en orden.
Bloque de parAcción
proponente fuera del roster (mainnet)rechazado
tamaño serializado > 4 MiBrechazado
ya canónicoignorado
previous_hash == tip.hash y slot == tip.slot + 1validado y aplicado directamente
slot fuera de [tip - 64, tip + 64]rechazado
poh_timestamp > reloj de pared + 2,000 msrechazado, se reintenta después
en otro casopuerta de cabecera y luego ForkBuffer

La puerta de cabecera comprueba dimensiones, campos PoH, raíz de Merkle y firma híbrida y recalcula el PoW antes de que un bloque entre en el búfer. ForkBuffer retiene como máximo 256 bloques y 260 MiB. Un hilo de recuperación ensambla ramas de como máximo 65 bloques desde un ancestro canónico a no más de 64 bloques de profundidad y reproduce la cadena candidata y la de referencia en ledgers temporales aislados a través del validador de bloques de producción.

work(block) = 2^96 / (effective_target_byte + 1) effective = target[0] si proposer == líder esperado = (target >> 2)[0] en otro caso adopt ⇔ Σ work(candidate) > Σ work(reference) tie → la rama cuyo primer hijo tras el ancestro tiene la menor identidad de recuperación

El trabajo se puntúa a partir del objetivo contra el que se validó cada bloque, de líder o de no líder, no a partir de los valores de hash obtenidos ni de la altura. Una rama candidata se adopta solo si es estrictamente más pesada. La publicación renombra el ledger candidato sobre el archivo canónico, hace fsync del directorio, intercambia el ledger en memoria, anota en el diario las transacciones desplazadas y reinicia el registrador de PoH en la nueva punta.

Una ruta aparte de sincronización profunda (§10.2) reproduce un historial completo suministrado por el roster bajo la misma comparación de trabajo y puede sustituir bloques a cualquier profundidad. La cota de 64 bloques rige frente a la red pública; no rige frente al roster.

3Bloques, transacciones y ejecución

Un bloque es una cabecera firmada sobre una lista de como máximo 40,000 transacciones comprometida en una raíz de Merkle. Una transacción es un mensaje en formato Solana, firmado con Ed25519, que lleva claves de cuenta, un blockhash reciente y como máximo 16 instrucciones. Un único filtro de admisión se ejecuta en la entrada RPC y P2P; la validación de bloque vuelve a aplicar los mismos topes estructurales. Cada transacción paga una tarifa plana antes de su primera instrucción (§3.4) y sus instrucciones se confirman una a una (§3.5).

3.1Estructura del bloque

Block { slot: u64 hash: Vec<u8> // hash PoW Scrypt de 32 bytes nonce: u64 transactions: Vec<Transaction> // <= 40,000 merkle_root: Vec<u8> proposer: Pubkey // Ed25519 poh_timestamp: u128 // ms de reloj de pared previous_hash: Vec<u8> poh_hash: Vec<u8> proposer_sig: Vec<u8> // vacía desde el slot 1 hybrid_proposer_sig: Option<HybridSignature> // { version: 1, ed25519_sig: 64 B, dilithium3_sig: 3,309 B } proposer_dilithium3_pk: Vec<u8> // 1,952 B, inline }

Un bloque viaja como bincode dentro de una trama con prefijo de longitud de 4 bytes big-endian, de como máximo 5 MiB, y se almacena como una línea JSON por bloque en ledger.dat, con fsync antes de que el bloque se confirme. La cabecera firmada ocupa unos 5.6 KiB; el productor reserva ese espacio antes de llenar la lista de transacciones. hybrid_proposer_sig y la clave inline proposer_dilithium3_pk son obligatorias desde el slot 1 (§9.1; vinculación al registro desde el slot 2, §8.2). El slot 0 no se mina: el registrador PoH avanza de 0 a 1 antes de la primera propuesta, y un bloque del slot 0 debe llevar cero transacciones.

3.2Límites

Tabla 5. Límites de bloque y de transacción.
ConstanteValorSe aplica en
MAX_TXS_PER_BLOCK40,000validación de bloque
MAX_BLOCK_SIZE_BYTES4 MiB serializadosvalidación de bloque; ensamblado por el productor
MAX_IX_PER_TX16entrada; validación de bloque
MAX_ACCOUNTS_PER_TX64entrada; validación de bloque
MAX_IX_DATA_SIZE8 KiB por instrucciónentrada; validación de bloque
MAX_SLASH_IX_DATA_SIZE65,535 B, solo SlashReportentrada; validación de bloque
MAX_GROTH16_VERIFICATIONS_PER_BLOCK64validación de bloque
MAX_RWA_POLICY_WORK_PER_BLOCK256 recorridos de titularesejecución; instrucción rechazada
MAX_MODEL_MUTATIONS_PER_BLOCK16ejecución; instrucción rechazada

Los topes por transacción son un único conjunto de constantes compartido por la entrada y la validación de bloque, de modo que un productor no puede incluir en un bloque lo que el mempool rechazaría (XWC-15).

3.3Admisión de transacciones

Figura 3. Admisión de transacciones. Cada filtro se ejecuta en la entrada RPC y P2P y de nuevo en la validación del bloque.
ESTRUCTURAsanitize()≤16 ix · ≤64 claves≤8 KiB por ixmalformadaFIRMASverificar Ed25519count == requiredfiltro semánticofirma inválidaESTADOno procesadablockhash ≤150 atráspagador ≥ 0.001 XRSreplay / expiradaMEMPOOL50,000 / 64 MiB256 tx por pagadorprioridad planacupoBLOQUE≤40,000 tx · 4 MiBtarifa antes de ix 0confirmación por ixomitida, tarifa cobradarecibo: confirmed · partial · failed

Cada ruta de entrada, POST /submit y los dos manejadores de transacciones P2P, ejecuta un único filtro en orden fijo. sanitize() debe pasar; num_required_signatures es al menos 1; signatures.len() es igual a ese valor; la lista de instrucciones no está vacía y respeta los topes de la Tabla 5; cada instrucción se decodifica como XerisInstruction o como SystemInstruction. Después, tx.verify() comprueba las firmas Ed25519. Después, un filtro semántico sin estado rechaza el SystemInstruction::Transfer heredado, un NativeTransfer cuyo to no es una clave pública canónica o nombra una clave reservada __*, un amount de cero en NativeTransfer, TokenMint o RWATransfer, un AgentExecute o ConditionalOrder que anida otro, y SubDelegate (XWC-82).

Siguen las comprobaciones de estado. La primera firma debe estar ausente del conjunto de procesadas y del mempool. recent_blockhash debe ser uno de los últimos 150 hashes de bloque, evaluado en el slot del bloque siguiente. Un pagador cuyo saldo es inferior a BASE_TX_FEE se encamina al cupo de pagadores sin fondos en lugar de a la cola normal, salvo que la transacción sea una atestación exenta de tarifa (§4.4). El filtro Stake de mainnet está en §12.1.

Tabla 6. Límites del mempool.
Límite del mempoolValor
Entradas50,000
Bytes64 MiB
Por transacción128 KiB
Por pagador256 transacciones; 4 MiB
Transacciones SlashReport128 en el pool; 4 por informante
Pagadores sin fondos5,000 (10 % de las entradas)
Prioridadplana; BASE_TX_FEE para toda transacción
Desalojos por admisión64, las más baratas primero, solo por debajo de la tarifa entrante

Las transacciones de una propuesta en vuelo quedan reservadas durante la ventana entre minado y confirmación y cuentan como presentes para la deduplicación (XWC-17). El orden y el desalojo están en §10.4; los límites de las RPC de escritura, en §10.5.

3.4Tarifas y replay

La tarifa es BASE_TX_FEE = 1,000,000 lamports (0.001 XRS) por transacción, independiente del número y del tamaño de las instrucciones. Se debita de account_keys[0] antes de la instrucción 0 y se acredita al proponente del bloque. Las tarifas no se queman; XRS solo sale del supply mediante slashing. Una transacción cuyo pagador no puede cubrir la tarifa se omite sin ejecutarse, el bloque sigue siendo válido y su firma se registra igualmente como procesada. La única exención es una transacción cuya única instrucción es un ValidatorAttestation que cumple todas las reglas de la Tabla 10 (XWC-60).

recent_blockhash in { hash(b) : b in últimos 150 bloques } // BLOCKHASH_EXPIRY_WINDOW or 0x00..00 (32 bytes) si y solo si la cadena está vacía signatures[0] not in block // duplicada dentro del bloque signatures[0] not in processed_signatures // <= 10,000,000 entradas, FIFO por slot SystemInstruction::Transfer // rechazada desde el slot 0

El blockhash es el hash Scrypt del bloque, servido por getLatestBlockhash (Tabla 21). El conjunto de firmas procesadas se escribe en cada snapshot (§10.3).

3.5Semántica de ejecución

Las instrucciones se ejecutan en el orden del mensaje. Cada una produce un resultado (signature, index, ok) que por defecto es fallido y pasa a ok solo cuando el manejador confirma. Una instrucción fallida no cambia ningún estado y la siguiente se ejecuta igualmente: la confirmación es por instrucción, sin rollback a nivel de transacción. Dentro de un manejador, toda comprobación precede a la primera mutación (llamadas a contrato: §5.1). Los presupuestos de trabajo por bloque de la Tabla 5 pueden rechazar una instrucción por lo demás válida, que entonces falla como cualquier otra.

Tabla 7. Estado de la transacción.
Estado de la transacciónRegla
confirmedtodas las instrucciones confirmadas
partialalgunas confirmadas; first_failed_index indica la primera que no lo fue
failedninguna confirmada, o la transacción nunca se ejecutó

Una transacción omitida en el filtro de tarifa no deja registro y nunca se reporta como historial. Los resultados y los recibos viven en un almacén SQLite derivado de la ejecución; el consenso nunca los lee (§10.5). Un integrador que necesite atomicidad envía una instrucción por transacción.

4Economía

XRS tiene 9 decimales; 1 XRS son 1,000,000,000 lamports. El génesis acredita a la tesorería 8evPjjozSHNcoGRcv7zzxwan9sf3ubJ8q9CFzms6AK97 200,000,000 XRS, de los que 1,000 XRS están en stake y 199,999,000 XRS son líquidos. Cualquier otro XRS se acuña como recompensa con cargo a un único presupuesto, MAX_EMISSION_SUPPLY = 500,000,000 XRS, compartido por minería, staking y atestación. El total nominal es 700,000,000 XRS.

Tabla 8. Suministro.
AsignaciónXRSPorcentajeMecanismo
Tesorería200,000,00028.6 %Saldo de génesis; 1,000 XRS de él en stake desde el génesis
Presupuesto de emisión500,000,00071.4 %Recompensas de minería + staking + atestación; MAX_EMISSION_SUPPLY
Total (nominal)700,000,000100 %Tesorería + presupuesto de emisión
Solo el presupuesto de emisión lo impone el consenso. La clave de la tesorería es la autoridad de acuñación del token envuelto xrs_native (wXRS, suministro máximo 700,000,000), y UnwrapXrs (14) acredita XRS nativo 1:1 sin tocar total_mined. El total de 700,000,000 XRS descansa, por tanto, en que la tesorería no acuñe wXRS, no en una comprobación del protocolo.

4.1Emisión

Cada bloque aceptado acredita a su proponente una recompensa de minería. La recompensa es BASE_BLOCK_REWARD = 10 XRS desplazado un bit a la derecha por cada 25,000,000 slots (HALVING_INTERVAL), con el desplazamiento acotado a 63. Una época son 25,000,000 slots × 4 s ≈ 3.17 años. La recompensa se acota después a la emisión restante tras todas las recompensas anteriores y las recompensas de atestación ya concedidas en el mismo bloque.

reward(slot) = 10 XRS >> min(slot / 25,000,000, 63) reward = min(reward, MAX_EMISSION_SUPPLY − (total_mined + block_attestation_rewards)) época 0 slots 1 – 24,999,999 10 XRS / bloque 249,999,990 XRS (no hay bloque en el slot 0) época 1 slots 25,000,000 – 49,999,999 5 XRS / bloque 125,000,000 XRS época 2 slots 50,000,000 – 74,999,999 2.5 XRS / bloque 62,500,000 XRS época 3 slots 75,000,000 – 99,999,999 1.25 XRS / bloque 31,250,000 XRS época 33 recompensa 1 lamport · desde la época 34 recompensa 0 límite de la serie 500,000,000 XRS · total entero exacto de minería 499,999,989.725 XRS
Figura 4. Emisión acumulada
0M100M200M300M400M500M600M700MH1H2H3H4TOPETESORERÍA0M25M50M75M100MALTURA DE BLOQUE
Las recompensas de minería, staking y atestación salen de un único presupuesto de 500,000,000 XRS. La curva es solo la serie de minería; el total nominal es de 700,000,000 XRS (§4).

Dentro de un bloque el presupuesto se consume en un orden fijo: recompensas de atestación a medida que se ejecutan las transacciones, luego la recompensa de minería, luego las recompensas de staking. Cada paso queda acotado por lo que dejaron los anteriores. total_mined es la suma de los tres y es el único contador que lee el tope.

4.2Recompensas de staking

En cada slot múltiplo de 900 (STAKING_REWARD_INTERVAL, una hora) cada cuenta con al menos 100 XRS en stake recibe un 7 % anual prorrateado sobre su propio stake. BLOCKS_PER_YEAR es 7,884,000, así que hay exactamente 8,760 pagos al año. La recompensa se acredita al saldo líquido; no se capitaliza. Los stakers cobran en orden de bytes de Pubkey; cuando el resto de la emisión no alcanza para todos, las claves anteriores cobran íntegro y la última clave pagable recibe el resto truncado (XWC-52).

trigger slot > 0 y slot % 900 == 0 eligible stake >= 100 XRS r = floor(stake × 7 × 900 / (100 × 7,884,000)) lamports // 1,000 XRS → 7,990,867 lamports por pago budget MAX_EMISSION_SUPPLY − (total_mined + block_attestation_rewards + mining_reward)
Un nuevo Stake debe llevar la cuenta a al menos 1,000 XRS y un Unstake parcial debe dejar 0 o al menos 1,000 XRS. Un stake de 100 a 999 XRS solo puede existir, por tanto, como resto tras un slash. Las recompensas no requieren claim (§12.2).

4.3Stake y unbonding

Stake (9) y Unstake (10) exigen que el firmante sea igual a pubkey. Stake mueve lamports del saldo líquido a la tabla de stake y cuenta en el siguiente sorteo de líder y en el siguiente pago. Unstake los mueve a una entrada de unbonding que vence 151,200 slots después (UNBONDING_PERIOD_SLOTS, 7 días). Tras cada bloque aplicado el nodo libera al saldo líquido toda entrada vencida; ninguna instrucción la reclama. El stake en unbonding no gana nada, no elige a nadie y sigue siendo slasheable. Un Stake o Unstake rechazado es una instrucción fallida (§3.5); su tarifa se cobra igualmente. En mainnet, un Stake que nombre una clave fuera del roster de tres productores se rechaza en toda ruta de entrada y de validación (§12.1).

Tabla 9. Reglas de stake y unbonding.
ReglaValor
Stake resultante de Stake≥ 1,000 XRS (MIN_STAKE_TO_MINE); un primer stake o una ampliación por debajo se rechaza
Importe parcial de Unstake≥ 1 XRS (MIN_UNSTAKE_AMOUNT); la salida total siempre se permite
Resto de Unstake0 o ≥ 1,000 XRS
Entradas pendientes por cuenta≤ 10; un 11.º Unstake se rechaza y el stake se reembolsa
Cola de unbonding≤ 10,000 entradas (MAX_UNBONDING_QUEUE); el exceso se rechaza y se reembolsa
Vencimientocompletion_slot = start_slot + 151,200; se libera tras el primer bloque en ese slot o posterior

4.4Atestación de light client

ValidatorAttestation (12) paga 0.01 XRS (ATTESTATION_REWARD) a un validador con stake que nombre un bloque reciente por slot y hash completo de 32 bytes. La recompensa se acredita al saldo líquido y se descuenta del presupuesto de emisión. Una transacción cuya única instrucción es una atestación válida no paga tarifa (XWC-60).

Tabla 10. Reglas de atestación, en el orden del handler.
ReglaValor
Firmanteigual a validator
Stake≥ 100 XRS (MIN_ATTESTOR_STAKE)
Antigüedadblock.slot − block_slot ≤ 200 (ATTESTATION_SLOT_WINDOW)
Hashblock_hash_prefix es el hash de 32 bytes del bloque en block_slot dentro de recent_blocks (últimos 1,000 bloques)
Deduplicación(block_slot, validator) se paga una sola vez
Tasaninguna atestación previa del validador para un slot > block_slot − 10; una recompensa cada 10 slots
Recompensamin(0.01 XRS, MAX_EMISSION_SUPPLY − (total_mined + block_attestation_rewards))

4.5Slashing

SlashReport (38) es la única ruta de slashing. La ofensa es una doble firma: dos cabeceras de bloque distintas en un mismo slot por un mismo proponente. Cualquier cuenta distinta del infractor puede reportarla. La evidencia es un array JSON de exactamente dos valores Block con listas de transacciones vacías; el handler reconstruye cada payload canónico de firma y verifica la firma del proponente bajo el régimen activo en ese slot, la comprobación híbrida Ed25519 + ML-DSA-65 con la clave PQ ligada al titular en ese slot (XWC-53). La evidencia con más de 302,400 slots de antigüedad (PQ_KEY_HISTORY_RETENTION_SLOTS, 14 días) se rechaza; el límite de tamaño de la evidencia está en la Tabla 5.

offence = dos cabeceras distintas, válidamente firmadas, en un mismo slot por un mismo proponente window = block.slot − violation_slot <= 302,400 slashable = stake activo + entradas de unbonding con completion_slot > block.slot slash = slashable / 10 // primero el stake activo, luego el unbonding de más antiguo a más reciente reporter = slash / 20 // 5 % del slash, al saldo líquido del reportero burned = slash − reporter // 95 %, no se acredita a nadie dedup = SHA-256("XRS_SLASH_OFFENSE_V2" ‖ owner ‖ violation_slot_le64 ‖ lo_digest ‖ hi_digest)

La clave de deduplicación se registra en el contrato de protocolo xeris_slashing_registry, de modo que una ofensa sufre slashing una sola vez con independencia de cómo se codifique o etiquete la evidencia (XWC-30). La parte penalizada de una entrada de unbonding se reduce en la propia entrada y nunca se libera. Un reporte cuyo slash aplicado es cero falla y no registra clave alguna, así que la ofensa sigue siendo reportable (XWC-13). Esta ruta no tiene importe mínimo de slash.

5El motor de contratos

Un contrato es una máquina de estados tipada. El motor define 23 variantes de ContractType, cada una con una estructura de estado fija y un conjunto fijo de métodos; no hay bytecode, ni máquina virtual, ni código aportado por el usuario. ContractDeploy (5) crea una instancia a partir de una cadena de tipo y un objeto JSON de parámetros; ContractCall (4) invoca un método por su nombre. Quince tipos son desplegables por usuarios. Ocho son singletons gestionados por el protocolo bajo ids reservados xeris_*, creados por el protocolo y gobernados solo por sus instrucciones dedicadas: DeviceRegistry, ZkVerifierRegistry, PqKeyRegistry, ConditionalOrderBook, DisputeRegistry, DealRegistry, TaskBoard, StateChannelRegistry. El Apéndice C enumera cada tipo con sus alias.

Figura 5. Los 23 tipos de contrato por grupo. * singleton gestionado por el protocolo, no desplegable por usuarios.
Runtime de XerisInstructionBÁSICOSTimeLockEscrow / MultiSigVestingStateChannelRegistry*DEFISwap (x·y=k)Launchpad (curva)LimitOrder / DcaOrderConditionalOrderBook*ALEXANDRIA (RWA)RealWorldAssetlegal_doc_hashapproved_holdersaccredited_onlyARIAgentRegistryIdentityRegistryCapabilityRegistryOracleRegistry / ModelRegistryARI (PROTOCOLO)TaskBoard*DisputeRegistry*DealRegistry*DeviceRegistry*CRIPTO / GOBERNANZAZkVerifierRegistry*PqKeyRegistry*GovernanceGroth16 · ML-DSA-65

5.1Reglas de despliegue y llamada

Todo despliegue y toda llamada pasan las reglas siguientes antes de que se ejecute la lógica específica del tipo. El llamante que ve un contrato es el pagador de la tarifa de la transacción. Una transacción paga la tarifa plana de 0.001 XRS y nada más; no hay tarifa de despliegue ni de llamada. Las llamadas que mutan estado se ejecutan sobre un clon del contrato, y el clon sustituye al contrato almacenado solo cuando el método devuelve Ok.

Tabla 11. Reglas compartidas por todos los tipos de contrato.
ReglaValor
Id del contrato1–128 caracteres; alfanuméricos (Unicode is_alphanumeric), _ o -
Id existentenunca se sobrescribe; el despliegue se omite
Espacios de nombres de id reservadosxeris_*, identity_*, agent_registry_*, __*, *_xrs_pool: no desplegables por usuarios
Tipos gestionados por el protocoloContractDeploy rechaza los ocho tipos singleton
Parámetros de despliegueparams_json se analiza como objeto JSON; una entrada no analizable pasa a ser {}
Llamanteaccount_keys[0], el pagador de la tarifa
Argumentos de llamadaun objeto JSON en UTF-8; current_slot se sobrescribe con el slot del bloque
Excepción binariaSwap swap_a_to_b / swap_b_to_a con exactamente 16 bytes: u64 LE amount ‖ u64 LE min_output
Métodos protegidosinternos de los singletons (p. ej. place_order, cancel_order, evaluate en xeris_conditional_orders), rechazados en las rutas genérica, delegada y de órdenes condicionales
Contrato inactivotoda llamada falla con "Contract is not active"
Lecturas de registropaginadas: como máximo 32 elementos y 16 KiB por consulta

5.2TimeLock, Escrow, Vesting, MultiSig

Cuatro tipos de custodia retienen un saldo de token y lo liberan bajo una única regla cada uno. Solo mueven token_balances, así que el XRS nativo entra en ellos envuelto como xrs_native (WrapXrs, 13). TimeLock, Escrow y Vesting rechazan un token RWA en el despliegue; una transferencia de un token RWA desde un MultiSig pasa por el filtro de cumplimiento de §7.2. Las marcas de tiempo son valores poh_timestamp del bloque en milisegundos; TimeLock no comprueba el rango de unlock_timestamp, y un valor en el pasado puede liberarse de inmediato.

Tabla 12. Los cuatro tipos de custodia.
TipoParámetros de despliegueMétodosRegla
TimeLockbeneficiary, token_id, amount, unlock_timestamp (ms); amount se debita a quien despliegareleasecualquiera puede llamarlo una vez que la marca de tiempo del bloque alcanza unlock_timestamp; paga al beneficiario; sin cancelación, prórroga ni reasignación
Escrowparty_a (debe ser igual al firmante), party_b (distinta), token_id, amountconfirm, cancelambas partes confirman → amount a party_b; party_a puede cancelar hasta la finalización, incluso después de que party_b haya confirmado; sin expiración
Vestingbeneficiary, token_id, total_amount, cliff_timestamp, end_timestamp (ms); cliff ≥ now, end > now, cliff ≤ endclaimsolo el beneficiario; consolidado = total × (now − start) / (end − start), lineal desde la marca de tiempo del despliegue; el cliff condiciona la primera llamada a claim; sin revocación
MultiSigsigners[] (claves públicas distintas), threshold (1 ≤ t ≤ n); no se bloquean fondospropose, approve, rejectuna propuesta pendiente a la vez, numerada con un nonce que approve y reject deben indicar; con threshold aprobaciones, una acción transfer mueve el saldo nativo (XRS, xrs_native) o de token de quien desplegó; min(t, n − t + 1) rechazos la descartan

5.3Swap

Un Swap es un pool de producto constante de dos tokens distintos no RWA. El despliegue debita amount_a y amount_b a quien despliega y acuña isqrt(amount_a × amount_b) shares, que deben superar 1,000; 1,000 shares van a una dirección muerta y nunca son canjeables. fee_bps vale 30 por defecto y puede fijarse entre 0 y 10,000 en el despliegue. La comisión permanece en las reservas y se acumula para los proveedores de liquidez; el protocolo no retiene ninguna parte de los swaps. add_liquidity y remove_liquidity reciben mínimos firmados y fallan por debajo de ellos. Las shares son entradas del estado del pool, no un token.

despliegue shares_0 = isqrt(amount_a × amount_b) require shares_0 > 1,000 lp[dead] = 1,000 · lp[owner] = shares_0 − 1,000 · T = shares_0 swap fee = floor(in × fee_bps / 10,000) // fee_bps por defecto 30 in_net = in − fee out = floor(R_out × in_net / (R_in + in_net)) require in > 0 · balance ≥ in · 0 < out ≤ R_out · out ≥ min_output R_in += in // la comisión permanece en el pool R_out −= out añadir shares = min(a × T / R_a, b × T / R_b) · accepted_x = ceil(shares × R_x / T) retirar out_x = floor(shares × R_x / T)

5.4Launchpad

Un Launchpad crea el token lp_<id> y vende parte de su supply sobre una curva de producto constante con reservas virtuales. El token es LaunchpadManaged: su supply solo se acuña mediante compras en la curva y TokenMint se rechaza. En el despliegue no se debita nada. liquidity_bps del supply (2,000 por defecto; acotado a 500–4,000) se reserva para el pool de graduación y la curva vende el resto; las reservas virtuales se eligen de modo que el precio de la curva al agotarse iguale el precio de apertura del pool. Cada compra y cada venta paga un 1 % al creador, acumulado hasta claim_rewards, y un 0.77 % a la dirección de tesorería, acreditado en la misma llamada. Los compradores pagan en xrs_native envuelto. El id de un contrato launchpad tiene como máximo 116 caracteres para que el id del pool quepa en 128.

por defecto total_supply = 1,000,000,000 tokens (9 decimales) T = target_liquidity_xrs = 10,000 XRS · liquidity_bps = 2,000 (500–4,000) reparto L = floor(total_supply × liquidity_bps / 10,000) // reserva del pool s = total_supply − L // inventario de la curva; require L > 0, s > L x_v = ceil(L × T / (s − L)) y_v = floor(L × s / (s − L)) reservas x = xrs_collected + x_v y = tokens_remaining + y_v compra net = xrs − floor(xrs × 100 / 10,000) − floor(xrs × 77 / 10,000) tokens_out = min(floor(y × net / (x + net)), tokens_remaining) venta raw = min(floor(x × t / (y + t)), xrs_collected) xrs_out = raw − floor(raw × 100 / 10,000) − floor(raw × 77 / 10,000) graduación require xrs_collected ≥ T Swap lp_<id>_xrs_pool { token_a: lp_<id>, token_b: xrs_native, L, xrs_collected, fee_bps: 30 } owner = __launchpad_pool__

finalize_curve puede invocarlo cualquiera una vez que xrs_collected ≥ T. El ledger acredita a la pseudocuenta __launchpad_pool__ el XRS recaudado y el tramo L y despliega el Swap anterior; las shares pertenecen a esa cuenta, que no puede firmar, de modo que la liquidez no puede retirarse. Si el id del pool ya existe o la siembra falla, la finalización se revierte y el launchpad sigue abierto. Los métodos del launchpad se ejecutan solo mediante un ContractCall directo: se rechazan AgentExecute y las llamadas internas de órdenes condicionales a un launchpad. vesting_enabled: true se rechaza en el despliegue.

5.5Órdenes permanentes

ConditionalOrder (23) coloca una orden permanente en el singleton xeris_conditional_orders. La orden pone en escrow locked_amount del saldo nativo del firmante en __escrow_order_<id>; el mínimo es la fianza de almacenamiento reembolsable de 0.01 XRS, y un NativeTransfer interno debe quedar cubierto por ella. Una orden vive como máximo 650,000 slots (≈ 30 días); un propietario mantiene como máximo 100 órdenes vivas; el libro, como máximo 10,000; la instrucción interna ocupa como máximo 2,048 bytes de bincode. El protocolo evalúa cada orden viva una vez por bloque, tras todas las transacciones, y ejecuta las órdenes disparadas en orden de order_id; no hay keepers. Una orden fallida o expirada se cancela y su escrow se reembolsa. CancelConditionalOrder (24) reembolsa al propietario.

Tabla 13. Tipos de condición y sus reglas de disparo.
CondiciónFuenteSe dispara cuando
slot_reached—slot actual ≥ umbral
price_above / price_belowel id de un contrato Swap desplegadoprecio mediano del pool ≥ / ≤ umbral, en unidades base de token_b por 10^9 unidades base de token_a
balance_above / balance_below—saldo nativo del propietario ≥ / ≤ umbral; una entrada ausente cuenta como 0
oracle_valueun feed de oráculo activoúltimo valor ≥ umbral; propietario y tipo del feed quedan fijados al colocar la orden; valor con antigüedad no mayor que min(update_interval_slots, 900) slots

El precio de un pool se observa una vez por bloque, tras las transacciones, para un máximo de 64 pools referenciados por órdenes de precio vivas: spot = reserve_b × 10^9 / reserve_a, omitido y con la ventana vaciada cuando cualquiera de las reservas baja de 1,000. El precio de disparo es la mediana de las últimas 61 observaciones y solo existe a partir de 20. Mover la mediana exige sostener un precio fuera de mercado durante 31 bloques consecutivos frente al arbitraje.

LimitOrder y DcaOrder se despliegan y ponen fondos en escrow, pero nunca se ejecutan: execute_limit_order y execute_dca_tick devuelven un error. cancel_limit_order y cancel_dca_order reembolsan el escrow íntegramente.

6La capa de agentes Ari

Ari (Autonomous Runtime Infrastructure) es un conjunto de registros a los que se accede mediante instrucciones dedicadas: identidades autosoberanas con reputación por categoría, delegación acotada de una clave de propietario a claves de agente, listados de capacidades, tareas con escrow, paneles de disputa ponderados por stake, acuerdos entre dos partes, canales de pago, feeds de oráculo con stake, dispositivos atestados, entradas de modelo y heartbeats de liveness. Cada registro es un contrato que se crea en su primer uso bajo el id de la Tabla 14; el Apéndice C indica qué tipos gestiona el protocolo. SubDelegate (22) se rechaza en la entrada y se omite en el dispatcher; la delegación tiene un solo nivel y no existe jerarquía de agentes (XWC-82).

Figura 6. Delegación de propietario a agente. SubDelegate (22) está deshabilitada; no hay segundo nivel.
PROPIETARIO (Ed25519)RegisterAgentREGISTRO DE AGENTESmax_per_tx | max_daily | allowed_contractsallowed_operations | expires_at_slot | revokedSubDelegate: deshabilitada (XWC-82)IDENTIDADreputación por categoríacredenciales ≤ 100CAPACIDADcategoría · tags · regiónprice_per_unitAGENTE DE TRADINGmax_per_tx 50 XRS · diario 500 XRSAGENTE DE DATOSallowed_operations: [ContractCall]AgentHeartbeat · liveness ≤ 5,400 slots
Tabla 14. Registros de Ari y sus ids de contrato fijos.
RegistroId de contratoCreado por
Raíz de identidadesxeris_identitiesCreateIdentity (18)
Identidadidentity_<sha256(pubkey)[..32]>CreateIdentity (18), uno por clave pública
Registro de agentesagent_registry_<sha256(owner)[..32]>RegisterAgent (15), uno por propietario
Oráculosxeris_oraclesRegisterOracle (25)
Dispositivosxeris_devicesHardwareAttest (27), una vez verificada la prueba
Capacidadesxeris_capabilitiesRegisterCapability (28)
Tareasxeris_tasksPostTask (31)
Modelosxeris_modelsRegisterModel (34)
Disputasxeris_disputesOpenDispute (36) o DisputeDeal (58)
Acuerdosxeris_dealsCreateDeal (54)
Canalesxeris_channelsOpenChannel (42)
Heartbeatsxeris_heartbeatsAgentHeartbeat (45)

6.1Delegación de agentes

RegisterAgent (15) lo firma el propietario y escribe un AgentEntry en el registro de ese propietario. Un registro contiene como máximo 50 agentes. UpdateAgent (16) cambia cualquier límite y activa o desactiva revoked; la revocación es el interruptor de emergencia y el propietario puede revertirla.

AgentEntry { agent_pubkey // la clave con la que firma el agente max_per_tx // lamports por instrucción delegada max_daily // lamports por ventana de 21,600 slots (DAILY_SLOTS) allowed_contracts // ids de contrato; vacío = todos allowed_operations // nombres de operación; vacío = todas expires_at_slot // 0 = nunca revoked // lo activa y lo desactiva el propietario daily_spent · daily_window_start · total_txs · total_spent }

AgentExecute (17) lo firma el agente y lleva una instrucción interna codificada con bincode que se ejecuta en nombre del propietario. La instrucción interna debe ser NativeTransfer, TokenTransfer, ContractCall, WrapXrs, UnwrapXrs, Stake, Unstake, TokenMint o TokenBurn; se rechaza cualquier otra variante. El gasto de una transferencia, un wrap, un stake, una acuñación o una quema es su amount. El gasto de un ContractCall se deriva del método, y se rechaza un método cuyo gasto no puede acotarse.

Tabla 15. Derivación del gasto de un ContractCall delegado.
Método de ContractCall delegadoGasto cargado al presupuesto
buy_tokens · sell_tokensxrs_amount · token_amount
swapamount_in + input_amount + amount
add_liquidityaccepted_a + accepted_b cotizados; si no, amount_a + amount_b
remove_liquidity · create_dca_order · place_ordershares · total_amount · locked_amount
post · open · createreward · deposit · amount
cancel · reclaim · claim_rewards · redeem · amend · list · status · get_stats · get_key0
confirm · verify · cualquier otro métodorechazado: el gasto reside en el estado del contrato (XWC-87)

La validación sigue este orden: el agente está registrado, no revocado y no caducado; el gasto es como máximo max_per_tx; la ventana se reinicia a los 21,600 slots y el gasto es como máximo max_daily menos daily_spent; la lista blanca de contratos se aplica al destino de un ContractCall; la lista blanca de operaciones se aplica al nombre de la operación. El presupuesto se anota solo después de que la instrucción interna tenga éxito. Se rechaza una llamada delegada a un registro de agentes, a un método sellado del protocolo, a un contrato Launchpad o a un contrato RWA. Un AgentExecute o un ConditionalOrder anidado se rechaza en la entrada.

6.2Identidad y reputación

CreateIdentity (18) crea un contrato por clave pública; el firmante debe ser la identidad. identity_type es agent, device, service o human; display_name ocupa como máximo 128 B y metadata_json, como máximo 4,096 B. Un parent_identity no vacío debe cofirmar la transacción, y el hijo se añade al padre, que admite como máximo 200 hijos. UpdateIdentity (19) cambia el nombre y los metadatos; deactivated: true es irreversible y no existe vía de reactivación. Una identidad activa es requisito para los listados de capacidades, las reclamaciones de tareas, las entradas de modelo, los heartbeats y los dispositivos vinculados.

AttestReputation (20) exige que el atestador tenga una identidad activa y rechaza la autoatestación. La categoría es reliability, accuracy, speed, honesty, safety o general; la puntuación se limita a 0–100 y la identidad mantiene una media acumulada por categoría. Una identidad admite como máximo 100 credenciales. AgentMessage (21) comprueba el tipo, el tamaño del payload (≤ 8,192 B) y que el remitente esté activo, y no escribe estado de contrato; el mensaje solo existe en el bloque.

6.3Capacidades y tareas

RegisterCapability (28) y UpdateCapability (29) los firma la provider_identity, que debe estar activa. Un listado se indexa por provider:category y contiene tags, region (por defecto global), description ≤ 2,048 B, price_per_unit, max_concurrent, metadata_json ≤ 4,096 B y reputation_snapshot, la media de fiabilidad del proveedor en el momento del registro. Un proveedor tiene como máximo 100 listados; pueden estar activos hasta 50,000. QueryCapabilities (30) no tiene efecto dentro de un bloque; el descubrimiento se hace con GET /capabilities/search.

PostTask (31) pone en escrow el reward del publicador. min_reputation debe ser 0; expires_at_slot cae dentro de los 648,000 slots (≈ 30 días) siguientes al slot de publicación; verification es poster_confirm u oracle, donde el oráculo es una clave aprobadora designada, y automatic se ha eliminado (XWC-74). Como máximo hay 100,000 tareas vivas. ClaimTask (32) exige que el reclamante sea el firmante, que tenga una identidad activa y, si required_category está fijada, un listado activo en esa categoría con al menos un required_tag. ResolveTask (33) lleva una de las resoluciones siguientes.

post el publicador pone reward en escrow → open claim claimant == signer; listado en required_category → claimed (cuando se alcanza max_claimants) complete un reclamante envía proof antes del plazo → completed verify poster | verification_oracle; el escrow se paga una vez → verified reject el mismo aprobador; rejections < 3; se borra proof → claimed reject tercer rechazo; escrow reembolsado al publicador → rejected cancel poster, solo desde open; escrow reembolsado → cancelled expiry cada bloque; open | claimed tras el plazo; reembolsado → expired

6.4Oráculos, dispositivos, modelos y heartbeats

Otros cuatro registros siguen el mismo patrón: una instrucción dedicada, un firmante vinculado a la entrada que escribe y un tope fijo. El registro de dispositivos lo gestiona el protocolo y se crea solo cuando se verifica la primera prueba de atestación.

Tabla 16. Reglas de oráculos, dispositivos, modelos y heartbeats.
RegistroInstrucciónRegla
OracleRegistryRegisterOracle (25)feed_type es price, event, sensor, weather o custom; stake >= 1 XRS bloqueado del saldo del llamante; description <= 512 B; como máximo 1,000 feeds activos
OracleRegistryOracleSubmit (26)solo el propietario; metadata <= 1,024 B; el feed conserva los últimos 1,000 puntos (slot, value)
DeviceRegistryHardwareAttest (27)device_type es humanoid, terminal, iot, mobile o secure_element; el firmante es el dispositivo o su identidad vinculada; la prueba es una firma Ed25519 de 64 bytes de la clave del dispositivo sobre el desafío siguiente; como máximo 100,000 dispositivos activos
ModelRegistryRegisterModel (34) · UpdateModel (35)identity_pubkey == firmante, con identidad activa; una entrada ocupa como máximo 2,048 B; como máximo 10,000 modelos vivos; 16 mutaciones de modelo por bloque
HeartbeatsAgentHeartbeat (45)identity_pubkey == firmante, con identidad activa; se descartan las entradas de más de 21,600 slots; como máximo 10,000 entradas; vivo = último heartbeat en los últimos 5,400 slots (≈ 6 h)
challenge = "XRS_HW_ATTEST_V2" ‖ identity(device_pubkey) ‖ identity(bound_identity) // 0x01 ‖ 32 B para una clave Ed25519; si no, 0x00 ‖ len ‖ bytes ‖ len ‖ device_type ‖ len ‖ manufacturer ‖ len ‖ model ‖ len ‖ firmware_version ‖ slot u64 LE // el slot del bloque que incluye la transacción proof = Ed25519(device_key, challenge) // 64 B

6.5Disputas

OpenDispute (36) nombra a un demandado y deposita una fianza; la fianza es de al menos 1 XRS en una disputa de acuerdo y puede ser 0 en los demás casos. El panel es un snapshot de todos los validadores con al menos 1,000 XRS en stake en el slot de apertura, excluidas las dos partes, ponderado por stake; se rechaza un panel vacío. Como máximo hay 10,000 disputas abiertas.

ResolveDispute (37) lleva una acción. Durante la ventana de impugnación de 21,600 slots (≈ 1 día) solo se aceptan evidence del disputante y defendant_evidence del demandado. Tras ella se aceptan vote_disputer, vote_defendant y vote_dismiss de los miembros del panel cuyo stake sigue siendo de al menos 1,000 XRS; un voto por validador, y cuenta el último. La disputa se falla cuando una opción alcanza la mayoría absoluta del peso del snapshot. El fallo solo mueve la fianza: se reembolsa si gana el disputante y se pierde en los demás casos. expire tras 648,000 slots (≈ 30 días) reembolsa la fianza.

6.6Acuerdos

Un acuerdo pone en escrow el mismo amount de cada parte; el bote es deposit_a + deposit_b. Toda instrucción posterior a CreateDeal lleva el instance del acuerdo, un contador que nunca se reutiliza. Como máximo hay 100,000 acuerdos vivos.

CreateDeal (54) A pone amount en escrow; contraparte distinta; amount > 0 → proposed CancelDeal (57) solo A, mientras proposed; se reembolsa a A → cancelled AcceptDeal (55) solo B; instance, party_a, amount, sha256(terms) deben coincidir; B pone amount en escrow → active ConfirmDeal (56) A o B; cuando ambos han confirmado, se reembolsa cada depósito → completed ReclaimDeal (60) A o B, mientras active, tras created_slot + 648,000 slots; se reembolsan ambos depósitos → completed DisputeDeal (58) A o B, mientras active; bond >= 1 XRS; escrow congelado; abre la disputa deal_<instance>_<deal_id> contra la otra parte → disputed SettleDeal (59) cualquiera, tras el fallo; el ganador se lleva el bote; dismissed | expired → se reembolsa cada depósito → settled

6.7Canales de estado

OpenChannel (42) pone en escrow el depósito de quien abre el canal; la contraparte se une mediante ContractCall join. CloseChannel (43) lleva la firma Ed25519 de 64 bytes de la otra parte sobre el mensaje de cierre siguiente, y los saldos finales deben sumar exactamente los depósitos. ForceCloseChannel (44) con state_sequence 0 reclama el reparto original; con un estado firmado más reciente abre una impugnación de 1,000 slots (≈ 67 min), durante la cual challenge_update lo sustituye por un estado firmado más reciente y tras la cual finalize_dispute paga el reparto registrado. Los canales caducados se liquidan automáticamente, como máximo 256 por bloque. Como máximo hay 100,000 canales sin liquidar.

close = "XRS_CLOSE_CH_V4" ‖ len ‖ chain_id ‖ len ‖ channel_id ‖ generation u64 LE ‖ created_slot u64 LE ‖ identity(party_a) ‖ identity(party_b) ‖ final_balance_a u64 LE ‖ final_balance_b u64 LE ‖ message_count u64 LE state = "XRS_CH_STATE_V3" ‖ mismo prefijo ‖ balance_a u64 LE ‖ balance_b u64 LE ‖ state_sequence u64 LE sig = Ed25519(counterparty_key, message) // 64 B; saldos siempre en orden (A, B)

7Activos del mundo real de Alexandria

Un token de Alexandria es una entrada del registro de tokens cuyo rwa_metadata lo vincula a un documento ricardiano off-chain mediante su hash SHA-256. TokenCreateRWA (6) crea la entrada; el firmante debe ser igual a mint_authority; el registro inicializa approved_holders con el emisor. El filtro de cumplimiento lee esta entrada, no el contrato del emisor de §7.3, antes de cada abono.

7.1Metadatos del token

RWAMetadata { asset_type: String // real_estate | equity | debt | commodity | ip | collectible | fund | bond legal_doc_hash: String // SHA-256 del documento ricardiano; no puede estar vacío; por lo demás, sin validar legal_doc_uri: String // IPFS, Arweave o HTTPS; sin validar jurisdiction: String // texto libre, p. ej. "US-WY"; se registra, nunca se aplica status: String // active | frozen | redeemed | disputed | revoked; "active" al crearse transfer_restricted: bool accredited_only: bool valuation: u64 // centavos de USD approved_holders: Vec<String> // claves públicas Ed25519 canónicas, ordenadas; <= 1,024 (MAX_RWA_APPROVED_HOLDERS) status_history: Vec<(u64, String, String)> // (slot, old_status, new_status); primera entrada (created_slot, "created", "active") }

RWAUpdateStatus (7), firmado por mint_authority, escribe cualquiera de los cinco estados desde cualquier estado actual, añade una tupla a status_history y sustituye valuation, legal_doc_hash y legal_doc_uri cuando se proporcionan.

7.2Filtro de cumplimiento

enforce_rwa_credit se ejecuta antes de cada abono de un token RWA: TokenMint (0), TokenTransfer (1), RWATransfer (8) y una transferencia aprobada por un MultiSig. Un token sin rwa_metadata pasa el filtro. Las comprobaciones se ejecutan en este orden; el primer fallo rechaza la instrucción.

  • Lista de titulares de más de 1,024 entradas: se rechaza.
  • status distinto de active: ni acuñación ni transferencia.
  • transfer_restricted: el destinatario debe ser un titular aprobado.
  • accredited_only: todo destinatario, por acuñación o por transferencia, debe ser un titular aprobado.

La cuarta comprobación se aplica aunque transfer_restricted sea falso: la lista de titulares es la lista de acreditación. RWATransfer añade amount > 0, from != to y signer == from; por lo demás es idéntico a TokenTransfer.

TimeLock, Escrow, Swap, Vesting, LimitOrder y DcaOrder rechazan un token RWA en cualquier lado de la operación. Un bloque admite como máximo 256 unidades de trabajo de política RWA (MAX_RWA_POLICY_WORK_PER_BLOCK): cada TokenMint o TokenTransfer de un token RWA, cada RWATransfer y cada llamada a approve_holder o revoke_holder cuesta 1, también como instrucción interna delegada o condicional. Un bloque que supera el límite no pasa la validación previa; una instrucción que excedería el total se rechaza, y una orden condicional disparada se aplaza.

7.3Contrato del emisor

Un ContractDeploy (5) de tipo rwa enviado por la mint_authority del token crea un contrato RealWorldAsset para un único token_id. asset_type, legal_doc_hash, legal_doc_uri, jurisdiction y approved_holders se copian del registro; los valores aportados por el llamante se ignoran. Todo método es exclusivo del emisor y solo acepta un ContractCall (4) directo; se rechazan las llamadas internas de AgentExecute y las llamadas de órdenes condicionales a un contrato RWA.

Tabla 17. Métodos del contrato RealWorldAsset.
MétodoArgumentosEfecto
approve_holder{"address"}añade una clave pública canónica a la lista del contrato y a la del registro en una sola escritura; ambas listas deben coincidir de antemano; se rechaza si la clave ya figura, si la lista tiene 1,024 entradas o si el activo está redimido
revoke_holder{"address"}elimina la clave de ambas listas; el emisor no puede revocarse
amend_legal_doc{"legal_doc_hash", "legal_doc_uri"}sustituye el hash y la URI en el contrato y en el registro; se rechaza tras la redención
redeemningunofija redeemed_at, desactiva el contrato y pone el status del registro en redeemed
distribute, toggle_distributionscualquieradeshabilitados; la llamada falla

No hay distribuciones on-chain; distributions_enabled y distribution_history nunca se escriben. El activo se redime con redeem o con RWAUpdateStatus y el estado redeemed.

8Criptografía ZK y post-cuántica

Tres primitivas son alcanzables desde el consenso: Ed25519, ML-DSA-65 (Dilithium3) y Groth16 sobre BN254; §9 da los contextos de firma. Groth16 verifica pruebas contra claves de verificación registradas por validadores con stake y solo tiene seguridad clásica. crypto.rs contiene además compromisos de Pedersen, una prueba de Schnorr, una prueba de rango y un verificador WOTS+/XMSS; ninguna rama del dispatcher los invoca.

Tabla 18. Primitivas criptográficas del nodo.
PrimitivaAlgoritmoUsoEstado
Ed25519Curve25519Transacciones; autenticación P2P; mensajes de canal, de dispositivo y de slashing; mitad clásica de la firma de bloqueactiva
ML-DSA-65 (Dilithium3)Reticular, FIPS 204, pqcrypto-mldsaMitad post-cuántica de la firma de bloque; registro xeris_pq_keys; pruebas de rotaciónactiva; obligatoria desde el slot 1
Groth16Pairing BN254, arkworksVerificación de pruebas contra VK registradas (ZkProofSubmit)activa; solo seguridad clásica
Sigma de Schnorr, compromisos de Pedersen, prueba de rangoRistretto255Transferencias confidencialesreserva; inalcanzable
WOTS+/XMSSBasado en hash SHA-256Firmas post-cuánticas de respaldoreserva; inalcanzable
PqSignedTransfer (52)ML-DSA-65Transferencia autorizada por una firma post-cuánticadeshabilitada; se cobra la tarifa y no se ejecuta nada

8.1Verificación Groth16

El registro xeris_zk_verifier es un singleton gestionado por el protocolo. Una prueba se acepta solo contra una clave de verificación ya registrada en él. El verificador es Groth16::<Bn254> de arkworks; cualquier resultado distinto de Ok(true) es un fallo.

ZkVkRegister (61) stake del firmante >= 1,000 XRS (MIN_STAKE_TO_MINE) vk_base64 -> VerifyingKey BN254 canónica y comprimida, 1..=16 KiB, consumo exacto vk_id, claim_type, description: sin token post-cuántico la primera escritura prevalece; tope del registro: 10,000 claves se almacena: {vk_base64, claim_type, description, registered_by, registered_slot} ZkProofSubmit (46) proof_system == "groth16" verification_key_hash == un vk_id registrado proof_data <= 512 B; public_inputs <= 64 x Fr de 32 bytes LE; consumo exacto proof_system, proof_type, metadata_json: sin token post-cuántico prueba que no verifica -> no se almacena; proof_id duplicado -> error se almacena: proof_type := claim_type de la VK (se ignora el valor del llamante), proof_data_hash, public_inputs_hash (SHA-256 hex), proof_size, verified = true ZkProofVerify (47) solo lectura; devuelve {proof_id, verified, verification_slot, proof_system} por bloque <= 64 ZkProofSubmit con proof_system == "groth16", se cuentan verifiquen o no

El tope de 64 envíos Groth16 por bloque lo aplica el validador y lo replica el productor, que aplaza la transacción que lo superaría. Una entrada almacenada guarda hashes, no los bytes de la prueba, así que verified no puede cambiar tras el envío.

La ruta ZK rechaza toda afirmación post-cuántica. La instrucción se rechaza si vk_id, claim_type, description, proof_system, proof_type o metadata_json contiene, sin distinguir mayúsculas de minúsculas, alguna de las subcadenas pq, post-quantum, post_quantum, postquantum, post quantum, dilithium, mldsa, ml-dsa o ml_dsa (XWC-13 del módulo criptográfico). Un campo que contenga las dos letras pq en cualquier palabra se rechaza. PqAttest (53) es un marcador de adopción autodeclarado: el nodo no realiza ninguna comprobación criptográfica, almacena la entrada en el mapa separado pq_attestations con verified = false y guarda el indicador del llamante en self_asserted.

ZkPrivateTransfer (48) y ZkIdentityProof (49) están deshabilitadas en el dispatcher (§12.2); no se escribe ningún recibo. Ninguna ruta activa escribe en la tabla de nullifiers del registro.

8.2Registro de claves post-cuánticas

xeris_pq_keys asocia una dirección Ed25519 con su clave pública ML-DSA-65 actual y con el historial de claves que ha tenido. La única cadena de algoritmo aceptada es dilithium3; una clave pública ocupa 1,952 bytes, una clave secreta 4,032 bytes y una firma separada 3,309 bytes. El crate es pqcrypto-mldsa, que sustituyó a pqcrypto-dilithium (RUSTSEC-2024-0380) con el mismo conjunto de parámetros y las mismas longitudes en bytes (XWC-12 del módulo criptográfico).

PqKeyRegister (50) signer == ed25519_pubkey pq_algorithm == "dilithium3"; security_level == 3 pq_public_key: exactamente 1,952 B, se analiza como clave ML-DSA-65, no todo ceros solo el primer registro; una entrada existente se rechaza tope del registro: 10,000,000 entradas key_history := [ {key, valid_from_slot: slot, valid_until_slot: None} ] PqKeyRotate (51) signer == ed25519_pubkey; debe existir una entrada clave nueva: "dilithium3", estructuralmente válida, exactamente 1,952 B rotation_proof = firma ML-DSA-65 de la clave ACTUAL sobre "xrs_pq_rotate_v5" || chain_id || old_pk (1,952) || new_pk (1,952) || rotation_count u64 LE (sin prefijos de longitud; chain_id = "xeris-mainnet-v1" o "xeris-testnet-v1") la vinculación actual se cierra en slot + 1; vinculación nueva [slot + 1, None) rotation_count += 1; last_rotation_slot = slot

Las vinculaciones de key_history son intervalos semiabiertos [valid_from_slot, valid_until_slot); una clave instalada en el bloque R está activa para la admisión desde R + 1. Las vinculaciones cerradas se conservan durante 302,400 slots (14 días, el doble del periodo de unbonding), de modo que la evidencia de slashing de un slot pasado se comprueba contra la clave activa en ese slot. Comprometer solo la clave Ed25519 no permite sustituir una clave registrada: el registro se hace una sola vez y la rotación exige la clave ML-DSA-65 actual.

Desde el slot 2 (PQ_REGISTRY_BINDING_ACTIVATION_SLOT), la clave inline proposer_dilithium3_pk de cada bloque debe coincidir byte a byte con la entrada del registro para block.proposer; una entrada ausente o distinta rechaza el bloque en las rutas en vivo y de reproducción, y en el prefiltro de reorganización solo tiene valor consultivo. El bloque del slot 1 registra automáticamente la clave inline de su proponente, con la cadena Dilithium3 en mayúscula inicial como pq_algorithm, y siembra su historial. Cualquier otro productor se registra con PqKeyRegister antes de su primer bloque.

Un nodo carga dilithium3_sk.bin y dilithium3_pk.bin desde su directorio de trabajo, valida la clave pública, firma y verifica el desafío fijo XRS_DILITHIUM_LOAD_CHECK_V1 y regenera el par ante cualquier fallo. La clave secreta se escribe con modo 0o600 y con fsync; un fallo de escritura es fatal. Un nodo sin ambas claves no produce bloques.

9Firmas, hashes y formato de transacción

Existen tres contextos de firma. Una transacción lleva solo firmas Ed25519. Un bloque lleva una firma híbrida Ed25519 + ML-DSA-65 (Dilithium3) del proponente. Un par se autentica con una firma Ed25519 sobre un nonce del servidor. Todo mensaje que el nodo firma o del que calcula un hash empieza por una etiqueta de dominio ASCII fija, y todo mensaje vinculado a la cadena incorpora además el chain id xeris-mainnet-v1 o xeris-testnet-v1.

9.1La firma híbrida de bloque

Desde HYBRID_SIG_ACTIVATION_SLOT = 1, todo bloque lleva proposer_dilithium3_pk y hybrid_proposer_sig. El mensaje firmado lo construye hybrid_canonical_message con el propósito block. Cada campo variable lleva prefijo de longitud, así que dos conjuntos de campos no pueden compartir una misma cadena de bytes, y una firma hecha con un propósito no verifica con otro.

canonical = "XRS_HYBRID_V1" // 13 B ‖ u8 5 ‖ "block" // propósito ‖ u8 16 ‖ chain_id // "xeris-mainnet-v1" | "xeris-testnet-v1" ‖ u8 9 // número de campos ‖ por cada campo: u32 LE len ‖ bytes fields = slot u64 LE · hash · previous_hash · merkle_root · proposer (32 B) · poh_timestamp u128 LE · nonce u64 LE · poh_hash · proposer_dilithium3_pk (1,952 B) HybridSignature { version: u8 = 1, ed25519_sig: 64 B, dilithium3_sig: 3,309 B } valid ⇔ version == 1 ∧ Ed25519.verify(proposer, canonical) ∧ ML-DSA-65.verify(proposer_dilithium3_pk, canonical)

Ambas mitades deben verificar; un bloque minado no tiene ruta solo clásica. La mitad ML-DSA-65 se comprueba contra la clave que lleva el bloque, y desde el slot 2 esa clave debe coincidir byte a byte con la entrada de xeris_pq_keys del proponente (§8.2). El worker de minería recibe solo la clave pública del proponente; el bloque se firma en el hilo propietario del validador cuando termina la búsqueda (XWC-26).

9.2Raíz de Merkle

leaf = SHA-256(0x00 ‖ bincode(tx)) // la transacción completa: firmas y mensaje node = SHA-256(0x01 ‖ left ‖ right) odd pad = SHA-256(0x01 ‖ "XRS_MERKLE_ODD_PAD_V2") // hermano derecho de un nodo sin pareja empty = SHA-256(0x01 ‖ "XRS_MERKLE_EMPTY_V2") // bloque sin transacciones root = 32 B

Desde MERKLE_FULLTX_ACTIVATION_SLOT = 1, una hoja se compromete con la transacción canónica completa (XWC-07). Una transacción con el vector de firmas vacío es un error, nunca un centinela: el productor abandona la plantilla y vuelve a encolar sus transacciones, y un validador rechaza el bloque (XWC-08). El hash de un nodo sin pareja se calcula con el relleno etiquetado, no con una copia de sí mismo, lo que elimina la ambigüedad de hojas duplicadas de CVE-2012-2459.

9.3Mensajes con separación de dominio

La Tabla 19 enumera todas las etiquetas alcanzables desde el consenso o la capa de red. lp(x) es u32 LE len ‖ x. id(s) es 0x01 ‖ 32-byte key si s se interpreta como clave pública y, si no, 0x00 ‖ lp(s). Los enteros son little-endian salvo indicación contraria.

Tabla 19. Etiquetas de dominio alcanzables desde el consenso y la capa de red.
EtiquetaFirmante o usoFormato tras la etiqueta
XRS_HYBRID_V1proponente del bloque, Ed25519 + ML-DSA-65§9.1
XRS_POW_V2preimagen Scrypt§2.2
XRS_LEADER_V1, XRS_LEADER_V1_RETRYsemilla de elección, SHA-256last_block_hash ‖ slot u64; reintento: seed
XRS_P2P_AUTH_V2\0par, Ed25519nonce (32 B) ‖ peer_version; se firma tal cual, sin hash previo
XRS_CH_STATE_V3contraparte del canal, Ed25519lp(chain_id) ‖ lp(channel_id) ‖ generation ‖ created_slot ‖ id(party_a) ‖ id(party_b) ‖ balance_a ‖ balance_b ‖ state_sequence
XRS_CLOSE_CH_V4contraparte del canal, Ed25519como el anterior, terminado en final_balance_a ‖ final_balance_b ‖ message_count
XRS_HW_ATTEST_V2clave del dispositivo, Ed25519id(device_pubkey) ‖ id(bound_identity) ‖ lp(device_type) ‖ lp(manufacturer) ‖ lp(model) ‖ lp(firmware_version) ‖ slot u64
xrs_pq_rotate_v5clave PQ actual, ML-DSA-65chain_id ‖ old_pk ‖ new_pk ‖ rotation_count u64; sin prefijos de longitud
XRS_SLASH_ID_V2SHA-256, resumen de la cabeceracanonical (el mensaje de §9.1)
XRS_SLASH_OFFENSE_V2SHA-256, id de la ofensaowner_pubkey ‖ violation_slot u64 ‖ min(d_a, d_b) ‖ max(d_a, d_b)
XRS_FEDERATION_V1\0SHA-256, dominio del rosterchain_id ‖ producer keys, sorted
XRS_FEDERATION_TIP_V2\0productor del roster, Ed25519domain (32 B) ‖ nonce (32 B) ‖ height u64 ‖ lp(hash) ‖ lp(identity)

Una firma vinculada a la cadena de una red no verifica en la otra. El chain id cambia en cada hard fork.

9.4Formato binario de la transacción

Una transacción es una solana_sdk::transaction::Transaction legacy serializada con bincode 1.x: enteros little-endian de ancho fijo y longitudes short_vec (compact-u16). No se aceptan mensajes versionados (v0). account_keys[0] es el firmante y el pagador de la tarifa. El nodo ignora program_id_index y accounts en una XerisInstruction; la wallet de referencia usa como id de programa la clave todo ceros.

01 ShortU16: 1 firma sig 64 B Ed25519 sobre los bytes del mensaje que siguen 01 00 01 cabecera: 1 firmante requerido, 0 de solo lectura firmados, 1 de solo lectura sin firmar 02 ShortU16: 2 claves de cuenta payer 32 B account_keys[0]: firmante y pagador de la tarifa 00 × 32 account_keys[1]: id de programa, se ignora blockhash 32 B recent_blockhash, válido durante 150 bloques 01 ShortU16: 1 instrucción 01 program_id_index 00 ShortU16: 0 índices de cuenta len ‖ data ShortU16 data = bincode(XerisInstruction), ≤ 8,192 B

Los datos de instrucción son el bincode del enum XerisInstruction: un índice de variante u32 LE y después los campos en orden de declaración. String y Vec llevan una longitud u64 LE; Option es 0x00 o 0x01 ‖ T; bool ocupa un byte; [u8; 32] va tal cual. El decodificador tolera bytes sobrantes al final, pero estos cambian la firma y la hoja de Merkle; un cliente emite bincode canónico.

NativeTransfer { from: "Alice", to: "Bob", amount: 5_000_000_000 } // 5 XRS; las cadenas representan claves base58 0b000000 variante 11 0500000000000000 416c696365 from: u64 LE len 5 ‖ "Alice" (debe coincidir con account_keys[0] en base58) 0300000000000000 426f62 to: u64 LE len 3 ‖ "Bob" 00f2052a01000000 amount: u64 LE lamports

getLatestBlockhash devuelve el hash en hexadecimal; el cliente lo convierte a 32 bytes antes de firmar. Una transacción firmada se envía con POST /submit y el cuerpo {"tx_base64"} (límites en §10.5). Todo rechazo se devuelve como HTTP 200 con {"error"}.

9.5Autenticación de pares

Los pares intercambian tramas bincode NetworkMessage sobre TCP en claro. El código fuente no hace referencia a los crates rustls, tokio-rustls y openssl declarados en Cargo.toml.

connect : "XRS1" // magic de 4 bytes, una vez por conexión frame : u32 BE len ‖ bincode(NetworkMessage) // len ≤ 5 MiB server → : AuthChallenge { nonce: 32 B CSPRNG, server_version } client → : AuthResponse { pubkey: 32 B, signature: 64 B, peer_version } // en 15 s como máximo signature = Ed25519(pubkey, "XRS_P2P_AUTH_V2\0" ‖ nonce ‖ peer_version)

El servidor admite a un par solo después de que la firma verifique contra la clave declarada. El servidor no prueba su identidad ante el cliente; un productor del roster sí lo hace, mediante la punta firmada de §10.2. En mainnet, un par ajeno al roster se descarta tras la autenticación (§12.1).

10Red, mempool y sincronización

Los nodos intercambian tramas bincode con prefijo de longitud sobre TCP sin cifrar. Toda conexión se abre con el desafío y la respuesta Ed25519 de §9.5.

Tabla 20. Constantes de transporte y sincronización.
ParámetroValor
MagicXRS1, 4 bytes en crudo, en un plazo de 5 s
Trama≤ 5 MiB
Bloque≤ 4 MiB, estrictamente por debajo de la trama
Handshake15 s para el desafío y para la respuesta
Pares3,000
Entrantes por subred8 por /24 IPv4 o /64 IPv6, autenticados
Preautenticación por IP4
Handshakes pendientes256
Tasa de mensajes600 por 45 s por conexión; exentos los bloques de sincronización solicitados
Inactividad45 s
Vida de la conexión1 h
Plazo de envío10 s por escritura
Turno de sincronización100 bloques o 16 MiB
Cadencia de GetBlocksuno cada 2 s por conexión
Bytes de sincronización retenidos64 MiB por carril (servicio, seed, público)
Bloques recientes en memoria1,000
Intervalo de snapshot10,000 bloques
PuertosP2P 4000; RPC 56001; explorador 50008
El descubrimiento de pares lo configura el operador: XRS_SEED_PEERS admite hasta 200 entradas IPv4 host[:port]. El seed DNS integrado es un gist de GitHub y la lista de respaldo está vacía, marcada "RESTORE BEFORE ANY DEPLOYMENT". En mainnet la lista de seeds es el roster; se rechaza a todo iniciador ajeno a él.

10.1Gossip y retransmisión

Por la conexión circulan ocho variantes de NetworkMessage; el AuthRequest heredado se rechaza al recibirse.

0 Transaction(tx) gossip, en ambos sentidos 1 Block(block) envío por gossip y turnos de sincronización 2 AuthRequest(sig, version) heredado; rechazado 3 AuthChallenge { nonce[32], server_version } lado que acepta → iniciador 4 AuthResponse { pubkey[32], signature[64], peer_version } 5 GetBlocks(start_slot) petición de historial; cierra un turno dúplex 6 FederationTipRequest { nonce[32], domain[32] } solo pares del roster, ≤ 4 por conexión 7 FederationTip { nonce, height, hash, identity, signature[64] }

Un bloque minado o una transacción admitida se retransmite a ceil(sqrt(n)) pares salientes, acotado a 3–15, cada uno por una conexión nueva con su propio handshake, que transporta un solo mensaje y se cierra. Los bloques recibidos no se retransmiten de nuevo. active_peers contiene solo direcciones salientes, así que un par solo entrante nunca es destino de una retransmisión.

10.2Sincronización

start = forks.want ?? tip + 1, acotado a [tip − 64, tip + 1] turn = bloques con slot ≥ start, ≤ 100 bloques, ≤ 16 MiB, cada uno ≤ 4 MiB vacío y start > tip → solo la punta del nodo que sirve

El lado que acepta anuncia XRS-V2-SYNC-TURNS. Un iniciador que lo devuelve obtiene una conexión dúplex: el servidor envía un turno y después su propio GetBlocks, que cierra el turno y cede el lado de escritura. Un iniciador que responde XRS-V2 sondea en un solo sentido. El historial sale de los 1,000 bloques en memoria o de un recorrido de ledger.dat indexado por bytes, con un presupuesto de 5 s.

Un bloque recibido entra en el ledger en vivo solo como hijo exacto de la punta; cualquier otro bloque va al coordinador de recuperación de §2.4 (Tabla 4). La capa de red nunca invoca Ledger::reorg sobre el ledger en vivo.

La sincronización profunda es exclusiva del roster. Un productor que acredita una punta firmada reciente puede hacer que el nodo descargue su rama desde el slot 1 en blocks.jsonl, reproduzca ambas cadenas desde génesis y, si la rama gana por trabajo, renombre el ledger candidato y salga con código 75 para reiniciarse. Las descargas comparten el límite XRS_MAX_DEEP_SYNC_BYTES, 512 GiB por defecto.

10.3Estado, snapshots y reproducción

ledger.dat es el estado autoritativo: un bloque JSON por línea, añadido al final y con fsync antes de que el bloque cuente como confirmado. El arranque lo reproduce desde génesis y revalida cada bloque con el conjunto completo de reglas. La reproducción falla cerrada: un error de lectura o un registro no decodificable detiene el nodo. Solo se tolera, y se trunca, un registro final sin terminador de línea (XWC-57).

ledger_snapshot.json se escribe cada 10,000 bloques con saldos, stakes, tokens, contratos, firmas procesadas y mapas de gobernanza. No se lee al arrancar; la reproducción es la única ruta de carga, y una reorganización lo borra. contract_state.json es un volcado de diagnóstico sin papel en el consenso.

La recuperación clona el ledger con cp --reflink=always en Linux y clonefile(2) en macOS, al arrancar y antes de reproducir cada rama candidata. En un sistema de archivos sin reflinks, ext4 entre ellos, el nodo se niega a arrancar. Sirven XFS con reflink, btrfs y APFS.

10.4Mempool

El mempool es un max-heap ordenado por tarifa, y la tarifa es plana: mempool_priority_fee devuelve BASE_TX_FEE para toda transacción. El orden entre las transacciones residentes es el del heap y no hay mercado de tarifas. El desalojo exige una tarifa entrante estrictamente mayor, así que un pool lleno no admite nada hasta que la poda libera espacio. Todo rechazo ocurre antes de cualquier mutación. Los límites están en la Tabla 6.

La poda se ejecuta tras cada bloque aceptado: se descartan las entradas cuyo blockhash salió de la ventana de 150 bloques o cuya firma está confirmada, se liberan las reservas confirmadas y se concilia el conjunto de pagadores sin fondos. Las transacciones desplazadas por una reorganización se anotan en un diario SQLite, se readmiten de 256 en 256 y se retransmiten 16 cada 2 s hasta que se confirman o la cadena supera reorg_height + 64.

10.5Interfaces del nodo

Un nodo abre tres puertos de escucha: P2P, la RPC de escritura y la API del explorador con JSON-RPC (Tabla 20). XRS_LOCAL_ONLY=1 hace que P2P y RPC escuchen en loopback; el explorador escucha siempre en 0.0.0.0 y admite cualquier origen. Los endpoints de escritura aceptan un cuerpo de como máximo 256 KiB y 30 peticiones por minuto por IP, y rechazan una firma ya vista en los últimos 120 s.

Tabla 21. Métodos JSON-RPC en el puerto del explorador (POST /).
Método JSON-RPCResultado
getBalancelamports de la dirección
getAccountInfolamports, stake, isValidator
getSlotslot de la punta
getBlockHeightchain_height
getLatestBlockhash, alias getRecentBlockhashhash de la punta en 64 caracteres hexadecimales; lastValidBlockHeight = slot + 150
getBlockcampos de cabecera de un bloque entre los 1,000 en memoria; si no, null
getTransactionla transacción, a partir de los bloques en memoria, con el resultado persistido
getSignaturesForAddresshistorial del almacén de recibos, 20 entradas por defecto
getHealth"ok"
getVersionuna cadena fija en el código, sin relación con la compilación
RPC 56001 POST /submit /stake /unstake /pq-register GET /network/economics /stake/:addr /contract/:id /contracts /launchpads /governance/proposals /capabilities/search /tasks /zk/stats Explorador 50008 GET /v2/stats /v2/blocks /v2/block/slot/:n /v2/block/hash/:h /v2/transactions /v2/tx/:sig /v2/account/:a /v2/account/:a/transactions /v2/tokens /v2/token/:id/holders /v2/contracts /v2/contract/:id /v2/pools /v2/rwa /v2/rwa/:id /v2/validators /v2/search?q=

El historial de transacciones y los recibos son datos derivados en tx_receipts.sqlite que el consenso nunca lee. En testnet, un nodo sin almacén sigue validando y no sirve historial; en mainnet, un fallo al abrirlo detiene el arranque y un fallo de indexación escribe una marca de reconstrucción y hace salir al nodo con código 75. Los endpoints que devuelven 501 se listan en §12.2.

11Gobernanza

La gobernanza es el contrato Governance gestionado por el protocolo en xeris_governance. El primer CreateProposal lo crea con min_proposal_stake = 100 XRS. Tres instrucciones acceden a él: CreateProposal (39), CastVote (40) y ExecuteProposal (41). La ruta genérica de llamada a contratos no puede invocar propose, vote ni execute; las instrucciones son la única vía de entrada.

11.1Propuestas y votos

propose (CreateProposal, 39) stakes[signer] >= min_proposal_stake (100 XRS); sin fianza voting_period_slots 21,600 <= n <= 1,296,000 (1 día .. 60 días) quorum 0 -> DEFAULT_PROPOSAL_QUORUM (5,000 XRS); si no, 0 < q <= MAX_EMISSION_SUPPLY voting_end_slot = current_slot + voting_period_slots (suma con control de desbordamiento) title <= 256 bytes; proposal_type libre, por defecto "text"; parameter_json se guarda tal cual propuestas vivas <= 1,000 en "voting"; registros terminales podados por encima de 1,000, el más antiguo primero vote (CastVote, 40) weight = stakes[signer] en el slot en que se procesa el voto vote yes | no | abstain un voto por dirección; slot <= voting_end_slot; status debe ser "voting" execute (ExecuteProposal, 41; cualquier firmante) slot > voting_end_slot yes + no + abstain < quorum -> "rejected" yes > no -> "passed", total_executed += 1 en otro caso -> "rejected"

El peso del voto es el stake de consenso, no un bloqueo. Una dirección sin stake no puede proponer, y su voto suma peso cero. Un voto posterior a voting_end_slot se rechaza sin modificar la propuesta. El quórum cuenta las abstenciones; la aprobación exige quórum y una mayoría estricta de síes. La propuesta se resuelve en el primer ExecuteProposal tras cerrarse la ventana: no hay ejecución programada, ni timelock, ni segundo paso.

Una propuesta aprobada registra un resultado y no cambia nada on-chain. parameter_json se guarda y nunca se lee; ningún handler aplica un cambio de parámetros. proposal_type es una cadena de forma libre sin enumeración. Los estados son voting, passed y rejected; executed y expired aparecen en el comentario del struct y nunca se asignan.

La delegación y el bloqueo de votos están inactivos. El ledger conserva governance_delegates y governance_locks de forma persistente, pero ninguna instrucción escribe en esos mapas y la ruta de voto no lee ninguno de los dos.

11.2Slots de activación

Las reglas de consenso se condicionan a constantes de slot compiladas en el nodo, no a propuestas. Todas las condiciones están activas desde el primer bloque minado de esta cadena (el slot 0 es el marcador de génesis; el primer bloque es el slot 1), así que la tabla describe la configuración de lanzamiento, no una ruta de migración. Un cambio de reglas con una nueva condición de slot se publica como una nueva versión del nodo.

Tabla 22. Reglas condicionadas por slot; todas activas desde el primer bloque minado.
ConstanteSlotRegla desde ese slot
FEE_ACTIVATION_SLOT1Las tarifas de transacción son obligatorias.
STRICT_ADMISSION_ACTIVATION_SLOT1El conjunto completo de admisión, incluida la continuidad exacta de slots, se aplica a todo bloque que no sea el génesis.
MERKLE_FULLTX_ACTIVATION_SLOT1La hoja de Merkle se compromete con la transacción completa, no con su primera firma.
LEADER_ENFORCEMENT_ACTIVATION_SLOT1La elección de líder y la penalización de dificultad 4x para el no líder las impone el consenso.
HYBRID_SIG_ACTIVATION_SLOT1Todo bloque lleva un proposer_dilithium3_pk no vacío y una hybrid_proposer_sig válida.
POW_PREIMAGE_V2_SLOT1La preimagen de PoW liga parent_hash y poh_timestamp.
HW_ATTEST_V2_ACTIVATION_SLOT1Las atestaciones de hardware son firmas Ed25519 de la clave del dispositivo.
PQ_REGISTRY_BINDING_ACTIVATION_SLOT2La clave inline proposer_dilithium3_pk debe ser igual a la entrada de xeris_pq_keys; el slot 1 registra automáticamente la clave de arranque.
SCRYPT_V2_SLOT0Todo bloque se mina y se verifica con Scrypt v1.2 (N = 4,096, r = 4, p = 1).
SCRYPT_UPGRADE_SLOT0La rama de Scrypt v1.1 nunca se activa.
LEGACY_TRANSFER_SUNSET_SLOT0Se rechaza todo bloque que lleve SystemInstruction::Transfer.

11.3Superficie RPC

GET /governance/proposals lee xeris_governance y devuelve { proposals, total_proposals, total_executed }; convierte voting en Active, passed en Passed y rejected en Failed, y omite votes_abstain y voters. GET /governance/lock/{address} lee los mapas que nada escribe y devuelve locked_amount 0 y delegate null. Las cuatro rutas POST /governance/* devuelven HTTP 501 (§12.2); las escrituras de gobernanza son las instrucciones 39–41 enviadas a POST /submit.

12Mainnet, federación e historial de auditoría

La mainnet usa el chain id xeris-mainnet-v1 y la ejecuta xrs-node 0.1.1 con --mainnet. Con ella el nodo opera como beta federada: tres productores del roster proponen los bloques, el self-staking público está cerrado y las superficies de §12.2 se rechazan. Las reglas de consenso de §2 no cambian; lo que se restringe es la participación. Esta compilación es candidata a mainnet; a fecha de esta revisión, la mainnet no se ha lanzado.

12.1El roster de productores

XRS_FEDERATED_PRODUCERS=<pubkey>@<ipv4>:<port>,<pubkey>@<ipv4>:<port>,<pubkey>@<ipv4>:<port> entradas exactamente 3; claves distintas; direcciones distintas; IPv4; puerto != 0 --mainnet variable sin definir -> panic al arrancar --testnet variable sin definir -> sin roster; ninguno de los filtros siguientes se aplica hash del roster SHA-256("XRS_FEDERATION_V1\0" ‖ chain_id ‖ claves ordenadas por bytes) mensaje de punta "XRS_FEDERATION_TIP_V2\0" ‖ hash del roster ‖ nonce(32) ‖ height_le ‖ len(hash) ‖ hash ‖ len(identity) ‖ identity

El roster es un filtro operativo de la beta, no un voto de consenso. Se admite un conjunto fijo de tres claves en toda ruta que produce, retransmite o hace stake; la tabla enumera cada filtro. El roster es fijo para la cadena: cambiar las claves exige una migración de época revisada, no editar la variable al reiniciar.

Tabla 23. Filtros de la federación con --mainnet.
FiltroRegla
Arranque--mainnet sin XRS_FEDERATED_PRODUCERS provoca un panic. También lo provocan una entrada mal formada, una clave o una dirección duplicadas, una dirección que no es IPv4 o el puerto 0.
GénesisUna mainnet nueva (altura 0) solo arranca si una clave del roster tiene en génesis un stake de al menos 1,000 XRS.
BloquesTodo bloque en disco y todo bloque de un par debe nombrar como proponente a una clave del roster; federation_block_gate se aplica al archivo de la cadena almacenado al arrancar y a cada bloque de un par.
StakeUna instrucción Stake que nombra una clave ajena al roster se rechaza en la entrada, en la retransmisión y en la reproducción: "Public self-staking is closed during federated beta".
ParesUn par entrante que se autentica con una clave ajena al roster se descarta tras el handshake. Las conexiones salientes solo van a direcciones del roster. La lista de seeds es el roster.
PropuestaUn productor del roster propone sobre un padre solo después de haber observado a otro par del roster en esa altura y ese hash en un plazo de 3 s. Es un filtro de liveness 2 de 3, no de finalidad ("not finality"); la finalidad sigue siendo de 64 bloques.
Sincronización profundaSolo una punta recién firmada por un productor del roster autoriza una descarga de historial sin límite. La vía rápida de 64 bloques sigue siendo pública.

12.2Superficies deshabilitadas

Las superficies siguientes existen en la compilación y se rechazan. Una instrucción rechazada en la entrada nunca entra en el mempool. Una instrucción omitida en el dispatcher se incluye en el bloque, paga la tarifa base y no ejecuta nada. Toda escritura RPC que antes mutaba el estado fuera del consenso responde con un error; la sustituye una transacción firmada enviada a POST /submit.

Tabla 24. Superficies que rechaza xrs-node 0.1.1; la lista de seeds de respaldo, vacía, se trata en §10.
SuperficieEstadoSustituto
SubDelegate (22)Se rechaza en la entrada y se omite en el dispatcher (XWC-82).Ninguno.
ZkPrivateTransfer (48)Se omite en el dispatcher; se cobra la tarifa (NEW-CRIT-3).Ninguno.
ZkIdentityProof (49)Se omite en el dispatcher; se cobra la tarifa (NEW-CRIT-1).Ninguno.
PqSignedTransfer (52)Se omite en el dispatcher; se cobra la tarifa (NEW-CRIT-4).Ninguno. PqKeyRotate está activa.
QueryCapabilities (30)Sin efecto dentro de un bloque.GET /capabilities/search.
El SystemInstruction::Transfer heredadoSe rechaza en la entrada y en los bloques desde el slot 0 (LEGACY_TRANSFER_SUNSET_SLOT).NativeTransfer.
execute_limit_order, execute_dca_tickDevuelven un error (CRIT-1, CRIT-2).cancel_limit_order y cancel_dca_order recuperan el escrow.
distribute y toggle_distributions de RWADevuelven un error (XWC-69 del módulo de contratos).Ninguno.
vesting_enabled de LaunchpadSe rechaza el despliegue (XWC-65 del módulo de contratos).Lanzamiento sin vesting.
/airdrop/:addr/:amountCuerpo JSON con status: 501 en una respuesta HTTP 200 (NEW-HIGH-7).Ninguno.
POST /stake/claimHTTP 501 (NEW-CRIT-6).Ninguno; las recompensas de staking se pagan cada 900 bloques sin reclamarlas.
POST /governance/{propose, vote, lock, delegate}HTTP 501 (NEW-CRIT-6).Instrucciones 39 a 41 (§11). No existe ninguna instrucción de bloqueo ni de delegación.
POST /token/{mint, transfer, burn, create}, /contract/{deploy, call}, /rwa/{create, update_status}Error JSON en una respuesta HTTP 200 que indica la instrucción que debe enviarse.Instrucciones 0 a 7 vía POST /submit.

12.3Historial de auditoría

CertiK revisó el nodo como tres módulos con numeración independiente. Cada módulo empieza en XWC-01, así que un id solo identifica un hallazgo junto con el nombre de su módulo: XWC-22 de consenso es la validación de firmas en reorganizaciones; XWC-22 del módulo de criptografía es la capacidad del mempool. Rangos de ids: consenso hasta XWC-88, criptografía hasta XWC-22, contratos y red hasta XWC-81. Xeris ha aplazado XWC-36 de consenso (actualización de dependencias) y XWC-86 (modelo de tarifas, siguiendo la recomendación de CertiK de rediseñar el precio del gas en lugar de parchearlo).

Todo documento de CertiK al que responde el repositorio se titula "Preliminary Comments". El repositorio no contiene ningún informe redactado por CertiK; los estados provisionales de CertiK se conocen tal como los transmiten las respuestas de Xeris. Los primeros comentarios tienen fecha 2026-05-01 y la última respuesta de Xeris, 2026-10-07. Este documento cita el id en el texto allí donde una regla existe a causa de ese hallazgo.

Tabla 25. Módulos de CertiK. Los rangos incluyen rondas posteriores respondidas mediante commit.
MóduloArchivosIds de hallazgosComentarios y respuestas de XerisAbiertos
Consensoledger.rs, pow.rs, poh.rs, main.rsXWC-01 a XWC-88Comentarios 2026-05-01; respuestas 2026-05-18, 2026-06-09, 2026-07-27.XWC-36 dependencias, XWC-86 modelo de tarifas: aplazados.
Criptografía, Merkle y tx poolcrypto.rs, merkle.rs, tx_pool.rsXWC-01 a XWC-22Respuestas 2026-06-22, 2026-07-03, 2026-07-27.Ninguno pendiente; XWC-12 reconocido: pqcrypto-mldsa sustituyó a pqcrypto-dilithium, pqcrypto-traits se mantiene.
Contratos y redcontracts.rs, network.rs, token.rs, genesis.rsXWC-01 a XWC-81Respuesta 2026-09-12; respuestas solo mediante commit 2026-09-29 y 2026-10-07.Ninguno registrado en el repositorio.
Prefijos de id en este documento: XWC-nn, un hallazgo de CertiK, con el módulo indicado cuando hay ambigüedad; AH-n, un problema de consenso que Xeris comunicó a partir de su propio ejercicio con dos nodos; NEW-CRIT-n, NEW-HIGH-n, CRIT-n, C-n, H-n, L-n, M-n, etiquetas de la revisión interna de Xeris previa a la auditoría que el código aún conserva. Solo los ids XWC son hallazgos de CertiK.

AApéndice A. Constantes

Todos los valores siguientes se leen de xrs-node 0.1.1 en el commit 243046d; las rutas son src/<file>:<line>. El lamport es la unidad base: 1 XRS = 1,000,000,000 lamports (9 decimales). Los valores que el código almacena en lamports se muestran en XRS.

Tabla A.1. Constantes del protocolo y límites operativos, xrs-node 0.1.1 en 243046d.
ConstanteValorArchivo:línea
SLOT_DURATION_MS4,000 ms (un bloque cada 4 s)main.rs:33
MAX_REORG_DEPTH64 bloques (≈ 4.3 min hasta la finalidad)ledger.rs:4555
mining_deadline3,900 ms por slotpow.rs:539
NON_LEADER_GRACE_SLOTS2 slots (8,000 ms de silencio del líder)main.rs:404-405
non_leader_targetobjetivo base >> 2 (4× más difícil)ledger.rs:407-421
scrypt_params_for_slotN = 4,096, r = 4, p = 1 (2 MiB por hash)pow.rs:41-49
SCRYPT_UPGRADE_SLOT0pow.rs:22
SCRYPT_V2_SLOT0pow.rs:31
MAX_FUTURE_TIMESTAMP_DRIFT_MS2,000 msledger.rs:244
BLOCKHASH_EXPIRY_WINDOW150 bloques (≈ 10 min)ledger.rs:239
MAX_RECENT_BLOCKS1,000 bloques en RAMledger.rs:36
SNAPSHOT_INTERVAL10,000 bloquesledger.rs:261
MAX_PROCESSED_SIGS10,000,000 firmasledger.rs:39
MAINNET_INITIAL_SUPPLY200,000,000 XRS (tesorería)ledger.rs:27
MAX_EMISSION_SUPPLY500,000,000 XRS (minería + staking + atestación)ledger.rs:65
MAINNET_INITIAL_SUPPLY + MAX_EMISSION_SUPPLY700,000,000 XRSledger.rs:27, 65
BASE_BLOCK_REWARD10 XRSledger.rs:70
HALVING_INTERVAL25,000,000 bloques (≈ 3.17 años)ledger.rs:75
BASE_TX_FEE0.001 XRS (1,000,000 lamports)ledger.rs:58
STAKING_APY_NUMERATOR / STAKING_APY_DENOMINATOR7 / 100 (7 % anual)ledger.rs:5144-5145
STAKING_REWARD_INTERVAL900 bloquesledger.rs:5138
BLOCKS_PER_YEAR7,884,000ledger.rs:5141
MIN_STAKE_TO_MINE1,000 XRSpow.rs:16; ledger.rs:232
mínimo para recompensas de staking100 XRS en stakeledger.rs:9399
MIN_ATTESTOR_STAKE100 XRSledger.rs:1285
ATTESTATION_REWARD0.01 XRSledger.rs:214
ATTESTATION_SLOT_WINDOW200 slotsledger.rs:218
límite de frecuencia de atestación1 recompensa cada 10 bloques por validadorledger.rs:6287-6296
UNBONDING_PERIOD_SLOTS151,200 slots (7 días)ledger.rs:201
MAX_UNBONDING_QUEUE10,000 entradas (10 por cuenta)ledger.rs:205, 5792
MIN_UNSTAKE_AMOUNT1 XRS (mínimo de un unstake parcial)ledger.rs:210
slash_amount10 % del saldo slasheableledger.rs:8219
reporter_reward5 % del slash; el 95 % se quemaledger.rs:8252-8255
MAX_TXS_PER_BLOCK40,000 transaccionesledger.rs:78
MAX_BLOCK_SIZE_BYTES4 MiBledger.rs:196
MAX_IX_PER_TX16 instruccionesledger.rs:94
MAX_ACCOUNTS_PER_TX64 claves de cuentaledger.rs:95
MAX_IX_DATA_SIZE8 KiB por instrucciónledger.rs:93
MAX_SLASH_IX_DATA_SIZE65,535 B (solo SlashReport)ledger.rs:119
MAX_GROTH16_VERIFICATIONS_PER_BLOCK64ledger.rs:154
MAX_GROTH16_PROOF_BYTES512 Bcrypto.rs:1041
MAX_GROTH16_VK_BYTES16 KiBcrypto.rs:1042
MAX_GROTH16_PUBLIC_INPUTS64 elementos de campocrypto.rs:1043
MAX_MEMPOOL_SIZE50,000 entradastx_pool.rs:160
MAX_MEMPOOL_BYTES64 MiBtx_pool.rs:175
MAX_TX_BYTES128 KiB por transaccióntx_pool.rs:183
MAX_TXS_PER_ACCOUNT256 por pagadortx_pool.rs:186
MAX_BYTES_PER_ACCOUNT4 MiB por pagadortx_pool.rs:197
MAX_UNDERFUNDED_TXS5,000 (MAX_MEMPOOL_SIZE / 10)tx_pool.rs:218
XERIS_CHAIN_ID_MAINNETxeris-mainnet-v1ledger.rs:286
SUPPORTED_PQ_ALGORITHMdilithium3 (ML-DSA-65, FIPS 204)crypto.rs:924
dilithium3_public_key_len()1,952 Bcrypto.rs:1184-1186
dilithium3_signature_len()3,309 Bcrypto.rs:1178-1180
clave secreta ML-DSA-654,032 Bcrypto.rs:1164
PQ_KEY_HISTORY_RETENTION_SLOTS302,400 slots (14 días)contracts.rs:1224
FEE_ACTIVATION_SLOT1ledger.rs:54
STRICT_ADMISSION_ACTIVATION_SLOT1ledger.rs:1073
MERKLE_FULLTX_ACTIVATION_SLOT1ledger.rs:1099
LEADER_ENFORCEMENT_ACTIVATION_SLOT1ledger.rs:1111
HYBRID_SIG_ACTIVATION_SLOT1ledger.rs:281
POW_PREIMAGE_V2_SLOT1pow.rs:109
HW_ATTEST_V2_ACTIVATION_SLOT1ledger.rs:7311
PQ_REGISTRY_BINDING_ACTIVATION_SLOT2ledger.rs:1137
LEGACY_TRANSFER_SUNSET_SLOT0ledger.rs:229
MAGIC_BYTESXRS1network.rs:23
MAX_MSG_SIZE5 MiB por tramanetwork.rs:28
MAX_PEERS3,000network.rs:29
CONN_TIMEOUT45 s de inactividadnetwork.rs:30
MAX_PEERS_PER_SUBNET8 por /24network.rs:34
MAX_PREAUTH_PER_IP4 conexiones en preautenticación por IPnetwork.rs:40
MAX_CONN_LIFETIME_SECS3,600 snetwork.rs:46
PEER_SEND_TIMEOUT_SECS10 snetwork.rs:50
MAX_MSGS_PER_WINDOW600 mensajes por 45 snetwork.rs:55
CHALLENGE_TIMEOUT_SECS15 s de handshakenetwork.rs:447
GETBLOCKS_MIN_INTERVAL2 snetwork.rs:560
MAX_SYNC_TURN_BLOCKS100 bloques por turnonetwork.rs:561
MAX_SYNC_TURN_BYTES16 MiB por turnonetwork.rs:562
MAX_RETAINED_SYNC_BYTES64 MiBnetwork.rs:563
MAX_PENDING_HANDSHAKES256network.rs:3786
MAGIC_TIMEOUT_SECS5 snetwork.rs:3791
min_proposal_stake100 XRSledger.rs:8275
MIN_VOTING_PERIOD21,600 slots (comprobación en la entrada)ledger.rs:8267
MIN_VOTING_PERIOD_SLOTS21,600 slots (≈ 1 día)contracts.rs:5527
MAX_VOTING_PERIOD_SLOTS1,296,000 slots (≈ 60 días)contracts.rs:5528
DEFAULT_PROPOSAL_QUORUM5,000 XRS de peso de voto emitidocontracts.rs:163
MAX_LIVE_PROPOSALS1,000contracts.rs:5505
DISPUTE_CHALLENGE_PERIOD_SLOTS21,600 slotscontracts.rs:995
DISPUTE_MAX_LIFETIME_SLOTS648,000 slotscontracts.rs:1000
arbitration_panelvalidadores con stake ≥ MIN_STAKE_TO_MINEledger.rs:5286-5291
MIN_DEAL_DISPUTE_BOND1 XRScontracts.rs:1008
DEAL_TIMEOUT_SLOTS648,000 slotscontracts.rs:1079
MAX_TASK_LIFETIME_SLOTS648,000 slotscontracts.rs:916
MAX_TASK_REJECTIONS3contracts.rs:914
DAILY_SLOTS21,600 slots (ventana de gasto de 24 h)contracts.rs:3439
agentes por registro50contracts.rs:3334
HEARTBEAT_RETENTION_SLOTS21,600 slotscontracts.rs:5947
MAX_HEARTBEAT_RECORDS10,000contracts.rs:5948
stale_threshold5,400 slots (≈ 6 h)contracts.rs:5984
ORDER_STORAGE_BOND0.01 XRSledger.rs:1290
MAX_ORDER_LIFETIME_SLOTS650,000 slotsledger.rs:1295
MAX_ACTIVE_ORDERS_PER_OWNER100ledger.rs:1325
MAX_ORACLE_VALUE_AGE_SLOTS900 slotsledger.rs:1300
PRICE_WINDOW_BLOCKS61 bloques (mediana)ledger.rs:1318
PRICE_MIN_OBSERVATIONS20ledger.rs:1319
MAX_TRACKED_PRICE_POOLS64contracts.rs:170
fee_bps (valor por defecto de Swap)30 bpscontracts.rs:1396
MINIMUM_LIQUIDITY1,000 sharescontracts.rs:6
creator_reward_bps100 bpscontracts.rs:1600
XERIS_FEE_BPS77 bpscontracts.rs:308
total_supply (valor por defecto de Launchpad)1,000,000,000 tokenscontracts.rs:1603-1604
target_liquidity_xrs (valor por defecto)10,000 XRScontracts.rs:1608-1609
liquidity_bps2,000 (acotado a 500–4,000)contracts.rs:1631-1633
MAX_RWA_APPROVED_HOLDERS1,024token.rs:7
valuation (RealWorldAsset)centavos de USDtoken.rs:872
challenge_period_slots (canales de estado)1,000 slotsledger.rs:8308
MAX_CHANNELS100,000contracts.rs:2105

BApéndice B. El conjunto de instrucciones

XerisInstruction tiene 62 variantes. El discriminante de bincode es el índice de declaración, codificado como u32 little-endian, así que la columna Índice es la etiqueta en el formato binario. La columna Regla del firmante nombra el campo que debe ser igual a la primera clave de cuenta, o la regla que aplica el handler. La columna Estado indica qué hace el dispatcher de bloques con la variante; las ramas deshabilitada omiten la instrucción y cobran igualmente la tarifa.

Tabla B.1. Variantes de XerisInstruction en el orden de declaración de bincode
ÍndiceVarianteRegla del firmanteEstado
0TokenMintautoridad de acuñaciónactiva
1TokenTransferfirmante == fromactiva
2TokenBurnfirmante == fromactiva
3TokenCreateautoridad de acuñaciónactiva
4ContractCallsegún el métodoactiva
5ContractDeploypropietarioactiva
6TokenCreateRWAautoridad de acuñaciónactiva
7RWAUpdateStatusautoridad de acuñaciónactiva
8RWATransferfirmante == fromactiva
9Stakefirmante == pubkeyactiva
10Unstakefirmante == pubkeyactiva
11NativeTransferfirmante == fromactiva
12ValidatorAttestationfirmante == validatoractiva
13WrapXrsfirmanteactiva
14UnwrapXrsfirmanteactiva
15RegisterAgentpropietarioactiva
16UpdateAgentpropietarioactiva
17AgentExecuteagente (ejecuta en nombre del propietario)activa
18CreateIdentityfirmante == identity_pubkeyactiva
19UpdateIdentityfirmante == identity_pubkeyactiva
20AttestReputationidentidad activaactiva
21AgentMessageidentidad activaactiva (no escribe estado)
22SubDelegateno aplicadeshabilitada (XWC-82)
23ConditionalOrderpropietarioactiva
24CancelConditionalOrderpropietarioactiva
25RegisterOraclepropietarioactiva
26OracleSubmitpropietarioactiva
27HardwareAttestfirmante == device_pubkey o bound_identityactiva
28RegisterCapabilityfirmante == provider_identityactiva
29UpdateCapabilityfirmante == provider_identityactiva
30QueryCapabilitiesno aplicasin efecto
31PostTaskcualquieraactiva
32ClaimTaskfirmante == claimant_identityactiva
33ResolveTaskparteactiva
34RegisterModelfirmante == identity_pubkeyactiva
35UpdateModelpropietarioactiva
36OpenDisputecualquieraactiva
37ResolveDisputestake ≥ 1,000 XRS o parteactiva
38SlashReportcualquieraactiva
39CreateProposalstake ≥ 100 XRSactiva
40CastVotecualquiera (peso = stake)activa
41ExecuteProposalcualquieraactiva
42OpenChannelparteactiva
43CloseChannelparteactiva
44ForceCloseChannelparteactiva
45AgentHeartbeatfirmante == identity_pubkeyactiva
46ZkProofSubmitcualquieraactiva
47ZkProofVerifycualquieraactiva (solo lectura)
48ZkPrivateTransferno aplicadeshabilitada (NEW-CRIT-3)
49ZkIdentityProofno aplicadeshabilitada (NEW-CRIT-1)
50PqKeyRegisterfirmante == ed25519_pubkeyactiva
51PqKeyRotatefirmante == ed25519_pubkeyactiva
52PqSignedTransferno aplicadeshabilitada (NEW-CRIT-4)
53PqAttestcualquieraactiva (marcador)
54CreateDealparteactiva
55AcceptDealparteactiva
56ConfirmDealparteactiva
57CancelDealparteactiva
58DisputeDealparteactiva
59SettleDealcualquieraactiva
60ReclaimDealparteactiva
61ZkVkRegisterstake ≥ 1,000 XRSactiva

CApéndice C. Tipos de contrato

ContractType tiene 23 variantes. ContractDeploy resuelve contract_type_str mediante ContractType::from_str, que convierte la entrada a minúsculas, así que ningún alias distingue mayúsculas de minúsculas. Ocho tipos los gestiona el protocolo: se rechaza su despliegue por usuarios y el protocolo crea cada singleton bajo un id fijo xeris_* en su primer uso.

Tabla C.1. Las 23 variantes de ContractType, sus alias de from_str y cómo se crea y se opera cada una.
TipoAliasDesplegableId del singletonOperado mediante
TimeLocktimelock, time_locksí—ContractDeploy, ContractCall
Escrowescrowsí—ContractDeploy, ContractCall
Swapswapsí—ContractDeploy, ContractCall
Vestingvestingsí—ContractDeploy, ContractCall
MultiSigmultisig, multi_sigsí—ContractDeploy, ContractCall
RealWorldAssetrwa, real_world_asset, realworldassetsolo la autoridad de acuñación del token RWA—ContractDeploy, ContractCall
Launchpadlaunchpad, launch_padsí—ContractDeploy, ContractCall
AgentRegistryagent_registry, agent, agentssíagent_registry_<sha256(owner)[..32]> por propietarioRegisterAgent, UpdateAgent, AgentExecute
IdentityRegistryidentity, identity_registrysíxeris_identities; identity_<sha256(pubkey)[..32]> por identidadCreateIdentity, UpdateIdentity, AttestReputation
ConditionalOrderBookconditional, conditional_orders, ordersnoxeris_conditional_ordersConditionalOrder, CancelConditionalOrder
LimitOrderlimit, limit_order, limit_orderssí—ContractDeploy, ContractCall
DcaOrderdca, dca_order, dollar_cost_averagingsí—ContractDeploy, ContractCall
OracleRegistryoracle, oracle_registry, oraclessíxeris_oraclesRegisterOracle, OracleSubmit
DeviceRegistrydevice, device_registry, hardwarenoxeris_devicesHardwareAttest
CapabilityRegistrycapability, capabilities, cap_registrysíxeris_capabilitiesRegisterCapability, UpdateCapability
TaskBoardtask, tasks, task_board, bountynoxeris_tasksPostTask, ClaimTask, ResolveTask
ModelRegistrymodel, model_registry, modelssíxeris_modelsRegisterModel, UpdateModel
DisputeRegistrydispute, disputes, arbitrationnoxeris_disputesOpenDispute, ResolveDispute, DisputeDeal
Governancegovernance, gov, daosí; las instrucciones actúan solo sobre xeris_governancexeris_governanceCreateProposal, CastVote, ExecuteProposal
StateChannelRegistrychannel, channels, state_channelno; también se rechaza dentro de deploy_contractxeris_channelsOpenChannel, CloseChannel, ForceCloseChannel
ZkVerifierRegistryzk, zk_verifier, zero_knowledgenoxeris_zk_verifierZkVkRegister, ZkProofSubmit, ZkProofVerify, PqAttest
PqKeyRegistrypq, pq_keys, post_quantum, quantumnoxeris_pq_keysPqKeyRegister, PqKeyRotate; registro automático del proponente en el slot 1
DealRegistrydeal, deals, escrow_dealnoxeris_dealsCreateDeal … ReclaimDeal (54–60)
xeris_heartbeats (AgentHeartbeat) y xeris_slashing_registry (SlashReport) se almacenan con contract_type: IdentityRegistry aunque su estado es Heartbeats; GET /contracts los devuelve como IdentityRegistry.