> 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/entwicklerleitfaden/dual-lane-app.md).

# Eine App mit zwei Lanes bauen

Eine **Dual-Lane-App** ist ein Solidity-Vertrag, den sowohl ein **MetaMask (EVM)** Benutzer als auch ein **Phantom (Solana)** Benutzer direkt verwenden — derselbe Vertrag, derselbe Zustand, jeder mit der Wallet, die er bereits hat. Diese Seite führt genau durch *genau* was bei jedem Schritt auf jeder Seite geschieht.

Das Beispiel ist ein winziger **Vault**: du `einzahlst` USDC und später `abhebst` ihn. „Stake / unstake“, „supply / redeem“, „tip / claim“ haben dieselbe Form.

## Was du schreibst — ein standardmäßiger ERC-20-Vault

Auf Rome erscheint das USDC eines Solana-Nutzers auf der EVM-Seite als ein gewöhnlicher **ERC-20-Token** — der SPL-Wrapper für dieses Mint (z. B. `wUSDC`). Dein Vertrag ist also ein normaler Token-Vault: Er zieht Tokens mit `transferFrom` und gibt sie mit `transfer`zurück. Nichts Rome-Spezifisches:

```solidity
interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract Vault {
    IERC20 public immutable token;                 // der wUSDC-Wrapper
    mapping(address => uint256) public balanceOf;
    constructor(IERC20 _token) { token = _token; }

    function deposit(uint256 amount) external {
        require(token.transferFrom(msg.sender, address(this), amount));
        balanceOf[msg.sender] += amount;
    }
    function withdraw(uint256 amount) external {
        balanceOf[msg.sender] -= amount;
        require(token.transfer(msg.sender, amount));
    }
}
```

> **Warum ERC-20 und nicht `payable` / `msg.value`?** Der ausgebbare Saldo eines Solana-Nutzers ist sein **SPL-Token-Konto der Wallet**, 1:1 als dieser ERC-20-Wrapper dargestellt — *nicht* der native EVM-Saldo. Ein Solana-Nutzer kann also immer einen `transferFrom`, aber ein `deposit() payable` würde nativen Wert benötigen, den er nicht als Guthaben hält. Wenn du um den Token herum baust, funktionieren beide Lanes gleich.

