> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canopy.deal/llms.txt
> Use this file to discover all available pages before exploring further.

# Redeeming Claim Tokens for Collateral After Unlock

Burn claim tokens to receive the underlying collateral on a 1:1 basis. Permissionless — no approval, whitelist, or expiry window.

## When Can You Redeem?

Redemption becomes available the moment `block.timestamp >= series.unlockTime`. You can check this on-chain by reading the series' `unlockTime` field. Attempting to call `redeem` before the unlock time will revert with `NotYetUnlocked()`.

## Function Signatures

The venue provides two redemption entry points:

### Simple Redemption

```solidity theme={null}
function redeem(bytes32 seriesId, uint256 amount) external;
```

Burns `amount` claim tokens from `msg.sender` and transfers the underlying collateral. Applies a default `minAssetReceived = 1`.

### Redemption with Minimum Output

```solidity theme={null}
function redeemWithMinimum(
    bytes32 seriesId,
    uint256 amount,
    uint256 minAssetReceived
) external;
```

Same as `redeem`, but reverts with `SlippageExceeded()` if the net asset tokens received by the caller are less than `minAssetReceived`. Use this when redeeming fee-on-transfer asset tokens.

## Parameters

<ParamField path="seriesId" type="bytes32" required>
  The deterministic series identifier, computed as `keccak256(abi.encode(asset, unlockTime))`. You can find this from the `SeriesCreated` event or compute it off-chain using the asset address and unlock time. The venue also provides a helper: `seriesIdFor(asset, unlockTime)`.
</ParamField>

<ParamField path="amount" type="uint256" required>
  The number of claim tokens to burn in this redemption. Must be greater than zero and must not exceed your current claim token balance. You can redeem your full balance in one call or redeem incrementally across multiple calls — partial redemption is fully supported.
</ParamField>

<ParamField path="minAssetReceived" type="uint256">
  The minimum net asset tokens that must arrive in your wallet after any transfer fees. If the delivered amount falls below this threshold, the transaction reverts with `SlippageExceeded()`. Only available in `redeemWithMinimum`.
</ParamField>

## 1:1 Redemption

Canopy enforces a strict 1:1 backing ratio between claim tokens and escrowed collateral. When you redeem, exactly `amount` units of the underlying asset are released from escrow:

```
series.backing -= amount;
```

The claim tokens are burned and the corresponding collateral is transferred directly to your wallet. For standard ERC-20 asset tokens, you receive exactly `amount` tokens. For fee-on-transfer tokens, you receive `amount` minus the transfer fee — use `redeemWithMinimum` to protect against this.

<Note>
  This is not a proportional share calculation — it is a direct 1:1 exchange. One claim token always redeems for one unit of the underlying asset (minus any outbound transfer fee on the asset token itself).
</Note>

## Events

```solidity theme={null}
event Redeemed(
    bytes32 indexed seriesId,
    address indexed holder,
    uint256 amount,
    uint256 assetReceived
);
```

`assetReceived` is the net quantity of underlying collateral transferred to the redeemer. For standard tokens this equals `amount`; for fee-on-transfer tokens it may be less.

## Revert Conditions

| Condition                                                 | Error                |
| --------------------------------------------------------- | -------------------- |
| `series.claimToken == address(0)` (series does not exist) | `SeriesUnknown()`    |
| `block.timestamp < series.unlockTime`                     | `NotYetUnlocked()`   |
| `amount == 0`                                             | `InvalidParams()`    |
| Caller does not hold enough claim tokens                  | ERC-20 burn revert   |
| Net asset received \< `minAssetReceived`                  | `SlippageExceeded()` |

## Example: Checking Unlock Status and Redeeming

<CodeGroup>
  ```typescript ethers.js v6 — Full Balance Redemption theme={null}
  import { ethers } from "ethers";

  const VENUE_ADDRESS = "0xYourVenueAddress";
  const venue = new ethers.Contract(VENUE_ADDRESS, VENUE_ABI, signer);

  // Compute seriesId
  const seriesId = await venue.seriesIdFor(ASSET_ADDRESS, unlockTime);

  // Step 1: Check that the series has unlocked
  const seriesData = await venue.series(seriesId);
  const now = Math.floor(Date.now() / 1000);

  if (now < Number(seriesData.unlockTime)) {
    throw new Error(
      `Not yet unlocked. Unlocks at ${new Date(Number(seriesData.unlockTime) * 1000).toISOString()}`
    );
  }

  // Step 2: Read your claim token balance
  const claimToken = new ethers.Contract(seriesData.claimToken, ERC20_ABI, signer);
  const myAddress = await signer.getAddress();
  const balance = await claimToken.balanceOf(myAddress);
  if (balance === 0n) {
    throw new Error("No claim tokens to redeem.");
  }

  // Step 3: Redeem the full balance
  const tx = await venue.redeem(seriesId, balance);
  const receipt = await tx.wait();
  console.log("Redeemed in tx:", receipt.hash);
  ```

  ```typescript Partial Redemption with Minimum theme={null}
  import { ethers } from "ethers";

  const venue = new ethers.Contract(VENUE_ADDRESS, VENUE_ABI, signer);
  const seriesId = await venue.seriesIdFor(ASSET_ADDRESS, unlockTime);

  const claimToken = new ethers.Contract(CLAIM_TOKEN_ADDRESS, ERC20_ABI, signer);
  const balance = await claimToken.balanceOf(myAddress);
  const redeemAmount = balance / 2n; // redeem half your position

  // Use redeemWithMinimum for fee-on-transfer asset tokens
  const minExpected = redeemAmount * 99n / 100n; // allow 1% fee
  const tx = await venue.redeemWithMinimum(seriesId, redeemAmount, minExpected);
  await tx.wait();
  // Remaining half of claim tokens stay in your wallet for later redemption
  ```
</CodeGroup>

## Finding the SeriesId and Claim Token Address

<Tip>
  You can find the claim token address and series information using these methods:

  1. **From the contract** — Call `seriesIdFor(asset, unlockTime)` to compute the series ID, then `series(seriesId)` to read the `claimToken` address.
  2. **From the event log** — Listen for `SeriesCreated` events. The event includes both the `seriesId` and the deployed `claimToken` address.
  3. **Off-chain computation** — Compute `keccak256(abi.encode(asset, unlockTime))` using ethers.js: `ethers.solidityPackedKeccak256(["address", "uint64"], [asset, unlockTime])`.
</Tip>

<Note>
  If you transferred some or all of your claim tokens to another wallet before the unlock time, only the tokens currently held in `msg.sender`'s wallet can be redeemed in that call. The receiving wallet must call `redeem` itself to convert those tokens into collateral. Claim tokens are fully portable ERC-20 tokens — whoever holds them at redemption time receives the collateral.
</Note>
