> 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/pt-br/conceitos-principais/token-interop.md).

# Interoperabilidade de tokens

Como tokens ERC-20 e SPL são a mesma conta subjacente na Rome.

Rome representa um token ERC-20 e seu token SPL subjacente como uma única conta compartilhada. Esta página explica como os tokens funcionam entre EVM e Solana.

## O modelo de estado compartilhado

Rome não trava tokens de um lado e emite cópias embrulhadas do outro. Um token ERC-20 no Rome é um **invólucro transparente** sobre uma conta de token SPL em Solana — o saldo ERC-20 *é* o saldo SPL.

<figure><img src="https://735130481-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F671PKS8ZQSNqtoC8PGvz%2Fuploads%2Fgit-blob-65af9700f322d308e2cb17da3d107f87b51201a7%2Ftoken-interop.svg?alt=media" alt="An ERC-20 wrapper over the same SPL token account on Solana"><figcaption></figcaption></figure>

* Sem atraso de ponte — o saldo ERC-20 é o saldo SPL
* Sem fragmentação de liquidez — o DeFi em ambos os lados vê os mesmos tokens
* Sem risco de ponte — não há custódia separada para explorar

> Os caminhos de importação abaixo usam `@rome-protocol/rome-solidity`. O pacote não está no npm; instale-o do [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) repositório público com `npm install github:rome-protocol/rome-solidity`, ou `forge install rome-protocol/rome-solidity` com um remapeamento; os contratos wrapper e factory também precisam do OpenZeppelin sob o Foundry (veja [Deploy de Contratos Solidity](/pt-br/guias-do-desenvolvedor/deploy-solidity.md#using-the-rome-solidity-sdk)). As interfaces de precompile ficam em [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/interface.sol).

## O contrato wrapper

`SPL_ERC20` (e sua variante de track em cache `SPL_ERC20_cached`, que a factory implanta para cada mint, exceto um com um transfer hook armado; veja [mints Token-2022](#token-2022-mints)) fornecem uma interface ERC-20 completa sobre um mint SPL:

* `balanceOf()` — lê o saldo ATA do usuário no Solana (para um endereço de contrato, lê o próprio livro-razão do wrapper, veja abaixo)
* `transfer()` / `transferFrom()` — move tokens no Solana
* `approve()` / `allowance()` — permissões ERC-20 por spender mantidas no armazenamento EVM (uma conta de token SPL não tem permissões no estilo EVM). Elas são separadas da concessão única abaixo: para mover os tokens de um usuário, de fato, o wrapper atua como o delegado SPL desse usuário, e o usuário concede esse direito uma vez por carteira
* `totalSupply()` — lê o suprimento do mint SPL

Um contrato não pode enviar a concessão única, então os tokens de um contrato ficam na própria conta de token do wrapper e são rastreados no livro-razão do wrapper. `balanceOf(contract)` lê esse livro-razão.

## A factory

`ERC20SPLFactory` implanta um wrapper para qualquer mint SPL:

```solidity
import {ERC20SPLFactory} from "@rome-protocol/rome-solidity/contracts/erc20spl/erc20spl_factory.sol";

// Implanta um wrapper, carregando nome/símbolo a partir dos metadados Token-2022 do mint ou do Metaplex
address wrapper = factory.add_spl_token_with_metadata(splMint);

// Ou especifique nome/símbolo manualmente
address wrapper = factory.add_spl_token_no_metadata(splMint, "USD Coin", "USDC");
```

Endereço ativo da factory, a partir das `live` entradas do registro (v0.2.0): Hadrian `0x37c13e4ff21fb914eb24014dcbd0f8dbeaa31078`. As factories anteriores ainda estão on-chain, mas estão marcadas como `obsoletas` ou `aposentadas` da cadeia em [registro](https://github.com/rome-protocol/rome-registry/tree/main/chains); não as use. O registro é a fonte da verdade, então leia dele o endereço ativo (veja [Lendo a lista de contratos](/pt-br/referencia/contract-addresses.md#reading-the-contracts-list)) em vez de copiar um para o seu código.

## Mints canônicos

Não existe contrato on-chain de registro de tokens. Wrappers canônicos e tokens de gas/ponte são curados no [`rome-protocol/registry`](https://github.com/rome-protocol/rome-registry)off-chain; wrappers sem permissão criados via `add_spl_token_no_metadata` são descobertos pelo evento on-chain `TokenCreated` . Isso mantém cada ativo mapeado para um único mint SPL canônico sem fragmentar a liquidez.

## Operações SPL a partir de Solidity

Para primitivas SPL assinadas por PDA do usuário, use a **HelperProgram** precompile (`0xFF…09`) — criação de ATA, transferências SPL e conversão de gas↔lamports:

```solidity
import {IHelperProgram} from "@rome-protocol/rome-solidity/contracts/interface.sol";

IHelperProgram helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

helper.create_ata(user, mint);              // cria o ATA do usuário para um mint
helper.transfer_spl(to, tokens, mint);      // transfere SPL a partir do PDA do chamador
```

`transfer_spl` tem várias sobrecargas (inclusive uma variante delegate para `transferFrom` fluxos); veja `interface.sol` para as assinaturas exatas. No track em cache, as operações equivalentes ficam em `ISplCached` (`0xFF…05`e `IAssociatedSplCached` (`0xFF…06`). Um contrato usa um track de forma consistente.

## Concessão única por carteira

O wrapper move os tokens de um usuário como o **delegado SPL** da conta de token desse usuário. Portanto, uma carteira precisa conceder esse direito uma vez por wrapper, antes que o wrapper possa mover qualquer coisa por ela. A concessão é uma chamada para a precompile Helper (`0xff00000000000000000000000000000000000009`):

```solidity
helper.approve_spl(wrapper, type(uint64).max, mint);   // approve_spl(wrapper, 2^64-1, mint)
```

`approve_spl` é assinada pela própria conta do usuário, então é o usuário que a envia, não o wrapper nem o seu contrato. O mint é `mint_id()` no wrapper.

* **Quem precisa disso.** Toda carteira EVM, e todo endereço sintético de usuário Solana, para cada wrapper cujos tokens ela queira mover. Um contrato que puxa tokens com `transferFrom` funciona apenas para usuários que tenham concedido o wrapper.
* **Como verificar.** Chame `isEnabled(user)` no wrapper. Ele retorna `enquanto qualquer parte dela permanecer, então o app não saberia que precisa pedir de novo.` assim que a concessão estiver em vigor.
* **Como fica quando está ausente.** A transferência falha com o erro SPL `0x4` (owner mismatch).
* **Um delegado por conta de token.** Uma conta de token SPL tem um único delegado. Dar a outro programa uma concessão na mesma conta de token, como uma ponte ou um roteador, substitui a concessão do wrapper, e o wrapper para de funcionar para aquele usuário até que ele conceda novamente. Verifique `permanece` antes de uma transferência, em vez de assumir que uma concessão anterior ainda existe.

Os [guia do app de duas vias](/pt-br/guias-do-desenvolvedor/dual-lane-app.md#one-time-grant-the-wrapper) tem o código completo para ambas as vias.

## Depósito e saque

* **Para EVM** — o lado SPL credita o ATA do usuário de propriedade do PDA; o wrapper ERC-20 reflete o saldo imediatamente. Transferências de entrada entre cadeias são liquidadas sem confiança por meio de uma autorização assinada pelo usuário na ponte.
* **Envolver gas no token de gas** — chame o `Withdraw` precompile (`0x42…16`). `withdraw_to_ata(wei)` queima gas nativo e credita o valor equivalente, nas próprias casas decimais do token de gas, na conta de token do chamador, que então é mostrado como saldo pelo wrapper do token de gas (por exemplo, wUSDC). `withdraw_to_pda(wei)` paga lamports ao PDA do chamador e se aplica apenas a uma cadeia cujo gas é SOL; falha em Hadrian e Rubicon, que usam USDC como gas, então use `withdraw_to_ata` lá. O valor está em unidades de gas com 18 casas decimais e precisa ser um número inteiro de unidades do token de gas: em uma cadeia com gas em USDC, um múltiplo de 10^12 (uma unidade com 6 casas decimais), caso contrário a chamada falha. `HelperProgram.deposit_from_ata(wei)` faz o caminho inverso e converte o token de gas na conta de token do chamador de volta em gas nativo, com a mesma regra de múltiplos. Essas são conversões de token de gas, não uma saída para outros tokens.
* **Para fora para Solana, qualquer outro token** — o próprio saldo do wrapper já é o saldo SPL, então movê-lo para fora é uma transferência SPL:
  * `HelperProgram.transfer_spl_to_signer(amount, mint)` envia os tokens do chamador para a conta de token da carteira Solana que assinou a transação. Esta é a etapa de retorno para usuários com carteira Solana.
  * `RomeBridgeWithdraw.bridgeOutToSolana(solanaRecipient, amount, mint)` envia-os para qualquer carteira Solana. Ele precisa da concessão do usuário para o contrato da ponte (`approve_spl(bridge, …)`, veja a observação sobre um delegado acima), e a conta de token do destinatário precisa existir; `ensureRecipientAta(solanaRecipient, mint)` a cria.
  * `RomeBridgeWithdraw` também queima USDC para outra cadeia via CCTP (`burnUSDC`) e envia outros ativos via Wormhole (`burnETH`, `burnToWormhole`).

Pegue o `RomeBridgeWithdraw` endereço ativo do registro.

## Token de gas

Cada cadeia tem seu próprio token de gas — qualquer token SPL, precificado por meio de uma pool Meteora DAMM (v1 ou v2, configurável). As cadeias públicas (Rubicon e Hadrian) usam USDC; em Hadrian é USDC de teste, em Rubicon é USDC real. Não há um token de gas padrão universal.

## mints Token-2022

A factory também embrulha mints Token-2022. Ela pergunta à precompile Helper qual é o mint, com `mint_info(mint)` (seletor `0xe24bf5d4`), e escolhe o wrapper com base na resposta. `mint_info` retorna cinco valores:

| Valor          | Significado                                                                                                 |
| -------------- | ----------------------------------------------------------------------------------------------------------- |
| `tokenProgram` | o programa de token que possui o mint (o programa SPL Token clássico ou o Token-2022)                       |
| `decimais`     | as casas decimais do mint                                                                                   |
| `hookProgram`  | o programa de transfer hook, ou zero quando não há hook ou quando o hook existe mas não está armado         |
| `feeBps`       | a taxa de transferência em pontos-base, ou zero quando não há nenhuma ou quando ela está definida como zero |
| `extensions`   | uma bitmap das extensões Token-2022 que o mint contém, bit N para a extensão do tipo N                      |

O que um mint recebe:

| Mint                                                                                   | Wrapper                                                                                       | `wrapper_kind_by_mint` |
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------- |
| Um mint comum, um mint Token-2022 sem hook, ou um cujo hook existe mas não está armado | o wrapper em cache (`SPL_ERC20_cached`)                                                       | `1`                    |
| Um mint Token-2022 com um transfer hook armado                                         | o wrapper com hook (`SPL_ERC20_Token2022Hooked`), implantado pela `hooked_wrapper_deployer()` | `2`                    |

Um mint com taxa de transferência recebe o wrapper em cache, e seu evento `Transfer` relata o valor que chegou após a taxa, não o valor enviado. Você pode ler qual tipo um mint recebeu da factory com `wrapper_kind_by_mint(mint)`; na factory ativa de Hadrian, os wrappers de USDC, ETH e SOL todos retornam `1`.

O que é recusado:

* Um mint com transfer hook armado não pode receber um dos wrappers comuns. Implantar `SPL_ERC20` ou `SPL_ERC20_cached` diretamente nele falha com `ArmedTransferHookUnsupported(mint, hookProgram)`. Pela factory, esse caso vai para o wrapper com hook em vez disso.
* Um endereço que não seja um mint SPL faz `mint_info` falhar com `Invalid SPL mint account: <address>`.
* Um mint que já tem um wrapper falha com `Token exists`, e um símbolo que já está em uso falha com `Token with symbol exists`.
* `add_spl_token_with_metadata` lê o nome e o símbolo primeiro dos metadados Token-2022 do próprio mint e depois do Metaplex. Se o mint não tiver nenhum dos dois, falha com `Mint asserts no name: use add_spl_token_no_metadata`.

Como o wrapper com hook se comporta:

* Token-2022 executa o hook em cada transferência, e o hook precisa de contas que dependem da transferência. O wrapper não consegue inferi-las, então o chamador resolve a lista completa de contas a partir da lista de extra-account-metas do mint e a passa adiante. O wrapper verifica a parte fixa dessa lista (o programa do hook e sua conta de validação) e a encaminha sem alterações.
* A simples `transfer`, `transferFrom` e `bridgeOutToSolana` falhar com `HookAccountPlanRequired`. Use `transferWithHookAccounts`, `transferFromWithHookAccounts` e `bridgeOutToSolanaWithHookAccounts`, que recebem a lista de contas como um argumento extra.
* Se o programa de hook do mint mudar depois que o wrapper foi implantado, as transferências falham com `HookConfigurationChanged`.
* Ele move tokens por chamada direta entre programas, o track legado, e não usa o track em cache. Um contrato usa um track de forma consistente, e chamadas no track legado rodam apenas em execução atômica, então uma chamada que usa o wrapper com hook precisa caber em uma única transação atômica.
* Ele não mantém nenhum livro-razão para saldos de contratos. Tokens enviados a um contrato vão para a própria conta de token do contrato, e o contrato só pode enviá-los depois que tiver feito a `approve_spl` concessão ao próprio wrapper.
* Ele precisa da mesma [concessão única](#one-time-grant-per-wallet) que qualquer outro wrapper.

## Restrições

* Os valores de token SPL são `uint64` (máx. 18.446.744.073.709.551.615)
* As permissões ERC-20 (`approve` / `allowance`) ficam no armazenamento EVM. O wrapper move tokens como o delegado SPL do usuário, que o usuário concede uma vez por wrapper (veja [Concessão única por carteira](#one-time-grant-per-wallet))
* Os símbolos dos wrappers ERC-20 devem ser únicos por factory

## O que Vem a Seguir

* [referência de Endereços de Contrato](/pt-br/referencia/contract-addresses.md) — precompiles e endereços por cadeia
* [Chamar Solana a partir da EVM](/pt-br/guias-do-desenvolvedor/call-solana-from-evm.md) — CPI e operações SPL a partir de Solidity


---

# 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/pt-br/conceitos-principais/token-interop.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.