Deploye ihn mit Foundry oder Hardhat und richte den Konstruktor auf die Wrapper-Adresse für deinen Token aus (aus dem [Registry](https://github.com/rome-protocol/rome-registry)). Auf Rome ist der Gas-Token USDC, also brauchst du ein kleines USDC-Gas-Guthaben für das Deployment.

## Die beiden Lanes

| Lane       | Wallet                         | Wie die App ihn aufruft                                    |
| ---------- | ------------------------------ | ---------------------------------------------------------- |
| **EVM**    | MetaMask (ein EVM-Schlüssel)   | `submitRomeTx` — der standardmäßige Rome-Write             |
| **Solana** | Phantom (ein Solana-Schlüssel) | `submitRomeTxSolanaLane` — kein EVM-Schlüssel erforderlich |

Die EVM-Lane ist gewöhnlich. Der Rest dieser Seite ist die **Solana-Lane** — die interessante Hälfte.

## Die Kernidee: Das synthetische Konto ist ein Durchlaufkonto

Die EVM-Identität eines Solana-Nutzers ist seine **synthetische Adresse** — `keccak256(solana_pubkey)[12:]`. Sie ist sein `msg.sender` im Vertrag, hält aber **nichts als Guthaben.** Das Geld des Nutzers liegt in seiner **Solana-Wallet** (als SPL USDC), und auf der EVM-Seite ist derselbe Saldo das, was `wUSDC.balanceOf(synthetic)` ausliest. Wert fließt *durch* das synthetische Konto:

* **In** (deposit): Wallet-Token-Konto → synthetisches Token-Konto → Vertrag (über `transferFrom`).
* **Aus** (withdraw): Vertrag → synthetisches Token-Konto → Wallet-Token-Konto.

Das synthetische Konto endet nach jedem Hin-und-her wieder bei null.

## Einmalig: Aktivieren (das synthetische Konto bereitstellen)

Das On-Chain-Konto eines brandneuen synthetischen Kontos existiert nicht, bis du es erstellst. Wenn ein Solana-Nutzer zum ersten Mal handelt, wird sein synthetisches Konto **bereitgestellt** mit einem `create_pda` Aufruf — danach können wertbewegende Aufrufe (der ERC-20 `transferFrom`, der Sweep) von ihm signiert werden.

`submitRomeTxSolanaLane` macht das **beim ersten Gebrauch automatisch** (`autoProvision` ist standardmäßig aktiviert). Wenn du lieber einen expliziten „Aktivieren“-Bildschirm anzeigen möchtest (eine einmalige Kontoeinrichtung), mach es selbst:

```javascript
import { provisionSynthetic, isSyntheticProvisioned } from "@rome-protocol/sdk";

const deps = { connection, proxyUrl, programId, chainId, payer: wallet.publicKey, signTransaction: wallet.signTransaction };
if (!(await isSyntheticProvisioned(connection, programId, synthetic))) {
  await provisionSynthetic(deps); // ein create_pda; danach Writes mit autoProvision: false senden
}
```

## Was jede Seite benötigt

|                             | Benötigt                               | Warum                                                                                              |
| --------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Solana-Nutzer (Phantom)** | **SOL** (ein bisschen)                 | zahlt die Solana-Transaktionsgebühr bei jeder Lane-Transaktion                                     |
|                             | **USDC als SPL-Token** in ihrer Wallet | den Wert, den sie einzahlen (auf der EVM-Seite als `wUSDC`)                                        |
| **EVM-Nutzer (MetaMask)**   | **USDC** als ihr Rome-Gas-Guthaben     | Gas + Wert; sie füllen es auf, indem sie **USDC bridgen** nach Rome hinein (es gibt keinen Faucet) |
| **Du (der Entwickler)**     | eine Wallet mit **USDC** Gas auf Rome  | um den Vertrag zu deployen                                                                         |

## Wert IN — Einzahlung (Schritt für Schritt)

Der Solana-Nutzer hat SOL + USDC in seiner Phantom-Wallet. Jede Lane-Transaktion wird **von Phantom signiert** — die Wallet signiert sie und sendet sie an den Solana-RPC (der Proxy wird nur zum Auffinden von Konten verwendet):

1. **Fund-Leg** — `buildFundLeg(...)` → `submitSolanaInstructions(...)`. Erstellt das USDC-Token-Konto des synthetischen Kontos (falls nötig) und führt **`ActivateAta`**, wobei `amount` USDC aus dem Token-Konto der Wallet **in das des synthetischen Kontos**. Jetzt `wUSDC.balanceOf(synthetic)` zeigt dieses Guthaben an.
2. **Genehmigen** — `submitRomeTxSolanaLane({ to: wUSDC, data: approve(vault, amount) })`. Ermöglicht es dem Vault, die Tokens abzuziehen. *(Das ist normalerweise der erste Lane-Aufruf, also wird das synthetische Konto hier automatisch bereitgestellt.)*
3. **Einzahlen** — `submitRomeTxSolanaLane({ to: vault, data: deposit(amount) })`. Der Vault führt `transferFrom(synthetic, vault, amount)` aus — die USDC wandern vom synthetischen Konto in den Vault und werden der Adresse des synthetischen Kontos gutgeschrieben.

**Nettoeffekt:** USDC ging **Phantom-Wallet → (synthetisches Konto) → der Vault.**

```javascript
import { syntheticAddress, buildFundLeg, submitSolanaInstructions, submitRomeTxSolanaLane } from "@rome-protocol/sdk";
import { encodeFunctionData, erc20Abi } from "viem";

const synthetic = syntheticAddress(wallet.publicKey);
const deps = { connection, proxyUrl, programId, chainId, payer: wallet.publicKey, signTransaction: wallet.signTransaction };

// 1) Fund-Leg — Wallet-USDC → synthetisches Token-Konto (Phantom signiert)
await submitSolanaInstructions(
  buildFundLeg({ programId, chainId, mint: usdcMint, amount: depositAmount, wallet: wallet.publicKey, synthetic }),
  { connection, feePayer: wallet.publicKey, signTransaction: wallet.signTransaction },
);

// 2) den Vault genehmigen (erster Lane-Aufruf → synthetisches Konto automatisch bereitgestellt)
await submitRomeTxSolanaLane(deps, { to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });

// 3) Einzahlung — der Vault zieht via transferFrom
await submitRomeTxSolanaLane(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

## Wert OUT — Abhebung (Schritt für Schritt)

Nun hebt der Nutzer ab. Ebenfalls von Phantom signiert:

1. **Abheben** — `submitRomeTxSolanaLane({ to: vault, data: withdraw(amount) })`. `vault.withdraw` führt `transfer(synthetic, amount)` aus — USDC wandert vom Vault zurück in das **synthetische** Token-Konto.
2. **Sweep-Leg** — `buildSweepLeg(...)` gibt dir den `HelperProgram.transfer_spl` Aufruf + Konten; führe ihn aus (erstelle bei Bedarf das Token-Konto der Wallet, dann einen `DoTxUnsigned` an das Helper-Precompile), um die USDC vom synthetischen Konto **zurück in die eigene Solana-Wallet des Nutzers**zu verschieben. Das synthetische Konto endet bei null.

**Nettoeffekt:** USDC ging **der Vault → (synthetisches Konto) → die Phantom-Wallet des Nutzers.** Nichts bleibt liegen.

```javascript
import { buildSweepLeg } from "@rome-protocol/sdk";

// 1) Abheben — der Vault gibt USDC an das Token-Konto des synthetischen Kontos zurück
await submitRomeTxSolanaLane(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "withdraw", args: [amount] }) });

