> 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/tr/gelistirici-kilavuzlari/dual-lane-app.md).

# Çift hatlı bir uygulama oluşturma

Çift şeritli bir uygulamanın adım adım incelemesi — hem bir MetaMask kullanıcısı hem de bir Solana cüzdanı kullanıcısı tarafından kullanılan tek bir Solidity sözleşmesi ve her adımda tam olarak ne olduğu.

Bir **çift şeritli uygulama** hem bir **MetaMask (EVM)** kullanıcı ve bir **Solana cüzdanı** kullanıcı doğrudan kullanır — aynı sözleşme, aynı durum, her biri zaten sahip oldukları cüzdanla. Bu sayfa üzerinden geçer *tam olarak* her adımda, her iki tarafta ne olduğunu.

Örnek, küçük bir **kasa**: siz `deposit` USDC yatırır ve daha sonra `withdraw` onu. "Stake / unstake", "supply / redeem", "tip / claim" aynı kalıptadır.

## Yazdığınız şey — standart bir ERC-20 kasası

Rome'da, bir Solana kullanıcısının USDC'si EVM tarafında sıradan bir **ERC-20 tokenı** — o mint için SPL sarmalayıcısı (ör. `wUSDC`). Bu yüzden sözleşmeniz normal bir token kasasıdır: tokenları `transferFrom` ile çeker `transfer`ve onları

```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;                 // wUSDC sarmalayıcısı
    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));
    }
}
```

> **Neden ERC-20, neden `ödenebilir` / `msg.value`?** Bir Solana kullanıcısının harcanabilir bakiyesi **cüzdanının SPL token hesabıdır**, bu ERC-20 sarmalayıcısı olarak bire bir görünür — *değil* EVM yerel bakiyesi. Bu yüzden bir Solana kullanıcısı her zaman bir `transferFrom`, fonlayabilir `deposit() payable` ancak rest halinde ellerinde olmayan yerel değeri gerektirir. Çözümü token etrafında kurun; her iki şerit de aynı şekilde çalışır.

Foundry veya Hardhat ile dağıtın; constructor'ı tokenınızın sarmalayıcı adresine işaret edecek şekilde ayarlayın (şuradan [registry](https://github.com/rome-protocol/rome-registry)). Rome'da gaz tokenı USDC'dir, bu yüzden dağıtım için biraz USDC gaz bakiyesine ihtiyacınız var.

## İki şerit

| Şerit      | Cüzdan                                                 | Uygulamanın bunu nasıl çağırdığı                                                     |
| ---------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| **EVM**    | MetaMask (bir EVM anahtarı)                            | `submitRomeTx` — standart Rome yazımı                                                |
| **Solana** | Solflare gibi bir Solana cüzdanı (bir Solana anahtarı) | `submitRomeTxV1` — her çağrı için tek bir Solana v1 işlemi, EVM anahtarına gerek yok |

EVM şeridi sıradandır. Bu sayfanın geri kalanı **Solana hattı** — ilginç yarıdır.

## Ana fikir: sentetik bir aktarım aracıdır

Bir Solana kullanıcısının EVM kimliği onların **sentetik adresidir** — `keccak256(solana_pubkey)[12:]`. Bu onların `msg.sender` sözleşmedeki **ama dinlenme halinde hiçbir şey tutmaz.** Kullanıcının parası onların **Solana cüzdanı** (SPL USDC olarak) yaşar ve EVM tarafında aynı bakiye şudur `wUSDC.balanceOf(synthetic)` okur. Değer akışı *içinden* sentetik üzerinden:

* **İçe** (yatırma): cüzdan token hesabı → sentetik token hesabı → sözleşme ( `transferFrom`).
* **Dışa** (çekme): sözleşme → sentetik token hesabı → cüzdan token hesabı.

Sentetik, her gidiş-dönüşten sonra sıfıra geri döner.

## Tek seferlik: Etkinleştir (sentetiği hazırla)

Yepyeni bir sentetiğin zincir üstü hesabı, onu oluşturana kadar mevcut olmaz. Bir Solana kullanıcısı ilk kez işlem yaptığında, sentetikleri **hazırlanır** bir `create_pda` çağrısıyla — bundan sonra, değeri hareket ettiren çağrılar (ERC-20 `transferFrom`, süpürme) onun tarafından imzalanabilir.

