Giveaway Skills
by @franckstone
Call guide and best practices for the BSC on-chain giveaway contract based on contracts/contracts/Giveaway.sol, including contract address, core method signa...
clawhub install giveaway-skillsπ About This Skill
name: giveaway-skills display_name: BSC Giveaway Contract Call Skill description: > Call guide and best practices for the BSC on-chain giveaway contract based on contracts/contracts/Giveaway.sol, including contract address, core method signatures, parameter meanings, and conventions for calling the contract from scripts, frontends, or OpenClow workflows via ethers/web3. Use this skill when you need to create giveaways, claim giveaways, manage whitelists, or withdraw expired giveaways on BSC. author: frank version: 0.1.0 language: en tags: - bsc - evm - giveaway - contract-call
BSC Giveaway Contract Call Skill
1. Deployment information
contracts/contracts/Giveaway.sol0xc9Db158004fEFe15633eF2Ac3C3eA209e58Db5B9IERC20, Ownable, ReentrancyGuard, internal TransferHelper libraryAssumptions when calling:
https://bsc-dataseed.binance.org)2. Core enums and structs (pass as uint in calls)
DistributionType
0 = AVERAGE: equal share, each claim gets amount / count1 = RANDOM: random share, a single claim is roughly in the range \(0 \sim 2 \times\) the averageClaimRestriction
0 = PUBLIC: public, no additional restriction1 = TOKEN_HOLDER: only addresses holding restrictionToken with balance β₯ minTokenBalance2 = WHITELIST: only whitelisted addresses can claimGiveawayInfo
token: token address for the giveaway, address(0) means BNBsender: creator addressamount: current remaining total giveaway amount in the contract (after creation fee is deducted)count: current remaining claimable slotsdistributionType: distribution type (0/1)restriction: claim restriction type (0/1/2)restrictionToken: restriction token address (only meaningful for TOKEN_HOLDER)minTokenBalance: minimum token balance requiredlastDate: expiration timestamp (seconds)3. Write function cheatsheet
3.1 Create giveaway createGiveaway
Signature:
function createGiveaway(
address token,
uint256 amount,
uint256 count,
DistributionType distributionType,
ClaimRestriction restriction,
address restrictionToken,
uint256 minTokenBalance,
string memory content,
uint256 lastDate
) public payable
Key constraints:
bytes(content).length < 128amount > 0amount / count > 0distributionType β {0,1}restriction β {0,1,2}restriction == TOKEN_HOLDER (1):restrictionToken != address(0)
- minTokenBalance > 0Fees and transfers:
feeAmount = (amount / 1000) * FEERATE, sent directly to FEEADDRESSsendAmount = amount - feeAmounttoken == address(0) (BNB giveaway):msg.value >= amount
- feeAmount and sendAmount are both paid in BNB
token != address(0) (ERC20 giveaway):msg.value == 0
- You must first approve on-chain:
- approve(contract, amount) (user β contract)
- The contract internally uses safeTransferFrom to send feeAmount to FEEADDRESS and sendAmount to the contract itself3.2 Claim giveaway claimGiveaway
function claimGiveaway(uint256 id) public nonReentrant
Internal checks:
giveawayInfos[id].amount != 0: giveaway exists and has remaining amountgiveawayInfo_exist[id][msg.sender] == 0giveawayInfos[id].lastDate > block.timestampIERC20(restrictionToken).balanceOf(msg.sender) >= minTokenBalancegiveawayWhitelist[id][msg.sender] == trueDistribution logic:
count == 1: send all remaining amount to the current claimer
- Otherwise, single claim amount is sendAmount = amount / count
count == 1: also send all remaining amount
- Otherwise:
- randomNumber = uint8(keccak256(...)) % 100
- sendAmount = (amount / count * 2) * randomNumber / 1003.3 Withdraw expired giveaway withdrawExpiredGiveaway
function withdrawExpiredGiveaway(uint256 id) public nonReentrant
Constraints:
giveawayInfos[id].amount != 0giveawayInfos[id].sender == msg.sendergiveawayInfos[id].lastDate < block.timestampWithdrawal fee:
feeAmount = (amount / 1000) * EXPIREDRATEsendAmount = amount - feeAmount3.4 Whitelist management
function addToWhitelist(uint256 giveawayId, address[] memory addresses) public
function removeFromWhitelist(uint256 giveawayId, address[] memory addresses) public
Constraints:
giveawayInfos[giveawayId].sender == msg.senderrestriction == ClaimRestriction.WHITELIST4. Read function cheatsheet
function getGiveawayInfo(uint256 id) external view returns (GiveawayInfo memory)
function isInWhitelist(uint256 giveawayId, address user) external view returns (bool)
function canClaim(uint256 id, address user) external view returns (bool)
Key points of canClaim:
false if amount is 0, already claimed, or block.timestamp >= lastDatetrue (PUBLIC)5. Script / frontend usage example (ethers.js)
Assume you already have a provider / signer and have loaded the compiled ABI:
import { ethers } from "ethers";
import GiveawayAbi from "../artifacts/contracts/Giveaway.sol/Giveaway.json";const GIVEAWAY_ADDRESS = "0xc9Db158004fEFe15633eF2Ac3C3eA209e58Db5B9";
// Create contract instance (read or write)
export function getGiveawayContract(providerOrSigner: ethers.Signer | ethers.providers.Provider) {
return new ethers.Contract(GIVEAWAY_ADDRESS, GiveawayAbi.abi, providerOrSigner);
}
// Example: create a BNB giveaway
export async function createBnbGiveaway(
signer: ethers.Signer,
params: {
amountWei: ethers.BigNumberish;
count: number;
distributionType: 0 | 1;
restriction: 0 | 1 | 2;
restrictionToken: string;
minTokenBalance: ethers.BigNumberish;
content: string;
lastDate: number;
}
) {
const contract = getGiveawayContract(signer);
const tx = await contract.createGiveaway(
ethers.constants.AddressZero,
params.amountWei,
params.count,
params.distributionType,
params.restriction,
params.restrictionToken,
params.minTokenBalance,
params.content,
params.lastDate,
{ value: params.amountWei }
);
return tx.wait();
}
// Example: claim a giveaway
export async function claimGiveaway(signer: ethers.Signer, id: number) {
const contract = getGiveawayContract(signer);
const tx = await contract.claimGiveaway(id);
return tx.wait();
}
When integrating in a frontend or script:
getGiveawayInfo, canClaim, isInWhitelist) can use a provider instance;createGiveaway, claimGiveaway, withdrawExpiredGiveaway, whitelist add/remove) must use a signer with signing capability;amount via value; for ERC20 giveaways you must approve beforehand.7. ABI information and minimal examples
contracts/contracts/Giveaway.sol in this repo (for example artifacts/.../Giveaway.json); in scripts/frontends you should generally import the abi field from that file.7.1 Key event fragments
[
{
"type": "event",
"name": "GiveawayCreated",
"anonymous": false,
"inputs": [
{ "indexed": false, "name": "id", "type": "uint256" },
{ "indexed": false, "name": "token", "type": "address" },
{ "indexed": false, "name": "sender", "type": "address" },
{ "indexed": false, "name": "amount", "type": "uint256" },
{ "indexed": false, "name": "count", "type": "uint256" },
{ "indexed": false, "name": "distributionType", "type": "uint8" },
{ "indexed": false, "name": "restriction", "type": "uint8" },
{ "indexed": false, "name": "restrictionToken", "type": "address" },
{ "indexed": false, "name": "minTokenBalance", "type": "uint256" },
{ "indexed": false, "name": "content", "type": "string" },
{ "indexed": false, "name": "lastDate", "type": "uint256" }
]
},
{
"type": "event",
"name": "GiveawayClaimed",
"anonymous": false,
"inputs": [
{ "indexed": false, "name": "id", "type": "uint256" },
{ "indexed": false, "name": "sender", "type": "address" },
{ "indexed": false, "name": "count", "type": "uint256" },
{ "indexed": false, "name": "amount", "type": "uint256" }
]
},
{
"type": "event",
"name": "GiveawayWithdrawn",
"anonymous": false,
"inputs": [
{ "indexed": false, "name": "id", "type": "uint256" }
]
},
{
"type": "event",
"name": "WhitelistAdded",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "giveawayId", "type": "uint256" },
{ "indexed": false, "name": "addresses", "type": "address[]" }
]
},
{
"type": "event",
"name": "WhitelistRemoved",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "giveawayId", "type": "uint256" },
{ "indexed": false, "name": "addresses", "type": "address[]" }
]
}
]
7.2 Common function fragments
> Only the most common read/write function signatures are listed below, to make it easy to construct ethers.Contract / web3.eth.Contract. For the full ABI, always refer to the build artifacts.
[
{
"type": "function",
"stateMutability": "payable",
"name": "createGiveaway",
"inputs": [
{ "name": "token", "type": "address" },
{ "name": "amount", "type": "uint256" },
{ "name": "count", "type": "uint256" },
{ "name": "distributionType", "type": "uint8" },
{ "name": "restriction", "type": "uint8" },
{ "name": "restrictionToken", "type": "address" },
{ "name": "minTokenBalance", "type": "uint256" },
{ "name": "content", "type": "string" },
{ "name": "lastDate", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "claimGiveaway",
"inputs": [
{ "name": "id", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "withdrawExpiredGiveaway",
"inputs": [
{ "name": "id", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "addToWhitelist",
"inputs": [
{ "name": "giveawayId", "type": "uint256" },
{ "name": "addresses", "type": "address[]" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "removeFromWhitelist",
"inputs": [
{ "name": "giveawayId", "type": "uint256" },
{ "name": "addresses", "type": "address[]" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "view",
"name": "getGiveawayInfo",
"inputs": [
{ "name": "id", "type": "uint256" }
],
"outputs": [
{
"components": [
{ "name": "token", "type": "address" },
{ "name": "sender", "type": "address" },
{ "name": "amount", "type": "uint256" },
{ "name": "count", "type": "uint256" },
{ "name": "distributionType", "type": "uint8" },
{ "name": "restriction", "type": "uint8" },
{ "name": "restrictionToken", "type": "address" },
{ "name": "minTokenBalance", "type": "uint256" },
{ "name": "lastDate", "type": "uint256" }
],
"type": "tuple"
}
]
},
{
"type": "function",
"stateMutability": "view",
"name": "canClaim",
"inputs": [
{ "name": "id", "type": "uint256" },
{ "name": "user", "type": "address" }
],
"outputs": [
{ "type": "bool" }
]
},
{
"type": "function",
"stateMutability": "view",
"name": "isInWhitelist",
"inputs": [
{ "name": "giveawayId", "type": "uint256" },
{ "name": "user", "type": "address" }
],
"outputs": [
{ "type": "bool" }
]
}
]
The fragments above can be combined with the TypeScript example earlier, for example:
const abi = [...eventsFragment, ...functionsFragment];
const contract = new ethers.Contract(GIVEAWAY_ADDRESS, abi, signerOrProvider);
6. Relationship with existing giveaway-protocol skill
giveaway-protocol focuses more on the protocol-level description (enum semantics, logical constraints, general call conventions);giveaway-skills is specifically about this concrete contract deployed on BSC mainnet (fixed contract address + specific network info + call examples).When you need βthe contract address and direct on-chain interactionβ, you should prefer this skill.
If you need more complete error descriptions or protocol details, you can also refer to giveaway-protocolβs reference.md.