> 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/integrate-solana-program.md).

# Integrasikan program Solana

Integrasikan program Solana dengan Rome EVM — siapa penandatangan Solana, bagaimana nilai mencapai program Anda dan kembali lagi, aturan lintasan tunggal, atomik vs iteratif, dan penyediaan akun.

Panduan ini ditujukan untuk tim program Solana yang ingin agar pengguna Rome EVM memakai program mereka — program privasi, peminjaman, atau vault yang seharusnya menyimpan nilai untuk akun EVM. Panduan ini dibangun di atas [Call Solana from EVM](/id/panduan-pengembang/call-solana-from-evm.md) (mekanika precompile) dan menjawab pertanyaan yang muncul setelah program nyata berada di sisi lain: *siapa* yang menandatangani, *bagaimana* token masuk dan keluar, dan *batasan* yang membentuk desain.

Alur yang digunakan di seluruh panduan ini: sebuah akun EVM memindahkan USDC ke program Anda ("shield" atau "deposit"), program Anda menyimpannya untuk pengguna tersebut, lalu nanti melepaskannya kembali ("unshield" atau "withdraw"). Ganti kata kerjanya dengan milik Anda sendiri.

## 1. Setiap alamat EVM memiliki satu identitas Solana

Rome menurunkan sebuah akun Solana untuk setiap alamat EVM — baik pengguna maupun kontrak — yang disebut **otoritas eksternal**:

```
external_auth(addr) = find_program_address(["EXTERNAL_AUTHORITY", addr_h160], rome_evm_program_id)
```

Akun ini memiliki akun token terkait milik alamat tersebut (di sanalah `wUSDC.balanceOf(addr)` membaca) dan akun ini adalah kunci yang menandatangani CPI yang dibuat atas nama alamat tersebut. Program Anda melihat pubkey Solana 32 byte; program itu tidak pernah melihat H160.

**Siapa yang menandatangani ditentukan oleh siapa yang memanggil precompile:**

| Precompile dipanggil oleh                                                       | Penandatangan Solana                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Sebuah **kontrak**, dengan `CALL`                                               | Panduan **sendiri milik kontrak** otoritas eksternal (`external_auth(contract)`), ditambah sub-PDA yang diberi salt milik kontrak yang dimintanya                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Sebuah **pengguna**, dalam transaksi yang dikirim langsung ke alamat precompile | Panduan **milik pengguna sendiri** otoritas eksternal                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Kontrak melalui `DELEGATECALL`                                                  | Otoritas mentah ditolak (`DelegatecallOwnerAuthority`). `invoke` tanpa penandatangan, dan `invoke_signed` dengan sub-PDA yang diberi salt, masih berjalan — tetapi sub-PDA tersebut diberi namespace oleh alamat kontrak yang sedang berjalan (`["EXTERNAL_AUTHORITY", caller, running_contract, salt]`), jadi `pda_with_salt` tidak menurunkannya. Dari HelperProgram hanya selektor pembuatan dan pendanaan yang lolos (`create_ata*`, `create_pda*`, `swap_gas_to_lamports`, `init_spl_mint`, `create_and_init_mint`); mereka dikecualikan karena targetnya terikat pada pemanggil atau didanai oleh operator, bukan karena mereka tidak menandatangani apa pun |

Konsekuensinya adalah aturan ERC-20 yang sudah Anda kenal: **sebuah kontrak tidak dapat memindahkan aset pengguna sendiri.** Kontrak bertindak sebagai identitasnya sendiri, dan pengguna terlebih dahulu memberinya hak. Untuk token yang harus bergerak di sisi Solana, pemberian hak itu adalah delegasi SPL pada akun token pengguna; `approve` wrapper hanyalah allowance di sisi EVM yang dihormati oleh transfer milik wrapper sendiri. Itulah cara kerja wrapper token milik Rome, dan itulah cara kerja adapter Anda (bagian 3).

Sub-PDA yang diberi salt: `invoke_signed` mengambil daftar `bytes32` salt, dan Rome menandatangani dengan `["EXTERNAL_AUTHORITY", caller_h160, salt]` untuk setiap salt (bump ditambahkan; pemanggilnya adalah kontrak pada panggilan langsung). `HelperProgram.pda_with_salt(addr, salt)` mengembalikan alamat dari Solidity. Gunakan satu salt per pengguna (misalnya alamat pengguna) ketika program Anda menginginkan akun pemilik yang berbeda untuk setiap posisi.