`submitRomeTxV1` bunu sizin yerinize yapmaz, bu yüzden ilk değeri hareket ettiren çağrıdan önce, açıkça ve bir kez hazırlayın. Bunu koymak için iyi bir yer bir "Etkinleştir" adımıdır ya da ilk approve'dan hemen önce:

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

const synthetic = syntheticAddress(wallet.publicKey);
const deps = { connection, proxyUrl, chainId, programId, signer }; // signer: scriptlerde bir Keypair, cüzdan uygulamasında bir LaneSigner
if (!(await isSyntheticProvisioned(connection, programId, synthetic))) {
  await submitRomeTxV1(deps, buildCreatePdaCall(synthetic)); // tek create_pda
}
```

## Tek seferlik: sarmalayıcıya yetki verin

wUSDC sarmalayıcısı, kullanıcının tokenlarını **SPL delegesi** olarak taşır, bu yüzden her kullanıcının ilk yatırmadan önce, bu yetkiyi her sarmalayıcı için bir kez vermesi gerekir. Bu, bir MetaMask kullanıcısı ve bir Solana kullanıcısının sentetik adresi için de geçerlidir. İzin olmadan, cüzdandan çıkan her `transferFrom` başarısız olur ve yatırma SPL hatasıyla geri alınır `0x4` (sahip uyuşmazlığı).

Sarmalayıcının görünüm `isEnabled(user)`. Eğer `false`dönerse, `approve_spl(wrapper, 2^64-1, mint)` doğrudan şuradaki Helper ön derlemesine gönderin `0xff00000000000000000000000000000000000009`; mint `mint_id()` sarmalayıcı üzerindedir. EVM şeridinde bu bir `submitRomeTx` çağrıdır, Solana şeridinde ise bir `submitRomeTxV1` çağrı:

```javascript
import { submitRomeTx, submitRomeTxV1, syntheticAddress } from "@rome-protocol/sdk";
import { createPublicClient, encodeFunctionData, http, parseAbi } from "viem";

const HELPER = "0xff00000000000000000000000000000000000009";
const wrapperAbi = parseAbi(["function isEnabled(address user) view returns (bool)", "function mint_id() view returns (bytes32)"]);
const grantAbi = parseAbi(["function approve_spl(address spender, uint64 amount, bytes32 mint)"]);

const publicClient = createPublicClient({ transport: http(rpcUrl) }); // zincirin RPC'sinde bir viem istemcisi

