Skip to content

Instantly share code, notes, and snippets.

@0xdeployer
Last active May 1, 2026 05:57
Show Gist options
  • Select an option

  • Save 0xdeployer/2cc7f96d51cb2695fede36ed15def44b to your computer and use it in GitHub Desktop.

Select an option

Save 0xdeployer/2cc7f96d51cb2695fede36ed15def44b to your computer and use it in GitHub Desktop.
test airdrop gist
name lienfi-airdrop-claim
description Claim from the LienFi (LFI) airdrop on Base mainnet. 500 pre-approved Bankr Club holders can each claim 2,000,000 LFI once via merkle proof. Use when the user asks to "claim my LFI airdrop", "claim LienFi", "check if my wallet is on the LFI list", or any wallet on the eligible list asks about an LFI claim.

LienFi Airdrop — Claim

A live equal-share merkle airdrop deployed to Base mainnet (chain id 8453, chain name base). 500 pre-approved addresses can each call claim(bytes32[]) once and receive 2,000,000 LFI. Eligibility is enforced on-chain by a merkle proof; the leaf hashes msg.sender, so each recipient must claim from their own wallet — there is no third-party / sponsored-claim path.

Constants

Field Value
Chain base (id 8453)
BankrAirdrop 0x2913F08Ad152c9DA3CF2A38d8A86339ED4500216
LFI token (18 dec) 0x8E6f7a6F82d4827191264aFec550a1638be8Bba3
Merkle root 0x1dca93fc5f93e2ee469afbb8c1459b55bfb6b7fe7def57de84c341462349079b
Share per address 2000000000000000000000000 wei (= 2,000,000 LFI)
Total claimers 500
Claim deadline unix 1780203518Sat 30 May 2026 ~21:58 PT
Owner (can sweep at any time) 0xa96A29a713AD3e59249180831A0b902385C2dEa2
Proofs URL https://gist.githubusercontent.com/0xdeployer/c7b78c9b3d311685f8987127808b3615/raw/06dce68c4141fb1cc89fc2bb8e8eb6ae457c55e6/proofs.json

The proofs file is keyed by checksummed address. The merkle root inside that file MUST match the Merkle root constant above — re-check both before trusting any proof you pull from it.

Step-by-step

1. Generate the merkle proof for the connected wallet

Use execute_cli to run a small inline script that fetches the hosted proofs file and prints the proof for the connected wallet (case-insensitive lookup). If the wallet isn't on the list the script exits non-zero with a clear message — that is the eligibility check, do NOT then call write_contract.

execute_cli(
  files='{"getProof.ts": "const url = \"https://gist.githubusercontent.com/0xdeployer/c7b78c9b3d311685f8987127808b3615/raw/06dce68c4141fb1cc89fc2bb8e8eb6ae457c55e6/proofs.json\";\nconst target = (process.argv[2] ?? \"\").trim().toLowerCase();\nif (!/^0x[a-f0-9]{40}$/.test(target)) { console.error(\"usage: bun getProof.ts <0x...wallet>\"); process.exit(2); }\nconst data = await (await fetch(url)).json();\nconst expectedRoot = \"0x1dca93fc5f93e2ee469afbb8c1459b55bfb6b7fe7def57de84c341462349079b\";\nif (data.merkleRoot.toLowerCase() !== expectedRoot.toLowerCase()) { console.error(\"merkle root mismatch — refusing to proceed\"); process.exit(3); }\nconst entry = Object.entries(data.proofs).find(([k]) => k.toLowerCase() === target);\nif (!entry) { console.error(\"NOT_ELIGIBLE: \" + target + \" is not in the LFI airdrop list\"); process.exit(1); }\nconsole.log(JSON.stringify(entry[1]));\n"}',
  commands=["bun getProof.ts <CONNECTED_WALLET>"]
)

The script:

  • Fetches the proofs file from the URL above.
  • Verifies the file's merkle root matches the constant — refuses to continue if it doesn't.
  • Returns exit code 1 with NOT_ELIGIBLE: <addr> ... if the wallet isn't on the list — translate this for the user verbatim and stop.
  • On success, prints a single JSON line: a bytes32[] proof (8 or 9 hashes). Capture it as PROOF for step 2.

2. Submit the claim

Use write_contract. The proof is a single bytes32[] argument; the tool expects it as a JSON-array string. Pass through exactly what the script in step 1 printed.

write_contract(
  to="0x2913F08Ad152c9DA3CF2A38d8A86339ED4500216",
  functionSignature="claim(bytes32[] proof)",
  args=['<PROOF from step 1, e.g. ["0x75cd…","0xe7e8…",…]>'],
  value="0",
  chain="base"
)

Notes:

  • args is a JS array of strings. The single string element is itself a JSON-encoded array of bytes32 proof hashes — exactly what step 1 printed, no transformation needed.
  • value is "0". The claim is not payable.
  • The function does NOT take the recipient as an argument — eligibility is determined by msg.sender.

If the call reverts, decode the revert selector against the table below and surface a plain-English message to the user. Do NOT retry blindly.

3. Post-claim verification

After write_contract returns a successful transactionHash:

  1. Read hasClaimed(<wallet>) on the airdrop contract — should now be true.
  2. Read balanceOf(<wallet>) on the LFI token (0x8E6f7a6F82d4827191264aFec550a1638be8Bba3) — should be at least 2000000000000000000000000 higher than before.

Report the tx hash and the new LFI balance to the user.

Common revert reasons

Custom error Selector Meaning What to tell the user
InvalidProof() 0x09bde339 Wallet not in the merkle tree, or wrong proof passed The connected wallet is not on the LFI airdrop list.
AlreadyClaimed() 0x646cf558 This wallet already claimed Already claimed. Show the prior tx if you can find it.
ClaimWindowClosed() 0xf0f25a33 block.timestamp > claimDeadline The claim window closed on Sat 30 May 2026 ~21:58 PT.
InsufficientBalance() 0xf4d678b8 Contract balance < share (owner swept) The owner swept the airdrop; claims are no longer possible.

Owner-only paths (do NOT use unless the connected wallet is the owner)

The owner is 0xa96A29a713AD3e59249180831A0b902385C2dEa2. If — and only if — the connected wallet matches:

  • sweep(address to) — pulls all remaining LFI out of the airdrop. Emergency escape hatch + post-deadline cleanup.
  • rescueERC20(address token, address to) — recovers any non-LFI ERC20 accidentally sent to the contract. Reverts with CannotRescueAirdropToken() if called with the LFI address — use sweep for that.
  • setToken(address token) — already called at deploy time. A second call reverts with TokenAlreadySet(). There is nothing more to do here.

Never call these on behalf of a non-owner. They will revert with OwnableUnauthorizedAccount(<caller>).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment