네, 테더(USDT)와 같은 ERC-20 기반 토큰을 만드실 때 transfertransferFrom의 차이를 이해하는 것은 매우 중요합니다. 두 함수 모두 토큰을 전송하지만, 누가, 어떤 권한으로 호출하느냐에 근본적인 차이가 있습니다.

아주 간단히 요약하면 다음과 같습니다.

  • transfer: **토큰 소유자(나)**가 직접 다른 사람에게 토큰을 보내는 함수입니다. (내 지갑 👉 다른 지갑)

  • transferFrom: **제3자(예: 스마트 컨트랙트)**가 토큰 소유자의 허락을 받고 대신 토큰을 보내는 함수입니다. (내 지갑 👉 [허락받은 제3자] 👉 다른 지갑)


💡 transfer: 직접 전송

transfer 함수는 가장 기본적인 토큰 전송 방식입니다.

  • 동작 원리: 함수를 호출하는 사람(msg.sender)의 잔액에서 토큰을 차감하고, 받는 사람(recipient)의 잔액에 더해줍니다.

  • 호출 주체: 토큰을 보내는 소유자 본인이 직접 호출해야 합니다.

  • 함수 형태: transfer(address recipient, uint256 amount)

  • 주요 사용 사례:

    • 개인 지갑(A)에서 다른 개인 지갑(B)으로 토큰을 보낼 때

    • 중앙화 거래소(CEX)에 입금하기 위해 내 지갑에서 거래소 지갑으로 토큰을 보낼 때

예시: 제가 제 지갑에 있는 100 USDT를 친구에게 직접 보내는 경우, transfer 함수를 호출합니다.


transferFrom: 위임 전송 (Delegated Transfer)

transferFrom 함수는 다른 주소(주로 스마트 컨트랙트)가 내 토큰을 대신 전송할 수 있도록 허용하는, 조금 더 복잡한 방식입니다. 이는 두 단계로 이루어집니다.

1단계: approve (승인)

  • 먼저 토큰 소유자가 approve 함수를 호출하여, 특정 주소(spender)가 내 자산 중 일정 금액(amount)을 대신 사용할 수 있도록 사전 허가를 해줘야 합니다.

  • 이 허가된 한도를 “allowance(허용량)“라고 부릅니다.

2단계: transferFrom (전송 실행)

  • approve로 권한을 부여받은 spendertransferFrom 함수를 호출하여, 원래 토큰 소유자(sender)의 지갑에서 최종 수신자(recipient)의 지갑으로 토큰을 전송합니다.

  • 호출 주체: approve로 권한을 위임받은 **제3자(Spender)**가 호출합니다.

  • 함수 형태: transferFrom(address sender, address recipient, uint256 amount)

  • 주요 사용 사례:

    • 탈중앙화 거래소(DEX)에서 토큰을 교환(swap)할 때: 사용자가 DEX 컨트랙트에게 내 토큰 A를 가져가서 토큰 B로 바꿔달라고 approve로 위임하면, DEX 컨트랙트가 transferFrom을 호출하여 내 지갑에서 토큰 A를 가져갑니다.

    • 디파이(DeFi) 서비스에 예치할 때: 랜딩 프로토콜이나 유동성 풀에 토큰을 예치할 때, 해당 컨트랙트가 내 토큰을 가져갈 수 있도록 approve 해줍니다.

예시: 유니스왑(DEX)에서 제 USDT를 이더리움(ETH)으로 바꾸고 싶을 때, 저는 먼저 유니스왑 컨트랙트에게 제 USDT 중 일정량을 가져갈 수 있도록 approve를 합니다. 그 후 스왑을 실행하면 유니스왑 컨트랙트가 transferFrom을 호출하여 제 지갑에서 USDT를 가져가고, 제게 ETH를 보내줍니다.


↔️ 한눈에 보는 비교

구분transfertransferFrom
기능토큰 소유자가 직접 전송제3자가 위임받아 대신 전송
호출 주체토큰 소유자 (msg.sender)권한을 위임받은 자 (spender)
사전 절차없음토큰 소유자의 approve 함수 호출이 반드시 필요
주요 사용 사례개인 간의 직접적인 송금스마트 컨트랙트와의 상호작용 (DEX, DeFi 등)

Sheets로 내보내기

샘플 코드

아래는 기본적인 ERC-20 컨트랙트에서 두 함수가 어떻게 구현되는지 보여주는 예시입니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
 
contract MyTether is ERC20 {
    constructor(uint256 initialSupply) ERC20("MyTether", "mUSDT") {
        _mint(msg.sender, initialSupply);
    }
 
    // transfer 함수는 OpenZeppelin의 ERC20.sol에 이미 구현되어 있습니다.
    // function transfer(address to, uint256 amount) public virtual override returns (bool) {
    //     address owner = _msgSender();
    //     _transfer(owner, to, amount);
    //     return true;
    // }
 
    // transferFrom 함수도 OpenZeppelin의 ERC20.sol에 이미 구현되어 있습니다.
    // function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
    //     address spender = _msgSender();
    //     // spender가 from의 토큰을 amount만큼 사용할 수 있도록 허가(allowance) 받았는지 확인
    //     _spendAllowance(from, spender, amount);
    //     _transfer(from, to, amount);
    //     return true;
    // }
 
    // approve 함수도 마찬가지로 이미 구현되어 있습니다.
    // function approve(address spender, uint256 amount) public virtual override returns (bool) {
    //     address owner = _msgSender();
    //     _approve(owner, spender, amount);
    //     return true;
    // }
}
 

참고: 실제 개발에서는 보안과 표준 준수를 위해 OpenZeppelin 같은 검증된 라이브러리의 ERC20 구현을 그대로 사용하는 것이 일반적입니다. 위 코드처럼 상속받기만 하면 transfer, transferFrom, approve 등의 핵심 기능이 모두 안전하게 구현됩니다.

결론적으로 transfertransferFrom의 구분은 다른 스마트 컨트랙트와 안전하게 상호작용하기 위한 핵심적인 설계입니다. 이 덕분에 사용자는 자신의 자산을 컨트랙트에 직접 보내지 않고도, 필요한 권한만 잠시 위임하여 다양한 디파이 서비스를 이용할 수 있습니다.