> 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/de/kernkonzepte/token-interop.md).

# Token-Interoperabilität

Wie ERC-20- und SPL-Token auf Rome dasselbe zugrunde liegende Konto sind.

Rome stellt ein ERC-20-Token und sein zugrunde liegendes SPL-Token als ein gemeinsames Konto dar. Diese Seite erklärt, wie Tokens über EVM und Solana hinweg funktionieren.

## Das Modell mit gemeinsamem Zustand

Für ein gewöhnliches Wallet ist ein ERC-20-Token auf Rome ein **transparenter Wrapper** über einem SPL-Token-Konto auf Solana. Der ERC-20-Saldo des Wallets ist sein SPL-Saldo. Eine von der EVM-Adresse abgeleitete Programmadresse besitzt das Token-Konto. Dies ist seine **PDA mit externer Autorität**.

<figure><img src="https://511478552-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F671PKS8ZQSNqtoC8PGvz%2Fuploads%2Fgit-blob-552c639322c2d0e90b312def5db39d06c1871d18%2Ftoken-interop.svg?alt=media" alt="An EVM address and its Solana token account, owned by its external-authority PDA. Multiple wrappers read the same token account. The wrapper escrow account and Solana pool vaults hold tokens of the same Solana mint."><figcaption></figcaption></figure>

* Keine Bridge-Verzögerung: Der ERC-20-Saldo des Wallets ist sein SPL-Saldo.
* Gewöhnliche Wallets, einschließlich MetaMask-Wallets und synthetischer Adressen, haben keine Liquiditätsfragmentierung. Jeder Wrapper über einem Mint zeigt denselben Wallet-Saldo an. Die `balanceOf` Methode liest diesen Saldo aus dem Token-Konto des Wallets aus. Tokens, die in EVM-Verträgen gehalten werden, werden pro Wrapper gehalten (siehe [der Wrapper-Vertrag](#the-wrapper-contract)).
* Für die Tokens eines Wallets wird auf keiner Seite etwas gesperrt und auf der anderen Seite gemintet. Tokens, die von Verträgen gehalten werden, liegen im eigenen Escrow-Token-Konto des Wrappers auf Solana. Jeder Wrapper verfolgt diese Tokens separat.

**Warum ein zweiter Wrapper den Saldo nicht aufteilt**

Zwei Wrapper über demselben Mint zeigen denselben Wallet-Saldo an. Wrapper lesen `totalSupply` vom Solana-Mint aus. Auf Hadrian (Testnetz) gaben drei wUSDC-Wrapper übereinstimmende Wallet-Salden zurück. Alle drei wUSDC-Wrapper (9.0.0, 8.0.0 und Mai 2026) gaben die Umlaufmenge des Solana-Mints zurück.

Jede Factory erlaubt einen Wrapper pro Mint. Eine neue Factory-Version kann einen weiteren Wrapper für diesen Mint bereitstellen. Das Registry nennt den kanonischen Wrapper pro Chain.

Ein Solana-Wallet, das USDC direkt hält, und eine EVM-Adresse halten Tokens desselben Mints. Solana-Pools verwenden für beide dieselben Vaults und Reserven.

Die Haltertabelle im [Über den Explorer](/de/apps-auf-rome/via.md) wird aus den `Transfer` Ereignissen jedes Wrappers erstellt. Sie kann sich vom Token-Kontostand unterscheiden, weil direkte SPL-Transfers, `ActivateAta`und Überweisungen über einen anderen Wrapper in den Ereignissen dieses Wrappers nicht erscheinen.

> Die Importpfade unten verwenden `@rome-protocol/rome-solidity`. Das Paket ist nicht auf npm; installiere es aus dem öffentlichen [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) Repo mit `npm install github:rome-protocol/rome-solidity`oder `forge install rome-protocol/rome-solidity` mit einem Remapping; die Wrapper- und Factory-Verträge benötigen unter Foundry außerdem OpenZeppelin (siehe [Solidity-Verträge deployen](/de/entwicklerleitfaden/deploy-solidity.md#using-the-rome-solidity-sdk)). Precompile-Schnittstellen befinden sich in [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/interface.sol).

## der Wrapper-Vertrag

`SPL_ERC20` (und seiner Cache-Pfad-Variante `SPL_ERC20_cached`, die die Factory für jeden Mint außer einem mit einem aktivierten Transfer-Hook bereitstellt; siehe [Token-2022-Mints](#token-2022-mints)) bieten eine vollständige ERC-20-Schnittstelle über einen SPL-Mint:

* `balanceOf()` — liest den ATA-Saldo des Nutzers aus Solana aus (bei einer Vertragsadresse liest es das eigene Ledger des Wrappers, siehe unten)
* `transfer()` / `transferFrom()` — verschiebt Tokens auf Solana
* `approve()` / `allowance()` — pro Empfänger gespeicherte ERC-20-Allowances in EVM-Speicher (ein SPL-Token-Konto hat keine Allowances im EVM-Stil). Sie sind getrennt von der einmaligen Freigabe unten: Um die Tokens eines Nutzers überhaupt zu bewegen, handelt der Wrapper als SPL-Delegate dieses Nutzers, und der Nutzer gewährt ihm dieses Recht einmal pro Wallet
* `totalSupply()` — liest die Umlaufmenge des SPL-Mints

Ein Vertrag kann die einmalige Freigabe nicht senden, daher werden die Tokens eines Vertrags im eigenen Token-Konto des Wrappers gehalten und im Ledger des Wrappers verfolgt. `balanceOf(contract)` liest dieses Ledger aus.

## Die Factory

`ERC20SPLFactory` stellt für jeden SPL-Mint einen Wrapper bereit:

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

// Stelle einen Wrapper bereit und lade Name/Symbol aus den Token-2022-Metadaten des Mints oder aus Metaplex
address wrapper = factory.add_spl_token_with_metadata(splMint);

// Oder Name/Symbol manuell angeben
address wrapper = factory.add_spl_token_no_metadata(splMint, "USD Coin", "USDC");
```

Live-Factory-Adresse aus den `live` Einträgen des Registrys (v0.2.0): Hadrian `0x37c13e4ff21fb914eb24014dcbd0f8dbeaa31078`. Frühere Factories sind noch on-chain, sind aber als `veraltet` oder `außer Betrieb` in der [Registry](https://github.com/rome-protocol/rome-registry/tree/main/chains)markiert; verwende sie nicht. Das Registry ist die Quelle der Wahrheit, lies also die Live-Adresse daraus aus (siehe [Die Vertragsliste lesen](/de/referenz/contract-addresses.md#reading-the-contracts-list)) statt sie in deinen Code zu kopieren.

## Kanonische Mints

Es gibt keinen On-Chain-Token-Registry-Vertrag. Kanonische Wrapper und Gas-/Bridge-Token werden im Off-Chain- [`rome-protocol/registry`](https://github.com/rome-protocol/rome-registry)kuratiert; erlaubnisfreie Wrapper, die über `add_spl_token_no_metadata` erstellt werden, werden aus dem On-Chain- `TokenCreated` Ereignis entdeckt. Das Registry benennt den kanonischen Wrapper für jeden Mint auf jeder Chain. Wallet-Salden werden über Wrapper hinweg geteilt. Jeder Wrapper verfolgt separat die von EVM-Verträgen gehaltenen Salden.

## SPL-Operationen aus Solidity

Für vom Benutzer-PDA-signierte SPL-Primitiven verwende das **HelperProgram** Precompile (`0xFF…09`) — ATA-Erstellung, SPL-Überweisungen und Gas↔Lamports-Umwandlung:

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

IHelperProgram helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

helper.create_ata(user, mint);              // erstelle das ATA des Nutzers für einen Mint
helper.transfer_spl(to, tokens, mint);      // übertrage SPL vom PDA des Aufrufers
```

`transfer_spl` hat mehrere Überladungen (einschließlich einer Delegate-Variante für `transferFrom` Abläufe); siehe `interface.sol` für die exakten Signaturen. Auf dem Cache-Pfad liegen die entsprechenden Operationen auf `ISplCached` (`0xFF…05`) und `IAssociatedSplCached` (`0xFF…06`). Ein Vertrag verwendet durchgängig einen Pfad.

## Einmalige Freigabe pro Wallet

Der Wrapper bewegt die Tokens eines Nutzers als **SPL-Delegate** des Token-Kontos dieses Nutzers. Ein Wallet muss ihm daher dieses Recht einmal pro Wrapper gewähren, bevor der Wrapper irgendetwas dafür bewegen kann. Die Erteilung ist ein Aufruf an die Helper-Precompile (`0xff00000000000000000000000000000000000009`):

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

`approve_spl` wird vom eigenen Konto des Nutzers signiert, also sendet der Nutzer ihn, nicht der Wrapper oder dein Vertrag. Der Mint ist `mint_id()` auf dem Wrapper.

* **Wer es braucht.** Jedes EVM-Wallet und die synthetische Adresse jedes Solana-Nutzers für jeden Wrapper, dessen Tokens es bewegen möchte. Ein Vertrag, der Tokens mit `transferFrom` bewegt, funktioniert nur für Nutzer, die dem Wrapper die Erlaubnis erteilt haben.
* **Wie man es prüft.** Rufe `isEnabled(user)` auf dem Wrapper auf. Er gibt `true` zurück, sobald die Erlaubnis erteilt wurde.
* **Wie es aussieht, wenn sie fehlt.** Die Übertragung wird mit SPL-Fehler zurückgesetzt `0x4` (Besitzer stimmt nicht überein).
* **Ein Delegate pro Token-Konto.** Ein SPL-Token-Konto hat genau einen Delegate. Wenn man einem anderen Programm für dasselbe Token-Konto eine Freigabe erteilt, etwa einer Bridge oder einem Router, ersetzt das die Freigabe des Wrappers, und der Wrapper funktioniert für diesen Nutzer erst wieder, wenn dieser die Freigabe erneut erteilt. Prüfe `isEnabled` vor einer Übertragung, statt anzunehmen, dass eine frühere Freigabe noch vorhanden ist.

Wenn eine neue Factory-Version einen Wrapper ersetzt, erteilt jedes Wallet dem neuen Wrapper die Berechtigung mit `approve_spl`. Der alte Wrapper kann die Tokens dieses Wallets nur bewegen, solange er Delegate des Token-Kontos bleibt. Das Erteilen der Berechtigung an den neuen Wrapper ersetzt diesen Delegate.

Der [Anleitung für Dual-Lane-Apps](/de/entwicklerleitfaden/dual-lane-app.md#one-time-grant-the-wrapper) enthält den vollständigen Code für beide Lanes.

## Einzahlen und auszahlen

* **In die EVM** — die SPL-Seite schreibt dem ATA des vom PDA des Nutzers gehaltenen Kontos gut; der ERC-20-Wrapper spiegelt den Saldo sofort wider. Cross-Chain-Inbound-Transfers werden vertrauenslos über eine vom Nutzer signierte Autorisierung auf der Bridge abgewickelt.
* **Gas in den Gas-Token wrappen** — rufe die `Abheben` Precompile (`0x42…16`). `withdraw_to_ata(wei)` verbrennt natives Gas und schreibt den entsprechenden Betrag in den eigenen Dezimalstellen des Gas-Tokens dem Token-Konto des Aufrufers gut, das der Wrapper des Gas-Tokens (zum Beispiel wUSDC) dann als Saldo anzeigt. `withdraw_to_pda(wei)` zahlt Lamports an das PDA des Aufrufers aus und gilt nur für eine Chain, deren Gas SOL ist; auf Hadrian und Rubicon, die USDC als Gas verwenden, schlägt es fehl, also verwende `withdraw_to_ata` dort. Der Betrag ist in Gas-Einheiten mit 18 Dezimalstellen angegeben und muss eine ganze Anzahl von Gas-Token-Einheiten sein: auf einer USDC-Gas-Chain ein Vielfaches von 10^12 (eine 6-Dezimalstellen-Einheit), andernfalls schlägt der Aufruf fehl. `HelperProgram.deposit_from_ata(wei)` geht in die andere Richtung und wandelt den Gas-Token im Token-Konto des Aufrufers mit derselben Vielfachheitsregel wieder in natives Gas um. Das sind Gas-Token-Konvertierungen, kein Ausstieg für andere Tokens.
* **Raus nach Solana, jeder andere Token** — der eigene Saldo des Wrappers ist bereits der SPL-Saldo, daher ist das Herausschieben eine SPL-Übertragung:
  * `HelperProgram.transfer_spl_to_signer(amount, mint)` sendet die Tokens des Aufrufers an das Token-Konto des Solana-Wallets, das die Transaktion signiert hat. Dieser Rückweg erreicht das eigene Wallet des Nutzers nur auf dem von der Wallet signierten Solana-Pfad. Wenn er aus MetaMask oder über das Relay aufgerufen wird, ist der Signierer Rome's Proxy-Payer, daher schlägt der Aufruf fehl oder die Tokens gehen an Rome. Verwende `transfer_spl` oder `RomeBridgeWithdraw.bridgeOutToSolana` stattdessen dort.
  * `RomeBridgeWithdraw.bridgeOutToSolana(solanaRecipient, amount, mint)` sendet sie an jedes beliebige Solana-Wallet. Dafür braucht es die Freigabe des Nutzers für den Bridge-Vertrag (`approve_spl(bridge, …)`, siehe den Hinweis zu einem Delegate oben), und das Token-Konto des Empfängers muss existieren; `ensureRecipientAta(solanaRecipient, mint)` erstellt es.
  * `RomeBridgeWithdraw` verbrennt außerdem USDC über CCTP auf einer anderen Chain (`burnUSDC`) und sendet andere Assets über Wormhole (`burnETH`, `burnToWormhole`).

Nimm die Live-Adresse `RomeBridgeWithdraw` aus der Registry.

## Gas-Token

Jede Chain hat ihren eigenen Gas-Token: SOL oder ein SPL-Token. Die Preisquelle des Rome Proxy kann ein Hermes-API-Feed, ein Meteora-DAMM-v1-Pool, ein Meteora-DAMM-v2-Pool oder ein fester Preis sein. Hadrian (Testnetz) und Rubicon (Mainnet) verwenden USDC. Hadrian verwendet Test-USDC. Rubicon verwendet echtes USDC. Es gibt keinen universellen Standard-Gas-Token.

## Token-2022-Mints

Die Factory wickelt außerdem Token-2022-Mints ein. Sie fragt die Helper-Precompile ab, was der Mint ist, mit `mint_info(mint)` (Selektor `0xe24bf5d4`), und wählt den Wrapper anhand der Antwort aus. `mint_info` gibt fünf Werte zurück:

| Wert            | Bedeutung                                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `tokenProgram`  | das Token-Programm, dem der Mint gehört (das klassische SPL-Token-Programm oder Token-2022)                               |
| `decimals`      | die Dezimalstellen des Mints                                                                                              |
| `hookProgram`   | das Transfer-Hook-Programm oder null, wenn kein Hook vorhanden ist oder der Hook zwar vorhanden, aber nicht aktiviert ist |
| `feeBps`        | die Transfergebühr in Basispunkten oder null, wenn keine vorhanden ist oder sie auf null gesetzt ist                      |
| `Erweiterungen` | eine Bitmaske der Token-2022-Erweiterungen, die der Mint trägt, Bit N für Erweiterungstyp N                               |

Was ein Mint bekommt:

| Mint                                                                                                                | Wrapper                                                                                             | `wrapper_kind_by_mint` |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------- |
| Ein gewöhnlicher Mint, ein Token-2022-Mint ohne Hook oder ein Mint, dessen Hook vorhanden, aber nicht aktiviert ist | der Cache-Wrapper (`SPL_ERC20_cached`)                                                              | `1`                    |
| Ein Token-2022-Mint mit einem aktivierten Transfer-Hook                                                             | der Hook-Wrapper (`SPL_ERC20_Token2022Hooked`), bereitgestellt über den `hooked_wrapper_deployer()` | `2`                    |

Ein Mint mit einer Transfergebühr erhält den Cache-Wrapper, und sein `Transfer` Ereignis meldet den Betrag, der nach der Gebühr angekommen ist, nicht den gesendeten Betrag. Welchen Typ ein Mint von der Factory erhalten hat, kannst du mit `wrapper_kind_by_mint(mint)`; in der Live-Hadrian-Factory geben die USDC-, ETH- und SOL-Wrapper alle `1`.

Was abgelehnt wird:

* Ein Mint mit einem aktivierten Transfer-Hook kann keinen der gewöhnlichen Wrapper erhalten. Ein direktes Deployen `SPL_ERC20` oder `SPL_ERC20_cached` darauf direkt aufgerufen, wird mit `ArmedTransferHookUnsupported(mint, hookProgram)`abgebrochen. Über die Factory geht dieser Fall stattdessen an den Hook-Wrapper.
* Eine Adresse, die kein SPL-Mint ist, bewirkt, dass `mint_info` mit `Ungültiges SPL-Mint-Konto: <address>`.
* Ein Mint, der bereits einen Wrapper hat, bricht mit `Token existiert bereits`, und ein bereits verwendetes Symbol bricht mit `Token mit diesem Symbol existiert bereits`.
* `add_spl_token_with_metadata` liest Name und Symbol zuerst aus den eigenen Token-2022-Metadaten des Mints und zweitens aus Metaplex. Wenn der Mint keines von beiden hat, bricht es mit `Mint meldet keinen Namen: add_spl_token_no_metadata verwenden`.

Wie sich der Hook-Wrapper verhält:

* Token-2022 führt den Hook bei jeder Übertragung aus, und der Hook benötigt Konten, die von der Übertragung abhängen. Der Wrapper kann das nicht selbst herausfinden, daher löst der Aufrufer die vollständige Kontoliste aus der extra-account-metas-Liste des Mints auf und übergibt sie. Der Wrapper prüft den festen Teil dieser Liste (das Hook-Programm und sein Validierungskonto) und reicht sie unverändert weiter.
* Der normale `transfer`, `transferFrom` und `bridgeOutToSolana` mit `HookAccountPlanRequired`. Verwende `transferWithHookAccounts`, `transferFromWithHookAccounts` und `bridgeOutToSolanaWithHookAccounts`, die die Kontoliste als zusätzliches Argument übernehmen.
* Wenn sich das Hook-Programm des Mints ändert, nachdem der Wrapper bereitgestellt wurde, brechen Übertragungen mit `HookConfigurationChanged`.
* Er bewegt Tokens per direktem Cross-Program-Call, dem Legacy-Pfad, und verwendet nicht den Cache-Pfad. Ein Vertrag verwendet durchgängig einen Pfad, und Aufrufe auf dem Legacy-Pfad laufen nur in atomischer Ausführung, daher muss ein Aufruf, der den Hook-Wrapper verwendet, in eine einzelne atomare Transaktion passen.
* Er führt kein Ledger für Vertragssalden. Tokens, die an einen Vertrag gesendet werden, landen im eigenen Token-Konto des Vertrags, und der Vertrag kann sie erst weiterleiten, nachdem er die `approve_spl` Freigabe an den Wrapper selbst erteilt hat.
* Es benötigt dieselbe [einmalige Freigabe](#one-time-grant-per-wallet) wie jeder andere Wrapper.

## Beschränkungen

* SPL-Token-Beträge sind `uint64` (max. 18.446.744.073.709.551.615)
* ERC-20-Allowances (`Genehmigung` / `allowance`) sind EVM-Speicher. Der Wrapper bewegt Tokens als SPL-Delegate des Nutzers, den der Nutzer einmal pro Wrapper erteilt (siehe [Einmalige Freigabe pro Wallet](#one-time-grant-per-wallet))
* ERC-20-Wrapper-Symbole müssen pro Factory eindeutig sein

## Wie es weitergeht

* [Contract Addresses](/de/referenz/contract-addresses.md) — Precompiles und ketten-spezifische Adressen
* [Solana aus EVM aufrufen](/de/entwicklerleitfaden/call-solana-from-evm.md) — CPI- und SPL-Operationen aus Solidity
* [Wie Solana und die EVM sich auf Rome treffen](/de/kernkonzepte/how-solana-and-evm-meet.md)
* [Integrieren Sie ein Solana-Programm](/de/entwicklerleitfaden/integrate-solana-program.md)


---

# 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/de/kernkonzepte/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.
