Install
openclaw skills install giveaway-skillsCall guide and best practices for the BSC on-chain giveaway contract based on contracts/contracts/Giveaway.sol, including contract address, core method signa...
openclaw skills install giveaway-skillscontracts/contracts/Giveaway.sol0xc9Db158004fEFe15633eF2Ac3C3eA209e58Db5B9IERC20, Ownable, ReentrancyGuard, internal TransferHelper libraryAssumptions when calling:
https://bsc-dataseed.binance.org)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)createGiveawaySignature:
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 >= amountfeeAmount and sendAmount are both paid in BNBtoken != address(0) (ERC20 giveaway):
msg.value == 0approve on-chain:
approve(contract, amount) (user → contract)safeTransferFrom to send feeAmount to FEEADDRESS and sendAmount to the contract itselfclaimGiveawayfunction 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 claimersendAmount = amount / countcount == 1: also send all remaining amountrandomNumber = uint8(keccak256(...)) % 100sendAmount = (amount / count * 2) * randomNumber / 100withdrawExpiredGiveawayfunction withdrawExpiredGiveaway(uint256 id) public nonReentrant
Constraints:
giveawayInfos[id].amount != 0giveawayInfos[id].sender == msg.sendergiveawayInfos[id].lastDate < block.timestampWithdrawal fee:
feeAmount = (amount / 1000) * EXPIREDRATEsendAmount = amount - feeAmountfunction addToWhitelist(uint256 giveawayId, address[] memory addresses) public
function removeFromWhitelist(uint256 giveawayId, address[] memory addresses) public
Constraints:
giveawayInfos[giveawayId].sender == msg.senderrestriction == ClaimRestriction.WHITELISTfunction 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)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.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.[
{
"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[]" }
]
}
]
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);
giveaway-protocol skillgiveaway-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.