Claim Refund
Recovering funds from a curve that did not graduate. There is a hard deadline — unclaimed refunds are forfeited to the protocol after 604,800 s (7 days).
Interface
const CURVE_ABI = [
"function initiate_refund()",
"function refund_claim(uint256 token_lots)",
"function sweep_unclaimed_refunds()",
"function is_refund_mode() view returns (bool)",
"function is_swept() view returns (bool)",
"function refund_per_lot() view returns (uint256)",
"function refund_claim_deadline() view returns (uint256)",
"function get_user_token_balance(address) view returns (uint256)"
];
const curve = new ethers.Contract(CURVE_ADDRESS, CURVE_ABI, signer);CURVE_ABI = json.load(open("BondingCurveTemplate.abi.json"))
curve = w3.eth.contract(address=CURVE_ADDRESS, abi=CURVE_ABI)Step 1 — Open refund mode
Refund mode is not automatic. Someone has to open it once 259,200 s (3 days) have passed without graduation. Anyone can — it costs only gas:
if (!(await curve.is_refund_mode())) {
await (await curve.initiate_refund()).wait();
}if not curve.functions.is_refund_mode().call():
send(curve.functions.initiate_refund())This takes 10% of collected fees for the protocol and fixes refund_per_lot from the remaining balance. The rate is set once and does not move as claims are made, so it does not matter whether you claim first or last.
Step 2 — Claim
const me = await signer.getAddress();
const lots = await curve.get_user_token_balance(me); // already in lots
const rate = await curve.refund_per_lot();
if (lots > 0n) {
await (await curve.refund_claim(lots)).wait();
// receives lots * rate, tokens are burned
}me = acct.address
lots = curve.functions.get_user_token_balance(me).call() # already in lots
rate = curve.functions.refund_per_lot().call()
if lots > 0:
send(curve.functions.refund_claim(lots))
# receives lots * rate, tokens are burnedNo approval is needed — the curve is the token's minter and burns directly. Partial claims are allowed; claim any number of lots you hold.
The deadline
const deadline = await curve.refund_claim_deadline(); // unix seconds, 0 if not refunding
const now = Math.floor(Date.now() / 1000);
if (deadline > 0n && BigInt(now) < deadline) {
const hoursLeft = (deadline - BigInt(now)) / 3600n;
}import time
deadline = curve.functions.refund_claim_deadline().call() # unix seconds, 0 if not refunding
now = int(time.time())
if deadline > 0 and now < deadline:
hours_left = (deadline - now) // 3600You have 604,800 s (7 days) from initiate_refund(). After that refund_claim reverts with Refund window closed and the remainder can be swept to the protocol permanently. There is no extension and no recovery path. Any interface showing a refundable position should surface this deadline prominently.
Sweeping
After the window, anyone can move the remainder to the protocol:
if (!(await curve.is_swept())) {
await (await curve.sweep_unclaimed_refunds()).wait();
}if not curve.functions.is_swept().call():
send(curve.functions.sweep_unclaimed_refunds())This sets FLAG_SWEPT and runs once per curve. It is the terminal state — nothing further happens to that curve, ever.
Who cannot claim
- The deployer.
refund_claimrejects the deployer address, mirroring the ban on selling. - Protocol curves. $LP24's curve carries
FLAG_PROTOCOLand can never enter refund mode; it stays open indefinitely until it graduates.
Refund rights follow the tokens
There is no per-address purchase record. Eligibility is checked against your live balance, so refund rights transfer with the tokens. If you acquired tokens from someone else, you can claim against them; if you transferred yours away, you cannot.
Common reverts
| Revert | Cause |
|---|---|
Period not over | Less than 259,200 s (3 days) since deployment |
Invalid state | Already graduated, or already refunding |
No refunds | Zero balance or no eligible lots above the floor |
Protocol curve cannot refund | This is $LP24's curve |
Refund window closed | Past the claim deadline |
Deployer cannot refund | Caller is the deployer |
Refund window still open | Sweeping too early |
Already swept | Terminal state reached |