// 2) Sweep-Leg — Token-Konto des synthetischen Kontos → Token-Konto der eigenen Wallet
const sweep = buildSweepLeg({ programId, mint: usdcMint, amount, wallet: wallet.publicKey, synthetic });
await submitSolanaInstructions([sweep.ensureWalletAtaIx], { connection, feePayer: wallet.publicKey, signTransaction: wallet.signTransaction });
await submitRomeTxSolanaLane(deps, { to: sweep.helperTo, data: sweep.calldata, extraAccounts: sweep.extraAccounts });
```

## Die Stolpersteine — alle vom SDK gehandhabt

Das sind die Dinge, die eine manuell gebaute Solana-Lane-Transaktion falsch macht; `submitRomeTxSolanaLane` macht sie für dich:

* **Bereitstellung.** Das Konto eines frischen synthetischen Kontos muss erstellt werden (`create_pda`) bevor irgendein wertbewegender Aufruf erfolgt, sonst kann es die Überweisung nicht signieren. Beim ersten Gebrauch automatisch; mit `autoProvision: false` + `provisionSynthetic`.
* **Den Wrapper ausgeben, nicht `msg.value`.** Der Saldo eines Solana-Nutzers ist sein SPL-Token-Konto, dargestellt als der ERC-20-Wrapper — bewege ihn mit `transfer` / `transferFrom`, niemals nativen Wert.
* **ComputeBudget.** Die EVM von Rome braucht ein erhöhtes CU-Limit (\~1,35 Mio.) und einen großen Heap-Frame (\~250 KB). Solanas Standardwerte von 200K CU / 32 KB verursachen Fehler.
* **Treasure-Wallet.** Die Ausführung zahlt an ein chain-spezifisches Treasure-Konto eine kleine Gebühr; die Kontoerkennung lässt es aus, also hängt das SDK es an.
* **Wohin es gesendet wird.** Die Wallet **signiert die Solana-Transaktion und sendet sie an den Solana-RPC** — nicht an den Proxy. Der Proxy wird nur für die Kontoerkennung verwendet. On-Chain leitet das Programm `msg.sender` aus dem Solana-Signer ab.
* **Gas ist USDC.** Kein Faucet — bridge USDC hinein (siehe [Finanzierung](/de/ressourcen/faucets.md)).

## Dieselbe App von MetaMask aus

Ein EVM-Nutzer ruft denselben Vertrag mit `submitRomeTx` auf — standardmäßige EVM-Tools, Gas in USDC. Er muss trotzdem `genehmigen` und dann `einzahlst` (ERC-20 wie üblich), ohne Fund-/Sweep-Legs (seine Tokens liegen bereits an seiner EVM-Adresse). Beide Nutzer teilen denselben `balanceOf` Zustand.

## Was als Nächstes kommt

* [EVM von Solana aus aufrufen](/de/entwicklerleitfaden/call-evm-from-solana.md) — die Solana-Lane-Mechanik im Detail
* [Solana von EVM aus aufrufen](/de/entwicklerleitfaden/call-solana-from-evm.md) — die andere Richtung (CPI)
* [Finanzierung](/de/ressourcen/faucets.md) — USDC als Gas-Token; bridge es hinein


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.rome.builders/de/entwicklerleitfaden/dual-lane-app.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
