> 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/ko/developer-guides/call-evm-from-solana.md).

# Solana에서 EVM 호출

Solana 지갑이 Ethereum 키 없이 Rome에서 EVM 계약을 구동하게 하세요. 지갑이 서명하고, 그 트랜잭션은 Solana로 전송됩니다.

의 반대 개념 [EVM에서 Solana 호출](/ko/developer-guides/call-solana-from-evm.md): Solana 지갑(예: Solflare)이 Rome의 어떤 EVM 계약이든 직접 호출하며, **Ethereum 키 없이**. 이것이 [Aerarium](/ko/rome/apps.md) Solana 네이티브 사용자가 EVM 사용자와 동일한 계약을 공유할 수 있게 하는 방식이다.

한 번의 호출로 하는 방식은 SDK의 `submitRomeTxV1`이며, 프록시가 호출을 위해 Solana v1 트랜잭션 하나를 구성한다; 이 페이지에서는 그 메커니즘을 설명한다.

## 합성 주소

Solana 지갑의 EVM 정체성은 **합성 주소** 이다 — `keccak256` 의 마지막 20바이트, 즉 32바이트 Solana 공개 키의 결과이다:

```
synthetic_evm_address = keccak256(solana_pubkey)[12:]
```

이것이 `msg.sender` 이며, 지갑이 계약을 호출할 때와 nonce 및 저장 상태가 유지되는 안정적인 정체성이다. 이 파생 규칙은 프로토콜에 고정되어 있어, 지갑은 항상 같은 주소로 매핑된다.

```javascript
import { syntheticAddress } from "@rome-protocol/sdk";
const from = syntheticAddress(solanaPubkey); // 0x… 20 bytes
```

## 합성 주소는 중계만 하는 통로이며, 휴면 상태에서는 어떤 토큰도 보유하지 않는다

