@bbuilders/djeon402-contracts
v2.0.2
Published
DONGJEON402 (DJEON402) - Enterprise-grade gasless payment token contracts with EIP-3009 (x402) support
Downloads
53
Maintainers
Readme
@bbuilders/djeon402-contracts
DONGJEON402 (DJEON402) - Enterprise-grade gasless payment token SDK with x402 protocol support
Build your own x402-compatible payment token in minutes by importing DONGJEON402.sol
📖 Table of Contents
🎯 What is DONGJEON402?
DONGJEON402 (also known as DJEON402) is a production-ready smart contract SDK for building gasless payment tokens that support the x402 protocol (HTTP 402 Payment Required).
Why DONGJEON402?
- Zero Gas Fees: Users can transfer tokens without holding native ETH
- Enterprise Ready: Role-based access control, compliance features, upgradeable
- Developer Friendly: Simple inheritance pattern, extensive examples
- Battle Tested: Based on OpenZeppelin contracts with additional security layers
x402 Protocol Support
The x402 protocol enables HTTP 402 Payment Required responses to trigger on-chain payments using EIP-3009 (TransferWithAuthorization), allowing gasless, off-chain authorized transfers.
✨ Features
Core Token Features
- ✅ ERC-20 - Standard token interface
- ✅ ERC-2612 Permit - Gasless approvals via signatures
- ✅ EIP-3009 - TransferWithAuthorization for x402 compatibility
- ✅ 18 Decimals - Standard ERC-20 decimal precision
Enterprise Features
- ✅ Role-Based Access Control (RBAC) - 6 roles: Admin, Minter, Burner, Pauser, Blacklister, KYC Manager
- ✅ Blacklist - Compliance and regulatory requirements
- ✅ Pausable - Emergency stop mechanism
- ✅ KYC Registry - Optional 5-tier KYC level system with configurable daily limits
- ✅ UUPS Upgradeable - Safely upgrade contract logic
- ✅ Diamond-Ready Storage - Isolated storage slots for future EIP-2535 migration
🚀 Installation
Using npm (Recommended)
npm install @bbuilders/djeon402-contractsUsing Foundry
Note: Private repo인 경우 GitHub 인증(PAT 토큰)이 필요합니다.
forge install b-builders/dongjeon402-contract-sdkAdd to your remappings.txt:
@bbuilders/djeon402-contracts/=lib/dongjeon402-contract-sdk/🎯 Quick Start
1. Simplest Token (Just Inherit)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@bbuilders/djeon402-contracts/src/DONGJEON402.sol";
contract MyToken is DONGJEON402 {
// That's it! All features inherited:
// - ERC-20 with 18 decimals
// - Gasless transfers (EIP-3009)
// - Permit (ERC-2612)
// - RBAC, Blacklist, Pausable
// - UUPS upgradeable
}2. Deploy with Proxy
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@bbuilders/djeon402-contracts/src/DONGJEON402.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract DeployMyToken {
function deploy() external returns (address) {
// Step 1: Deploy implementation
DONGJEON402 implementation = new DONGJEON402();
// Step 2: Encode initialization data
bytes memory initData = abi.encodeWithSelector(
DONGJEON402.initialize.selector,
msg.sender, // admin address
"My Token", // token name
"MTK" // token symbol
);
// Step 3: Deploy proxy
ERC1967Proxy proxy = new ERC1967Proxy(
address(implementation),
initData
);
// Step 4: Return proxy address (this is your token address)
return address(proxy);
}
}3. Use the Token
// Wrap proxy as DONGJEON402 interface
DONGJEON402 token = DONGJEON402(proxyAddress);
// Mint tokens (requires MINTER_ROLE)
token.mint(user, 1000 * 10**18); // 1000 tokens (18 decimals)
// Standard ERC-20 transfers
token.transfer(recipient, 100 * 10**18);
// Gasless transfer with signature (x402 compatible)
token.transferWithAuthorization(
from,
to,
amount,
validAfter,
validBefore,
nonce,
v, r, s
);
// Pause in emergency (requires PAUSER_ROLE)
token.pause();📚 Examples
See the examples/ directory for complete working examples:
1. BasicToken.sol
The simplest possible implementation - just inherit DONGJEON402.
import "@bbuilders/djeon402-contracts/src/DONGJEON402.sol";
contract BasicToken is DONGJEON402 {
// Ready to use!
}2. CustomToken.sol ⭐ Recommended for Custom Tokens
Deploy custom tokens with configurable name, symbol, and initial supply.
CustomTokenDeployer deployer = new CustomTokenDeployer();
// Deploy fully custom token
address token = deployer.deployCustomToken(
admin,
"My Token", // custom name
"MTK", // custom symbol
1000000 // 1M tokens initial supply
);
// Or use pre-configured examples
address usds = deployer.deployUSDStablecoin(admin); // USD Stablecoin
address krws = deployer.deployKRWStablecoin(admin); // Korean Won
address eurs = deployer.deployEuroStablecoin(admin); // Euro3. StablecoinWithInitialSupply.sol
Mint initial supply during deployment.
contract MyStablecoin is DONGJEON402 {
function initializeWithSupply(
address admin,
string memory name,
string memory symbol,
uint256 initialSupply
) public initializer {
initialize(admin, name, symbol);
_mint(admin, initialSupply);
}
}4. RewardToken.sol
Automatically give 1% rewards on every transfer.
contract RewardToken is DONGJEON402 {
uint256 public rewardRate = 100; // 1%
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override {
super._afterTokenTransfer(from, to, amount);
if (from != address(0) && to != address(0)) {
uint256 reward = (amount * rewardRate) / 10000;
if (reward > 0) _mint(to, reward);
}
}
}5. DeployExample.sol
Complete deployment example with UUPS proxy pattern.
See examples/ directory for all examples and examples/README.md for detailed usage instructions.
📖 API Reference
Core Functions
initialize(address admin, string name, string symbol)
Initialize the contract (called once during deployment via proxy).
function initialize(
address admin,
string memory name,
string memory symbol
) external initializerStandard ERC-20
function name() external view returns (string)
function symbol() external view returns (string)
function decimals() external view returns (uint8) // Returns 18
function totalSupply() external view returns (uint256)
function balanceOf(address account) external view returns (uint256)
function transfer(address to, uint256 amount) external returns (bool)
function approve(address spender, uint256 amount) external returns (bool)
function transferFrom(address from, address to, uint256 amount) external returns (bool)
function allowance(address owner, address spender) external view returns (uint256)ERC-2612 Permit (Gasless Approvals)
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) externalEIP-3009 (x402 Protocol)
// Anyone can execute (relayer pays gas)
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external
// Only the receiver (to == msg.sender) can execute
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external
// Cancel an unused authorization (marks nonce as used)
function cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external
// Check if a nonce has been used
function authorizationState(
address authorizer,
bytes32 nonce
) external view returns (bool)Admin Functions
Minting & Burning
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE)
function burn(uint256 amount) external onlyRole(BURNER_ROLE)Pause/Unpause
function pause() external onlyRole(PAUSER_ROLE)
function unpause() external onlyRole(PAUSER_ROLE)
function paused() external view returns (bool)Blacklist
function blacklist(address account) external onlyRole(BLACKLISTER_ROLE)
function unBlacklist(address account) external onlyRole(BLACKLISTER_ROLE)
function isBlacklisted(address account) external view returns (bool)Role Management
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant BLACKLISTER_ROLE = keccak256("BLACKLISTER_ROLE");
bytes32 public constant KYC_MANAGER_ROLE = keccak256("KYC_MANAGER_ROLE");
function hasRole(bytes32 role, address account) external view returns (bool)
function grantRole(bytes32 role, address account) external onlyRole(DEFAULT_ADMIN_ROLE)
function revokeRole(bytes32 role, address account) external onlyRole(DEFAULT_ADMIN_ROLE)
function renounceRole(bytes32 role, address account) externalKYC Registry
KYC Levels:
| Level | Name | Default Daily Limit | |-------|-------|-------------------| | 0 | None | $0 (no access) | | 1 | Tier1 | $1,000 | | 2 | Tier2 | $10,000 | | 3 | Tier3 | $1,000,000 | | 4 | Tier4 | Unlimited |
Daily limits are configurable by admin via
setLevelLimit(). Defaults are set on deployment.
// KYC Management (admin only)
function verifyKYC(address user, KYCLevel level, uint256 expiryDate, string kycHash) external onlyKYCAdmin
function updateKYC(address user, KYCLevel level, uint256 expiryDate) external onlyKYCAdmin
function revokeKYC(address user) external onlyKYCAdmin
// Level Limit Configuration (admin only)
function setLevelLimit(KYCLevel level, uint256 newLimit) external onlyKYCAdmin
function setDailyLimit(address user, uint256 newLimit) external onlyKYCAdmin
// View Functions
function getLevelLimit(KYCLevel level) external view returns (uint256)
function getKYCData(address user) external view returns (KYCLevel, uint256, string, bool, uint256, uint256)
function getKYCLevel(address user) external view returns (KYCLevel)
function isKYCValid(address user) external view returns (bool)
function getRemainingDailyLimit(address user) external view returns (uint256)
function checkDailyLimit(address user, uint256 amount) external returns (bool)Upgradeability (UUPS)
function upgradeToAndCall(address newImplementation, bytes data) external onlyRole(DEFAULT_ADMIN_ROLE)
function proxiableUUID() external view returns (bytes32)🏗️ Architecture
Storage Layout (Diamond-Ready)
DONGJEON402 uses isolated storage slots to enable future migration to EIP-2535 (Diamond Standard):
src/
├── DONGJEON402.sol # Main contract
├── storage/
│ ├── TokenStorage.sol # balances, allowances, supply
│ ├── PermitStorage.sol # nonces, DOMAIN_SEPARATOR
│ ├── AuthorizationStorage.sol # authorization states
│ ├── AccessStorage.sol # roles
│ ├── BlacklistStorage.sol # blacklisted addresses
│ ├── PausableStorage.sol # paused state
│ └── KYCStorage.sol # KYC levels
└── interfaces/
└── IERC3009.sol # EIP-3009 interfaceEach storage contract defines its own namespace to prevent collisions:
library TokenStorage {
bytes32 constant STORAGE_SLOT = keccak256("dongjeon402.storage.token");
struct Layout {
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowances;
uint256 totalSupply;
string name;
string symbol;
}
}Proxy Pattern (UUPS)
User → ERC1967Proxy → DONGJEON402 Implementation
↓
Storage (in Proxy)- All state is stored in the proxy contract
- Implementation contract contains logic only
- Upgrades change the implementation address in the proxy
- User always interacts with the same proxy address
🔒 Security
Audits
- Based on OpenZeppelin Contracts (industry standard)
- Custom storage patterns reviewed for upgrade safety
- UUPS upgrade pattern prevents unauthorized upgrades
Best Practices
- Always deploy via proxy - Direct deployment of DONGJEON402 should only be used as implementation
- Secure admin keys - Admin role can upgrade contract, mint, pause
- Test upgrades - Use
forge testto verify upgrade compatibility - Monitor events - All critical actions emit events
- Use multi-sig - Consider using Gnosis Safe for admin role
Known Limitations
- 18 decimals (standard ERC-20, set during initialization)
- UUPS upgrades require admin role
- Blacklisted addresses cannot transfer or receive
🛠️ Development
Build
forge buildTest
forge testDeploy
Refer to the examples/DeployExample.sol for deployment patterns, or create your own deployment script:
# Create your deployment script
forge script script/YourDeploy.s.sol --rpc-url <RPC_URL> --broadcast📦 Package Contents
dongjeon402-contract-sdk/
├── src/
│ ├── DONGJEON402.sol # Main contract
│ ├── KYCRegistry.sol # Optional KYC registry
│ ├── interfaces/ # Contract interfaces
│ │ └── IERC3009.sol
│ └── storage/ # Diamond-ready storage (7 files)
├── examples/ # 5 usage examples
│ ├── BasicToken.sol
│ ├── CustomToken.sol # ⭐ NEW - Custom tokens
│ ├── StablecoinWithInitialSupply.sol
│ ├── RewardToken.sol
│ └── DeployExample.sol
├── lib/ # Foundry dependencies
├── foundry.toml # Foundry config
├── remappings.txt # Import remappings
└── package.json # npm config🤝 Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure
forge testpasses - Submit a pull request
📄 License
MIT License - see LICENSE file for details.
🔗 Links
- GitHub: (Update with your repository URL)
- Examples: ./examples/
- NPM: @bbuilders/djeon402-contracts (when published)
Note: Update URLs in package.json before publishing
📦 Related Packages
- @bbuilders/djeon402-core - Core types and utilities
- @bbuilders/djeon402-sdk-node - Node.js/Backend SDK
- @bbuilders/djeon402-sdk-client - Browser/Frontend SDK
💬 Support
- Issues: Create issues in your repository
- Documentation: See examples/README.md for usage guides
Built with ❤️ for the x402 protocol ecosystem
