A decentralized red packet (lucky money) distribution system built on Ethereum using Chainlink VRF for verifiable random distribution. This contract allows users to create and claim digital red packets with either ETH or any ERC20 token.
- Two Distribution Modes:
- Random Mode: Recipients receive random amounts (using Chainlink VRF)
- Fixed Mode: Recipients receive equal amounts
- Token Support:
- Native ETH
- Any ERC20 token
- Security & Reliability:
- Signature-based claiming mechanism
- Time-limited availability (24-hour expiration)
- Refund mechanism for unclaimed funds
- UUPS upgradeable contract pattern
- Chainlink VRF for verifiable randomness
- RedPacket.sol: Main contract implementing the red packet functionality
- Deployment Scripts:
DeployRedPacket.s.sol
: Deploys implementation and proxy contractsUpgradeRedPacket.s.sol
: Upgrades the implementation contractUpdateSigner.s.sol
: Updates the signature verification address
- Foundry development environment
- Chainlink VRF subscription
- Access to Ethereum RPC endpoints
$ forge install
Create a .env
file with the following variables:
OWNER_ADDRESS=<your-wallet-address>
VRF_COORDINATOR=<chainlink-vrf-coordinator-address>
VRF_KEY_HASH=<vrf-key-hash-for-your-network>
VRF_SUBSCRIPTION_ID=<your-subscription-id>
SIGNER_ADDRESS=<address-for-verifying-signatures>
$ source .env
$ forge script script/deploy/DeployRedPacket.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast
$ export PROXY_ADDRESS=<your-deployed-proxy-address>
$ forge script script/deploy/UpgradeRedPacket.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast
$ export REDPACKET_ADDRESS=<your-deployed-proxy-address>
$ export NEW_SIGNER=<new-signer-address>
$ forge script script/deploy/UpdateSigner.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast
To create a red packet, you need to:
- Generate a unique packet ID
- Specify the token (ETH or ERC20)
- Define total amount and number of packets
- Choose distribution mode (random or fixed)
ETH Red Packet Example:
// Generate a unique packet ID
bytes32 packetId = keccak256(abi.encodePacked("my-unique-id", block.timestamp, msg.sender));
// Create ETH red packet with 1 ETH divided into 10 random packets
redPacket.createRedPacket{value: 1 ether}(
packetId,
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, // ETH address constant
1 ether,
10,
RedPacket.ClaimMode.RANDOM
);
ERC20 Red Packet Example:
// First approve the contract to spend your tokens
IERC20(tokenAddress).approve(address(redPacket), tokenAmount);
// Create token red packet with fixed distribution
redPacket.createRedPacket(
packetId,
tokenAddress,
tokenAmount,
5,
RedPacket.ClaimMode.FIXED
);
This contract uses a signature-based claiming mechanism (similar to a password red packet system) to prevent bot attacks and ensure only authorized users can claim red packets:
-
Setup: During contract deployment, a signer address is specified. This address corresponds to a private key that will be used to generate signatures.
-
Backend Infrastructure:
- The signer's private key should be securely stored on a centralized backend server
- Users request a signature from this backend server to claim a red packet
- The backend can enforce business rules (e.g., KYC verification, rate limiting) before issuing signatures
-
Signature Generation Process:
- The backend signs a message containing:
keccak256(abi.encodePacked(userAddress, packetId, chainId))
- This creates a unique signature that links a specific user to a specific red packet on a specific chain
- The signature can only be used once as the contract tracks claims
- The backend signs a message containing:
-
Security Benefits:
- Prevents bot attacks and sybil attacks
- Enables control over who can claim red packets
- Allows for custom distribution strategies through backend logic
-
Updating the Signer: The contract owner can update the signer address if needed using the
updateSigner
function.
Users need a valid signature to claim a red packet:
-
Generate signature off-chain by signing:
keccak256(abi.encodePacked(userAddress, packetId, chainId))
-
Call the claim function:
redPacket.claimRedPacket(packetId, signature);
After the 24-hour expiry period, creators can recover unclaimed funds:
redPacket.refundExpiredPackets(packetId);
Run the complete test suite:
$ forge test
With gas reporting:
$ forge test --gas-report
$ forge build
$ forge fmt
$ forge snapshot
$ anvil
$ forge --help
$ anvil --help
$ cast --help
- The random distribution relies on Chainlink VRF for verifiable randomness
- Signature verification prevents unauthorized claims
- The contract follows best security practices:
- Checks-Effects-Interactions pattern
- Protection against re-entrancy
- Proper validation of user inputs
- Prevention of multiple claims by the same user
MIT