Transactions

finalize-crowdloan

Finalize a crowdloan that reached its cap (creator only).

View as Markdown

Settles a successful raise: the pot is transferred to the loan's target address, or its inner call is dispatched with the creator's origin. Only the creator may finalize, and only once the cap has been reached. After finalization the crowdloan can no longer be updated, refunded, or dissolved.

SignerOriginPalletWraps
coldkeysigned account (pallet role may apply)CrowdloanCrowdloan.finalize

Parameters

ParameterTypeRequiredDescription
crowdloan_idintegeryesIdentifier 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 finalize-crowdloan \
  --crowdloan-id <int> --dry-run
btcli tx finalize-crowdloan \
  --crowdloan-id <int> -w my_coldkey

Python

import bittensor as bt
from bittensor.wallet import Wallet

wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
intent = bt.FinalizeCrowdloan(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("finalize_crowdloan", {...}, wallet)

On-chain implementation

Crowdloan.finalize — pallets/crowdloan/src/lib.rs#L615:

#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::finalize())]
pub fn finalize(
    origin: OriginFor<T>,
    #[pallet::compact] crowdloan_id: CrowdloanId,
) -> DispatchResult {
    let who = ensure_signed(origin.clone())?;

    let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?;

    // Ensure the origin is the creator of the crowdloan and the crowdloan has raised the cap
    // and is not finalized.
    ensure!(who == crowdloan.creator, Error::<T>::InvalidOrigin);
    ensure!(crowdloan.raised == crowdloan.cap, Error::<T>::CapNotRaised);
    ensure!(!crowdloan.finalized, Error::<T>::AlreadyFinalized);
    ensure!(
        CurrentCrowdloanId::<T>::get().is_none(),
        Error::<T>::AlreadyFinalizing
    );

    // Exclude unsolicited deposits from the amount the finalizer must spend.
    let remaining_balance = CurrencyOf::<T>::balance(&crowdloan.funds_account)
        .checked_sub(&crowdloan.raised)
        .ok_or(Error::<T>::InsufficientBalance)?;

    crowdloan.finalized = true;
    Crowdloans::<T>::insert(crowdloan_id, &crowdloan);

    match (&crowdloan.call, &crowdloan.target_address) {
        (Some(call), None) => {
            // Set the current crowdloan id so the dispatched call
            // can access it temporarily
            CurrentCrowdloanId::<T>::put(crowdloan_id);

            // Retrieve the call from the preimage storage
            let stored_call = match T::Preimages::peek(call) {
                Ok((call, _)) => call,
                Err(_) => {
                    // If the call is not found, we drop it from the preimage storage
                    // because it's not needed anymore
                    T::Preimages::drop(call);
                    return Err(Error::<T>::CallUnavailable)?;
                }
            };

            // Keep the creator's inherited filters (for example, proxy permissions).
            // Reconstructing a signed origin would authorize the stored call more broadly
            // than the account that is finalizing the crowdloan is allowed to act.
            stored_call
                .dispatch(origin)
                .map(|_| ())
                .map_err(|e| e.error)?;

            // Clear the current crowdloan id
            CurrentCrowdloanId::<T>::kill();
        }
        (None, Some(target_address)) => {
            CurrencyOf::<T>::transfer(
                &crowdloan.funds_account,
                target_address,
                crowdloan.raised,
                Preservation::Expendable,
            )?;
        }
        (_, _) => {
            return Err(Error::<T>::InvalidFinalizationConfig)?;
        }
    }

    // Wrappers such as utility::batch can return Ok after a child fails. Do not
    // commit finalization (or partial child effects) while raised funds remain.
    ensure!(
        CurrencyOf::<T>::balance(&crowdloan.funds_account) <= remaining_balance,
        Error::<T>::FundsNotSettled
    );

    Self::deposit_event(Event::<T>::Finalized { crowdloan_id });

    Ok(())
}

Every file is browsable under /code exactly as built into the runtime, or as plain text under /code/raw/<path> (index: /code/index.json).