# contribute-crowdloan (/docs/tx/contribute-crowdloan)

Moves the amount from the signer into the crowdloan's pot. The contribution
must meet the loan's minimum (and per-contributor maximum, if set) and the
loan must still be open — contributing after `end` or past the cap fails.
Contributions are not refunded automatically if the loan fails: they come
back when the creator calls `refund_crowdloan`, or the contributor can
`withdraw_crowdloan` themselves any time before finalization. On
success, the funds go to the loan's target or fund its inner call.

| Signer    | Origin                                 | Pallet    | Wraps                                                                  |
| --------- | -------------------------------------- | --------- | ---------------------------------------------------------------------- |
| `coldkey` | signed account (pallet role may apply) | Crowdloan | [`Crowdloan.contribute`](/code/pallets/crowdloan/src/lib.rs#L438-L536) |

## Parameters [#parameters]

| Parameter      | Type              | Required | Description                                                                                                               |
| -------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `crowdloan_id` | integer           | yes      | Identifier of the crowdloan, assigned when it was created.                                                                |
| `amount_tao`   | number \| `"all"` | yes      | Amount to contribute; moved into the crowdloan's pot. Recoverable via refund or withdraw while the loan is not finalized. |

Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58
address, an address-book or proxy-book name, or a local wallet/hotkey name.

## CLI [#cli]

Preview with `--dry-run` (shows fee, effects, and policy result without
submitting), then submit:

```bash
btcli tx contribute-crowdloan \
  --crowdloan-id <int> \
  --amount-tao <amount|all> --dry-run
btcli tx contribute-crowdloan \
  --crowdloan-id <int> \
  --amount-tao <amount|all> -w my_coldkey
```

## Python [#python]

```python
import bittensor as bt
from bittensor.wallet import Wallet

wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
intent = bt.ContributeCrowdloan(crowdloan_id=0, amount_tao=1.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](/docs/concepts/client).) Or build the intent by op name, as
an agent would:

```python
result = sub.execute_tool("contribute_crowdloan", {...}, wallet)
```

## On-chain implementation [#on-chain-implementation]

`Crowdloan.contribute` — [`pallets/crowdloan/src/lib.rs#L440`](/code/pallets/crowdloan/src/lib.rs#L438-L536):

```rust
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::contribute())]
pub fn contribute(
    origin: OriginFor<T>,
    #[pallet::compact] crowdloan_id: CrowdloanId,
    #[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResult {
    let contributor = ensure_signed(origin)?;
    let now = frame_system::Pallet::<T>::block_number();

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

    // Ensure crowdloan has not ended and has not raised cap
    ensure!(now < crowdloan.end, Error::<T>::ContributionPeriodEnded);
    ensure!(crowdloan.raised < crowdloan.cap, Error::<T>::CapRaised);

    // Ensure contribution is at least the minimum contribution
    ensure!(
        amount >= crowdloan.min_contribution,
        Error::<T>::ContributionTooLow
    );

    // Ensure the crowdloan has not reached the maximum number of contributors
    ensure!(
        crowdloan.contributors_count < T::MaxContributors::get(),
        Error::<T>::MaxContributorsReached
    );

    // Compute how much room is left before the crowdloan reaches its cap.
    let left_to_raise = crowdloan
        .cap
        .checked_sub(&crowdloan.raised)
        .ok_or(Error::<T>::Underflow)?;

    // The requested contribution must meet the minimum contribution, but
    // the accepted amount may be lower when only a smaller remainder can
    // be accepted before reaching the crowdloan cap or the contributor's
    // maximum contribution.
    let amount = if let Some(max_contribution) = MaxContributions::<T>::get(crowdloan_id) {
        let current_contribution =
            Contributions::<T>::get(crowdloan_id, &contributor).unwrap_or_else(Zero::zero);
        ensure!(
            current_contribution < max_contribution,
            Error::<T>::MaxContributionReached
        );
        let left_to_contribute = max_contribution
            .checked_sub(&current_contribution)
            .ok_or(Error::<T>::Underflow)?;
        amount.min(left_to_contribute).min(left_to_raise)
    } else {
        amount.min(left_to_raise)
    };

    // Ensure contribution does not overflow the actual raised amount
    crowdloan.raised = crowdloan
        .raised
        .checked_add(&amount)
        .ok_or(Error::<T>::Overflow)?;

    // Compute the new total contribution and ensure it does not overflow, we
    // also increment the contributor count if the contribution is new.
    let contribution =
        if let Some(contribution) = Contributions::<T>::get(crowdloan_id, &contributor) {
            contribution
                .checked_add(&amount)
                .ok_or(Error::<T>::Overflow)?
        } else {
            // We have a new contribution
            crowdloan.contributors_count = crowdloan
                .contributors_count
                .checked_add(1)
                .ok_or(Error::<T>::Overflow)?;
            amount
        };

    // Ensure contributor has enough balance to pay
    ensure!(
        CurrencyOf::<T>::balance(&contributor) >= amount,
        Error::<T>::InsufficientBalance
    );

    CurrencyOf::<T>::transfer(
        &contributor,
        &crowdloan.funds_account,
        amount,
        Preservation::Expendable,
    )?;

    Contributions::<T>::insert(crowdloan_id, &contributor, contribution);
    Crowdloans::<T>::insert(crowdloan_id, &crowdloan);

    Self::deposit_event(Event::<T>::Contributed {
        crowdloan_id,
        contributor,
        amount,
    });

    Ok(())
}
```

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