> 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/core-concepts/token-interop.md).

# 토큰 상호운용성

Rome에서는 ERC-20과 SPL 토큰이 같은 기본 계정입니다.

Rome은 ERC-20 토큰과 그 기반 SPL 토큰을 하나의 공유 계정으로 표현합니다. 이 페이지는 EVM과 Solana 전반에서 토큰이 어떻게 동작하는지 설명합니다.

## 공유 상태 모델

Rome은 한쪽에서 토큰을 잠그고 다른 쪽에서 래핑된 복사본을 발행하지 않습니다. Rome의 ERC-20 토큰은 **투명한 래퍼** 로서 Solana의 SPL 토큰 계정 위에 존재하며 — ERC-20 잔액은 *인 것을 선택하세요* SPL 잔액입니다.

<figure><img src="https://1350471780-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F671PKS8ZQSNqtoC8PGvz%2Fuploads%2Fgit-blob-65af9700f322d308e2cb17da3d107f87b51201a7%2Ftoken-interop.svg?alt=media" alt="An ERC-20 wrapper over the same SPL token account on Solana"><figcaption></figcaption></figure>

* 브리징 지연 없음 — ERC-20 잔액이 곧 SPL 잔액입니다
* 유동성 분절 없음 — 양쪽의 DeFi가 동일한 토큰을 봅니다
* 브리지 위험 없음 — 악용할 별도의 에스크로가 없습니다

