> For the complete documentation index, see [llms.txt](https://docs.rome.builders/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rome.builders/es/apps-en-rome/bloom/building.md).

# Construyendo sobre Bloom

Esta guía responde una pregunta: **¿cómo hace un desarrollador esto por su cuenta?** Toma un contrato de activo del mundo real en Solidity, colócalo en una cadena Rome como un token con permisos, ábrelo también a billeteras de Solana y a billeteras EVM sin puente y sin segundo token, e integra tu propia decisión de KYC/cumplimiento.

El público es un desarrollador que evalúa o adopta el enfoque. Bloom es el ejemplo trabajado a lo largo de todo el texto — su activo en vivo **ARCV ("Mineral Vault I")** en Hadrian (`200010`) es una instancia real de cada paso siguiente, y su recibo de despliegue (`deployments/200010.tokens/ARCV.json`) registra exactamente los tres contratos y las ocho transacciones que produce este método.

Nada de esto modifica los contratos del activo. Bloom ejecuta el framework Arc de Plume **sin modificaciones** (el árbol vendorizado en `contracts/`, idéntico byte a byte a `plumenetwork/contracts` en el pin de `NOTICE`); el único contrato del lado de Rome es `ArcTokenFactoryV2`, que añade una función. El trabajo está en *cómo* tú despliegas y *cómo* tú diriges los contratos, no en cambiarlos.

> **El método, de un vistazo.** Un token con permisos es **tres** contratos desplegados. Se despliega **por partes** — un contrato por transacción — porque una llamada de la fábrica en una sola transacción supera el límite de cuentas por transacción de Rome. Sus proxies deben provenir de **artefactos bloqueados por procedencia** o el registro en cadena falla de forma segura. El cumplimiento es un **booleano por dirección**, escrito por quien tu proceso de KYC autorice. Y el mismo despliegue es accesible desde una billetera de Solana a través de un **emisor sintético** derivado en cadena — sin puente, sin token envuelto, sin segunda lista de permitidos.

## Requisitos previos

> **Antes de empezar.** La herramienta de emisor que usa esta guía aún no se ha publicado. Eso incluye los scripts (`deploy-infra.ts`, `create-token.ts` y el resto), los árboles de `contracts/` y `rome-contracts/` y los recibos de despliegue que nombra. Los emisores obtienen esta herramienta al incorporarse con Rome. Hasta entonces, lee la guía como una descripción del método y de lo que hace cada script.

La herramienta de referencia vive en un directorio `scripts/` (proporcionado durante la incorporación; instala sus dependencias con `npm install`). Cada comando resuelve los datos de la cadena desde el registro en lugar de codificarlos, así que apuntar a otra cadena Rome es un cambio de entorno, no de código:

```bash
export PRIVATE_KEY=…       # clave financiada de despliegue/emisor en la cadena objetivo
export CHAIN_ID=200010     # selecciona la cadena
export REGISTRY_ROOT=…     # una copia local del registro de cadenas de Rome
# Los flujos de la vía Solana también necesitan:
export SOLANA_KEYPAIR=…    # ruta a un JSON de par de claves de Solana financiado (el pagador de comisiones)
```

El gas en las cadenas Rome se denomina en USDC; la secuencia completa de despliegue cuesta del orden de 10–15 unidades nativas en devnet. Las claves provienen solo del entorno — nunca del repo. Compila los contratos una vez antes de desplegar, porque los artefactos **deben** provenir de `rome-contracts/out` (ver paso 3):

```bash
cd contracts       && forge build --via-ir     # la suite Arc vendorizada
cd rome-contracts  && forge build --via-ir     # ArcTokenFactoryV2 + el proxy canónico
```

***

## Paso 1 — Entiende qué es un token con permisos *es* aquí

Un token Bloom con permisos no es un contrato. Son **tres contratos desplegados**, más cuatro contratos compartidos que operaciones despliega una vez por cadena y que cada emisor reutiliza.

**Los tres contratos por token** — un activo posee exactamente estos:

| Contrato                     | Fuente                                                      | Qué es                                                                                                                            |
| ---------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `ArcTokenProxy`              | `contracts/src/proxy/ArcTokenProxy.sol`                     | El token en sí — un proxy UUPS que apunta a la `ArcToken` implementación compartida. Los saldos, titulares y roles viven aquí.    |
| `WhitelistRestrictions`      | `contracts/src/restrictions/WhitelistRestrictions.sol`      | El módulo de lista de permitidos. Un booleano por dirección; se aplica en cada transferencia mientras el activo esté restringido. |
| `YieldBlacklistRestrictions` | `contracts/src/restrictions/YieldBlacklistRestrictions.sol` | El módulo de rendimiento. Excluye direcciones de las distribuciones de rendimiento sin tocar su tenencia.                         |

**Los cuatro contratos compartidos, uno por cadena** (desplegados por `deploy-infra.ts`):

| Contrato                    | Rol                                                                                            |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
| `RestrictionsRouter`        | Registro de tipos de módulo — cableado de la cadena, configurado una sola vez por operaciones. |
| `ArcTokenFactoryV2`         | Registro + el codehash canónico del proxy (pasos 2–3).                                         |
| `ArcToken` (implementación) | La lógica de activo compartida y reutilizable a la que apunta cada token.                      |
| `ArcTokenPurchase`          | Un único punto de venta, compartido por cada token y emisor (paso 4).                          |
| wUSDC                       | La pata en efectivo — moneda de rendimiento y moneda de compra-venta.                          |

La restricción de transferencias y la restricción de rendimiento son **módulos deliberadamente separados**: un titular puede ser excluido del ingreso mientras conserva el activo, que es así como funcionan realmente las sanciones y las órdenes judiciales.

Este hecho de los "tres contratos" es esencial para la verificación. `verify-deployments.ts` y `register-asset.ts` ambos esperan que un token resuelva a tres contratos desplegados — verificar un token a mano una vez se pasó por alto en siete de ellos en toda una cadena (cada módulo de rendimiento y tres módulos de lista blanca).

El ciclo de vida tiene tres fases y una puerta de sentido único:

```
borrador     proxy activo, sin módulo vinculado — nada restringe una transferencia
  ↓
sin restricción   módulo vinculado, transfersAllowed = true   (WhitelistRestrictions.initialize establece esto)
  ↓  acuña suministro, construye la lista de permitidos               [reversible]
restringido     setTransfersAllowed(false)                [PUERTA DE SENTIDO ÚNICO — el suministro se vuelve definitivo]
```

La restricción es de sentido único para el suministro porque acuñar y quemar pasan por `address(0)` como contraparte, y `address(0)` nunca puede añadirse a la lista de permitidos — así que, una vez restringido, nunca más se podrá acuñar nuevo suministro. Esa es la garantía que un activo con permisos ofrece a sus titulares.

***

## Paso 2 — Despliega por partes, nunca como una sola transacción

Arc trae un `createToken` de una sola vez que despliega el proxy y ambos módulos y los conecta en una única llamada. **En Rome no puedes usarlo.** Esa transacción necesita **77 bloqueos de cuentas de Solana**, y el límite por transacción de Rome es **62 bloqueos**. Nunca cabe — ni con ajustes, ni con un pase del optimizador.

Así que el despliegue es **por partes**: cada contrato en su propia transacción, y el token obtiene un estatus equivalente al de la fábrica mediante `ArcTokenFactoryV2.registerToken` en lugar del monolítico `createToken`. Este es el asistente de creación, y es lo que hace la consola del emisor paso a paso:

![el asistente de creación](https://1082203750-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F671PKS8ZQSNqtoC8PGvz%2Fuploads%2Fgit-blob-94683a3a49ac2418aed8cc66dbf47fad940814f7%2Fcreate-wizard.png?alt=media)

La implementación de referencia es `arc/plan/create.ts` (`createSequence()`), que la app presenta como seis tarjetas (`bloom/lib/createCards.ts`) sobre **ocho transacciones**, en el orden exacto que imponen los contratos:

| # | Transacción                                 | Llamada al contrato                                                             |
| - | ------------------------------------------- | ------------------------------------------------------------------------------- |
| 1 | Desplegar el activo                         | `new ArcTokenProxy(impl, initData)` donde `initData = ArcToken.initialize(...)` |
| 2 | Implementa el módulo de lista de permitidos | `new WhitelistRestrictions()`                                                   |
| 3 | Te establece como su administrador          | `whitelist.initialize(issuer)`                                                  |
| 4 | Aplica en las transferencias                | `token.setRestrictionModule(TRANSFER, whitelist)`                               |
| 5 | Implementa el módulo de rendimiento         | `new YieldBlacklistRestrictions()`                                              |
| 6 | Te establece como su administrador          | `yieldBlacklist.initialize(issuer)`                                             |
| 7 | Aplica en el rendimiento                    | `token.setRestrictionModule(YIELD, yieldBlacklist)`                             |
| 8 | Regístralo con la fábrica                   | `factoryV2.registerToken(token, impl)`                                          |

La forma en script es un solo comando:

```bash
# una vez por cadena (operaciones, no por token) — despliega los cuatro contratos compartidos
npx tsx deploy-infra.ts

# por token — el asistente, reproducido como script
NAME="Mineral Vault I" SYMBOL=ARCV SUPPLY=1000000 DECIMALS=6 npx tsx create-token.ts
```

El emisor (`PRIVATE_KEY`) firma cada transacción y termina poseyendo los roles del token (`ArcToken.initialize` otorgamientos a `msg.sender` — ver paso 7). El registro (tx 8) es lo que desbloquea el `enableToken` y cualquier actualización mediada por la fábrica.

> **No "optimizas" esto para convertirlo de nuevo en un monolito.** Agrupar estas llamadas en una sola transacción para ahorrar idas y vueltas vuelve a introducir exactamente el desbordamiento de 62 bloqueos que la ruta por partes existe para evitar. El mismo límite es la razón por la que la distribución de rendimiento avanza un titular por transacción (paso 8). Si una llamada falla con `Demasiadas cuentas: N > 62`, una transacción está tocando demasiadas cuentas — divídela, no la ajustes.

***

## Paso 3 — Despliega proxies solo desde los artefactos bloqueados por procedencia

`registerToken` es la frontera de seguridad que permite que los despliegues por partes sean seguros. Solo aceptará un token cuyo **codehash en tiempo de ejecución coincida con el canónico `ArcTokenProxy`** integrado en la fábrica en el momento del despliegue:

```solidity
// rome-contracts/src/ArcTokenFactoryV2.sol
bytes32 private immutable CANONICAL_PROXY_CODEHASH =
    keccak256(type(ArcTokenProxy).runtimeCode);

function registerToken(address token, address implementation) external {
    if (token.codehash != CANONICAL_PROXY_CODEHASH) revert ProxyCodehashUnknown();
    // …la implementación debe estar permitida por la fábrica, y msg.sender debe poseer el
    //    ADMIN_ROLE del token, y el token no debe estar ya registrado.
}
```

Como ese codehash es un **inmutable**, un proxy construido a partir de cualquier otro artefacto falla de forma segura con `ProxyCodehashUnknown`. De esto se derivan dos reglas, y ambas se aplican por CI y por las reglas estrictas en `CLAUDE.md`:

1. **LOS proxies de token DEBEN desplegarse desde `rome-contracts/out`** — nunca desde `contracts/out`, nunca desde una compilación distinta. Esa es la compilación que fija el codehash canónico de la fábrica.
2. **`bytecode_hash = "none"` y `cbor_metadata = false`** en `rome-contracts/foundry.toml` son fundamentales. Con los metadatos activados, el `ArcTokenProxy` de ejecución *integrado* en la fábrica (`type().runtimeCode`) lleva una cola CBOR/IPFS distinta a la del `out/` artefacto independiente desde el que despliega el asistente — así que en cadena `registerToken` falla mientras **todas las pruebas de Foundry en el código fuente pasan** (Foundry incorpora ambas copias, así que no puede ver la discrepancia). `rome-contracts/test/ArtifactProvenance.t.sol` fija esta propiedad; la ejecución financiada fue lo que la detectó originalmente.

> **Una suite verde en el código fuente no prueba la procedencia.** Antes de publicar una imagen, y después de CUALQUIER cambio en la configuración del compilador, demuestra la propiedad contra la fábrica *desplegada* (solo lectura, sin claves):
>
> ```bash
> cd scripts && CHAIN_ID=<id> npx tsx verify-artifact-provenance.ts
> ```
>
> Hashea tu `ArcTokenProxy` artefacto local y afirma que ese hash aparece literalmente dentro del bytecode de la fábrica desplegada.

Si deben cambiar los ajustes del compilador, **rota la fábrica** — despliega una nueva cuya inmutable fije el nuevo codehash y vuelve a cablear — en lugar de parchear la comprobación a mano:

```bash
cd scripts && CHAIN_ID=<id> npx tsx rotate-factory.ts
```

La rotación conserva la dirección antigua en el recibo; los tokens registrados contra la fábrica antigua deben registrarse de nuevo contra la nueva.

***

## Paso 4 — Abre una venta a través del punto de venta compartido

Vender es un acto distinto de crear, y se ejecuta en el único `ArcTokenPurchase` punto de venta compartido. Deben cumplirse tres cosas antes de abrir una venta, y `ArcTokenPurchase.enableToken` comprueba cada una en cadena:

```solidity
// contracts/src/ArcTokenPurchase.sol
function enableToken(address _tokenContract, uint256 _numberOfTokens, uint256 _tokenPrice)
    external onlyTokenAdmin(_tokenContract)                       // (a) tú posees el ADMIN_ROLE del token
{
    // (b) el token debe ser conocido por la fábrica — esto es lo que compró registerToken (paso 2):
    if (ArcTokenFactory(ps.tokenFactory).getTokenImplementation(_tokenContract) == address(0))
        revert TokenNotCreatedByFactory();
    // (c) el punto de venta debe poseer ya el inventario de venta:
    if (ArcToken(_tokenContract).balanceOf(address(this)) < _numberOfTokens)
        revert ContractMissingRequiredTokens();
    // …el precio y la cantidad deben ser positivos.
}
```

Hay un cuarto requisito implícito que fuerza un activo restringido: como **cada transferencia pasa por la puerta de la lista de permitidos**, mover inventario al punto de venta solo tiene éxito si la dirección del punto de venta está también en la lista de permitidos de tu token. Así que la secuencia completa para abrir una venta es:

1. **Añade el punto de venta a la lista de permitidos** en el módulo de lista de permitidos de tu token (`batchAddToWhitelist([storefront])` — ver paso 5). Sin esto, la transferencia de inventario en (2) revierte `TransferRestricted()` una vez restringido.
2. **Transfiere el inventario** a la dirección del punto de venta (desde el recibo de infra, `arcTokenPurchase`).
3. **`enableToken(token, amount, price)`** — ahora los compradores pagan wUSDC y reciben el RWA.

Vale la pena conocer el reparto de retiradas en un *punto de venta compartido* , y no es simétrico (`arc/roles.ts` es la autoridad):

| Acción                                                     | Autoridad                                    | Poseído por                                |
| ---------------------------------------------------------- | -------------------------------------------- | ------------------------------------------ |
| `enableToken` / `disableToken` / `withdrawUnsoldArcTokens` | el onlyTokenAdmin `ADMIN_ROLE` (`del token`) | tú, el emisor                              |
| `withdrawPurchaseTokens` (los ingresos en wUSDC)           | el del punto de venta `DEFAULT_ADMIN_ROLE`   | quienquiera que desplegó el punto de venta |

En una cadena autoalojada eres ambas cosas. En un punto de venta compartido, el fondo de ingresos es lo recaudado por cada emisor, así que su retirada es a nivel de plataforma — diseña tu liquidación en torno a eso en lugar de asumir control unilateral.

***

## Paso 5 — Integra tu propio KYC / cumplimiento

Esta es la sección que un cliente lee antes de decir que sí, así que es código, no prosa. **El cumplimiento lo impone el propio contrato del token** — no una pantalla fuera de cadena, ni un filtro del secuenciador, ni una política del lugar. Las reglas viajan con el token a cualquier ruta de ejecución, en ambas vías de billetera. Lo que la cadena registra es la **resultado** de tu decisión de KYC, como un único booleano:

```solidity
// contracts/src/restrictions/WhitelistRestrictions.sol — el estado en cadena, completo
struct WhitelistStorage {
    mapping(address => bool) isWhitelisted;   // ← un booleano por dirección. Nunca hay PII.
    bool transfersAllowed;                    // false = restringido; solo las direcciones en la lista de permitidos pueden transferir
    EnumerableSet.AddressSet whitelistedAddresses;
}
```

### La interfaz a la que escribe tu proceso

La decisión de aprobar o denegar de tu proveedor de KYC se convierte exactamente en una de estas llamadas:

```solidity
interface IWhitelistRestrictions {
    function addToWhitelist(address account) external;                  // onlyRole(MANAGER_ROLE)
    function batchAddToWhitelist(address[] calldata accounts) external; // onlyRole(MANAGER_ROLE)
    function removeFromWhitelist(address account) external;             // onlyRole(MANAGER_ROLE)
    function isWhitelisted(address account) external view returns (bool);
    function setTransfersAllowed(bool allowed) external;                // onlyRole(ADMIN_ROLE)
}
```

### Quién puede escribir

> **La autoridad que necesita una aprobación es `MANAGER_ROLE` en el&#x20;*****el módulo*****&#x20;— no `ADMIN_ROLE` el token.** `addToWhitelist` / `batchAddToWhitelist` / `removeFromWhitelist` son todos `onlyRole(MANAGER_ROLE)`, y ese rol vive en la `WhitelistRestrictions` instancia. Autenticar `ADMIN_ROLE` en el token y luego escribir en el módulo es un defecto real por el que esta base de código ya ha pagado. No ofrezcas `WHITELIST_ADMIN_ROLE` como la solución para una denegación — se concede en initialize y **no se comprueba en ninguna parte de la suite** (`arc/roles.ts` afirma que es el único rol que no restringe nada).

`WhitelistRestrictions.initialize(issuer)` (tx 3 del asistente) concede al emisor `DEFAULT_ADMIN_ROLE`, `ADMIN_ROLE`, `MANAGER_ROLE`, `WHITELIST_ADMIN_ROLE`y `UPGRADER_ROLE` en ese módulo. Así que el emisor tiene `MANAGER_ROLE` de forma predeterminada y también puede delegarlo: como el emisor tiene `DEFAULT_ADMIN_ROLE`, una `grantRole(MANAGER_ROLE, serviceAccount)` entrega la escritura a un firmante del backend.

### Una decisión que se convierte en una escritura en cadena

Apunta el webhook de tu proveedor a un firmante que posea `MANAGER_ROLE`, y al aprobar, agrupa las direcciones:

```typescript
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

// `signer` debe poseer MANAGER_ROLE en el módulo de lista blanca de ESTE token.
const wallet = createWalletClient({ account, transport: http(chain.rpcUrl) });

// el proveedor dijo "aprobado" para estas direcciones:
await wallet.writeContract({
  address: whitelistModule,                 // resuelto DESDE el token mediante getRestrictionModule
  abi: whitelistAbi,
  functionName: 'batchAddToWhitelist',
  args: [approvedAddresses],
});
```

Resolver `whitelistModule` desde el propio token (`ArcToken.getRestrictionModule(TRANSFER_RESTRICTION_TYPE)`), nunca desde una proyección en caché — apuntar una escritura al módulo equivocado es cómo "aprobar sobre cualquier activo" una vez escribió en la lista blanca de un token.

### La variante firmada por la billetera (demostración de autoservicio)

Para una implementación de prueba sin permisos en la que los visitantes se dan de alta ellos mismos, concede `MANAGER_ROLE` a un `TestApprover` (`rome-contracts/src/TestApprover.sol`) cuyo `el módulo` es **inmutable**. Luego `approve(address)` puede ser invocado por cualquiera, desde su propia billetera, pagando su propio gas — llama a `addToWhitelist` solo si la dirección no está ya incluida:

```solidity
function approve(address account) external {
    IWhitelistRestrictions target = IWhitelistRestrictions(module);   // inmutable — un módulo, para siempre
    if (!target.isWhitelisted(account)) target.addToWhitelist(account);
    emit Approved(account, msg.sender);
}
```

Esta es la "puerta de entrada" — muestra al visitante el mecanismo real (una aprobación que se convierte en una entrada de lista blanca en cadena que luego el token aplica) sin custodia de nada. **Nunca concedas `MANAGER_ROLE` a un `TestApprover` en un token de producción** — eso hace que la lista blanca de ese token sea sin permisos.

### Cuando la puerta se activa, y cambiar de proveedores

La puerta solo restringe una vez que el activo está **restringido** (`setTransfersAllowed(false)`, `ADMIN_ROLE` en el módulo). Mientras no está restringido, las transferencias están abiertas — construye primero tu lista blanca y luego activa la puerta. Una vez restringido, una transferencia en la que cualquiera de las partes esté fuera de la lista revierte con el error tipado `TransferRestricted()` (`0xe827105e`).

> **Cambiar de proveedor KYC más adelante no requiere ningún cambio en cadena.** El módulo de lista blanca, su interfaz y el booleano que almacena son agnósticos al proveedor. Para cambiar de proveedor, apunta una fuente de decisión distinta al mismo `MANAGER_ROLE` firmante — sin redeploy, sin migración, sin cambio de estado. La cadena nunca supo qué proveedor hizo la llamada, solo el resultado.

***

## Paso 6 — Abrir el mismo activo a billeteras de Solana

Los mismos tres contratos son accesibles desde una billetera de Solana **sin puente y sin un segundo token**. Un usuario nativo de Solana no tiene ninguna clave EVM; en su lugar, su identidad EVM es *sintética*, derivada en cadena a partir del firmante real de Solana de la transacción:

```
synthetic EVM address = keccak256(solana_pubkey)[12..32]
```

El programa Rome EVM lo deriva del propio firmante (`do_tx_unsigned::derive_sender`), así que no puede ser suplantado y no existe una clave privada secp256k1 para él — **la firma de Solana es lo único que puede activar esta dirección**. La derivación de referencia es `arc/materialise/solana/identity.ts` (`syntheticAddress`), compatible a nivel de bytes con la regla en cadena:

```typescript
export function syntheticAddress(pubkey: SolanaPubkeyInput): `0x${string}` {
  return `0x${keccak256(pubkeyBytes(pubkey)).slice(-40)}`;
}
```

**Lo que hace un desarrollador para admitir el carril** — ten en cuenta que *nada de eso es un cambio de contrato*:

1. **Incluye al usuario de Solana en la lista blanca mediante su dirección sintética.** Para la capa de cumplimiento, una dirección sintética es una dirección ordinaria: pega la clave pública de Solana del usuario, deriva la sintética y `batchAddToWhitelist([synthetic])`. Una sola lista blanca abarca ambos mundos de billeteras.
2. **Provisiona una vez el PDA de firma, antes de su primera llamada que mueva valor.** El PDA de autoridad externa que firma para una sintética debe existir primero, y crearlo es una `create_pda` llamada. Cualquiera de los dos lados puede enviarlo: la propia billetera de Solana del usuario a través del carril (la del SDK `buildCreatePdaCall`, después de `isSyntheticProvisioned` devuelve false), o el emisor desde una clave EVM. La herramienta del emisor de Bloom lo envía hoy como parte de la incorporación de un titular; la app aún no lo hace en la primera acción del usuario, así que aprovisiona antes de la primera compra del titular. Cómo, con las llamadas del SDK, está en [Aplicación de doble carril](/es/guias-para-desarrolladores/dual-lane-app.md#one-time-activate-provision-the-synthetic).
3. **Envía a través de la biblioteca del carril, no a mano.** `submitDoTxUnsigned` (`arc/materialise/solana/submit.ts`) le pide al proxy de la cadena que componga la transacción, y maneja lo que una construida a mano hace mal. Cada llamada de contrato en el carril es una **transacción Solana v1 compuesta por el proxy** (`rome_composeUnsignedAtomicV1`) sin tabla de búsqueda de direcciones, y la biblioteca del carril falla de forma cerrada en lugar de retroceder a un formato más antiguo. El movimiento de ingreso de efectivo en el paso 5 es una transacción de Solana separada que la app construye por sí misma, no una llamada de contrato compuesta.
   * **El descubrimiento de cuentas y el presupuesto de cómputo son trabajo del proxy.** Emula la llamada, decide la capacidad de escritura de cada cuenta (la `valor` forma parte de lo que emula, así que pásalo), y establece el presupuesto de cómputo y de heap en la transacción que devuelve. Nada en tu código construye instrucciones de presupuesto de cómputo ni agrega la cuenta del pool de comisiones.
   * **Dos firmas por llamada, ambas desde la clave de Solana.** La billetera primero firma un mensaje de autorización legible (*autorización de Rome* más el hash del payload EIP-1559 sin firmar; Wallet Standard `solana:signMessage`), que el proxy verifica antes de componer la llamada. Luego firma la transacción v1 que devuelve el proxy (`solana:signTransaction` sobre el cable en bruto). La biblioteca del carril los toma como `signAuthHash` y `signV1Wire`, nunca como un solo método fusionado. Verifica que tu billetera firme transacciones v1 antes de construir sobre ella: Solflare lo hace en devnet hoy y Phantom aún no.
   * **La app envía la transacción firmada a un RPC de Solana por sí misma.** El proxy compone; no emite una llamada atómica.
   * **Valida antes de pedir una firma.** La biblioteca del carril analiza el mensaje compuesto y rechaza uno mal formado antes del aviso de transacción.
4. **Pon la concesión única del wrapper antes del primer gasto de wUSDC.** wUSDC mueve los tokens de un usuario como delegado SPL de ese usuario, así que una dirección sintética a la que nunca se le ha concedido permiso obtiene el error SPL `0x4` en su primera transferencia de wUSDC. Lee `isEnabled(user)` en el wrapper y, cuando sea falso, antepone `approve_spl(wrapper, 2^64-1, mint)` en el precompile Helper como la primera etapa de la ejecución. Es una llamada ordinaria en cualquiera de los dos carriles, una vez por billetera. Ver [Concesión única por wallet](/es/conceptos-basicos/token-interop.md#one-time-grant-per-wallet).
5. **Mueve efectivo antes de una llamada que lo gaste.** La sintética no retiene nada en reposo. Cuando tiene menos wUSDC del que gasta la llamada, mueve primero la diferencia desde la propia cuenta de token USDC de la billetera (una creación idempotente de la cuenta de token de la sintética, luego la instrucción activate-token-account del programa Rome, que mueve los tokens sin ejecutar el EVM). Es una sola transacción de Solana firmada con un solo aviso, y se omite cuando la sintética ya tiene suficiente.
6. **Reenvía solo lo que el compositor atómico rechaza.** Una llamada que no cabe en una transacción de Solana (aprox. 1,4 M de unidades de cómputo, 64 cuentas, 4 KB) es rechazada por el compositor con *el plan atómico externo requiere composición iterativa*. Precisamente ante ese rechazo, y nunca antes, llama a `rome_relayUnsignedV1` con la misma firma de autorización: el proxy la compone y la envía, paga las comisiones de Solana y responde con un hash de transacción EVM (no una firma de Solana). La sintética paga el gas nativo de la cadena en wei, así que cuando su saldo es inferior a `gasLimit x maxFeePerGas`, recárgalo primero con `rome_composeExternalDepositV1` (una transacción v1 firmada por la billetera; el importe debe ser un múltiplo de 10^12 wei), en la práctica una vez por billetera. Cualquier otro error, como un revert, un fallo de red o una firma rechazada, se vuelve a lanzar, no se relaya. Los detalles están en [Llamadas reenviadas y llamadas largas](/es/guias-para-desarrolladores/call-evm-from-solana.md#relayed-calls-and-long-calls).

El usuario es el **pagador de comisiones** y paga lamports (SOL) desde su billetera de Solana para una llamada directa; la sintética que controla mantiene los activos y nunca mantiene SOL. Después de cada envío por el carril, consulta el nonce de EVM antes del siguiente — el indexador se retrasa un poco respecto a la confirmación de Solana, y de lo contrario la siguiente llamada se construiría sobre un nonce que la cadena ya ha gastado.

> **Primero atómico, reenvío si se rechaza.** El valor predeterminado para cada llamada es una transacción EVM ejecutada dentro de una transacción de Solana, firmada por la billetera del usuario. El reenvío es la excepción para una llamada que no cabe, no una segunda ruta para elegir por preferencia: una llamada directa son dos avisos, una llamada reenviada es uno, y la llamada reenviada se paga en gas de Rome en lugar de lamports. Un recorrido de varios pasos (aprobar y luego comprar) son varias llamadas completas, cada una su propia transacción, no una sola ejecución dividida entre transacciones.

**Por qué el RWA no puede filtrarse fuera del perímetro.** El activo reside en el estado EVM — sus saldos son almacenamiento dentro de las cuentas del programa Rome EVM. No hay **ningún mint SPL** de él y nada nativo de Solana que mover; las primitivas de movimiento de activos del carril operan sobre cuentas de token SPL, que un saldo de almacenamiento EVM no tiene. Cada movimiento que la firma de Solana puede expresar es una llamada al contrato del token, que ejecuta la puerta de la lista blanca — la suite financiada afirma que una transferencia a una dirección no incluida en la lista blanca revierte de forma idéntica tanto si está firmada por una clave EVM como por una clave de Solana. La parte de efectivo (wUSDC) es la excepción deliberada: está respaldada por SPL y *no* se transfiere a la propia cuenta de token del usuario.

***

## Paso 7 — Saber quién tiene la autoridad después del despliegue

`ArcToken.initialize` se ejecuta desde el constructor del proxy (tx 1) y concede roles a `msg.sender` — el controlador del asistente, es decir, el emisor. Concede **cinco** roles explícitamente:

```solidity
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(MANAGER_ROLE, msg.sender);
_grantRole(YIELD_MANAGER_ROLE, msg.sender);
_grantRole(YIELD_DISTRIBUTOR_ROLE, msg.sender);
```

Dos consecuencias que un desarrollador no debe pasar por alto:

* **`MINTER_ROLE`, `BURNER_ROLE`y `UPGRADER_ROLE` NO se conceden en initialize.** El suministro inicial se acuña dentro de `initialize` (un `_mint`, no se necesita ningún rol), pero un *posterior* `mint` o `quema` requiere que el emisor primero se conceda a sí mismo el rol. Pueden hacerlo, porque poseen `DEFAULT_ADMIN_ROLE` (el administrador de cada rol — nada llama a `_setRoleAdmin`, así que `DEFAULT_ADMIN_ROLE` los administra todos).
* **`UPGRADER_ROLE` nunca se concede a la fábrica.** Las actualizaciones mediadas por la fábrica (`ArcTokenFactoryV2.upgradeToken`) requieren que la **propia fábrica** tenga `UPGRADER_ROLE` en el token — concedértelo a ti mismo no hace nada. Esa concesión es una puerta de una sola dirección que la fábrica no puede revertir, así que es optativa: si quieres actualizaciones mediadas por la fábrica, `grantRole(UPGRADER_ROLE, factory)` explícitamente; de lo contrario, el emisor actualiza el token directamente.

El modelo completo de 21 autoridades (11 nombres de rol repartidos en seis contratos — el mismo nombre es una autoridad distinta en cada contrato) está codificado en `arc/roles.ts`. Pregunta a `rolesRequiredFor(contract, functionName)` en lugar de leer el propio contrato de un rol, porque cuatro funciones de la interfaz y ambas funciones de fábrica están protegidas por una autoridad que vive en un *diferente* contrato.

***

## Paso 8 — Distribuir rendimiento

El token paga rendimiento en wUSDC (fijado en `initialize`, modificable mediante `setYieldToken` menos de `YIELD_MANAGER_ROLE`).

```solidity
function distributeYieldWithLimit(uint256 totalAmount, uint256 startIndex, uint256 maxHolders)
    external onlyRole(YIELD_DISTRIBUTOR_ROLE) nonReentrant
    returns (uint256 nextIndex, uint256 totalHolders, uint256 amountDistributed);
```

Los mecanismos que importan para ejecutarlo correctamente:

* **El total `totalAmount` se toma del llamador solo en la `startIndex == 0` ventana** (`safeTransferFrom(msg.sender, this, totalAmount)`). Aprueba ese total para el token antes de la primera ventana; las ventanas posteriores pagan con el saldo ya mantenido.
* **`nextIndex` vuelve a `0` cuando termina el recorrido.** Bucle `distributeYieldWithLimit(total, nextIndex, …)` hasta que `nextIndex` vuelva `0`.
* **Las participaciones de los titulares restringidos por rendimiento permanecen en el contrato del token** — se omiten, no se redistribuyen.

> **En Rome el límite práctico es `maxHolders = 1` por transacción** — el mismo límite de 62 cuentas del paso 2 — así que el rendimiento se recorre un titular por transacción (medido en \~318 K de gas por titular). Cada titular incluido en la lista blanca, EVM o nativo de Solana, recibe prorrateado; el script de referencia es `measure-yield-batch.ts` y el paso de rendimiento del smoke financiado lo verifica de extremo a extremo.

```bash
# Forma de referencia en USAGE.md: recorre un titular por tx hasta que nextIndex vuelva a 0
distributeYieldWithLimit(total, offset, 1)
```

***

## Paso 9 — Verificar lo que desplegaste

Nada envía automáticamente las fuentes a ningún sitio en Rome, así que la verificación es un paso que ejecutas. Después de cualquier despliegue — infraestructura o un token nuevo — ejecuta el verificador de solo lectura, sin claves e idempotente (los contratos ya verificados se omiten, así que volver a ejecutarlo después de emitir un token verifica solo los nuevos):

```bash
cd scripts && CHAIN_ID=<id> npx tsx verify-deployments.ts
```

Espera los **tres** contratos por token (proxy + ambos módulos) más la infraestructura, e informa lo que no pudo verificar en lugar de fallar un despliegue. Hacer esto a mano una vez omitió siete contratos.

Registrar el activo en el registro de Rome trata la verificación como una **puerta**, no como decoración (`register-asset.ts`): un token obtiene su `apps/arc/<chain>.json` registro — llevando `standard: arc-permissioned-erc20` y un `transferRestriction` que nombra su puerta de lista blanca — solo cuando sus tres contratos están verificados en Sourcify. Ese registro lee la cadena para obtener sus hechos (un token desplegado por el asistente no tiene recibo confirmado) y solo escribe un checkout local del registro; aterrizar el PR es trabajo de una persona.

```bash
CHAIN_ID=200010 npx tsx register-asset.ts <tokenAddress>        # verificar + preparar la entrada del registro
CHAIN_ID=200010 DRY=1 npx tsx register-asset.ts <tokenAddress>  # vista previa de solo lectura
```

Para una prueba de extremo a extremo de todo el ciclo — crear, incluir en la lista blanca, activar la puerta, vender en ambos carriles, distribuir rendimiento — ejecuta el smoke financiado contra cualquier cadena:

```bash
PRIVATE_KEY=… CHAIN_ID=… SOLANA_KEYPAIR=… npx tsx smoke.ts
```

***

## Paso 10 — Saber qué queda fuera de la cadena

El modelo de confianza es deliberadamente honesto, y un desarrollador debe representarlo igual ante sus propios usuarios:

* **El indicador de lista blanca es tu decisión fuera de la cadena.** KYC/AML ocurre en tu proceso — tus proveedores, tus reglas. La cadena registra y hace cumplir solo el *resultado*, `isWhitelisted(addr)`.
* **El único estado en cadena es un booleano por dirección.** Sin PII, sin documentos, sin reclamaciones de identidad, sin atestaciones en cadena, sin prueba criptográfica de a quién pertenece una dirección. Este es el modelo de Arc tal como se usa en Plume; intercambia las transferencias más pesadas de un estándar de identidad en cadena (p. ej., ERC-3643) por mecanismos más ligeros y confianza en manos del emisor.
* **La recuperación es un poder del emisor, no una vía de escape.** Una billetera perdida — EVM o Solana — se recupera incluyendo en la lista blanca una dirección de reemplazo y, si hace falta, usando mint/burn/upgrade bajo tu propio proceso legal (Arc no tiene un primitivo de transferencia forzada integrado). La custodia de la clave del emisor es por tanto parte de la postura de cumplimiento.
* **Sin relayer propio de Bloom, sin superficie de custodia.** Los usuarios mantienen su propio gas en ambos carriles. Un servicio de pago de comisiones ejecutado por la app significaría custodiar claves — una superficie que un producto RWA no debería añadir. El único reenvío en el carril de Solana es el del proxy de la cadena, para una llamada demasiado grande para una sola transacción, y el usuario sigue autorizándolo con su propia firma y pagándolo con su propio gas de Rome.

***

## Referencia: las herramientas

Cada script se ejecuta desde el `scripts/` directorio de las herramientas, con `PRIVATE_KEY`, `CHAIN_ID`y `REGISTRY_ROOT` exportado.

| Script                          | Qué hace                                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------------------- |
| `deploy-infra.ts`               | Una vez por cadena: los cuatro contratos compartidos, configurados para wUSDC.              |
| `create-token.ts`               | El asistente por etapas — ocho transacciones, un token.                                     |
| `rotate-factory.ts`             | Despliega una fábrica nueva después de un cambio en el compilador o el artefacto; recablea. |
| `verify-artifact-provenance.ts` | Demuestra que tu artefacto de proxy es aceptado por la fábrica desplegada (solo lectura).   |
| `verify-deployments.ts`         | Verifica los tres contratos por token + la infraestructura en Sourcify (solo lectura).      |
| `register-asset.ts`             | Verifica la puerta y luego prepara el registro de cumplimiento.                             |
| `measure-yield-batch.ts`        | Vuelve a medir el límite de rendimiento por transacción en una cadena.                      |
| `smoke.ts`                      | La suite financiada de extremo a extremo en ambos carriles.                                 |
| `sweep-synthetic-native.ts`     | Recupera gas nativo de una cuenta sintética (higiene de pruebas).                           |

El contexto más profundo está junto a esta guía: `docs/ARCHITECTURE.md` (el modelo de aplicación por capas), `(los dos carriles y la custodia),` (el modelo de confianza), y `(el manual de operaciones).` (el modelo de confianza), y `docs/USAGE.md` (el manual de operaciones).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.rome.builders/es/apps-en-rome/bloom/building.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
