> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stablenet.network/llms.txt
> Use this file to discover all available pages before exploring further.

# 수수료 위임

이 페이지는 StableNet의 WEMIX 스타일 수수료 위임 메커니즘을 문서화합니다.\
수수료 위임은 별도의 계정(`FeePayer`)이 트랜잭션 가스 비용을 지불하고, 트랜잭션 발신자(`From`)는 값 전송만 제공할 수 있도록 하는 구조입니다.\
이 기능은 애플리케이션 운영자가 사용자 트랜잭션 비용을 보조하는 사용 사례를 가능하게 합니다.

가스 수수료 정책 및 팁 요구사항에 대한 정보는 [가스 수수료 정책](/ko/transaction-processing/6.4-gas-fee-policy)을 참조하세요.\
일반 트랜잭션 처리 흐름은 [트랜잭션 라이프사이클](/ko/transaction-processing/6.1-transaction-lifecycle)을 참조하세요.

## 목적 및 범위

수수료 위임은 트랜잭션의 재정적 책임을 두 당사자 간에 명확히 분리합니다.

* **발신자(`From`)**\
  트랜잭션 의도를 서명하고 전송할 `Value`를 제공하며 유효한 nonce를 보유해야 합니다.
* **수수료 지불자(`FeePayer`)**\
  가스 지불을 승인하기 위해 별도로 서명하며 가스 비용을 충당할 수 있는 충분한 잔액을 보유해야 합니다.

이 메커니즘은 전용 트랜잭션 타입 `FeeDelegateDynamicFeeTxType = 0x16`을 통해 구현되며 Applepie 포크가 활성화되어 있어야 합니다.

## 트랜잭션 타입 및 구조

### 트랜잭션 타입 정의

수수료 위임은 트랜잭션 타입 `0x16`(10진수 22)을 사용합니다.

| Constant                      | Value  | Description                              |
| ----------------------------- | ------ | ---------------------------------------- |
| `FeeDelegateDynamicFeeTxType` | `0x16` | Fee delegated EIP-1559 style transaction |

### 핵심 데이터 구조

`Message` 구조체는 수수료 위임 필드를 포함합니다.

```
Message {
  From common.Address      // Transaction sender (signs transaction intent)
  To *common.Address      // Recipient
  Value *big.Int          // Value to transfer from sender
  GasLimit uint64
  GasPrice *big.Int
  GasFeeCap *big.Int
  GasTipCap *big.Int
  FeePayer *common.Address // Account paying for gas (optional)
  ...
}
```

`TxData` 인터페이스는 수수료 위임 관련 메서드를 포함합니다.

```
type TxData interface {
  feePayer() *common.Address
  rawFeePayerSignatureValues() (v, r, s *big.Int)
  ...
}
```

## 이중 서명 요구사항

수수료 위임 트랜잭션은 **두 개의 독립적인 서명**을 필요로 합니다.

### 서명 1: 트랜잭션 발신자

* 트랜잭션 데이터(to, value, data, gas 매개변수, nonce)에 서명합니다.
* 서명 값: `V`, `R`, `S`
* 트랜잭션 실행 의도와 권한을 증명합니다.

### 서명 2: 수수료 지불자

* 해당 트랜잭션에 대한 가스 지불에 동의함을 서명합니다.
* 서명 값: `FV`, `FR`, `FS`
* `rawFeePayerSignatureValues()` 메서드를 통해 조회됩니다.

이 이중 서명 구조는 발신자와 수수료 지불자 모두가 자신의 책임 범위에 대해 명시적으로 동의했음을 보장합니다.

## 상태 전환 프로세스

### 트랜잭션 메시지 변환

수수료 위임 트랜잭션은 상태 전환 진입 시 `Message`로 변환됩니다.

```
FeeDelegateDynamicFeeTx            Message
+--------------------------+       +---------------------------+
| SenderTx.Nonce           | ----> | Nonce                     |
| SenderTx.GasFeeCap       | ----> | GasFeeCap                 |
| SenderTx.GasTipCap       | ----> | GasTipCap                 |
| SenderTx.Gas             | ----> | GasLimit                  |
| SenderTx.To              | ----> | To                        |
| SenderTx.Value           | ----> | Value                     |
| SenderTx.Data            | ----> | Data                      |
| SenderTx.AccessList      | ----> | AccessList                |
| FeePayer                 | ----> | FeePayer                  |
+--------------------------+       +---------------------------+
                                   | From = recovered sender   |
                                   | GasPrice = effectivePrice |
                                   +---------------------------+
```