> 아래의 import 경로는 `@rome-protocol/rome-solidity`를 사용합니다. 이 패키지는 npm에 없으며, 공개 [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) 저장소에서 `npm install github:rome-protocol/rome-solidity`, 또는 `forge install rome-protocol/rome-solidity` 리매핑을 통해 설치합니다. 래퍼 및 팩토리 계약은 Foundry에서 OpenZeppelin도 필요합니다(참조 [Solidity 컨트랙트 배포](/ko/developer-guides/deploy-solidity.md#using-the-rome-solidity-sdk)). 프리컴파일 인터페이스는 [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/interface.sol).

## 래퍼 계약

`SPL_ERC20` (그리고 캐시된 트랙 변형인 `SPL_ERC20_cached`, 이는 팩토리가 armed transfer hook이 있는 하나를 제외한 모든 mint에 대해 배포합니다. 참조 [Token-2022 mint](#token-2022-mints))가 SPL mint 위에 완전한 ERC-20 인터페이스를 제공합니다:

* `balanceOf()` — Solana에서 사용자의 ATA 잔액을 읽습니다(계약 주소의 경우 래퍼 자체 원장을 읽습니다. 아래 참조)
* `transfer()` / `transferFrom()` — Solana에서 토큰을 이동합니다
* `approve()` / `allowance()` — 개별 지출자별 ERC-20 허용량을 EVM 저장소에 보관합니다(SPL 토큰 계정에는 EVM 스타일의 허용량이 없습니다). 이는 아래의 일회성 권한 부여와는 별개입니다. 사용자의 토큰을 이동하려면, 래퍼가 그 사용자의 SPL delegate 역할을 하며, 사용자는 지갑당 한 번 그 권한을 부여합니다
* `totalSupply()` — SPL mint 공급량을 읽습니다

계약은 일회성 권한 부여를 보낼 수 없으므로, 계약의 토큰은 래퍼 자신의 토큰 계정에 보관되고 래퍼의 원장에서 추적됩니다. `balanceOf(contract)` 가 그 원장을 읽습니다.

## 팩토리

`ERC20SPLFactory` 는 임의의 SPL mint에 대해 래퍼를 배포합니다:

```solidity
import {ERC20SPLFactory} from "@rome-protocol/rome-solidity/contracts/erc20spl/erc20spl_factory.sol";

// 래퍼를 배포하며, mint의 Token-2022 메타데이터 또는 Metaplex에서 이름/심볼을 불러옵니다
address wrapper = factory.add_spl_token_with_metadata(splMint);

// 또는 이름/심볼을 수동으로 지정합니다
address wrapper = factory.add_spl_token_no_metadata(splMint, "USD Coin", "USDC");
```

실시간 팩토리 주소는 `live` 레지스트리(v0.2.0)의 항목에서 가져온 것으로: Hadrian `0x37c13e4ff21fb914eb24014dcbd0f8dbeaa31078`입니다. 이전 팩토리들은 여전히 온체인에 있지만 `deprecated` 또는 `retired` 안에 [레지스트리](https://github.com/rome-protocol/rome-registry/tree/main/chains)로 표시되어 있습니다; 사용하지 마세요. 레지스트리가 진실의 원천이므로, 코드에 값을 복사하는 대신 그곳에서 실시간 주소를 읽으세요(참조 [컨트랙트 목록 읽기](/ko/reference/contract-addresses.md#reading-the-contracts-list))

## 정식 mint

온체인 토큰 레지스트리 계약은 없습니다. 정식 래퍼와 가스/브리지 토큰은 오프체인 [`rome-protocol/registry`](https://github.com/rome-protocol/rome-registry)에서 선별되며, `add_spl_token_no_metadata` 를 통해 생성된 무허가 래퍼는 온체인 `TokenCreated` 이벤트에서 발견됩니다. 이렇게 하면 각 자산이 유동성을 분절하지 않고 하나의 정식 SPL mint에 매핑됩니다.

## Solidity에서 SPL 작업

사용자-PDA 서명 SPL 프리미티브의 경우 **HelperProgram** 프리컴파일(`0xFF…09`) — ATA 생성, SPL 전송, 가스↔lamports 변환:

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

IHelperProgram helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

helper.create_ata(user, mint);              // mint에 대한 사용자의 ATA를 생성
helper.transfer_spl(to, tokens, mint);      // 호출자의 PDA에서 SPL 전송
```

`transfer_spl` 에는 여러 오버로드가 있습니다(포함:  `transferFrom` 흐름을 위한 delegate 변형); 정확한 시그니처는 `interface.sol` 를 참조하세요. 캐시된 트랙에서는 대응하는 작업이 `ISplCached` (`0xFF…05`) 및 `IAssociatedSplCached` (`0xFF…06`)에 있습니다. 계약은 한 트랙을 일관되게 사용합니다.

## 지갑당 1회 권한 부여

래퍼는 사용자의 토큰을 다음의 **SPL delegate** 로서 이동합니다. 따라서 지갑은 래퍼가 무언가를 이동하기 전에, 래퍼당 한 번 그 권한을 부여해야 합니다. 그 권한 부여는 Helper 프리컴파일(`0xff00000000000000000000000000000000000009`):

```solidity
helper.approve_spl(wrapper, type(uint64).max, mint);   // approve_spl(wrapper, 2^64-1, mint)
```

`approve_spl` 사용자 자신의 계정으로 서명되므로, 사용자가 전송하며 래퍼나 귀하의 계약이 전송하는 것이 아닙니다. mint는 `mint_id()` 래퍼 상에 있습니다.

* **누가 필요한가.** 토큰을 이동하고자 하는 각 래퍼에 대해, 모든 EVM 지갑과 모든 Solana 사용자의 합성 주소가 필요합니다. `transferFrom` 를 사용해 토큰을 당겨오는 계약은 래퍼에게 권한을 부여한 사용자에게만 동작합니다.
* **확인 방법.** 를 호출하세요. `isEnabled(user)` 래퍼에서 이를 호출하세요. 그러면 `true` 를 반환합니다. 권한 부여가 적용되면.
* **누락되었을 때의 모습.** 전송은 SPL 오류 `0x4` (owner mismatch)로 되돌아갑니다.
* **토큰 계정당 하나의 delegate.** SPL 토큰 계정에는 단 하나의 delegate만 있습니다. 브리지나 라우터처럼 같은 토큰 계정에 다른 프로그램에 권한을 부여하면 래퍼의 권한이 대체되며, 사용자가 다시 권한을 부여할 때까지 래퍼는 그 사용자에 대해 동작을 멈춥니다. 전송 전에 `isEnabled` 를 확인하고, 이전 권한이 아직 남아 있다고 가정하지 마세요.

위의 [듀얼 레인 앱 가이드](/ko/developer-guides/dual-lane-app.md#one-time-grant-the-wrapper) 에는 두 레인에 대한 전체 코드가 있습니다.

## 입금과 출금

* **EVM으로** — SPL 측이 사용자의 PDA 소유 ATA에 크레딧을 부여하고; ERC-20 래퍼가 즉시 잔액을 반영합니다. 크로스체인 인바운드 전송은 브리지에서 사용자가 서명한 승인으로 신뢰 없이 정산됩니다.
* **가스를 가스 토큰으로 래핑** — `출금` 프리컴파일(`0x42…16`). `withdraw_to_ata(wei)` 를 호출하면 네이티브 가스를 소각하고, 가스 토큰 자체의 소수 자릿수로 환산한 동등한 수량을 호출자의 토큰 계정에 크레딧으로 부여하며, 가스 토큰의 래퍼(예: wUSDC)가 이를 잔액으로 표시합니다. `withdraw_to_pda(wei)` 는 lamports를 호출자의 PDA에 지급하며 가스가 SOL인 체인에만 적용됩니다. USDC를 가스로 사용하는 Hadrian과 Rubicon에서는 되돌아가므로 `withdraw_to_ata` 를 사용하세요. 그곳에서는 금액이 18 소수 가스 단위로 표시되며 가스 토큰 단위의 정수여야 합니다: USDC 가스 체인에서는 10^12의 배수(6 소수 단위 1개)여야 하며, 그렇지 않으면 호출이 되돌아갑니다. `HelperProgram.deposit_from_ata(wei)` 는 반대 방향으로 작동하여 호출자의 토큰 계정에 있는 가스 토큰을 동일한 배수 규칙으로 네이티브 가스로 되돌립니다. 이는 가스 토큰 변환이며, 다른 토큰의 출구가 아닙니다.
* **Solana로, 다른 어떤 토큰이든** — 래퍼 자체의 잔액이 이미 SPL 잔액이므로, 외부로 옮기는 것은 SPL 전송입니다:
  * `HelperProgram.transfer_spl_to_signer(amount, mint)` 는 호출자의 토큰을 트랜잭션에 서명한 Solana 지갑의 토큰 계정으로 보냅니다. 이는 Solana 지갑 사용자를 위한 반환 경로입니다.
  * `RomeBridgeWithdraw.bridgeOutToSolana(solanaRecipient, amount, mint)` 는 이를 임의의 Solana 지갑으로 보냅니다. 브리지 계약에 대한 사용자의 권한 부여(`approve_spl(bridge, …)`가 필요하며, 수신자의 토큰 계정은 존재해야 합니다; `ensureRecipientAta(solanaRecipient, mint)` 가 이를 생성합니다.
  * `RomeBridgeWithdraw` 또한 CCTP(`burnUSDC`)를 통해 다른 체인으로 USDC를 소각하고, Wormhole(`burnETH`, `burnToWormhole`).

의 실시간 `RomeBridgeWithdraw` 주소를 레지스트리에서 가져오세요.

## 가스 토큰

각 체인에는 자체 가스 토큰이 있습니다 — Meteora DAMM 풀(v1 또는 v2, 구성 가능)로 가격이 책정되는 임의의 SPL 토큰입니다. 공개 체인(Rubicon과 Hadrian)은 USDC를 사용합니다; Hadrian에서는 테스트 USDC, Rubicon에서는 실제 USDC입니다. 전역 기본 가스 토큰은 없습니다.

## Token-2022 mint

팩토리는 Token-2022 mint도 래핑합니다. Helper 프리컴파일에 mint가 무엇인지 `mint_info(mint)` (selector `0xe24bf5d4`)를 통해 묻고, 답에 따라 래퍼를 선택합니다. `mint_info` 는 다섯 개의 값을 반환합니다:

| 값              | 의미                                                 |
| -------------- | -------------------------------------------------- |
| `tokenProgram` | mint를 소유한 토큰 프로그램(기존 SPL Token 프로그램 또는 Token-2022) |
| `소수점 자리수`      | mint의 소수 자릿수                                       |
| `hookProgram`  | 전송 훅 프로그램, 또는 훅이 없거나 훅이 있지만 armed되지 않았을 때는 0       |
| `feeBps`       | 기본 포인트 단위의 전송 수수료, 또는 수수료가 없거나 0으로 설정되어 있으면 0      |
| `extensions`   | mint가 보유한 Token-2022 확장 기능의 비트맵, 확장 유형 N에 대해 비트 N  |

mint에 따라 배정되는 것:

| Mint                                                        | Wrapper                                                                 | `wrapper_kind_by_mint` |
| ----------------------------------------------------------- | ----------------------------------------------------------------------- | ---------------------- |
| 일반 mint, 훅이 없는 Token-2022 mint, 또는 훅이 존재하지만 armed되지 않은 mint | 캐시된 래퍼(`SPL_ERC20_cached`)                                              | `1`                    |
| armed transfer hook이 있는 Token-2022 mint                     | 훅이 있는 래퍼(`SPL_ERC20_Token2022Hooked`), 팩토리의 `hooked_wrapper_deployer()` | `2`                    |

를 통해 배포됩니다. `전송` 이벤트는 전송된 양이 아니라 수수료를 제하고 도착한 양을 보고합니다. 팩토리에서 mint가 어떤 종류를 받았는지 `wrapper_kind_by_mint(mint)`로 읽을 수 있습니다; 실시간 Hadrian 팩토리에서는 USDC, ETH, SOL 래퍼가 모두 `1`.

거부되는 것:

* armed transfer hook이 있는 mint는 일반 래퍼 중 하나를 받을 수 없습니다. `SPL_ERC20` 또는 `SPL_ERC20_cached` 를 직접 배포하면 `ArmedTransferHookUnsupported(mint, hookProgram)`로 되돌아갑니다. 팩토리를 통하면 이 경우 대신 훅이 있는 래퍼로 처리됩니다.
* SPL mint가 아닌 주소는 `mint_info` 를 `Invalid SPL mint account: <address>`.
* 이미 래퍼가 있는 mint는 `Token exists`로 되돌아가고, 이미 사용 중인 심볼은 `Token with symbol exists`.
* `add_spl_token_with_metadata` 는 먼저 mint 자체의 Token-2022 메타데이터에서, 그 다음에는 Metaplex에서 이름과 심볼을 읽습니다. mint에 둘 다 없으면 `Mint asserts no name: use add_spl_token_no_metadata`.

훅이 있는 래퍼의 동작:

* Token-2022는 모든 전송에서 훅을 실행하며, 훅은 전송에 따라 달라지는 계정들을 필요로 합니다. 래퍼는 이를 스스로 계산할 수 없으므로, 호출자가 mint의 extra-account-metas 목록에서 전체 계정 목록을 해석해 전달해야 합니다. 래퍼는 그 목록의 고정 부분(훅 프로그램과 그 검증 계정)을 확인하고 변경 없이 전달합니다.
* 일반 `전송에서는 후크가 작동하지 않습니다`, `transferFrom` 및 `bridgeOutToSolana` 를 `HookAccountPlanRequired`를 사용하세요. `transferWithHookAccounts`, `transferFromWithHookAccounts` 및 `bridgeOutToSolanaWithHookAccounts`, 이들은 계정 목록을 추가 인수로 받습니다.
* 래퍼가 배포된 후 mint의 훅 프로그램이 변경되면 전송은 `HookConfigurationChanged`.
* 직접적인 cross-program call, 즉 레거시 트랙으로 토큰을 이동하며 캐시된 트랙을 사용하지 않습니다. 계약은 한 트랙을 일관되게 사용하며, 레거시 트랙의 호출은 원자적 실행에서만 동작하므로, 훅이 있는 래퍼를 사용하는 호출은 하나의 원자적 트랜잭션에 맞아야 합니다.
* 계약 잔액에 대한 원장을 유지하지 않습니다. 계약으로 전송된 토큰은 계약 자신의 토큰 계정에 도착하며, 계약은 `approve_spl` 래퍼 자체에 권한 부여를 한 뒤에만 이를 보낼 수 있습니다.
* 다른 래퍼와 동일한 [일회성 권한 부여](#one-time-grant-per-wallet) 가 필요합니다.

## 제약 사항

* SPL 토큰 금액은 `uint64` (최대 18,446,744,073,709,551,615)
* ERC-20 허용량(`approve` / `allowance`)은 EVM 저장소입니다. 래퍼는 사용자의 SPL delegate로서 토큰을 이동하며, 사용자는 래퍼당 한 번 그 권한을 부여합니다(참조 [지갑당 1회 권한 부여](#one-time-grant-per-wallet))
* ERC-20 래퍼 심볼은 팩토리별로 고유해야 합니다

## 다음 단계

* [컨트랙트 주소](/ko/reference/contract-addresses.md) — 프리컴파일과 체인별 주소
* [EVM에서 Solana 호출](/ko/developer-guides/call-solana-from-evm.md) — Solidity에서의 CPI 및 SPL 작업


---

# 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/core-concepts/token-interop.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.
