AVATAR (AVA) Whitepaper
v1.0.0Transparent Token Economy with On-Chain Vesting

5. Transfer Rules
AVA Token은 역할(Role) 기반 접근 제어를 통해 토큰 전송을 관리합니다. 각 역할에 따라 사용 가능한 전송 방식과 제한 조건이 다르며, 모든 전송은 컨트랙트 레벨에서 강제됩니다.
5.1 역할별 전송 권한 요약
| 역할 | 일반 전송transfer() |
베스팅 전송 (락업 토큰) |
그룹지갑 출금 (언락 토큰) |
소각 | 긴급 정지 |
|---|---|---|---|---|---|
| 일반 사용자 | ✓ 자유잔액 | ✓ 본인 락업 | ✗ | ✗ | ✗ |
| 그룹 지갑 | ✗ 차단 | ✗ 차단 | ✗ (Admin 필요) | ✗ (Admin 필요) | ✗ |
| Admin VESTING_MANAGER |
✓ 자유잔액 | ✓ 모든 주소 | ✓ | ✗ | ✗ |
| Admin BURNER |
✓ 자유잔액 | ✗ | ✗ | ✓ 조건부 | ✗ |
| Owner DEFAULT_ADMIN |
✓ 자유잔액 | ✓ 모든 주소 | ✓ | ✓ 조건부 | ✓ |
모든 전송은 컨트랙트가 Paused 상태일 경우 차단됩니다. Owner만 Pause/Unpause를 실행할 수 있습니다.
5.2 일반 사용자 (Regular User)
일반 사용자는 두 가지 전송 방식을 사용할 수 있습니다.
A. 일반 전송 — transfer() / transferFrom()
- 자유잔액(초기언락 + 선형해제 분)만 전송 가능
- 받는 지갑에 베스팅 조건 없음 — 자유 토큰으로 수령
- 락업잔액 전송 시도 시 트랜잭션 실패 (
InsufficientAvailableBalance) - 표준 ERC-20
approve/allowance메커니즘 사용 가능
B. 베스팅 전송 — vestingTransferBySender()
일반 사용자가 본인의 락업 토큰을 직접 다른 지갑으로 전송할 수 있는 함수입니다.
- 발신자는 항상
msg.sender(본인 지갑) — 타인의 토큰 전송 불가 - 락업잔액만 전송 가능 (
InsufficientLockedBalance) - 받는 지갑에 초기언락 0, 해당 그룹의 클리프 + 선형해제 조건 상속
- 1회 호출당 1개 그룹 / 1개 스케줄만 지정 가능
- 그룹 지갑은 호출 불가 (
GroupWalletNotAllowed)
vestingTransferBySender(group, to, amount, scheduleIndex)
자유잔액 = ERC20 잔액 - 총 락업잔액
총 락업잔액 = Σ (스케줄별 totalAmount - 언락된 수량)
언락 수량은 매일 UTC 00:00 기준으로 갱신됩니다.
5.3 그룹 지갑 (Group Wallet)
7개 베스팅 그룹의 대표 지갑(Foundation, Ecosystem, Marketing, Liquidity, TeamAdvisor, Partnership, VC)은 직접 토큰을 전송할 수 없습니다.
차단되는 동작
| 함수 | 상태 | 에러 |
|---|---|---|
transfer() | 차단 | GroupWalletTransferRestricted |
transferFrom() | 차단 | GroupWalletTransferRestricted |
vestingTransferBySender() | 차단 | GroupWalletNotAllowed |
Admin을 통한 전송
그룹 지갑의 토큰은 ADMIN_VESTING_MANAGER_ROLE을 가진 Admin(MultiSig)만 전송할 수 있습니다.
- 락업 토큰 전송:
vestingTransferByAdmin()— 베스팅 조건 상속 - 언락 토큰 전송:
groupWalletTransferByAdmin()— 자유 토큰으로 전송 - 소각:
burnByAdmin()— 그룹별 조건 충족 시 (6. Burn Policy 참조)
그룹 지갑은 대규모 토큰(총 발행량의 5~25%)을 보유합니다. 개인 키 유출로 인한 무단 전송을 방지하기 위해, 모든 그룹 지갑 출금은 Gnosis Safe MultiSig 서명이 필요한 Admin 권한을 통해서만 가능하도록 설계되었습니다.
5.4 관리자 전송 (Admin: ADMIN_VESTING_MANAGER_ROLE)
Admin은 베스팅 운영을 위한 3가지 전송 함수를 사용할 수 있습니다.
A. 베스팅 전송 — vestingTransferByAdmin()
임의의 주소 간 락업 토큰을 전송하고, 베스팅 조건을 상속합니다.
- 그룹 지갑 → 개인 지갑, 개인 → 개인 모두 가능
- 받는 지갑에 초기언락 0, 해당 그룹의 클리프 + 선형해제 조건 상속
- 몇 단계의 전송을 거치든 베스팅 조건이 유지됨 (A → B → C → D)
- 발신자의 해당 스케줄 잔액에서 차감, 수신자에게 새 스케줄 생성
vestingTransferByAdmin(group, from, to, amount, scheduleIndex)
B. 그룹지갑 언락 토큰 전송 — groupWalletTransferByAdmin()
그룹 지갑의 자유잔액(언락된 토큰)을 다른 지갑으로 전송합니다.
- 자유잔액만 전송 가능 (
InsufficientAvailableBalance) - 받는 지갑에 베스팅 조건 없음 — 자유 토큰으로 수령
- 그룹 지갑의
transfer()차단을 우회하여 Admin이 대신 실행
groupWalletTransferByAdmin(group, to, amount)
C. 초기 배분 — distributeToGroupByAdmin()
Deployer에서 그룹 지갑으로 최초 토큰을 배분합니다. 그룹당 1회만 실행 가능합니다.
- 그룹 설정(
setGroupConfigByAdmin)과 TGE 설정 이후에만 실행 가능 - 배분 수량은 해당 그룹의
totalAllocation이내 - 그룹 지갑에 베스팅 스케줄 생성 (초기언락 적용)
- 이미 배분된 그룹에 재배분 시도 시
GroupAlreadyDistributed
distributeToGroupByAdmin(group, from, amount)
Admin 실행 방식: EOA vs Safe{Wallet}
ADMIN_VESTING_MANAGER_ROLE은 EOA(개인 지갑) 또는 Safe{Wallet}(멀티시그)에 부여할 수 있으며, 어떤 주체가 Role을 보유하느냐에 따라 트랜잭션 실행 과정이 달라집니다. 컨트랙트 레벨에서는 두 방식 모두 동일한 hasRole() 검증을 통과합니다.
Case 1 — EOA가 Admin Role을 보유한 경우
단일 개인키로 서명하여 즉시 실행됩니다.
msg.sender= Admin EOA 주소- 트랜잭션 서명 즉시 체인에 전파 → 블록 포함 시 실행 완료
- 단일 키 유출 시 무단 실행 위험이 있어, 운영 환경에서는 Safe{Wallet} 사용을 권장
Case 2 — Safe{Wallet}이 Admin Role을 보유한 경우
Safe{Wallet}(Gnosis Safe)은 멀티시그 지갑으로, Role은 Safe 컨트랙트 주소에 부여됩니다. 실제 트랜잭션 제안은 Safe에 등록된 Delegate 또는 Signer(서명자)가 수행하며, 설정된 임계값(Threshold) 이상의 서명이 모여야 온체인에서 실행됩니다.
- Delegate가 Dashboard 접속 — Safe{Wallet} 앱 또는 WalletConnect를 통해 AVA Dashboard에 연결합니다. 이때 Delegate의 EOA가 연결되지만, 트랜잭션 호출 주체는 Safe 컨트랙트 주소입니다.
- 트랜잭션 제안 (Propose) — Delegate가 전송 폼에서 함수를 실행하면, Safe SDK가 트랜잭션을 가로채(intercept) Safe Transaction Service에 제안(Proposal)으로 등록합니다. 이 단계에서는 온체인 실행이 발생하지 않습니다.
- 서명자 승인 (Confirm) — Safe에 등록된 서명자(Signer)들이 Safe{Wallet} 앱에서 해당 제안을 검토하고 서명합니다. Threshold(예: 2/3, 3/5) 이상의 서명이 수집되어야 합니다.
- 온체인 실행 (Execute) — 임계값이 충족되면, 마지막 서명자 또는 별도의 실행자가 트랜잭션을 체인에 제출합니다. 이때
msg.sender는 Safe 컨트랙트 주소이며, 컨트랙트의hasRole(ADMIN_ROLE, msg.sender)검증을 통과합니다.
| 구분 | EOA Admin | Safe{Wallet} Admin |
|---|---|---|
| Role 보유자 | 개인 지갑 주소 | Safe 컨트랙트 주소 |
| 트랜잭션 제안 | 본인 직접 서명 | Delegate 또는 Signer가 Propose |
| 승인 과정 | 불필요 (즉시 실행) | Threshold 이상 서명 수집 필요 |
온체인 msg.sender |
EOA 주소 | Safe 컨트랙트 주소 |
| 실행 지연 | 없음 (블록 포함 즉시) | 서명 수집 시간 + 실행 트랜잭션 |
| 보안 수준 | 단일 키 의존 | 다중 서명 보호 |
AVA Token의 ADMIN_VESTING_MANAGER_ROLE은 Gnosis Safe 멀티시그에 부여되어 운영됩니다.
Safe에 등록된 Delegate가 AVA Dashboard에서 전송을 요청(Propose)하고, 지정된 서명자 합의(Threshold)를 거쳐 실행됩니다.
이를 통해 단일 개인의 임의 조작을 방지하고, 모든 Admin 작업에 투명한 감사 추적(Audit Trail)을 확보합니다.
5.5 Owner 제어 (DEFAULT_ADMIN_ROLE)
Owner는 모든 Admin 기능을 포함하며, 추가로 시스템 보안 제어 권한을 가집니다.
| 기능 | 함수 | Role | 설명 |
|---|---|---|---|
| 긴급 정지 | pauseByOwner() |
OWNER_PAUSER_ROLE | 모든 전송/베스팅 작업 즉시 중단 |
| 정지 해제 | unpauseByOwner() |
OWNER_PAUSER_ROLE | 정상 운영 재개 |
| 베스팅 컨트랙트 설정 | setVestingContractByOwner() |
DEFAULT_ADMIN_ROLE | 1회 설정, 변경 불가 |
| 컨트랙트 업그레이드 | _authorizeUpgrade() |
OWNER_UPGRADER_ROLE | UUPS 프록시 업그레이드 승인 |
| 권한 관리 | grantRole() / revokeRole() |
DEFAULT_ADMIN_ROLE | Admin 역할 부여/해제 |
- 차단됨: transfer, transferFrom, vestingTransferByAdmin, vestingTransferBySender, groupWalletTransferByAdmin, burnByAdmin
- 허용됨: 토큰 발행 (mint), 잔액 조회, 베스팅 조회 등 읽기 작업
- Pause는 AVAToken과 AVAVesting 두 컨트랙트에 독립적으로 적용됩니다.
5.6 전송 흐름도
락업 토큰 전송 경로
언락 토큰 전송 경로
5.7 멀티 스케줄 전송 예시
하나의 지갑이 여러 그룹에서 베스팅 토큰을 받은 경우, 락업 토큰 전송 시 1회 호출당 1개 그룹/1개 스케줄만 지정할 수 있습니다. 아래 예시를 통해 구체적인 전송 방법을 설명합니다.
지갑(A)가 3개 그룹에서 각각 베스팅 토큰을 수령한 상태입니다.
지갑(A)에서 지갑(B)로 250만 개를 전송하려 합니다.
* Admin은 vestingTransferByAdmin, 일반 사용자는 vestingTransferBySender를 사용합니다.
지갑(A)의 베스팅 스케줄 현황
| Index | 그룹 | 보유량 | 상태 |
|---|---|---|---|
| 0 | TeamAdvisor | 100만 AVA | 전량 락업 |
| 1 | Partnership | 200만 AVA | 전량 락업 |
| 2 | VC | 300만 AVA | 전량 락업 |
방법 A — 단일 그룹에서 1회 전송
VC 스케줄(300만)에서 250만을 한 번에 전송합니다.
// Admin 실행 vestingTransferByAdmin(VC, walletA, walletB, 2,500,000, 2)
// 또는 사용자(A) 직접 실행 vestingTransferBySender(VC, walletB, 2,500,000, 2)
| 지갑 | 그룹 | 변경 전 | 변경 후 | 상속 조건 |
|---|---|---|---|---|
| A | TeamAdvisor | 100만 | 100만 | 변경 없음 |
| A | Partnership | 200만 | 200만 | 변경 없음 |
| A | VC | 300만 | 50만 | 변경 없음 |
| B | VC | — | 250만 | VC cliff + 선형해제 상속 |
지갑(B)는 VC 그룹의 베스팅 조건을 상속받으며, 초기언락(Initial Unlock)은 0입니다.
방법 B — 복수 그룹에서 분할 전송
TeamAdvisor(100만) + Partnership(150만) = 250만을 2회에 나누어 전송합니다.
// 1차: TeamAdvisor 전량 vestingTransferBySender(TeamAdvisor, walletB, 1,000,000, 0)
// 2차: Partnership 일부 vestingTransferBySender(Partnership, walletB, 1,500,000, 1)
| 지갑 | 그룹 | 변경 전 | 변경 후 | 상속 조건 |
|---|---|---|---|---|
| A | TeamAdvisor | 100만 | 0 | 전량 이전 |
| A | Partnership | 200만 | 50만 | 변경 없음 |
| A | VC | 300만 | 300만 | 변경 없음 |
| B | TeamAdvisor | — | 100만 | TeamAdvisor cliff + 선형해제 |
| B | Partnership | — | 150만 | Partnership cliff + 선형해제 |
지갑(B)에 2개의 독립된 베스팅 스케줄이 생성되며, 각 스케줄은 해당 그룹의 조건을 독립적으로 따릅니다.
- 수신 지갑의 베스팅 스케줄은 최대 50개까지 생성 가능합니다. (
MaxSchedulesExceeded) - 수신 지갑의 초기언락(Initial Unlock)은 항상 0입니다. 오직 그룹 대표 지갑만 초기언락이 적용됩니다.
- 자기 자신에게 전송 불가 (
SelfTransferNotAllowed) - 전송 수량은 해당 스케줄의 락업잔액 이내여야 합니다. (
InsufficientLockedBalance) - 컨트랙트 Paused 상태에서는 모든 전송이 차단됩니다.