네, 테더(USDT)와 같은 ERC-20 기반 토큰을 만드실 때 transfer와 transferFrom의 차이를 이해하는 것은 매우 중요합니다. 두 함수 모두 토큰을 전송하지만, 누가, 어떤 권한으로 호출하느냐에 근본적인 차이가 있습니다.
아주 간단히 요약하면 다음과 같습니다.
-
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로 권한을 부여받은spender가transferFrom함수를 호출하여, 원래 토큰 소유자(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를 보내줍니다.
↔️ 한눈에 보는 비교
| 구분 | transfer | transferFrom |
|---|---|---|
| 기능 | 토큰 소유자가 직접 전송 | 제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등의 핵심 기능이 모두 안전하게 구현됩니다.
결론적으로 transfer와 transferFrom의 구분은 다른 스마트 컨트랙트와 안전하게 상호작용하기 위한 핵심적인 설계입니다. 이 덕분에 사용자는 자신의 자산을 컨트랙트에 직접 보내지 않고도, 필요한 권한만 잠시 위임하여 다양한 디파이 서비스를 이용할 수 있습니다.