> 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/id/panduan-pengembang/dual-lane-app.md).

# Bangun aplikasi jalur ganda

A **aplikasi dua-lajur** adalah satu kontrak Solidity yang baik seorang **MetaMask (EVM)** pengguna dan seorang **Phantom (Solana)** menggunakan langsung — kontrak yang sama, state yang sama, masing-masing dengan dompet yang sudah mereka miliki. Halaman ini menjelaskan *secara tepat* apa yang terjadi pada setiap langkah, di setiap sisi.

Contohnya adalah sebuah **vault**: Anda `setor` USDC dan nanti `tarik` kembali. "Stake / unstake", "supply / redeem", "tip / claim" memiliki pola yang sama.

## Yang Anda tulis — sebuah vault ERC-20 standar

Di Rome, USDC milik pengguna Solana muncul di sisi EVM sebagai sebuah **token ERC-20** — wrapper SPL untuk mint tersebut (mis. `wUSDC`). Jadi kontrak Anda adalah vault token biasa: ia menarik token dengan `transferFrom` dan mengembalikannya dengan `transfer`. Tidak ada yang khas Rome:

```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;                 // pembungkus wUSDC
    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));
    }
}
```

> **Mengapa ERC-20, bukan `payable` / `msg.value`?** Saldo yang dapat dibelanjakan oleh pengguna Solana adalah **akun token SPL milik dompet mereka**, ditampilkan 1:1 sebagai pembungkus ERC-20 ini — *bukan* saldo native EVM. Jadi pengguna Solana selalu bisa mendanai sebuah `transferFrom`, tetapi sebuah `deposit() payable` akan membutuhkan nilai native yang tidak mereka simpan saat tidak digunakan. Bangun di sekitar token, dan kedua lajur bekerja dengan cara yang sama.

Deploy dengan Foundry atau Hardhat, arahkan konstruktor ke alamat pembungkus untuk token Anda (dari [register](https://github.com/rome-protocol/rome-registry)). Di Rome, token gas adalah USDC, jadi Anda memerlukan sedikit saldo gas USDC untuk deploy.

## Dua lajur

| Lajur      | Dompet                 | Cara aplikasi memanggilnya                            |
| ---------- | ---------------------- | ----------------------------------------------------- |
| **EVM**    | MetaMask (kunci EVM)   | `submitRomeTx` — penulisan standar Rome               |
| **Solana** | Phantom (kunci Solana) | `submitRomeTxSolanaLane` — tidak memerlukan kunci EVM |

Lajur EVM bersifat biasa. Sisa halaman ini adalah **lajur Solana** — bagian yang menarik.

## Gagasan utamanya: synthetic adalah penerus lewat

Identitas EVM pengguna Solana adalah **alamat synthetic** — `keccak256(solana_pubkey)[12:]`. Itu adalah `msg.sender` di kontrak, tetapi **ia tidak menyimpan apa pun saat tidak digunakan.** Uang pengguna tersimpan di **dompet Solana** (sebagai SPL USDC), dan di sisi EVM saldo yang sama itulah yang `wUSDC.balanceOf(synthetic)` dibaca. Nilai mengalir *melalui* synthetic:

* **Masuk** (setor): akun token dompet → akun token synthetic → kontrak (melalui `transferFrom`).
* **Keluar** (tarik): kontrak → akun token synthetic → akun token dompet.

Synthetic kembali netral menjadi nol setelah setiap perjalanan bolak-balik.

## Sekali saja: Aktifkan (siapkan synthetic)

Akun on-chain synthetic yang baru tidak ada sampai Anda membuatnya. Saat pertama kali pengguna Solana bertindak, synthetic mereka **disediakan** dengan sebuah `create_pda` panggilan — setelah itu, panggilan yang memindahkan nilai (ERC-20 `transferFrom`, sweep) dapat ditandatangani olehnya.

`submitRomeTxSolanaLane` melakukan ini **secara otomatis saat pertama kali digunakan** (`autoProvision` aktif secara default). Jika Anda lebih suka menampilkan layar "Aktifkan" yang eksplisit (penyiapan akun sekali pakai), lakukan sendiri:

```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); // satu create_pda; lalu kirim penulisan dengan autoProvision: false
}
```

## Apa yang dibutuhkan masing-masing sisi

|                               | Kebutuhan                                   | Mengapa                                                                                 |
| ----------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------- |
| **Pengguna Solana (Phantom)** | **SOL** (sedikit)                           | membayar biaya transaksi Solana pada setiap tx lajur                                    |
|                               | **USDC sebagai token SPL** di dompet mereka | nilai yang mereka setorkan (terlihat di sisi EVM sebagai `wUSDC`)                       |
| **Pengguna EVM (MetaMask)**   | **USDC** sebagai saldo gas Rome mereka      | gas + nilai; mereka menambahnya dengan **menjembatani USDC** ke Rome (tidak ada faucet) |
| **Anda (pembangunnya)**       | dompet dengan **USDC** gas di Rome          | untuk men-deploy kontrak                                                                |

## Nilai MASUK — setor (langkah demi langkah)

Pengguna Solana memiliki SOL + USDC di dompet Phantom mereka. Setiap transaksi lajur adalah **ditandatangani Phantom** — dompet menandatanganinya dan mengirimkannya ke RPC Solana (proxy hanya digunakan untuk menemukan akun):

1. **Bagian pendanaan** — `buildFundLeg(...)` → `submitSolanaInstructions(...)`. Membuat akun token USDC synthetic (jika perlu) dan menjalankan **`ActivateAta`**, memindahkan `amount` USDC dari akun token dompet **ke milik synthetic**. Sekarang `wUSDC.balanceOf(synthetic)` menampilkan saldo tersebut.
2. **Setujui** — `submitRomeTxSolanaLane({ to: wUSDC, data: approve(vault, amount) })`. Memungkinkan vault menarik token. *(Ini biasanya panggilan lajur pertama, jadi synthetic diprovisioning otomatis di sini.)*
3. **Setor** — `submitRomeTxSolanaLane({ to: vault, data: deposit(amount) })`. Vault menjalankan `transferFrom(synthetic, vault, amount)` — USDC berpindah dari synthetic ke vault, dan dikreditkan ke alamat synthetic.

**Efek bersih:** USDC berpindah **dompet Phantom → (synthetic) → 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) bagian pendanaan — USDC dompet → akun token synthetic (Phantom menandatangani)
await submitSolanaInstructions(
  buildFundLeg({ programId, chainId, mint: usdcMint, amount: depositAmount, wallet: wallet.publicKey, synthetic }),
  { connection, feePayer: wallet.publicKey, signTransaction: wallet.signTransaction },
);

// 2) setujui vault (panggilan lajur pertama → synthetic diprovisioning otomatis)
await submitRomeTxSolanaLane(deps, { to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });

// 3) setor — vault menarik melalui transferFrom
await submitRomeTxSolanaLane(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

## Nilai KELUAR — tarik (langkah demi langkah)

Sekarang pengguna menarik. Juga ditandatangani Phantom:

1. **Tarik** — `submitRomeTxSolanaLane({ to: vault, data: withdraw(amount) })`. `vault.withdraw` menjalankan `transfer(synthetic, amount)` — USDC bergerak dari vault kembali ke **milik synthetic** akun token.
2. **Bagian penyapuan** — `buildSweepLeg(...)` memberi Anda `HelperProgram.transfer_spl` panggilan + akun; jalankan (buat akun token dompet jika perlu, lalu `DoTxUnsigned` ke precompile Helper) untuk memindahkan USDC dari synthetic **kembali ke dompet Solana milik pengguna**. Synthetic menjadi nol.

**Efek bersih:** USDC berpindah **vault → (synthetic) → dompet Phantom milik pengguna.** Tidak ada yang tertinggal.

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

// 1) tarik — vault mengembalikan USDC ke akun token synthetic
await submitRomeTxSolanaLane(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "withdraw", args: [amount] }) });