Solana 네이티브 사용자의 사용 가능한 잔액은 그들의 **Solana 지갑** (SPL 토큰으로) 존재하며, EVM 측에서는 토큰의 **ERC20SPL 래퍼** (예: `wUSDC`로 1:1로 표시된다. 합성 주소는 휴면 상태에서는 아무것도 보유하지 않으므로, 가치는 *을 통해* 흐르며 — 모든 단계는 Solana 지갑 서명으로 진행된다:

1. **프로비저닝(한 번).** 새로 생성된 합성 주소의 external-auth PDA는 `create_pda` 가 실행될 때까지 존재하지 않는다 — 그리고 아래의 전송은 그 PDA로 서명된다. `submitRomeTxSolanaLane` 첫 사용 시 자동으로 프로비저닝한다(또는 `provisionSynthetic` 을 호출해 명시적인 "Activate" 단계를 수행한다; `isSyntheticProvisioned`으로 확인하라). `submitRomeTxV1`와 함께 `isSyntheticProvisioned` 를 확인하고 `buildCreatePdaCall` 한 번 통해 제출하라.
2. **자금 이동 단계(지갑 → 합성 주소).** 토큰을 지갑의 ATA에서 합성 주소의 ATA로 옮긴다 — `ActivateAta` 단계이며, `buildFundLeg`가 생성한다. 이제 `wrapper.balanceOf(synthetic)` 가 그 잔액을 읽으므로, 합성 주소는 이를 일반 ERC-20(`전송에서는 후크가 작동하지 않습니다` / `transferFrom`)처럼 지출한다 — **가 아닌** 네이티브 `msg.value`토큰처럼, 그런데 합성 주소는 그것을 보유하지 않는다.
3. **호출들(`DoTxUnsigned`).** 합성 주소가 EVM 트랜잭션을 실행한다 — 예를 들면 `approve` 다음에 볼트 `deposit` 을 실행하며 `transferFrom`.
4. **회수 단계(합성 주소 → 지갑).** withdraw / borrow / claim 후 토큰이 합성 주소에 들어오면, `buildSweepLeg` 이를 사용자의 지갑 ATA로 다시 밀어 넣는다(`HelperProgram.transfer_spl`) 그래서 합성 주소의 순잔액은 0이 된다.

다음 섹션에서는 이전의 `submitRomeTxSolanaLane` 가 클라이언트 측에서 어떻게 구성되는지 설명한다. `submitRomeTxV1` 는 대신 프록시가 v1 트랜잭션을 구성하게 하며, 계정 탐색, ComputeBudget 또는 treasure wallet을 코드에 둘 필요가 없다; [외부-V1 엔드포인트](#the-external-v1-endpoints) 아래에서 다룹니다.

## 호출이 어떻게 구성되는가

`submitRomeTxSolanaLane` 가 이 모든 일을 수행한다. 메커니즘은 다음과 같다:

1. **서명되지 않은 EIP-1559 트랜잭션을 구성한다** 를 사용하여 `from` = 합성 주소 — secp256k1 서명 없음.
2. **계정 탐색** — [`rome_emulateCallAccounts`](/ko/reference/json-rpc.md) 를 호출하여 `from`, `다음에`, `data`, **및 `value`** (호출이 payable인 경우)와 함께 전달한다. `value` value를 넘기는 것이 중요하다: 프록시는 *실제* 호출을 에뮬레이션하므로, 값에 의존하는 모든 저장소 쓰기 — 예: 새 mapping 슬롯 — 가 할당되고 그 Solana 계정이 반환된다. 이를 생략하면 해당 계정이 누락되어 트랜잭션은 *"instruction modified data of a read-only account."*
3. **Solana 트랜잭션을 조합한다:** 두 개의 **ComputeBudget** 명령(Rome의 EVM은 상향된 CU 한도 ≈1.35M와 큰 heap frame ≈250 KB가 필요하다 — Solana 기본값으로는 오류가 난다) + **`DoTxUnsigned`** 명령(서명되지 않은 RLP + 발견된 계정들) + 체인별 **treasure 지갑** (실행 시 소액 수수료가 지급된다; 계정 탐색에는 포함되지 않으므로 SDK가 이를 덧붙인다).
4. **지갑이 서명하고 제출한다.** 위의 **Solana 지갑이 Solana 트랜잭션에 서명(Ed25519)하고 이를 Solana RPC로 보낸다 — 프록시가 아니라.** Solana 런타임이 서명을 검증한다; 온체인에서는 프로그램이 그 서명자로부터 `msg.sender` 를 파생한다. 권한은 Ethereum 서명이 아니라 Solana 서명이다. (프록시는 2단계의 에뮬레이션/탐색에만 사용된다.)

사용자의 PDA(`["EXTERNAL_AUTHORITY", synthetic_address]`)가 그들의 토큰 계정을 소유하고 SPL CPI를 대신 서명하므로 — Solana 사용자는 자신의 Solana 지갑만으로 공급, 대출, 스왑, LP를 모두 수행할 수 있다.

## 외부-V1 엔드포인트

`submitRomeTxV1` 는 한 번의 호출로 하는 방식이다: 프록시에게 조합된 v1 트랜잭션을 요청하고, 검증한 뒤, 지갑이 서명해 Solana RPC로 보낸다. 호출이 맞지 않을 때는 프록시를 통해 중계한다. 프록시는 RPC 메서드와 같은 경로를 제공하므로, 어떤 클라이언트든 SDK 없이 사용할 수 있다( [JSON-RPC 지원](/ko/reference/json-rpc.md)):

* `rome_composeUnsignedAtomicV1(rlp, pubkey, signature)` — Solana v1 트랜잭션 하나; 지갑이 서명하고 앱이 이를 Solana RPC에 브로드캐스트한다. 하나의 원자적 트랜잭션에 맞지 않는 호출은 거부된다.
* `rome_composeUnsignedIterativeV1(rlp, pubkey, signature)` — 실행의 모든 단계(맞으면 원자적, 아니면 반복적 단계들); 지갑이 각각에 서명하고, `rome_submitSignedV1Plan` 를 브로드캐스트한다. 긴 계획의 한계는 [릴레이된 호출 및 장시간 호출](#relayed-calls-and-long-calls).
* `rome_relayUnsignedV1(rlp, pubkey, signature)` — 지갑 서명 하나만 있으면 되고, 프록시의 payer가 각 단계를 실행하며 EVM tx 해시를 반환한다; 합성 주소가 Rome 가스를 지불하므로, 한 번은 `rome_composeExternalDepositV1`.

**권한 부여 서명.** `rlp` 는 9개 필드로 된 순수한 서명 없는 EIP-1559 본문이다. 지갑은 Ed25519 키로, 원시 32바이트 `keccak256(0x02 ‖ rlp)` 또는 읽을 수 있는 메시지

```
Rome authorization\n0x<그 해시의 64개 소문자 hex 문자>
```

둘 다 허용된다; 읽을 수 있는 형식은 지갑이 `signMessage` 프롬프트에서 사람이 읽을 수 있게 표시하는 형식이다. 프로그램은 자신이 실행하는 본문에서 해시를 다시 계산하고, 서명된 메시지가 정확히 두 형식 중 하나여야 하므로, 한 본문에 대한 서명이 다른 본문을 승인할 수 없고, 본문의 nonce가 재생 공격을 막는다.

## 릴레이된 호출 및 장시간 호출

일부 호출은 하나의 Solana 트랜잭션에 맞지 않는다. 이 섹션에서는 그때 어떤 일이 일어나는지와 어떤 제한이 적용되는지를 다룬다.

### 원자적 조합기가 거부할 때

`rome_composeUnsignedAtomicV1` 하나의 원자적 트랜잭션에 맞지 않는 호출은 거부한다. `submitRomeTxV1` 그런 다음 같은 **권한 부여 서명으로** 프록시를 통해 호출을 중계하므로, 지갑에 두 번째 요청이 가지 않는다. 결과에는 `route: "relay"` 와 Solana `서명`이 없다. `relayOnRefusal: false` 를 넘기면 거부를 오류로 받게 할 수 있다. 또는 `relayV1` 을 직접 호출하여 의도적으로 중계할 수도 있다.

중계된 호출에 대해 알아야 할 세 가지:

* **합성 주소가 가스를 지불한다.** relay 경로에서는 프록시가 Solana 트랜잭션을 제출하고 비용을 지불하며, 호출 비용은 합성 주소 잔액에서 해당 체인의 네이티브 가스 토큰으로 지불된다. 잔액은 최소 `gasLimit × maxFeePerGas`. `fundSyntheticGas(deps, amountWei)` 로 체인의 가스 토큰에 대한 지갑 자신의 토큰 계정( Hadrian과 Rubicon에서는 USDC)에서 충전한다; 지갑은 일반 v1 트랜잭션 하나에 서명하면 되며, Solana 수수료를 위해 약간의 SOL이 필요하다. `amountWei` 는 체인의 18자리 wei 단위이며 10^12의 배수여야 하고, 그렇지 않으면 프록시가 거부한다. SDK 없이도, `rome_composeExternalDepositV1` 가 같은 트랜잭션을 구성한다.
* **revert는 예외를 던지지 않는다.** EVM 쪽에서 revert된 중계 호출은 정상적으로 resolve되며, `receipt.status === "0x0"`. 확인하라 `receipt?.status === "0x1"` 호출을 완료된 것으로 처리하기 전에. `receipt` 인 것을 선택하세요 `null` 영수증이 제때 나타나지 않으면.
* **`relayOnRefusal: false` 는 SDK에서 정한 선택이지, 고정된 규칙은 아니다.** 프록시는 이미 해당 페이로드에 대한 권한 부여 서명을 보유하고 있으며, `gasLimit × maxFeePerGas`.

```javascript
const res = await submitRomeTxV1(deps, { to, data, value });
if (res.route === "relay" && res.receipt?.status !== "0x1") {
  // revert된 중계 호출은 resolve되며, 예외를 던지지 않는다
}
```

### 긴 호출

하나 이상의 트랜잭션이 필요한 호출은 반복 세션으로 실행된다: 프로그램은 각 Solana 트랜잭션에 들어갈 수 있는 만큼의 EVM 단계를 실행하고, 그 사이에 상태를 저장한다. Solana 지갑에서 이를 실행하는 두 가지 방법:

* **중계한다.** `rome_relayUnsignedV1` 는 지갑 서명 하나를 사용하고 프록시의 payer가 모든 단계를 보낸다. 합성 주소가 위에서 설명한 대로 가스를 지불한다.
* **직접 각 단계를 서명한다.** `rome_composeUnsignedIterativeV1` 는 빈 서명 슬롯이 있는 모든 단계를 반환하고, 지갑이 각각에 서명한 뒤 `rome_submitSignedV1Plan` 순서대로 브로드캐스트한다. 각 단계는 지갑이 서명해야 하는 별도의 Solana 트랜잭션이므로, 한 번에 한 트랜잭션만 서명하는 지갑은 권한 부여 서명 외에 각 단계마다 한 번씩 프롬프트를 띄운다. 지갑은 각 단계마다 Solana 수수료를 지불한다.

한계:

| 한계                                        | 값            | 출처                                                                                    |
| ----------------------------------------- | ------------ | ------------------------------------------------------------------------------------- |
| 하나의 `rome_submitSignedV1Plan` 호출 내 트랜잭션 수 | 64           | 프록시가 더 긴 계획을 거부한다                                                                     |
| 반복적 조합기가 계획할 단계 수                         | 128          | Rust Rome SDK의 반복적 조합기에서의 상한이며, 이것이 계획도 제한한다 `rome_composeUnsignedIterativeV1` 를 반환한다 |
| 하나의 세션 내 반복 횟수                            | 160          | Rome EVM 프로그램; 그 한계를 지나면 세션이 재개를 멈추고, 계정 잠금을 해제한 뒤 종료된다                               |
| Solana 트랜잭션 크기와 계정                        | 4 KB, 주소 64개 | 모든 단계는 Solana v1 트랜잭션이다                                                               |

실제 호출 대부분은 이런 상한보다 훨씬 적은 단계로 끝난다. 지갑 서명 계획에서 가장 먼저 만나게 되는 것은 64개 트랜잭션 한도다. 그보다 더 많은 단계가 필요한 호출은 `rome_submitSignedV1Plan`.

긴 호출은 legacy-track CPI를 만들 수 없다. legacy precompile(`0xff…08`, `0xff…09` 및 `0x42…16`)에 대한 상태 변경 호출은 반복 세션에서 `CpiProhibitedInIterativeTx`로 거부된다.`0xff…04`, `0xff…05`, `0xff…06`, `0xff…0b`의 Solana 상태 조회와 cached-track precompile은 동작한다. legacy-track CPI를 만드는 호출은 어느 경로로 보내든 하나의 원자적 트랜잭션에 맞아야 한다. 참조하라 [제약 사항](/ko/core-concepts/constraints.md) 및 [컴퓨트 예산](/ko/core-concepts/compute-budget.md).

## 참조 구현

* **Aerarium** — `DoTxUnsigned`을 통해 Solana 지갑에서 Compound v3(supply / borrow)를 구동한다. 이는 위에서 설명한 이전 legacy-transaction 경로에 대한 참조이며, `submitRomeTxV1`.
* **Rome DEX** 는 이 페이지의 예시가 아니다: Solana 측은 지갑이 직접 서명하는 네이티브 Solana 프로그램이고, EVM 측은 같은 풀을 공유한다.

Aerarium은 브리지나 두 번째 지갑이 필요 없다 — 합성 주소가 하나의 Solana 키를 1급 EVM 계정으로 만든다. v1 패턴은 [듀얼 레인 앱 빌드](/ko/developer-guides/dual-lane-app.md).

## 다음 단계

* [EVM에서 Solana 호출](/ko/developer-guides/call-solana-from-evm.md) 을 따르라 — 반대 방향, CPI를 통해
* [토큰 상호운용성](/ko/core-concepts/token-interop.md) — EVM과 Solana 사이에서 잔액이 어떻게 공유되는지


---

# 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/ko/developer-guides/call-evm-from-solana.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.