// kim: MetaMask adresi veya bir Solana kullanıcısı için syntheticAddress(wallet.publicKey)
let grant = null; // isEnabled doğruysa null kalır
if (!(await publicClient.readContract({ address: wUSDC, abi: wrapperAbi, functionName: "isEnabled", args: [who] }))) {
  const mint = await publicClient.readContract({ address: wUSDC, abi: wrapperAbi, functionName: "mint_id" });
  grant = { to: HELPER, data: encodeFunctionData({ abi: grantAbi, functionName: "approve_spl", args: [wUSDC, 2n ** 64n - 1n, mint] }) };
}
// EVM şeridi:    if (grant) await submitRomeTx(provider, { from: who, ...grant });
// Solana şeridi: if (grant) await submitRomeTxV1(deps, grant);
```

## Her tarafın ihtiyaçları

|                                | Gereken                                                        | Neden                                                                                                                                 |
| ------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Solana kullanıcısı**         | **SOL** (biraz)                                                | her şerit işleminde Solana işlem ücretini öder                                                                                        |
|                                | **SPL token olarak USDC** cüzdanlarında                        | yatırdıkları değer (EVM tarafında şu olarak görülür `wUSDC`)                                                                          |
| **EVM kullanıcısı (MetaMask)** | **USDC** Rome gaz bakiyesi olarak                              | gaz ve sarılmış hale geldikten sonra yatırdıkları değer; bunu **USDC köprüleyerek** Rome'a taşırlar (faucet yoktur)                   |
|                                | bir **sar** bu gazın bir kısmını **wUSDC** (`withdraw_to_ata`) | kasa wUSDC alır, yerel gazı değil. Bir MetaMask gaz USDC bakiyesi sarılana kadar wUSDC değildir                                       |
|                                | tek seferlik **sarmalayıcı izni** (`approve_spl`)              | wUSDC sarmalayıcısının tokenlarını taşımasına izin verir; bkz. [Tek seferlik: sarmalayıcıya yetki verin](#one-time-grant-the-wrapper) |
| **Siz (oluşturucu)**           | bir cüzdanla **USDC** Rome'da gaz                              | sözleşmeyi dağıtmak için                                                                                                              |

## Değer İÇERİ — yatırma (adım adım)

Solana kullanıcısının Solana cüzdanında SOL + USDC vardır. Her adım bir Solana v1 işlemidir, **o cüzdan tarafından imzalanır**, ve uygulama onu Solana RPC'sine gönderir. Fon ayağı uygulamada oluşturulur ve cüzdan tek bir istemle imzalar; her sözleşme çağrısı proxy tarafından oluşturulur ve cüzdan onu okunabilir bir yetkilendirme mesajından sonra imzalar:

1. **Fon ayağı** — `buildFundLeg(...)` → `submitSolanaInstructionsV1(...)`. Gerekirse sentetiğin USDC token hesabını oluşturur ve **`ActivateAta`**&#xE7;alıştırır, `miktar` USDC'nin **cüzdanın token hesabından**. Artık `wUSDC.balanceOf(synthetic)` sentetiğin bakiyesini gösterir.
2. **Sarmalayıcıya izin ver** (ne zaman `isEnabled` yanlışsa) — `submitRomeTxV1(deps, { to: HELPER, data: approve_spl(wUSDC, 2^64-1, mint) })`. Bkz. [Tek seferlik: sarmalayıcıya yetki verin](#one-time-grant-the-wrapper). *(Bu kullanıcının ilk şerit çağrısıysa bundan önce sentetiği hazırlayın.)*
3. **Onayla** — `submitRomeTxV1(deps, { to: wUSDC, data: approve(vault, amount) })`. Kasanın tokenları çekmesine izin verir.
4. **Yatırma** — `submitRomeTxV1(deps, { to: vault, data: deposit(amount) })`. Kasa çalıştırır `transferFrom(synthetic, vault, amount)` — USDC, sentetikten kasaya hareket eder ve sentetiğin adresine alacak kaydedilir.

**Net etki:** USDC hareket etti **Solana cüzdanı → (sentetik) → kasa.**

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

const synthetic = syntheticAddress(wallet.publicKey);
const deps = { connection, proxyUrl, chainId, programId, signer }; // signer: bir LaneSigner (payer, signAuthHash, signV1Wire) ya da bir Keypair

// 1) fon ayağı — cüzdan USDC'si → sentetik token hesabı (cüzdan imzalar)
await submitSolanaInstructionsV1(
  buildFundLeg({ programId, chainId, mint: usdcMint, amount: depositAmount, wallet: wallet.publicKey, synthetic }),
  { connection, signer },
);

// 2) sarmalayıcıya bir kez izin verin (sentetik zaten hazırlandı); `grant`, "Tek seferlik: sarmalayıcıya izin ver" altında oluşturulan çağrıdır ya da isEnabled doğruysa null'dır
if (grant) await submitRomeTxV1(deps, grant);

// 3) kasayı onayla
await submitRomeTxV1(deps, { to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });

// 4) yatırma — kasa transferFrom ile çeker
await submitRomeTxV1(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

## Değer DIŞARI — çekme (adım adım)

Şimdi kullanıcı çekim yapar. Yine cüzdan imzalı, çağrı başına iki istem:

1. **Çekim** — `submitRomeTxV1(deps, { to: vault, data: withdraw(amount) })`. `vault.withdraw` çalıştırır `transfer(synthetic, amount)` — USDC kasadan geri **sentetiğin** token hesabına hareket eder.
2. **Süpürme ayağı** — `buildSweepLeg(...)` size `HelperProgram.transfer_spl` çağrı + hesapları verir; çalıştırın (gerekirse cüzdanın token hesabını `submitSolanaInstructionsV1`ile oluşturun, ardından Helper çağrısını `submitRomeTxV1`üzerinden gönderin; proxy token hesaplarını bulur) ve USDC'yi sentetikten **kullanıcının kendi Solana cüzdanına geri**taşır. Sentetik sıfıra döner.

**Net etki:** USDC hareket etti **kasa → (sentetik) → kullanıcının Solana cüzdanı.** Hiçbir şey sıkışıp kalmaz.

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

// 1) çekme — kasa USDC'yi sentetiğin token hesabına geri verir
await submitRomeTxV1(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "withdraw", args: [amount] }) });

// 2) süpürme ayağı — sentetik token hesabı → kullanıcının kendi cüzdan token hesabı
const sweep = buildSweepLeg({ programId, mint: usdcMint, amount, wallet: wallet.publicKey, synthetic });
await submitSolanaInstructionsV1([sweep.ensureWalletAtaIx], { connection, signer });
await submitRomeTxV1(deps, { to: sweep.helperTo, data: sweep.calldata });
```