Pendanaan: sebuah PDA otoritas eksternal tidak harus ada untuk memiliki ATA (`create_ata` bersifat idempoten dan pembayar biaya Solana pada transaksi menanggung sewa). PDA itu harus ada dan menyimpan lamport hanya ketika PDA itu mendanai akun yang dibuat instruksi Anda. `HelperProgram.create_pda(addr)` membuatnya sebagai System `CreateAccount`, jadi panggil sekali saja; ini tidak idempoten. `swap_gas_to_lamports(n)` mengkreditkan PDA alamat tersebut dengan `n` lamport dari pembayar biaya Solana pada transaksi, dan pemanggil membayarnya dalam gas Rome: setiap efek CPI track legacy pada saldo pembayar biaya ditambahkan ke biaya transaksi dan dibebankan kepada pengguna pada harga gas, sama seperti sewa untuk akun yang dibuat oleh CPI dibebankan. Langkah provisioning pada jalur Solana dijelaskan dalam [Call EVM from Solana](/id/panduan-pengembang/call-evm-from-solana.md); sebuah kontrak memprovision PDA miliknya sendiri di konstruktor atau panggilan pertamanya.

## 2. Tiga cara untuk menyimpan posisi pengguna

**A. Sebuah kontrak adapter (disarankan untuk adapter yang dapat digunakan ulang).** Satu kontrak Solidity menjadi front-end untuk program Anda. Pengguna memberi otoritas adapter sebuah delegasi SPL pada akun token mereka; adapter menarik token ke akun token yang dimilikinya, memanggil program Anda sebagai dirinya sendiri, dan memberi kunci posisi pengguna di dalam program Anda dengan sub-PDA per pengguna atau dengan pengidentifikasi yang ia kirim dalam data instruksi. Saat keluar, adapter memberi tahu program Anda untuk melepaskan ke akun token milik pengguna sendiri. Adapter adalah satu-satunya identitas Solana yang perlu diketahui program Anda. Pemberian hak itu adalah delegasi SPL, bukan `approve`; bagian 3 menjelaskan mengapa dan apa biayanya bagi pengguna.

