Documentation
Deep Dives
Interact with Validators
Advanced
Redeemer Indexing

Redeemer Indexing

Redeemer Indexing design pattern (opens in a new tab) leverages the deterministic script evaluation property of the Cardano ledger to achieve substantial performance gains in onchain code.

In scenarios where the protocol necessitates spending from the script back to a specific output—such as returning funds from the script to the same script, directing them to another script, or transferring assets to a wallet, it is imperative to ensure that each script input is uniquely associated with an output.

This preventive measure is essential for mitigating the risk of Double Satisfaction Attack (opens in a new tab).

You can use a redeemer containing one-to-one correlation between script input UTxOs and output UTxOs. This is provided via ordered lists of input/output indices of required inputs/outputs present in the Script Context.

For example:

Inputs     : [scriptInputA, scriptInputB, randomInput1, scriptInputC, randomInput2, randomInput3]  // random inputs are not the concerned script inputs
Outputs    : [outputA, outputB, outputC, randomOutput1, randomOutput2, randomOutput3]
InputIdxs  : [0, 1, 3]
OutputIdxs : [0, 1, 2]
where type Redeemer = List<(inputIdx, outputIdx)>

While it is pretty straightforward to determine output indices (the order in which ouputs are created during transaction building), obtaining input indices is more involved. Currently, all inputs in a transaction need to be sorted lexicographically (ascending order of transaction id and transaction index).

So even if a user obtains the required input indicies beforehand, coin selection (post calculating the required fees and transaction balancing) performed by a transaction builder could introduce new inputs thereby changing the indices. Evolution library provides a high-level interface called RedeemerBuilder that abstracts away this complexity and makes it easy to obtain redeemer with required input indices.

Redeemer Builder Kinds

const redeemer: RedeemerBuilder = {
    kind: "selected",
    makeRedeemer: (inputIdxs: bigint[]) => {
        const tupleList = inputIdxs.map((inputIdx, i) => [inputIdx, BigInt(i)]); // List<(inputIdx, outputIdx)>
        return Data.to(tupleList);
    },
    inputs: scriptInputs,
};

This kind of redeemer builder takes in a list of selected inputs and provides makeRedeemer with their indices in the same order. The user supplied makeRedeemer function after having received the indices, builds the redeemer as per validator requirements which would typically involve mapping them to their corresponding output indices. Being a general purpose redeemer builder it can be used to create redeemers for Spend, Mint and Withdraw script purposes.

const scriptInputs: UTxO[] = [
  {
    txHash: "a",
    outputIndex: 1,
    address: "scriptAddress",
    assets: { lovelace: 10_000000n },
  },
  {
    txHash: "b",
    outputIndex: 2,
    address: "scriptAddress",
    assets: { lovelace: 20_000000n },
  },
  {
    txHash: "d",
    outputIndex: 4,
    address: "scriptAddress",
    assets: { lovelace: 40_000000n },
  },
];
const randomInputs: UTxO[] = [
  {
    txHash: "c",
    outputIndex: 3,
    address: "randomAddress",
    assets: { lovelace: 30_000000n },
  },
  {
    txHash: "e",
    outputIndex: 5,
    address: "randomAddress",
    assets: { lovelace: 50_000000n },
  },
  {
    txHash: "f",
    outputIndex: 6,
    address: "randomAddress",
    assets: { lovelace: 60_000000n },
  },
];    
 
const tx = await lucid
    .newTx()
    .collectFrom(scriptInputs, redeemer)
    .attach.SpendingValidator(script)
    .collectFrom(randomInputs)
    .pay.ToContract("scriptAddress", datum, { lovelace: 10_000000n })
    .pay.ToContract("scriptAddress", datum, { lovelace: 20_000000n })
    .pay.ToContract("scriptAddress", datum, { lovelace: 40_000000n })
    .pay.ToAddress("randomAddress", { lovelace: 30_000000n })
    .pay.ToAddress("randomAddress", { lovelace: 50_000000n })
    .pay.ToAddress("randomAddress", { lovelace: 60_000000n })
    .addSigner("randomAddress")
    .complete();