## Hadrian'da ölçüldü

Aşağıdaki her Solana şeridi adımı Hadrian'da tek bir v1 işlemi olarak çalıştı:

| Adım                        | Hesaplama birimleri | Bayt  | Hesaplar |
| --------------------------- | ------------------- | ----- | -------- |
| Hazırlama (`create_pda`)    | 39,008              | 703   | 9        |
| Sarıcı izni (`approve_spl`) | 50,687              | 899   | 13       |
| Onayla                      | 79,335              | 769   | 10       |
| Kasa yatırma                | 242,880             | 1,070 | 20       |
| Kasa çekme                  | 280,903             | 1,037 | 19       |
| Süpürme (`transfer_spl`)    | 46,810              | 899   | 13       |

## Püf noktaları

Bunlar, elle oluşturulmuş bir Solana şeridi işleminin yanlış yaptığı şeylerdir. SDK ve proxy'nin oluşturucusu bunların çoğunu halleder:

* **Hazırlama.** Yeni bir sentetiğin hesabı, herhangi bir değeri hareket ettiren çağrıdan önce oluşturulmalıdır (`create_pda`) yoksa transferi imzalayamaz. `submitRomeTxV1` bunu sizin yerinize yapmaz: kontrol edin `isSyntheticProvisioned` ve gönderin `buildCreatePdaCall(synthetic)` bir kez.
* **Sarmalayıcıya bir kez izin verin.** Sarmalayıcı, tokenları kullanıcının SPL delege'si olarak taşır. Kontrol edin `isEnabled` ve gönderin `approve_spl` ilk yatırmadan önce Helper ön derlemesine, yoksa yatırma SPL hatasıyla geri alınır `0x4`.
* **Sarmalayıcıyı harca, `msg.value`.** Bir Solana kullanıcısının bakiyesi onların SPL token hesabıdır, ERC-20 sarmalayıcısı olarak görünür — onu `transfer` / `transferFrom`ile taşıyın, asla yerel değerle değil.
* **Sizin için oluşturuldu.** ComputeBudget, hazine cüzdanı ve hesap keşfi proxy'nin oluşturucusu tarafından yapılır, bu yüzden hiçbiri kodunuzda görünmez. Cüzdan imzalamadan önce, SDK oluşturulan işlemi kontrol eder ve kullanıcının yetkilendirdiği çağrı dışında her şeyi reddeder.
* **Nereye gönderildiği.** Cüzdan v1 işlemini imzalar ve **uygulama bunu Solana RPC'sine gönderir** — bunu oluşturan proxy'ye değil (aşağıda, aktarılan çağrı tek istisnadır; bkz. [Yönlendirilen çağrılar ve uzun çağrılar](/tr/gelistirici-kilavuzlari/call-evm-from-solana.md#relayed-calls-and-long-calls)). Zincir üstünde program `msg.sender` Solana imzalayıcısından türetilir.
* **Çağrı başına iki istem.** Cüzdan okunabilir bir yetkilendirme mesajını (`Rome yetkilendirmesi` ve EVM işlem karmasını), ardından işlemin kendisini, ikisini de aynı anahtarla imzalar.
* **Cüzdanınızın v1 imzaladığını kontrol edin.** `signV1Wire` v1 mesajı üzerinde 64 baytlık imzayı döndürmelidir; Wallet Standard üzerinden bu `solana:signTransaction` ham wire üzerindedir ve `signedTransaction.slice(-64)`. Her cüzdan henüz v1 işlemlerini çözmüyor, bu yüzden üzerine inşa etmeden önce sizinkini test edin. Bugün devnet'te Solflare bunları imzalıyor; Phantom henüz imzalamıyor.
* **Büyük çağrılar aktarılarak gönderilir.** Tek bir işleme sığmayan bir çağrı, aynı yetkilendirme imzasıyla proxy tarafından aktarılır ve sonuç `route: "relay"`vardır. Aktarılan bir çağrı zincirin yerel gazı ile ödenir, bu yüzden sentetiği `fundSyntheticGas(deps, amountWei)`. Bkz. [Yönlendirilen çağrılar ve uzun çağrılar](/tr/gelistirici-kilavuzlari/call-evm-from-solana.md#relayed-calls-and-long-calls).
* **Gaz USDC'dir.** Faucet yok — USDC'yi köprüleyerek içeri alın (bkz. [Fonlanmak](/tr/kaynaklar/faucets.md)).

Daha önceki `submitRomeTxSolanaLane` hala SDK'da var: istemci tarafında eski bir Solana işlemi oluşturur (tek cüzdan istemi, ComputeBudget ve hazine cüzdanı SDK tarafından ele alınır).

## MetaMask'teki aynı uygulama

Bir EVM kullanıcısı aynı sözleşmeyi `submitRomeTx` ile çağırır — standart EVM araçları, gaz USDC'de. Solana kullanıcısından bir fark: bir MetaMask kullanıcısı **yerel gaz USDC'si**, wUSDC değil, tutar. Kasa wUSDC sarmalayıcısını alır, bu yüzden kullanıcı önce gazının bir kısmını sarar, sonra sarmalayıcıya bir kez izin verir, sonra ERC-20 adımlarını her zamanki gibi yapar. Fund veya sweep ayağı yoktur, çünkü sarılmış tokenlar zaten EVM adreslerinde yaşar.

1. **Gazı wUSDC'ye sarın** — gönderin `withdraw_to_ata(wei)` şuradaki Withdraw ön derlemesine `0x4200000000000000000000000000000000000016`. Bu kadar native gazı yakar ve eşdeğer miktarı kullanıcının gaz tokenındaki token hesabına krediler, ki `wUSDC.balanceOf(user)` sonra gösterir. `wei` 18 ondalıklı gaz birimindedir ve 10^12'nin (bir 6 ondalıklı wUSDC birimi) katı olmalıdır; aksi halde çağrı geri alınır. Kullanıcı zaten wUSDC tutuyorsa bu adımı atlayın.
2. **Sarmalayıcıya izin ver** (ne zaman `isEnabled` yanlışsa) — aynı `approve_spl` çağrı yukarıdaki gibi, ile gönderildi `submitRomeTx`. Bkz. [Tek seferlik: sarmalayıcıya yetki verin](#one-time-grant-the-wrapper).
3. **Onayla** — `approve(vault, amount)` wUSDC üzerinde.
4. **Yatırma** — `deposit(amount)` kasada. Çekme şudur `withdraw(amount)`.

```javascript
import { submitRomeTx } from "@rome-protocol/sdk";
import { encodeFunctionData, erc20Abi, parseAbi } from "viem";

const WITHDRAW = "0x4200000000000000000000000000000000000016";
const withdrawAbi = parseAbi(["function withdraw_to_ata(uint256 wei_)"]);

// 1) bazı gaz USDC'yi wUSDC'ye sarın (weiAmount 18 ondalıklı gaz birimlerindedir)
await submitRomeTx(provider, { from: who, to: WITHDRAW, data: encodeFunctionData({ abi: withdrawAbi, functionName: "withdraw_to_ata", args: [weiAmount] }) });

// 2) sarmalayıcıya bir kez izin verin: `grant`, "Tek seferlik: sarmalayıcıya izin ver" altında oluşturulan çağrıdır ya da null
if (grant) await submitRomeTx(provider, { from: who, ...grant });

// 3) onayla ve 4) yatır, herhangi bir ERC-20 için olduğu gibi
await submitRomeTx(provider, { from: who, to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });
await submitRomeTx(provider, { from: who, to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

Her iki kullanıcı da aynı `balanceOf` durumunu kasada paylaşır.

## Sırada ne var

* [Solana'dan EVM'yi çağırma](/tr/gelistirici-kilavuzlari/call-evm-from-solana.md) — Solana şeridi mekaniği ayrıntılı olarak
* [EVM'den Solana'yı çağırma](/tr/gelistirici-kilavuzlari/call-solana-from-evm.md) — diğer yön (CPI)
* [Fonlanmak](/tr/kaynaklar/faucets.md) — gaz tokenı olarak USDC; içeri köprüleyin


---

# 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/tr/gelistirici-kilavuzlari/dual-lane-app.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.
