Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

EIP-7702를 사용한 계정 추상화

이 가이드는 EIP-7702를 EOA에 적용하고 배치를 세 가지 패턴(일괄 결제, 지출 한도 및 세션 키)에 사용하는 방법을 설명합니다. EOA는 이 과정을 통해 주소와 개인 키를 유지합니다.

전제 조건

  • EOA와 스마트 계약 계정의 차이점에 대한 이해(EOA는 기본적으로 코드가 없음).
  • EVM 트랜잭션 유형(EIP-2718)에 대한 친숙함.

개요

EIP-7702는 authorizationList를 포함하는 새로운 트랜잭션 유형(0x04)을 도입합니다. 각 권한 부여는 EOA가 해당 트랜잭션에 대해 코드를 실행할 스마트 계약을 지정합니다. 흐름은 다음과 같습니다:

  1. 위임 계약 선택 또는 배포: EOA가 사용하려는 로직을 구현하는 표준 Solidity 계약입니다. 배포된 계약을 사용하거나 직접 배포할 수 있습니다. 가능한 경우 감사된 계약을 사용하십시오.
  2. 권한 부여 서명: EOA 소유자가 위임 계약을 승인하는 메시지에 서명합니다.
  3. EIP-7702 트랜잭션 제출: 트랜잭션에는 권한 부여가 포함되며, EOA는 실행 중에 위임자의 코드를 실행합니다.

사용 사례: 일괄 트랜잭션

아래 단계는 Multicall3를 위임 계약으로 사용하여 이 흐름을 설명합니다. Multicall3는 여러 호출을 단일 트랜잭션으로 집계하는 널리 배포된 유틸리티 계약입니다. Multicall3를 EIP-7702 위임자로 지정하면 EOA는 임의의 계약 상호 작용(토큰 전송, 승인, 계약 읽기 또는 모든 조합)을 하나의 원자적 트랜잭션으로 일괄 처리할 수 있습니다. 일괄 결제가 한 예입니다. 급여 실행을 위해 열 개의 별도 트랜잭션을 보내는 대신 EOA는 모든 트랜잭션을 한 번에 실행합니다.

1단계: Multicall3를 위임 계약으로 사용

Multicall3는 Stable의 0xcA11bde05977b3631167028862bE2a173976CA11에 배포되어 있습니다. 이미 배포되어 널리 사용되고 있으므로 직접 위임 계약을 배포할 필요가 없습니다. EIP-7702 권한 부여에 서명하면 위임자는 EOA에 대한 전체 실행 권한을 얻습니다.

// Multicall3 interface (relevant functions only)
interface IMulticall3 {
    struct Call3 {
        address target;
        bool allowFailure;
        bytes callData;
    }
    struct Result {
        bool success;
        bytes returnData;
    }
 
    /// @notice 여러 호출을 집계하여 각 호출이 독립적으로 성공 또는 실패하도록 허용합니다.
    function aggregate3(Call3[] calldata calls)
        external payable returns (Result[] memory returnData);
}

2단계: 권한 부여 서명

EOA 소유자는 위임 계약을 지정하는 권한 부여에 서명합니다. 이 권한 부여는 EIP-7702 트랜잭션에 포함됩니다.

// config.ts
import { ethers } from "ethers";
 
export const STABLE_TESTNET_RPC = "https://rpc.testnet.stable.xyz";
export const STABLE_TESTNET_CHAIN_ID = 2201;
export const USDT0_ADDRESS = "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9";
export const DELEGATE_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
 
export const provider = new ethers.JsonRpcProvider(STABLE_TESTNET_RPC);
export const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// signAuthorization.ts
import { ethers } from "ethers";
import { DELEGATE_ADDRESS, STABLE_TESTNET_CHAIN_ID, provider, wallet } from "./config";
 
export async function signAuthorization() {
  const authorization = {
    chainId: STABLE_TESTNET_CHAIN_ID,
    address: DELEGATE_ADDRESS,
    nonce: await provider.getTransactionCount(wallet.address),
  };
 
  return wallet.signAuthorization(authorization);
}

3단계: EIP-7702 트랜잭션 제출

권한 부여를 Multicall3.aggregate3 호출과 결합합니다. 이 예제는 단일 트랜잭션에서 세 개의 USDT0 전송을 일괄 처리합니다.

import { ethers } from "ethers";
import { wallet, USDT0_ADDRESS } from "./config";
import { signAuthorization } from "./signAuthorization";
 
const usdt0Interface = new ethers.Interface([
  "function transfer(address to, uint256 amount)",
]);
 
const batchInterface = new ethers.Interface([
  "function aggregate3((address target, bool allowFailure, bytes callData)[] calls) returns ((bool success, bytes returnData)[])",
]);
 
async function main() {
  const recipients = [
    { to: "0xAlice...", amount: ethers.parseUnits("100", 6) },
    { to: "0xBob...",   amount: ethers.parseUnits("200", 6) },
    { to: "0xCarol...", amount: ethers.parseUnits("150", 6) },
  ];
 
  const batchData = batchInterface.encodeFunctionData("aggregate3", [
    recipients.map(({ to, amount }) => ({
      target: USDT0_ADDRESS,
      allowFailure: false,
      callData: usdt0Interface.encodeFunctionData("transfer", [to, amount]),
    })),
  ]);
 
  const signedAuth = await signAuthorization();
 
  const tx = await wallet.sendTransaction({
    type: 4,                          // EIP-7702 트랜잭션 유형
    to: wallet.address,               // 호출은 EOA 자체로 향함
    data: batchData,                  // 실행할 aggregate3 호출
    authorizationList: [signedAuth],
    maxPriorityFeePerGas: 0n,
  });
 
  const receipt = await tx.wait(1);
  console.log("트랜잭션 일괄 처리됨:", receipt.hash);
}
일괄 트랜잭션이 tx에서 실행됨: 0x...