`TransactionToMessage`는 발신자 서명(`V/R/S`)으로부터 `From` 주소를 복구하고, `tx.FeePayer()`를 통해 `FeePayer` 필드를 설정합니다.\
수수료 지불자의 서명은 가스 지불 승인에만 사용되며 `From` 주소 계산에는 사용되지 않습니다.

### 가스 구매(buyGas)

`buyGas`는 수수료 위임 시 분리된 잔액 검증과 차감을 수행합니다.

#### 잔액 검증 규칙

| Scenario              | FeePayer Balance Check              | Sender Balance Check                          |
| --------------------- | ----------------------------------- | --------------------------------------------- |
| Fee Delegation Active | ≥ `gasLimit × gasPrice` + blob fees | ≥ `value`                                     |
| Standard Transaction  | N/A                                 | ≥ `gasLimit × gasPrice` + `value` + blob fees |

## EVM 실행 컨텍스트

수수료 위임은 EVM 실행 의미론을 변경하지 않습니다.

* **tx.origin**: 항상 트랜잭션 발신자(`From`)
* **최상위 caller**: 트랜잭션 발신자
* **FeePayer**: EVM 컨텍스트에 노출되지 않음

이로 인해 스마트 컨트랙트는 수수료 위임 여부를 직접적으로 구분할 수 없습니다.

## 가스 환불 프로세스

트랜잭션 실행 후 미사용 가스는 실제 가스를 지불한 계정으로 환불됩니다.

```
func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
    refund := st.gasUsed() / refundQuotient
    if refund > st.state.GetRefund() {
        refund = st.state.GetRefund()
    }
    st.gasRemaining += refund
    remaining := uint256.NewInt(st.gasRemaining)
    remaining = remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
    if st.msg.FeePayer != nil {
        st.state.AddBalance(*st.msg.FeePayer, remaining)
    } else {
        st.state.AddBalance(st.msg.From, remaining)
    }
    st.gp.AddGas(st.gasRemaining)
    return refund
}
```

## Coinbase에 대한 수수료 지불

검증자는 실제 가스 사용량에 기반한 수수료를 수령합니다.\
수수료 지불은 상태 전환 후반부에서 수행되며, 수수료 지불자에 대한 블랙리스트 검증도 포함됩니다.

## 포크 활성화 요구사항

수수료 위임은 **Applepie 포크 이후에만 허용**됩니다.

```
if isFeeDelegation && !st.evm.ChainConfig().IsApplepie(st.evm.Context.BlockNumber) {
    return ErrTxTypeNotSupported
}
```

Applepie 이전 블록에서는 `preCheck` 단계에서 즉시 거부됩니다.

## RPC를 통한 트랜잭션 구성

### TransactionArgs 필드

| Field          | Type              | Purpose            |
| -------------- | ----------------- | ------------------ |
| `From`         | `*common.Address` | Transaction sender |
| `FeePayer`     | `*common.Address` | Optional fee payer |
| `V`,`R`,`S`    | `*hexutil.Big`    | Sender signature   |
| `FV`,`FR`,`FS` | raw tx only       | FeePayer signature |

### JSON 표현

```
{
  "type": "0x16",
  "chainId": "0x2052",
  "nonce": "0x1",
  "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
  "gas": "0x76c0",
  "maxFeePerGas": "0x9184e72a000",
  "maxPriorityFeePerGas": "0x5f5e100",
  "value": "0xde0b6b3a7640000",
  "input": "0x",
  "accessList": [],
  "v": "0x0",
  "r": "0x...",
  "s": "0x...",
  "feePayer": "0x407d73d8a49eeb85d32cf465507dd71d507100c1",
  "fv": "0x0",
  "fr": "0x...",
  "fs": "0x..."
}
```

## 주요 불변량

1. **잔액 격리**\
   FeePayer는 가스만, 발신자는 value만 부담합니다.
2. **Origin 보존**\
   `tx.origin`은 항상 발신자입니다.
3. **이중 동의**\
   두 계정 모두 서명해야 트랜잭션이 유효합니다.
4. **포크 게이트**\
   Applepie 활성화 이후에만 사용 가능합니다.
5. **환불 일관성**\
   미사용 가스는 실제 지불자에게 반환됩니다.