// 2) bagian penyapuan — akun token synthetic → akun token dompet milik pengguna
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 });
```

## Hal-hal yang sering keliru — semuanya ditangani oleh SDK

Inilah hal-hal yang sering salah pada transaksi lajur Solana yang dibuat manual; `submitRomeTxSolanaLane` melakukannya untuk Anda:

* **Penyediaan.** Akun synthetic baru harus dibuat (`create_pda`) sebelum panggilan yang memindahkan nilai apa pun, atau ia tidak bisa menandatangani transfer. Otomatis saat pertama digunakan; nonaktifkan dengan `autoProvision: false` + `provisionSynthetic`.
* **Gunakan pembungkusnya, bukan `msg.value`.** Saldo pengguna Solana adalah akun token SPL mereka, yang ditampilkan sebagai pembungkus ERC-20 — pindahkan dengan `transfer` / `transferFrom`, jangan pernah nilai native.
* **ComputeBudget.** EVM Rome memerlukan batas CU yang dinaikkan (\~1,35 juta) dan heap frame besar (\~250 KB). Default Solana 200K-CU / 32-KB akan gagal.
* **Dompet treasury.** Eksekusi membayar biaya kecil ke akun treasury per-chain; penemuan akun tidak menyertakannya, jadi SDK menambahkannya.
* **Ke mana dikirim.** Dompet **menandatangani transaksi Solana dan mengirimkannya ke RPC Solana** — bukan ke proxy. Proxy hanya digunakan untuk penemuan akun. Di on-chain, program menurunkan `msg.sender` dari penanda tangan Solana.
* **Gas adalah USDC.** Tidak ada faucet — jembatani USDC masuk (lihat [Cara mendapatkan dana](/id/sumber-daya/faucets.md)).

## Aplikasi yang sama dari MetaMask

Pengguna EVM memanggil kontrak yang identik dengan `submitRomeTx` — tooling EVM standar, gas dalam USDC. Mereka tetap `menyetujui` lalu `setor` (ERC-20 seperti biasa), tanpa bagian pendanaan/penyapuan (token mereka sudah berada di alamat EVM mereka). Kedua pengguna berbagi `balanceOf` state yang sama.

## Apa berikutnya

* [Memanggil EVM dari Solana](/id/panduan-pengembang/call-evm-from-solana.md) — mekanisme lajur Solana secara detail
* [Memanggil Solana dari EVM](/id/panduan-pengembang/call-solana-from-evm.md) — arah sebaliknya (CPI)
* [Cara mendapatkan dana](/id/sumber-daya/faucets.md) — USDC sebagai token gas; jembatani masuk


---

# 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/id/panduan-pengembang/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.