**B. Pengguna sebagai penandatangan Solana.** Front end mengirim transaksi dari akun EVM pengguna **langsung ke precompile CpiProgram** (`to = 0xff…08`, calldata = ABI-encoded `invoke` atau `invoke_signed`), sehingga otoritas eksternal milik pengguna sendiri menandatangani dan program Anda memberi kunci posisi dengannya. Tidak ada kontrak adapter dan tidak ada delegasi, tetapi setiap aksi adalah transaksi calldata yang buram di dompet, dan apa pun yang dibutuhkan alur selain CPI (ATA, transfer sebelumnya) adalah transaksi terpisah. Ini cocok untuk pemberian satu kali ( `approve_spl` pengguna ke wrapper) dan untuk program sederhana, bukan untuk alur multi-langkah. [Cardo](https://github.com/rome-protocol/cardo) menggunakan bentuk ini: front end-nya mengirim `invoke` langsung ke precompile dari dompet pengguna.

**C. Sebuah router tanpa kustodi.** Kontrak yang tidak pernah memegang token pengguna. Pengguna memberi otoritas router sebuah delegasi SPL pada akun token miliknya sendiri; router melakukan CPI ke program Anda dengan otoritas itu sebagai transfer authority, sehingga token berpindah dari akun token pengguna ke akun milik program Anda (atau kembali ke milik pengguna), dan router tidak menyimpan apa pun di antara panggilan. Router menurunkan setiap akun sisi pengguna dari `msg.sender`, jadi pemberian hak milik satu pengguna tidak dapat diarahkan ke akun pengguna lain, dan ia memanggil satu program tetap dengan bentuk instruksi tetap, alih-alih bertindak sebagai `invoke` passthrough `RomeDexRouter` dari [rome-dex](https://github.com/rome-protocol/rome-dex) ditulis dengan cara ini; jalur EVM dari rome-dex juga menawarkan direct `invoke` mentah untuk bentuk B. Ia membutuhkan pemberian hak yang sama seperti A, dengan biaya satu-delegasi yang sama (bagian 3), dan tanpa langkah pull.

Sisa panduan ini mengikuti A; C menggunakan pemberian hak yang sama dan aturan penandatangan yang sama.

## 3. Token masuk, token keluar

USDC di Rome adalah satu mint SPL yang terlihat dari dua sisi: `wUSDC` wrapper ERC-20 di sisi EVM dan ATA milik otoritas eksternal di sisi Solana. Untuk alamat milik pengguna sendiri, keduanya adalah saldo yang sama: `wUSDC.balanceOf(user)` membaca ATA milik pengguna. Tidak ada jembatan atau langkah wrapping di tengah.

**Saldo wrapper milik kontrak bukan akun token yang dikendalikan kontrak.** Factory menerapkan `SPL_ERC20_cached` untuk mint biasa, dan Hadrian's `wUSDC` adalah salah satunya. Wrapper itu tidak dapat menandatangani untuk kontrak, jadi ketika `wUSDC.transferFrom(user, adapter, amount)` atau `transfer` mengirim token ke sebuah kontrak, token SPL berpindah ke **akun token escrow milik wrapper sendiri** (`escrow_ata()`), dan wrapper mencatat jumlah per kontrak di storage-nya. `wUSDC.balanceOf(adapter)` mengembalikan catatan itu, sebuah buku besar escrow, bukan saldo akun token yang dimiliki oleh `external_auth(adapter)`. Adapter Anda tidak dapat menyerahkan token itu ke program Anda dalam sebuah CPI; token itu hanya dapat keluar lagi melalui wrapper. Jadi adapter yang mendanai dirinya sendiri dengan `transferFrom` tidak memiliki apa pun yang dapat dibelanjakan di sisi Solana.

Jalur yang berhasil adalah delegasi SPL:

* **Pemberian hak.** Pengguna mengirim `approve_spl(adapter, amount, mint)` sebagai transaksi **langsung ke HelperProgram** (`to = 0xff…09`), di mana `mint` adalah `mint_id()`milik wrapper. Otoritas pengguna sendiri menandatangani, dan delegate yang diset pada akun token pengguna adalah otoritas eksternal adapter.
* **Masuk.** Fungsi `deposit(amount)` mendorong dengan overload delegate `HelperProgram.transfer_spl(srcAta, dstAta, amount, mint)`, dengan ATA milik pengguna sebagai source dan ATA milik adapter sendiri (`HelperProgram.ata(address(this), mint)`, dibuat sekali dengan `create_ata`) sebagai destination. Otoritas adapter menandatangani sebagai delegate. Token sekarang berada dalam akun token yang dimiliki oleh `external_auth(adapter)`, yang merupakan akun yang dinamai CPI Anda; penandatangan dan source adalah milik adapter sendiri, jadi tidak diperlukan izin tambahan. Token itu tidak muncul di `wUSDC.balanceOf(adapter)`.
* **Keluar.** Instruksi withdraw program Anda mentransfer ke **ATA milik pengguna** , `HelperProgram.ata(user, mint)`. Penerimaan tidak memerlukan tanda tangan dari pengguna, dan saldo langsung terlihat sebagai `wUSDC.balanceOf(user)`. Jika ATA belum ada, buat dulu dengan `HelperProgram.create_ata(user, mint)` (idempoten).

**Sebuah akun token memiliki satu delegate.** Pemberian hak di atas menggantikan pemberian hak milik wrapper pada akun itu. Sampai pengguna memberi hak pada wrapper lagi, setiap `wUSDC` `transfer` atau `transferFrom` keluar dari alamat mereka akan revert dengan error SPL `0x4`. Setelah alur apa pun yang menggunakan pemberian hak ini, panggil `wUSDC.isEnabled(user)`; ketika hasilnya `false`, minta pengguna mengirim `approve_spl(wrapper, 2^64-1, mint)` ke HelperProgram lagi, pemberian maksimum yang sama yang menjadi dasar desain wrapper (lihat [Sekali saja: berikan hak pada wrapper](/id/panduan-pengembang/dual-lane-app.md#one-time-grant-the-wrapper)). Pemberian hak yang dibatasi akan mematahkan transfer biasa pengguna lagi begitu nilainya menipis, dan `wUSDC` isEnabled `tetap` true `selama masih ada sebagian darinya, jadi aplikasi tidak akan tahu untuk meminta lagi.` Untuk memeriksa wrapper mana yang dimiliki sebuah mint, baca

wrapper\_kind\_by\_mint(mint) `milik factory (alamat factory ada di` rantai `contracts.json` di [registry](https://github.com/rome-protocol/rome-registry)). `1` adalah wrapper yang di-cache yang dijelaskan bagian ini. `2` adalah wrapper direct-CPI yang diterapkan factory untuk mint Token-2022 dengan transfer hook yang aktif; panduan ini tidak membahasnya. Registry `tokens.json` mencantumkan alamat wrapper dan mint tetapi bukan jenisnya.

## 4. Satu track per kontrak, dan per transaksi

Rome memiliki dua keluarga precompile yang mengubah state Solana, dan **sebuah transaksi hanya menggunakan salah satunya** — runtime menolak transaksi yang mencampur panggilan mutating dari keduanya, bahkan lintas kontrak.

| Track      | Precompile                                                                                          | Efek mendarat                                                                               | Bekerja dalam eksekusi iteratif                                                           |
| ---------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **Legacy** | `CpiProgram` `0xff…08` (`invoke`, `invoke_signed`), `HelperProgram` `0xff…09`, `Withdraw` `0x42…16` | Segera, sebagai CPI nyata di dalam langkah EVM                                              | **Tidak** — CPI legacy dalam transaksi iteratif gagal dengan `CpiProhibitedInIterativeTx` |
| **Cached** | `SplCached` `0xff…05`, `ASplCached` `0xff…06`, `SystemCached` `0xff…04`, `WithdrawCached` `0xff…0b` | Di-staging dalam overlay dan dikomit sebagai CPI di akhir transaksi; revert EVM membuangnya | Ya                                                                                        |

Apa arti ini bagi integrasi program:

* **Memanggil instruksi program Anda sendiri hanya bisa pada track legacy.** Track cached tidak memiliki `invoke`generik; track ini mencakup transfer SPL, pembuatan ATA, operasi system-program, dan penarikan gas.
* Jadi langkah yang mendanai adapter juga harus berada di track legacy jika dijalankan dalam transaksi yang sama dengan CPI. Panggilan wrapper yang di-cache `transferFrom` adalah panggilan pada track cached, jadi tidak dapat berbagi transaksi dengan CPI Anda (dan, seperti yang ditunjukkan bagian 3, itu hanya akan mengkredit buku besar escrow). Tarik dengan `HelperProgram.transfer_spl(userAta, adapterAta, amount, mint)` sebagai gantinya, setelah pengguna menjadikan adapter sebagai delegate SPL (`approve_spl`, dikirim oleh pengguna langsung ke HelperProgram). Pull dan CPI keduanya berada di track legacy, jadi keduanya berjalan dalam satu transaksi.
* **Jauhkan wrapper dari transaksi itu.** Jangan panggil fungsi mutating milik wrapper (`wUSDC.transfer`, `transferFrom`) dari adapter dalam transaksi yang sama dengan CPI legacy. Periksa `isEnabled(user)` dan saldo dari front end dengan `eth_call`.
* **Dan transaksi CPI harus bersifat atomik.** Itulah batas yang menjadi dasar desain ini.

Pembacaan berada di luar aturan: `account_info`, `account_data_at`, `account_u64_at`, `account_lamports`, `pdas_batch_derive` dan `HelperProgram.user_balance` / `allowance_of` adalah pembacaan akun yang telah dimuat sebelumnya (tanpa CPI), diizinkan pada kedua track dan dalam kedua mode eksekusi. Letakkan pemeriksaan awal Anda di atasnya. Satu aturan urutan: setelah mutasi cached terpicu dalam sebuah transaksi, pembacaan track legacy diblokir untuk sisa transaksi itu — baca dulu, lalu mutasi.

## 5. Atomic adalah anggaran

Transaksi atomik Rome adalah satu transaksi Solana: sekitar **1,4 juta unit komputasi**, sebuah **daftar akun 64 alamat** dan **4 KB** ukuran wire (transaksi Solana v1). Program Rome dan CPI Anda berbagi anggaran itu. Sebuah transfer wrapper berjalan sekitar 170K CU pada Hadrian dan instruksi Anda menambahkannya sendiri; ukur dengan `computeUnitsConsumed`kwitansi Solana, bukan EVM `gasUsed`.

Ketika emulasi memutuskan bahwa sebuah transaksi tidak muat, Rome menjalankannya **secara iteratif** melalui beberapa transaksi Solana, dan CPI legacy di dalamnya lalu gagal. Jaga setiap deposit dan withdraw hanya sampai satu perpindahan token plus satu CPI ke program Anda, dan jaga Solidity di sekitarnya tetap kecil. Pecah apa pun yang lebih besar menjadi dua transaksi pengguna.

Kedalaman CPI: Solana mengizinkan 4 level dan Rome menggunakan satu, jadi instruksi Anda berjalan di level 2 dengan dua level di bawahnya (program Anda → SPL Token menyisakan satu).

## 6. Provisioning akun

Program Anda tidak menerima akunnya dari Solidity saat runtime. Proxy **mengemulasi** setiap transaksi sebelum mengirimnya, mencatat setiap akun Solana yang disentuh oleh CPI, dan menempatkannya di transaksi Solana. Adapter Anda menamai akun-akun tersebut dalam `invoke_signed` daftar akun; emulasi membuktikan bahwa semuanya terselesaikan. Ada tiga hal yang mengikuti:

* Setiap akun yang disentuh instruksi Anda harus dapat diturunkan dari input yang dimiliki adapter (PDA miliknya sendiri dan sub-PDA, ID program Anda, mint, alamat pengguna). `SystemProgram.find_program_address` dan `CpiProgram.pdas_batch_derive` melakukan derivasi dalam Solidity.
* Akun yang belum ada (posisi baru, ATA baru) tetap harus dinamai dalam daftar akun; alamatnya adalah yang masuk ke transaksi Solana.
* Semua yang disentuh transaksi — termasuk akun milik Rome untuk pengguna, adapter, dan wrapper — harus tetap di bawah 64 alamat. Desain yang menyentuh sepuluh pasar dalam satu panggilan tidak akan muat.

Untuk jalur dompet Solana, `rome_emulateCallAccounts` mengekspos penemuan yang sama kepada klien; SDK menggunakannya.

Dua akun yang tidak pernah boleh dinamai oleh CPI: pembayar biaya Solana yang mengirim transaksi (kecuali di dalam instruksi transfer System biasa) dan program Rome itu sendiri. CPI kembali ke program Rome akan ditolak.

## 7. Deposit dan withdraw, dalam Solidity

Sketsa minimal sebuah adapter pada track legacy. Pengguna sudah mengirim `approve_spl(adapter, amount, mint)` ke HelperProgram (bagian 3). `PRIVACY_PROGRAM` adalah ID program Anda; `encodeDeposit` / `encodeWithdraw` membangun data instruksi Anda (Borsh, little-endian); meta akun mengikuti tata letak instruksi Anda.

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

contract PrivacyAdapter {
    ICrossProgramInvocation constant Cpi    = ICrossProgramInvocation(0xFF00000000000000000000000000000000000008);
    IHelperProgram          constant Helper = IHelperProgram(0xFF00000000000000000000000000000000000009);
    bytes32 constant SPL_TOKEN = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; // TokenkegQ...

    bytes32 immutable PRIVACY_PROGRAM;
    bytes32 immutable USDC_MINT;

    constructor(bytes32 program, bytes32 mint) {
        PRIVACY_PROGRAM = program; USDC_MINT = mint;
        Helper.create_pda(address(this));                 // identitas Solana milik adapter sendiri
        Helper.create_ata(address(this), USDC_MINT);      // dan akun USDC yang dimilikinya
    }

    function _salt(address user) internal pure returns (bytes32) { return bytes32(uint256(uint160(user))); }

    function deposit(uint64 amount) external {
        bytes32 self       = Helper.pda(address(this));                   // penandatangan: otoritas adapter
        bytes32 adapterAta = Helper.ata(address(this), USDC_MINT);
        bytes32 position   = Helper.pda_with_salt(address(this), _salt(msg.sender)); // sub-PDA per pengguna, juga menandatangani

        // Tarik dari akun token pengguna. Otoritas adapter menandatangani sebagai SPL
        // delegate yang diberikan pengguna; ini adalah panggilan pada track legacy, seperti CPI di bawah ini.
        Helper.transfer_spl(Helper.ata(msg.sender, USDC_MINT), adapterAta, amount, USDC_MINT);

        ICrossProgramInvocation.AccountMeta[] memory accs = new ICrossProgramInvocation.AccountMeta[](5);
        accs[0] = ICrossProgramInvocation.AccountMeta(self,       true,  true);
        accs[1] = ICrossProgramInvocation.AccountMeta(position,   true,  true);
        accs[2] = ICrossProgramInvocation.AccountMeta(adapterAta, false, true);                          // source: dimiliki oleh otoritas adapter
        accs[3] = ICrossProgramInvocation.AccountMeta(_vaultFor(position), false, true);                 // vault program Anda
        accs[4] = ICrossProgramInvocation.AccountMeta(SPL_TOKEN, false, false);

        bytes32[] memory salts = new bytes32[](1);
        salts[0] = _salt(msg.sender);
        Cpi.invoke_signed(PRIVACY_PROGRAM, accs, encodeDeposit(amount), salts);
    }

    function withdraw(uint64 amount) external {
        bytes32 position = Helper.pda_with_salt(address(this), _salt(msg.sender));
        bytes32 userAta  = Helper.ata(msg.sender, USDC_MINT);            // tujuan: akun milik pengguna sendiri
        // ... bentuknya sama: program Anda memindahkan `amount` dari vault ke userAta, ditandatangani oleh `position`
        Cpi.invoke_signed(PRIVACY_PROGRAM, accs, encodeWithdraw(amount), salts);
    }
}
```

Adapter tidak pernah memanggil `wUSDC` kontrak. Pull adalah panggilan HelperProgram yang membelanjakan delegasi pengguna, dan token berada dalam akun token yang `external_auth(adapter)` dimiliki, yang menjadi alasan CPI dapat membelanjakannya.

Catatan tentang penandatangan. Rome menandatangani untuk tepat dua jenis akun: otoritas mentah kontrak pemanggil (`Helper.pda(address(this))`), yang menandatangani tanpa harus dicantumkan dalam `salt`, dan satu sub-PDA yang diberi salt untuk setiap entri di `salt`. Setiap salt yang Anda cantumkan harus muncul dalam daftar akun sebagai penandatangan, atau panggilan gagal dengan `akun penandatangan yang dihitung dari seed yang diberikan tidak ditemukan`. Akun lain mana pun yang ditandai sebagai penandatangan, termasuk otoritas milik pengguna sendiri, tidak mendapat seed dari Rome, dan runtime Solana menolak CPI. Hak pengguna atas token mereka berasal dari delegasi, tidak pernah dari adapter yang menandatangani sebagai pengguna.

Delegasi tanpa pull: jika program Anda harus membelanjakan langsung dari akun token pengguna, pengguna mengirim `HelperProgram.approve_spl_raw_delegate(userAta, yourProgramPda, amount, mint, decimals)` langsung ke HelperProgram, dengan menamai PDA program Anda sebagai delegate SPL. Jembatan Rome menggunakan ini untuk Wormhole. Aturan satu-delegate dari bagian 3 juga berlaku di sini: pemberian hak itu menggantikan milik wrapper.

## 8. Pengujian

* Deploy ke **Hadrian** (testnet, chain 200010) terlebih dahulu; alamat dan RPC ada di [Contract Addresses](/id/referensi/contract-addresses.md) dan [registry](https://github.com/rome-protocol/rome-registry).
* `eth_call` dan `eth_estimateGas` jalankan emulasi yang sama yang digunakan proxy untuk mengirim, jadi CPI yang gagal di sana gagal di chain karena alasan yang sama, dan data revert membawa kesalahan Solana yang mendasarinya (`galat panggilan non-EVM: …` di log program Solana).
* Baca sisi Solana dari transaksi EVM yang telah berhasil mendarat dengan `rome_solanaTxForEvmTx(hash)`; receipt Solana adalah tempat CU dan log program Anda berada.
* CPI yang berfungsi di `eth_call` tetapi ditolak saat dikirim biasanya memicu batas yang dilaporkan emulasi: batas 64 alamat, anggaran CU atomik (transaksi menjadi iteratif), atau aturan track.

## 9. Referensi

* [Referensi precompile](/id/referensi/precompiles.md) — setiap selector pada setiap precompile, beserta kelas dispatch-nya dan aturan delegatecall.
* [Batasan](/id/konsep-inti/constraints.md) — batasan di atas dalam satu tabel.
* [Interop Token](/id/konsep-inti/token-interop.md) — wrapper, mint kanonik, deposit, dan penarikan.
* Aplikasi referensi di repositori publik: [cardo](https://github.com/rome-protocol/cardo) (pengguna memanggil precompile CPI secara langsung, bentuk B), [rome-dex](https://github.com/rome-protocol/rome-dex) (langsung `invoke` ditambah router tanpa kustodian, bentuk C), dan [aerarium](https://github.com/rome-protocol/aerarium) (aplikasi pinjaman Compound III di atas satu pasar bersama; aplikasi ini tidak memanggil program Solana dari Solidity (tanpa CPI), jadi ini bukan contoh adapter).


---

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