Transactions
refund-crowdloan
Refund contributors of a non-finalized crowdloan (creator only).
Returns contributions (excluding the creator's) to their contributors.
Only the creator may call it. Each call refunds at most 50 contributors,
so large loans may need several refund_crowdloan calls before
everyone is paid back. Once all contributors are refunded, the creator
runs dissolve_crowdloan, which returns the creator's remaining
contribution (including the deposit) and removes the loan.
| Signer | Origin | Pallet | Wraps |
|---|---|---|---|
coldkey | signed account (pallet role may apply) | Crowdloan | Crowdloan.refund |
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
crowdloan_id | integer | yes | Identifier of the crowdloan, assigned when it was created. |
Address parameters (--hotkey, --coldkey, --dest, ...) accept a raw ss58
address, an address-book or proxy-book name, or a local wallet/hotkey name.
CLI
Preview with --dry-run (shows fee, effects, and policy result without
submitting), then submit:
btcli tx refund-crowdloan \
--crowdloan-id <int> --dry-run
btcli tx refund-crowdloan \
--crowdloan-id <int> -w my_coldkeyPython
import bittensor as bt
from bittensor.wallet import Wallet
wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
intent = bt.RefundCrowdloan(crowdloan_id=0)
sub = bt.Subtensor()
plan = sub.plan(intent, wallet) # fee, effects, policy — no submission
result = sub.execute(intent, wallet)
if not result.success:
print(result.error.code, result.error.remediation)(bt.Subtensor is also the async client — async with bt.Subtensor() as client:
— see The client.) Or build the intent by op name, as
an agent would:
result = sub.execute_tool("refund_crowdloan", {...}, wallet)On-chain implementation
Crowdloan.refund — pallets/crowdloan/src/lib.rs#L688:
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::refund(T::RefundContributorsLimit::get()))]
pub fn refund(
origin: OriginFor<T>,
#[pallet::compact] crowdloan_id: CrowdloanId,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?;
// Ensure the crowdloan is not finalized
ensure!(!crowdloan.finalized, Error::<T>::AlreadyFinalized);
// Only the creator can refund the crowdloan
ensure!(who == crowdloan.creator, Error::<T>::InvalidOrigin);
let mut refunded_contributors: Vec<T::AccountId> = vec![];
let mut refund_count = 0;
// Assume everyone can be refunded
let mut all_refunded = true;
// We try to refund all contributors (excluding the creator)
let contributions = Contributions::<T>::iter_prefix(crowdloan_id)
.filter(|(contributor, _)| *contributor != crowdloan.creator);
for (contributor, amount) in contributions {
if refund_count >= T::RefundContributorsLimit::get() {
// Not everyone can be refunded
all_refunded = false;
break;
}
CurrencyOf::<T>::transfer(
&crowdloan.funds_account,
&contributor,
amount,
Preservation::Expendable,
)?;
refunded_contributors.push(contributor);
crowdloan.raised = crowdloan.raised.saturating_sub(amount);
refund_count = refund_count.checked_add(1).ok_or(Error::<T>::Overflow)?;
}
crowdloan.contributors_count = crowdloan
.contributors_count
.checked_sub(refund_count)
.ok_or(Error::<T>::Underflow)?;
Crowdloans::<T>::insert(crowdloan_id, &crowdloan);
// Clear refunded contributors
for contributor in refunded_contributors {
Contributions::<T>::remove(crowdloan_id, &contributor);
}
if all_refunded {
Self::deposit_event(Event::<T>::AllRefunded { crowdloan_id });
// The loop didn't run fully, we refund the unused weights.
Ok(Some(T::WeightInfo::refund(refund_count)).into())
} else {
Self::deposit_event(Event::<T>::PartiallyRefunded { crowdloan_id });
// The loop ran fully, we don't refund anything.
Ok(().into())
}
}Every file is browsable under /code exactly as built into the runtime, or as plain text under /code/raw/<path> (index: /code/index.json).