XerisCoin
Especificación técnica
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.
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).
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.
| Campo | Regla | Dónde |
|---|---|---|
| poh_hash | no vacío | toda ruta de admisión |
| poh_timestamp | distinto de cero | toda ruta de admisión |
| poh_timestamp | >= parent.poh_timestamp | toda ruta de admisión |
| poh_timestamp - parent.poh_timestamp | >= 2,000 ms; la igualdad falla | toda 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.
| Generación | N | r | p | Memoria por hash | Estado |
|---|---|---|---|---|---|
| Legacy | 1,024 | 1 | 1 | 128 KiB | inalcanzable |
| v1.1 | 16,384 | 8 | 1 | 16 MiB | inalcanzable |
| v1.2 | 4,096 | 4 | 1 | 2 MiB | todos 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.
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.
| Regla | Valor |
|---|---|
| Byte objetivo de génesis | 0x1f, también mientras existan menos de 10 bloques |
| Ventana | los min(len, 20) bloques más recientes |
| Intervalo objetivo | 4,000 ms |
| Paso | base × clamp(avg_ms / 4,000, 0.75, 1.25), punto fijo 1/10,000 |
| Acotación | de 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.
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.
| Bloque de par | Acción |
|---|---|
| proponente fuera del roster (mainnet) | rechazado |
| tamaño serializado > 4 MiB | rechazado |
| ya canónico | ignorado |
| previous_hash == tip.hash y slot == tip.slot + 1 | validado y aplicado directamente |
| slot fuera de [tip - 64, tip + 64] | rechazado |
| poh_timestamp > reloj de pared + 2,000 ms | rechazado, se reintenta después |
| en otro caso | puerta 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.
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.
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
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
| Constante | Valor | Se aplica en |
|---|---|---|
| MAX_TXS_PER_BLOCK | 40,000 | validación de bloque |
| MAX_BLOCK_SIZE_BYTES | 4 MiB serializados | validación de bloque; ensamblado por el productor |
| MAX_IX_PER_TX | 16 | entrada; validación de bloque |
| MAX_ACCOUNTS_PER_TX | 64 | entrada; validación de bloque |
| MAX_IX_DATA_SIZE | 8 KiB por instrucción | entrada; validación de bloque |
| MAX_SLASH_IX_DATA_SIZE | 65,535 B, solo SlashReport | entrada; validación de bloque |
| MAX_GROTH16_VERIFICATIONS_PER_BLOCK | 64 | validación de bloque |
| MAX_RWA_POLICY_WORK_PER_BLOCK | 256 recorridos de titulares | ejecución; instrucción rechazada |
| MAX_MODEL_MUTATIONS_PER_BLOCK | 16 | ejecució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
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.
| Límite del mempool | Valor |
|---|---|
| Entradas | 50,000 |
| Bytes | 64 MiB |
| Por transacción | 128 KiB |
| Por pagador | 256 transacciones; 4 MiB |
| Transacciones SlashReport | 128 en el pool; 4 por informante |
| Pagadores sin fondos | 5,000 (10 % de las entradas) |
| Prioridad | plana; BASE_TX_FEE para toda transacción |
| Desalojos por admisión | 64, 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).
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.
| Estado de la transacción | Regla |
|---|---|
| confirmed | todas las instrucciones confirmadas |
| partial | algunas confirmadas; first_failed_index indica la primera que no lo fue |
| failed | ninguna 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.
| Asignación | XRS | Porcentaje | Mecanismo |
|---|---|---|---|
| Tesorería | 200,000,000 | 28.6 % | Saldo de génesis; 1,000 XRS de él en stake desde el génesis |
| Presupuesto de emisión | 500,000,000 | 71.4 % | Recompensas de minería + staking + atestación; MAX_EMISSION_SUPPLY |
| Total (nominal) | 700,000,000 | 100 % | Tesorería + presupuesto de emisión |
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.
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).
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).
| Regla | Valor |
|---|---|
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 Unstake | 0 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 |
| Vencimiento | completion_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).
| Regla | Valor |
|---|---|
| Firmante | igual a validator |
| Stake | ≥ 100 XRS (MIN_ATTESTOR_STAKE) |
| Antigüedad | block.slot − block_slot ≤ 200 (ATTESTATION_SLOT_WINDOW) |
| Hash | block_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 |
| Tasa | ninguna atestación previa del validador para un slot > block_slot − 10; una recompensa cada 10 slots |
| Recompensa | min(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.
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.
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.
| Regla | Valor |
|---|---|
| Id del contrato | 1–128 caracteres; alfanuméricos (Unicode is_alphanumeric), _ o - |
| Id existente | nunca se sobrescribe; el despliegue se omite |
| Espacios de nombres de id reservados | xeris_*, identity_*, agent_registry_*, __*, *_xrs_pool: no desplegables por usuarios |
| Tipos gestionados por el protocolo | ContractDeploy rechaza los ocho tipos singleton |
| Parámetros de despliegue | params_json se analiza como objeto JSON; una entrada no analizable pasa a ser {} |
| Llamante | account_keys[0], el pagador de la tarifa |
| Argumentos de llamada | un objeto JSON en UTF-8; current_slot se sobrescribe con el slot del bloque |
| Excepción binaria | Swap swap_a_to_b / swap_b_to_a con exactamente 16 bytes: u64 LE amount ‖ u64 LE min_output |
| Métodos protegidos | internos 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 inactivo | toda llamada falla con "Contract is not active" |
| Lecturas de registro | paginadas: 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.
| Tipo | Parámetros de despliegue | Métodos | Regla |
|---|---|---|---|
| TimeLock | beneficiary, token_id, amount, unlock_timestamp (ms); amount se debita a quien despliega | release | cualquiera 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 |
| Escrow | party_a (debe ser igual al firmante), party_b (distinta), token_id, amount | confirm, cancel | ambas 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 |
| Vesting | beneficiary, token_id, total_amount, cliff_timestamp, end_timestamp (ms); cliff ≥ now, end > now, cliff ≤ end | claim | solo 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 |
| MultiSig | signers[] (claves públicas distintas), threshold (1 ≤ t ≤ n); no se bloquean fondos | propose, approve, reject | una 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.
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.
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.
| Condición | Fuente | Se dispara cuando |
|---|---|---|
slot_reached | — | slot actual ≥ umbral |
price_above / price_below | el id de un contrato Swap desplegado | precio 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_value | un 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.
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).
| Registro | Id de contrato | Creado por |
|---|---|---|
| Raíz de identidades | xeris_identities | CreateIdentity (18) |
| Identidad | identity_<sha256(pubkey)[..32]> | CreateIdentity (18), uno por clave pública |
| Registro de agentes | agent_registry_<sha256(owner)[..32]> | RegisterAgent (15), uno por propietario |
| Oráculos | xeris_oracles | RegisterOracle (25) |
| Dispositivos | xeris_devices | HardwareAttest (27), una vez verificada la prueba |
| Capacidades | xeris_capabilities | RegisterCapability (28) |
| Tareas | xeris_tasks | PostTask (31) |
| Modelos | xeris_models | RegisterModel (34) |
| Disputas | xeris_disputes | OpenDispute (36) o DisputeDeal (58) |
| Acuerdos | xeris_deals | CreateDeal (54) |
| Canales | xeris_channels | OpenChannel (42) |
| Heartbeats | xeris_heartbeats | AgentHeartbeat (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.
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.
| Método de ContractCall delegado | Gasto cargado al presupuesto |
|---|---|
| buy_tokens · sell_tokens | xrs_amount · token_amount |
| swap | amount_in + input_amount + amount |
| add_liquidity | accepted_a + accepted_b cotizados; si no, amount_a + amount_b |
| remove_liquidity · create_dca_order · place_order | shares · total_amount · locked_amount |
| post · open · create | reward · deposit · amount |
| cancel · reclaim · claim_rewards · redeem · amend · list · status · get_stats · get_key | 0 |
| confirm · verify · cualquier otro método | rechazado: 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.
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.
| Registro | Instrucción | Regla |
|---|---|---|
| OracleRegistry | RegisterOracle (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 |
| OracleRegistry | OracleSubmit (26) | solo el propietario; metadata <= 1,024 B; el feed conserva los últimos 1,000 puntos (slot, value) |
| DeviceRegistry | HardwareAttest (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 |
| ModelRegistry | RegisterModel (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 |
| Heartbeats | AgentHeartbeat (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) |
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.
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.
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
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.
statusdistinto deactive: 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.
| Método | Argumentos | Efecto |
|---|---|---|
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 |
redeem | ninguno | fija redeemed_at, desactiva el contrato y pone el status del registro en redeemed |
distribute, toggle_distributions | cualquiera | deshabilitados; 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.
| Primitiva | Algoritmo | Uso | Estado |
|---|---|---|---|
| Ed25519 | Curve25519 | Transacciones; autenticación P2P; mensajes de canal, de dispositivo y de slashing; mitad clásica de la firma de bloque | activa |
| ML-DSA-65 (Dilithium3) | Reticular, FIPS 204, pqcrypto-mldsa | Mitad post-cuántica de la firma de bloque; registro xeris_pq_keys; pruebas de rotación | activa; obligatoria desde el slot 1 |
| Groth16 | Pairing BN254, arkworks | Verificación de pruebas contra VK registradas (ZkProofSubmit) | activa; solo seguridad clásica |
| Sigma de Schnorr, compromisos de Pedersen, prueba de rango | Ristretto255 | Transferencias confidenciales | reserva; inalcanzable |
| WOTS+/XMSS | Basado en hash SHA-256 | Firmas post-cuánticas de respaldo | reserva; inalcanzable |
PqSignedTransfer (52) | ML-DSA-65 | Transferencia autorizada por una firma post-cuántica | deshabilitada; 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.
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).
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.
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.
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
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.
| Etiqueta | Firmante o uso | Formato tras la etiqueta |
|---|---|---|
XRS_HYBRID_V1 | proponente del bloque, Ed25519 + ML-DSA-65 | §9.1 |
XRS_POW_V2 | preimagen Scrypt | §2.2 |
XRS_LEADER_V1, XRS_LEADER_V1_RETRY | semilla de elección, SHA-256 | last_block_hash ‖ slot u64; reintento: seed |
XRS_P2P_AUTH_V2\0 | par, Ed25519 | nonce (32 B) ‖ peer_version; se firma tal cual, sin hash previo |
XRS_CH_STATE_V3 | contraparte del canal, Ed25519 | lp(chain_id) ‖ lp(channel_id) ‖ generation ‖ created_slot ‖ id(party_a) ‖ id(party_b) ‖ balance_a ‖ balance_b ‖ state_sequence |
XRS_CLOSE_CH_V4 | contraparte del canal, Ed25519 | como el anterior, terminado en final_balance_a ‖ final_balance_b ‖ message_count |
XRS_HW_ATTEST_V2 | clave del dispositivo, Ed25519 | id(device_pubkey) ‖ id(bound_identity) ‖ lp(device_type) ‖ lp(manufacturer) ‖ lp(model) ‖ lp(firmware_version) ‖ slot u64 |
xrs_pq_rotate_v5 | clave PQ actual, ML-DSA-65 | chain_id ‖ old_pk ‖ new_pk ‖ rotation_count u64; sin prefijos de longitud |
XRS_SLASH_ID_V2 | SHA-256, resumen de la cabecera | canonical (el mensaje de §9.1) |
XRS_SLASH_OFFENSE_V2 | SHA-256, id de la ofensa | owner_pubkey ‖ violation_slot u64 ‖ min(d_a, d_b) ‖ max(d_a, d_b) |
XRS_FEDERATION_V1\0 | SHA-256, dominio del roster | chain_id ‖ producer keys, sorted |
XRS_FEDERATION_TIP_V2\0 | productor del roster, Ed25519 | domain (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.
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.
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.
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.
| Parámetro | Valor |
|---|---|
| Magic | XRS1, 4 bytes en crudo, en un plazo de 5 s |
| Trama | ≤ 5 MiB |
| Bloque | ≤ 4 MiB, estrictamente por debajo de la trama |
| Handshake | 15 s para el desafío y para la respuesta |
| Pares | 3,000 |
| Entrantes por subred | 8 por /24 IPv4 o /64 IPv6, autenticados |
| Preautenticación por IP | 4 |
| Handshakes pendientes | 256 |
| Tasa de mensajes | 600 por 45 s por conexión; exentos los bloques de sincronización solicitados |
| Inactividad | 45 s |
| Vida de la conexión | 1 h |
| Plazo de envío | 10 s por escritura |
| Turno de sincronización | 100 bloques o 16 MiB |
Cadencia de GetBlocks | uno cada 2 s por conexión |
| Bytes de sincronización retenidos | 64 MiB por carril (servicio, seed, público) |
| Bloques recientes en memoria | 1,000 |
| Intervalo de snapshot | 10,000 bloques |
| Puertos | P2P 4000; RPC 56001; explorador 50008 |
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.
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
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.
| Método JSON-RPC | Resultado |
|---|---|
getBalance | lamports de la dirección |
getAccountInfo | lamports, stake, isValidator |
getSlot | slot de la punta |
getBlockHeight | chain_height |
getLatestBlockhash, alias getRecentBlockhash | hash de la punta en 64 caracteres hexadecimales; lastValidBlockHeight = slot + 150 |
getBlock | campos de cabecera de un bloque entre los 1,000 en memoria; si no, null |
getTransaction | la transacción, a partir de los bloques en memoria, con el resultado persistido |
getSignaturesForAddress | historial del almacén de recibos, 20 entradas por defecto |
getHealth | "ok" |
getVersion | una cadena fija en el código, sin relación con la compilación |
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
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.
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.
| Constante | Slot | Regla desde ese slot |
|---|---|---|
FEE_ACTIVATION_SLOT | 1 | Las tarifas de transacción son obligatorias. |
STRICT_ADMISSION_ACTIVATION_SLOT | 1 | El 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_SLOT | 1 | La hoja de Merkle se compromete con la transacción completa, no con su primera firma. |
LEADER_ENFORCEMENT_ACTIVATION_SLOT | 1 | La elección de líder y la penalización de dificultad 4x para el no líder las impone el consenso. |
HYBRID_SIG_ACTIVATION_SLOT | 1 | Todo bloque lleva un proposer_dilithium3_pk no vacío y una hybrid_proposer_sig válida. |
POW_PREIMAGE_V2_SLOT | 1 | La preimagen de PoW liga parent_hash y poh_timestamp. |
HW_ATTEST_V2_ACTIVATION_SLOT | 1 | Las atestaciones de hardware son firmas Ed25519 de la clave del dispositivo. |
PQ_REGISTRY_BINDING_ACTIVATION_SLOT | 2 | La 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_SLOT | 0 | Todo bloque se mina y se verifica con Scrypt v1.2 (N = 4,096, r = 4, p = 1). |
SCRYPT_UPGRADE_SLOT | 0 | La rama de Scrypt v1.1 nunca se activa. |
LEGACY_TRANSFER_SUNSET_SLOT | 0 | Se 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
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.
| Filtro | Regla |
|---|---|
| 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énesis | Una mainnet nueva (altura 0) solo arranca si una clave del roster tiene en génesis un stake de al menos 1,000 XRS. |
| Bloques | Todo 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. |
Stake | Una 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". |
| Pares | Un 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. |
| Propuesta | Un 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 profunda | Solo 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.
| Superficie | Estado | Sustituto |
|---|---|---|
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 /. |
El SystemInstruction:: heredado | Se rechaza en la entrada y en los bloques desde el slot 0 (LEGACY_). | NativeTransfer. |
execute_, execute_ | Devuelven un error (CRIT-1, CRIT-2). | cancel_ y cancel_ recuperan el escrow. |
distribute y toggle_ de RWA | Devuelven un error (XWC-69 del módulo de contratos). | Ninguno. |
vesting_ de Launchpad | Se rechaza el despliegue (XWC-65 del módulo de contratos). | Lanzamiento sin vesting. |
/ | Cuerpo JSON con status: 501 en una respuesta HTTP 200 (NEW-HIGH-7). | Ninguno. |
POST / | HTTP 501 (NEW-CRIT-6). | Ninguno; las recompensas de staking se pagan cada 900 bloques sin reclamarlas. |
POST / | HTTP 501 (NEW-CRIT-6). | Instrucciones 39 a 41 (§11). No existe ninguna instrucción de bloqueo ni de delegación. |
POST /, /, / | Error JSON en una respuesta HTTP 200 que indica la instrucción que debe enviarse. | Instrucciones 0 a 7 vía POST /. |
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.
| Módulo | Archivos | Ids de hallazgos | Comentarios y respuestas de Xeris | Abiertos |
|---|---|---|---|---|
| Consenso | ledger.rs, pow.rs, poh.rs, main.rs | XWC-01 a XWC-88 | Comentarios 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 pool | crypto.rs, merkle.rs, tx_pool.rs | XWC-01 a XWC-22 | Respuestas 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 red | contracts.rs, network.rs, token.rs, genesis.rs | XWC-01 a XWC-81 | Respuesta 2026-09-12; respuestas solo mediante commit 2026-09-29 y 2026-10-07. | Ninguno registrado en el repositorio. |
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.
| Constante | Valor | Archivo:línea |
|---|---|---|
| SLOT_DURATION_MS | 4,000 ms (un bloque cada 4 s) | main.rs:33 |
| MAX_REORG_DEPTH | 64 bloques (≈ 4.3 min hasta la finalidad) | ledger.rs:4555 |
| mining_deadline | 3,900 ms por slot | pow.rs:539 |
| NON_LEADER_GRACE_SLOTS | 2 slots (8,000 ms de silencio del líder) | main.rs:404-405 |
| non_leader_target | objetivo base >> 2 (4× más difícil) | ledger.rs:407-421 |
| scrypt_params_for_slot | N = 4,096, r = 4, p = 1 (2 MiB por hash) | pow.rs:41-49 |
| SCRYPT_UPGRADE_SLOT | 0 | pow.rs:22 |
| SCRYPT_V2_SLOT | 0 | pow.rs:31 |
| MAX_FUTURE_TIMESTAMP_DRIFT_MS | 2,000 ms | ledger.rs:244 |
| BLOCKHASH_EXPIRY_WINDOW | 150 bloques (≈ 10 min) | ledger.rs:239 |
| MAX_RECENT_BLOCKS | 1,000 bloques en RAM | ledger.rs:36 |
| SNAPSHOT_INTERVAL | 10,000 bloques | ledger.rs:261 |
| MAX_PROCESSED_SIGS | 10,000,000 firmas | ledger.rs:39 |
| MAINNET_INITIAL_SUPPLY | 200,000,000 XRS (tesorería) | ledger.rs:27 |
| MAX_EMISSION_SUPPLY | 500,000,000 XRS (minería + staking + atestación) | ledger.rs:65 |
| MAINNET_INITIAL_SUPPLY + MAX_EMISSION_SUPPLY | 700,000,000 XRS | ledger.rs:27, 65 |
| BASE_BLOCK_REWARD | 10 XRS | ledger.rs:70 |
| HALVING_INTERVAL | 25,000,000 bloques (≈ 3.17 años) | ledger.rs:75 |
| BASE_TX_FEE | 0.001 XRS (1,000,000 lamports) | ledger.rs:58 |
| STAKING_APY_NUMERATOR / STAKING_APY_DENOMINATOR | 7 / 100 (7 % anual) | ledger.rs:5144-5145 |
| STAKING_REWARD_INTERVAL | 900 bloques | ledger.rs:5138 |
| BLOCKS_PER_YEAR | 7,884,000 | ledger.rs:5141 |
| MIN_STAKE_TO_MINE | 1,000 XRS | pow.rs:16; ledger.rs:232 |
| mínimo para recompensas de staking | 100 XRS en stake | ledger.rs:9399 |
| MIN_ATTESTOR_STAKE | 100 XRS | ledger.rs:1285 |
| ATTESTATION_REWARD | 0.01 XRS | ledger.rs:214 |
| ATTESTATION_SLOT_WINDOW | 200 slots | ledger.rs:218 |
| límite de frecuencia de atestación | 1 recompensa cada 10 bloques por validador | ledger.rs:6287-6296 |
| UNBONDING_PERIOD_SLOTS | 151,200 slots (7 días) | ledger.rs:201 |
| MAX_UNBONDING_QUEUE | 10,000 entradas (10 por cuenta) | ledger.rs:205, 5792 |
| MIN_UNSTAKE_AMOUNT | 1 XRS (mínimo de un unstake parcial) | ledger.rs:210 |
| slash_amount | 10 % del saldo slasheable | ledger.rs:8219 |
| reporter_reward | 5 % del slash; el 95 % se quema | ledger.rs:8252-8255 |
| MAX_TXS_PER_BLOCK | 40,000 transacciones | ledger.rs:78 |
| MAX_BLOCK_SIZE_BYTES | 4 MiB | ledger.rs:196 |
| MAX_IX_PER_TX | 16 instrucciones | ledger.rs:94 |
| MAX_ACCOUNTS_PER_TX | 64 claves de cuenta | ledger.rs:95 |
| MAX_IX_DATA_SIZE | 8 KiB por instrucción | ledger.rs:93 |
| MAX_SLASH_IX_DATA_SIZE | 65,535 B (solo SlashReport) | ledger.rs:119 |
| MAX_GROTH16_VERIFICATIONS_PER_BLOCK | 64 | ledger.rs:154 |
| MAX_GROTH16_PROOF_BYTES | 512 B | crypto.rs:1041 |
| MAX_GROTH16_VK_BYTES | 16 KiB | crypto.rs:1042 |
| MAX_GROTH16_PUBLIC_INPUTS | 64 elementos de campo | crypto.rs:1043 |
| MAX_MEMPOOL_SIZE | 50,000 entradas | tx_pool.rs:160 |
| MAX_MEMPOOL_BYTES | 64 MiB | tx_pool.rs:175 |
| MAX_TX_BYTES | 128 KiB por transacción | tx_pool.rs:183 |
| MAX_TXS_PER_ACCOUNT | 256 por pagador | tx_pool.rs:186 |
| MAX_BYTES_PER_ACCOUNT | 4 MiB por pagador | tx_pool.rs:197 |
| MAX_UNDERFUNDED_TXS | 5,000 (MAX_MEMPOOL_SIZE / 10) | tx_pool.rs:218 |
| XERIS_CHAIN_ID_MAINNET | xeris-mainnet-v1 | ledger.rs:286 |
| SUPPORTED_PQ_ALGORITHM | dilithium3 (ML-DSA-65, FIPS 204) | crypto.rs:924 |
| dilithium3_public_key_len() | 1,952 B | crypto.rs:1184-1186 |
| dilithium3_signature_len() | 3,309 B | crypto.rs:1178-1180 |
| clave secreta ML-DSA-65 | 4,032 B | crypto.rs:1164 |
| PQ_KEY_HISTORY_RETENTION_SLOTS | 302,400 slots (14 días) | contracts.rs:1224 |
| FEE_ACTIVATION_SLOT | 1 | ledger.rs:54 |
| STRICT_ADMISSION_ACTIVATION_SLOT | 1 | ledger.rs:1073 |
| MERKLE_FULLTX_ACTIVATION_SLOT | 1 | ledger.rs:1099 |
| LEADER_ENFORCEMENT_ACTIVATION_SLOT | 1 | ledger.rs:1111 |
| HYBRID_SIG_ACTIVATION_SLOT | 1 | ledger.rs:281 |
| POW_PREIMAGE_V2_SLOT | 1 | pow.rs:109 |
| HW_ATTEST_V2_ACTIVATION_SLOT | 1 | ledger.rs:7311 |
| PQ_REGISTRY_BINDING_ACTIVATION_SLOT | 2 | ledger.rs:1137 |
| LEGACY_TRANSFER_SUNSET_SLOT | 0 | ledger.rs:229 |
| MAGIC_BYTES | XRS1 | network.rs:23 |
| MAX_MSG_SIZE | 5 MiB por trama | network.rs:28 |
| MAX_PEERS | 3,000 | network.rs:29 |
| CONN_TIMEOUT | 45 s de inactividad | network.rs:30 |
| MAX_PEERS_PER_SUBNET | 8 por /24 | network.rs:34 |
| MAX_PREAUTH_PER_IP | 4 conexiones en preautenticación por IP | network.rs:40 |
| MAX_CONN_LIFETIME_SECS | 3,600 s | network.rs:46 |
| PEER_SEND_TIMEOUT_SECS | 10 s | network.rs:50 |
| MAX_MSGS_PER_WINDOW | 600 mensajes por 45 s | network.rs:55 |
| CHALLENGE_TIMEOUT_SECS | 15 s de handshake | network.rs:447 |
| GETBLOCKS_MIN_INTERVAL | 2 s | network.rs:560 |
| MAX_SYNC_TURN_BLOCKS | 100 bloques por turno | network.rs:561 |
| MAX_SYNC_TURN_BYTES | 16 MiB por turno | network.rs:562 |
| MAX_RETAINED_SYNC_BYTES | 64 MiB | network.rs:563 |
| MAX_PENDING_HANDSHAKES | 256 | network.rs:3786 |
| MAGIC_TIMEOUT_SECS | 5 s | network.rs:3791 |
| min_proposal_stake | 100 XRS | ledger.rs:8275 |
| MIN_VOTING_PERIOD | 21,600 slots (comprobación en la entrada) | ledger.rs:8267 |
| MIN_VOTING_PERIOD_SLOTS | 21,600 slots (≈ 1 día) | contracts.rs:5527 |
| MAX_VOTING_PERIOD_SLOTS | 1,296,000 slots (≈ 60 días) | contracts.rs:5528 |
| DEFAULT_PROPOSAL_QUORUM | 5,000 XRS de peso de voto emitido | contracts.rs:163 |
| MAX_LIVE_PROPOSALS | 1,000 | contracts.rs:5505 |
| DISPUTE_CHALLENGE_PERIOD_SLOTS | 21,600 slots | contracts.rs:995 |
| DISPUTE_MAX_LIFETIME_SLOTS | 648,000 slots | contracts.rs:1000 |
| arbitration_panel | validadores con stake ≥ MIN_STAKE_TO_MINE | ledger.rs:5286-5291 |
| MIN_DEAL_DISPUTE_BOND | 1 XRS | contracts.rs:1008 |
| DEAL_TIMEOUT_SLOTS | 648,000 slots | contracts.rs:1079 |
| MAX_TASK_LIFETIME_SLOTS | 648,000 slots | contracts.rs:916 |
| MAX_TASK_REJECTIONS | 3 | contracts.rs:914 |
| DAILY_SLOTS | 21,600 slots (ventana de gasto de 24 h) | contracts.rs:3439 |
| agentes por registro | 50 | contracts.rs:3334 |
| HEARTBEAT_RETENTION_SLOTS | 21,600 slots | contracts.rs:5947 |
| MAX_HEARTBEAT_RECORDS | 10,000 | contracts.rs:5948 |
| stale_threshold | 5,400 slots (≈ 6 h) | contracts.rs:5984 |
| ORDER_STORAGE_BOND | 0.01 XRS | ledger.rs:1290 |
| MAX_ORDER_LIFETIME_SLOTS | 650,000 slots | ledger.rs:1295 |
| MAX_ACTIVE_ORDERS_PER_OWNER | 100 | ledger.rs:1325 |
| MAX_ORACLE_VALUE_AGE_SLOTS | 900 slots | ledger.rs:1300 |
| PRICE_WINDOW_BLOCKS | 61 bloques (mediana) | ledger.rs:1318 |
| PRICE_MIN_OBSERVATIONS | 20 | ledger.rs:1319 |
| MAX_TRACKED_PRICE_POOLS | 64 | contracts.rs:170 |
| fee_bps (valor por defecto de Swap) | 30 bps | contracts.rs:1396 |
| MINIMUM_LIQUIDITY | 1,000 shares | contracts.rs:6 |
| creator_reward_bps | 100 bps | contracts.rs:1600 |
| XERIS_FEE_BPS | 77 bps | contracts.rs:308 |
| total_supply (valor por defecto de Launchpad) | 1,000,000,000 tokens | contracts.rs:1603-1604 |
| target_liquidity_xrs (valor por defecto) | 10,000 XRS | contracts.rs:1608-1609 |
| liquidity_bps | 2,000 (acotado a 500–4,000) | contracts.rs:1631-1633 |
| MAX_RWA_APPROVED_HOLDERS | 1,024 | token.rs:7 |
| valuation (RealWorldAsset) | centavos de USD | token.rs:872 |
| challenge_period_slots (canales de estado) | 1,000 slots | ledger.rs:8308 |
| MAX_CHANNELS | 100,000 | contracts.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.
| Índice | Variante | Regla del firmante | Estado |
|---|---|---|---|
| 0 | TokenMint | autoridad de acuñación | activa |
| 1 | TokenTransfer | firmante == from | activa |
| 2 | TokenBurn | firmante == from | activa |
| 3 | TokenCreate | autoridad de acuñación | activa |
| 4 | ContractCall | según el método | activa |
| 5 | ContractDeploy | propietario | activa |
| 6 | TokenCreateRWA | autoridad de acuñación | activa |
| 7 | RWAUpdateStatus | autoridad de acuñación | activa |
| 8 | RWATransfer | firmante == from | activa |
| 9 | Stake | firmante == pubkey | activa |
| 10 | Unstake | firmante == pubkey | activa |
| 11 | NativeTransfer | firmante == from | activa |
| 12 | ValidatorAttestation | firmante == validator | activa |
| 13 | WrapXrs | firmante | activa |
| 14 | UnwrapXrs | firmante | activa |
| 15 | RegisterAgent | propietario | activa |
| 16 | UpdateAgent | propietario | activa |
| 17 | AgentExecute | agente (ejecuta en nombre del propietario) | activa |
| 18 | CreateIdentity | firmante == identity_pubkey | activa |
| 19 | UpdateIdentity | firmante == identity_pubkey | activa |
| 20 | AttestReputation | identidad activa | activa |
| 21 | AgentMessage | identidad activa | activa (no escribe estado) |
| 22 | SubDelegate | no aplica | deshabilitada (XWC-82) |
| 23 | ConditionalOrder | propietario | activa |
| 24 | CancelConditionalOrder | propietario | activa |
| 25 | RegisterOracle | propietario | activa |
| 26 | OracleSubmit | propietario | activa |
| 27 | HardwareAttest | firmante == device_pubkey o bound_identity | activa |
| 28 | RegisterCapability | firmante == provider_identity | activa |
| 29 | UpdateCapability | firmante == provider_identity | activa |
| 30 | QueryCapabilities | no aplica | sin efecto |
| 31 | PostTask | cualquiera | activa |
| 32 | ClaimTask | firmante == claimant_identity | activa |
| 33 | ResolveTask | parte | activa |
| 34 | RegisterModel | firmante == identity_pubkey | activa |
| 35 | UpdateModel | propietario | activa |
| 36 | OpenDispute | cualquiera | activa |
| 37 | ResolveDispute | stake ≥ 1,000 XRS o parte | activa |
| 38 | SlashReport | cualquiera | activa |
| 39 | CreateProposal | stake ≥ 100 XRS | activa |
| 40 | CastVote | cualquiera (peso = stake) | activa |
| 41 | ExecuteProposal | cualquiera | activa |
| 42 | OpenChannel | parte | activa |
| 43 | CloseChannel | parte | activa |
| 44 | ForceCloseChannel | parte | activa |
| 45 | AgentHeartbeat | firmante == identity_pubkey | activa |
| 46 | ZkProofSubmit | cualquiera | activa |
| 47 | ZkProofVerify | cualquiera | activa (solo lectura) |
| 48 | ZkPrivateTransfer | no aplica | deshabilitada (NEW-CRIT-3) |
| 49 | ZkIdentityProof | no aplica | deshabilitada (NEW-CRIT-1) |
| 50 | PqKeyRegister | firmante == ed25519_pubkey | activa |
| 51 | PqKeyRotate | firmante == ed25519_pubkey | activa |
| 52 | PqSignedTransfer | no aplica | deshabilitada (NEW-CRIT-4) |
| 53 | PqAttest | cualquiera | activa (marcador) |
| 54 | CreateDeal | parte | activa |
| 55 | AcceptDeal | parte | activa |
| 56 | ConfirmDeal | parte | activa |
| 57 | CancelDeal | parte | activa |
| 58 | DisputeDeal | parte | activa |
| 59 | SettleDeal | cualquiera | activa |
| 60 | ReclaimDeal | parte | activa |
| 61 | ZkVkRegister | stake ≥ 1,000 XRS | activa |
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.
| Tipo | Alias | Desplegable | Id del singleton | Operado mediante |
|---|---|---|---|---|
TimeLock | timelock, time_ | sí | — | ContractDeploy, ContractCall |
Escrow | escrow | sí | — | ContractDeploy, ContractCall |
Swap | swap | sí | — | ContractDeploy, ContractCall |
Vesting | vesting | sí | — | ContractDeploy, ContractCall |
MultiSig | multisig, multi_ | sí | — | ContractDeploy, ContractCall |
RealWorldAsset | rwa, real_, realworldasset | solo la autoridad de acuñación del token RWA | — | ContractDeploy, ContractCall |
Launchpad | launchpad, launch_ | sí | — | ContractDeploy, ContractCall |
AgentRegistry | agent_, agent, agents | sí | agent_ por propietario | RegisterAgent, UpdateAgent, AgentExecute |
IdentityRegistry | identity, identity_ | sí | xeris_; identity_ por identidad | CreateIdentity, UpdateIdentity, AttestReputation |
ConditionalOrderBook | conditional, conditional_, orders | no | xeris_ | ConditionalOrder, CancelConditionalOrder |
LimitOrder | limit, limit_, limit_ | sí | — | ContractDeploy, ContractCall |
DcaOrder | dca, dca_, dollar_ | sí | — | ContractDeploy, ContractCall |
OracleRegistry | oracle, oracle_, oracles | sí | xeris_ | RegisterOracle, OracleSubmit |
DeviceRegistry | device, device_, hardware | no | xeris_ | HardwareAttest |
CapabilityRegistry | capability, capabilities, cap_ | sí | xeris_ | RegisterCapability, UpdateCapability |
TaskBoard | task, tasks, task_, bounty | no | xeris_ | PostTask, ClaimTask, ResolveTask |
ModelRegistry | model, model_, models | sí | xeris_ | RegisterModel, UpdateModel |
DisputeRegistry | dispute, disputes, arbitration | no | xeris_ | OpenDispute, ResolveDispute, DisputeDeal |
Governance | governance, gov, dao | sí; las instrucciones actúan solo sobre xeris_ | xeris_ | CreateProposal, CastVote, ExecuteProposal |
StateChannelRegistry | channel, channels, state_ | no; también se rechaza dentro de deploy_ | xeris_ | OpenChannel, CloseChannel, ForceCloseChannel |
ZkVerifierRegistry | zk, zk_, zero_ | no | xeris_ | ZkVkRegister, ZkProofSubmit, ZkProofVerify, PqAttest |
PqKeyRegistry | pq, pq_, post_, quantum | no | xeris_ | PqKeyRegister, PqKeyRotate; registro automático del proponente en el slot 1 |
DealRegistry | deal, deals, escrow_ | no | xeris_ | CreateDeal … 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.