EOA는 Multicall3.aggregate3를 통해 세 가지 호출을 단일 원자적 트랜잭션으로 실행합니다. 위임은 명시적으로 변경되거나 지워질 때까지 유지됩니다. 이 예는 일괄 결제를 보여주지만, 동일한 패턴은 모든 종류의 계약 호출 조합에 적용됩니다.

사용 사례: 지출 한도

위임 계약은 계정 마이그레이션 없이 EOA에 대한 트랜잭션당 또는 일일 한도를 강제할 수 있습니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
/// @title SpendingLimitExecutor
/// @notice 일일 지출 한도를 강제하는 위임 계약
contract SpendingLimitExecutor {
    mapping(address => uint256) public dailyLimit;
    mapping(address => uint256) public spentToday;
    mapping(address => uint256) public lastResetDay;
 
    function setDailyLimit(uint256 limit) external {
        dailyLimit[msg.sender] = limit;
    }
 
    function executeWithLimit(
        address target,
        uint256 value,
        bytes calldata data
    ) external payable {
        uint256 today = block.timestamp / 1 days;
 
        if (today > lastResetDay[msg.sender]) {
            spentToday[msg.sender] = 0;
            lastResetDay[msg.sender] = today;
        }
 
        spentToday[msg.sender] += value;
        require(
            spentToday[msg.sender] <= dailyLimit[msg.sender],
            "일일 한도 초과"
        );
 
        (bool success,) = target.call{value: value}(data);
        require(success, "호출 실패");
    }
}

사용 사례: 세션 키

세션 키를 사용하면 dApp이 EOA를 대신하여 범위가 지정된 권한(시간 창 및 허용되는 대상 계약 세트) 내에서 트랜잭션을 실행할 수 있습니다. 이는 빈번한 온체인 상호 작용이 반복적인 지갑 승인을 필요로 하는 dApp에 유용합니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
/// @title SessionKeyExecutor
/// @notice 세션 키에 범위가 지정된 시간 제한 액세스 권한을 부여하는 위임 계약
contract SessionKeyExecutor {
    struct Session {
        address key;
        uint256 validUntil;
        uint256 spendingLimit;
        uint256 spent;
    }
 
    mapping(address => Session) public sessions;
    mapping(address => mapping(address => bool)) public allowedTargets;
 
    /// @notice 범위가 지정된 권한으로 세션 키 등록
    function startSession(
        address key,
        uint256 validUntil,
        uint256 spendingLimit,
        address[] calldata targets
    ) external {
        sessions[msg.sender] = Session({
            key: key,
            validUntil: validUntil,
            spendingLimit: spendingLimit,
            spent: 0
        });
 
        for (uint256 i = 0; i < targets.length; i++) {
            allowedTargets[msg.sender][targets[i]] = true;
        }
    }
 
    /// @notice 세션 키를 사용하여 호출 실행
    function executeAsSessionKey(
        address owner,
        address target,
        uint256 value,
        bytes calldata data
    ) external {
        Session storage session = sessions[owner];
 
        require(msg.sender == session.key, "세션 키가 아님");
        require(block.timestamp <= session.validUntil, "세션 만료됨");
        require(allowedTargets[owner][target], "대상이 허용되지 않음");
 
        uint256 beforeBalance = owner.balance;
 
        (bool success,) = target.call{value: value}(data);
        require(success, "호출 실패");
 
        session.spent += owner.balance - beforeBalance;
        require(session.spent <= session.spendingLimit, "예산 초과");
    }
 
    /// @notice 활성 세션 해지
    function revokeSession() external {
        delete sessions[msg.sender];
    }
}

중요한 고려 사항

  • 지속적인 위임: 위임은 EOA가 명시적으로 변경하거나 지울 때까지 유지됩니다. 단일 트랜잭션에 국한되지 않습니다.
  • 가스 비용: EIP-7702 트랜잭션은 권한 부여 처리로 인해 기본 가스 비용이 약간 높지만, 위임자가 여러 호출을 일괄 처리할 때 상쇄됩니다.
  • 감사된 위임자 사용: 악의적인 위임 계약은 EOA의 자산을 소진할 수 있습니다. 감사된 계약에만 위임하십시오.

주요 내용

  • EIP-7702는 EOA가 새 계정 유형으로 마이그레이션하지 않고도 스마트 계약 로직을 실행할 수 있도록 합니다.
  • Stable에서 EIP-7702는 기존 EOA에 일괄 결제, 지출 한도 및 범위가 지정된 세션 키를 활성화합니다.
  • 위임은 명시적으로 변경될 때까지 유지됩니다. 항상 감사된 위임 계약을 사용하십시오.

다음 권장 사항

  • 구독 및 수집: SubscriptionManager를 사용하여 EIP-7702를 정기 구독 결제에 적용합니다.
  • EIP-7702 개념: 위임 모델을 배포하기 전에 이해하십시오.
  • EIP-7702 참조: 0x04 트랜잭션 형식 및 권한 부여 필드를 찾아보십시오.