# Pebble — full docs corpus (v0.3.0) > This file is the concatenation of every public docs page. It exists for LLM ingestion. For a curated short index see llms.txt; for the rendered site see harmoniclabs.tech. Generated: 2026-06-07T03:04:46Z --- === examples/Hello World.md === --- sidebar_position: 0 --- # Hello World The smallest useful Pebble contract: a script that locks a UTxO until the owner signs a transaction that includes a fixed greeting in the redeemer. It exercises the `contract`/`state` machinery, signature-checking, and a redeemer payload — everything the larger examples build on. ## On-chain: `hello_world.pebble` ```ts contract HelloWorld { state Locked { owner: PubKeyHash spend greet(message: bytes) { const { tx, state: { owner } } = context; assert tx.signatories.includes(owner); assert message == "Hello pebble".toBytes(); } } } ``` ### Why this shape - **One `state` constructor**, so the datum is `Locked{ owner }` — a single CBOR `Constr(0, [B owner])`. - **The redeemer is `(message: bytes)`** — the method's parameters become the redeemer at the call site. - **The script aborts** if the message doesn't match exactly. See [Failures](../onchain/reference/Failures.md). ## Off-chain: TypeScript with `@harmoniclabs/buildooor` ```ts import { Address, Credential, Hash28, DataConstr, DataB, TxBuilder, TxOut, } from "@harmoniclabs/buildooor"; import { readFile } from "fs/promises"; import { provider } from "./provider"; const scriptCbor = await readFile("./hello_world.uplc"); const helloHash = new Hash28(/* blake2b_224(scriptCbor) */); const helloAddr = new Address("mainnet", Credential.script(helloHash)); ``` ### Lock ```ts async function lock({ ownerPkh, lockValue, wallet, privateKey }) { // Constr(0, [B ownerPkh]) — single-constructor sum for state `Locked` const datum = new DataConstr(0, [ new DataB(ownerPkh.toBuffer()) ]); const txBuilder = new TxBuilder(await provider.getProtocolParameters()); const tx = txBuilder.buildSync({ inputs: wallet.utxos.map((utxo) => ({ utxo })), outputs: [ new TxOut({ address: helloAddr, value: lockValue, datum, }), ], changeAddress: wallet.address, }); tx.signWith(privateKey); return await provider.submitTx(tx); } ``` ### Greet (unlock) ```ts async function greet({ lockedUtxo, ownerWallet, ownerPrivateKey, scriptRefUtxo }) { const txBuilder = new TxBuilder(await provider.getProtocolParameters()); const message = new TextEncoder().encode("Hello pebble"); const redeemer = new DataConstr(0, [ new DataB(message) ]); // greet(bytes) const tx = txBuilder.buildSync({ inputs: [ { utxo: lockedUtxo, referenceScript: { refUtxo: scriptRefUtxo, redeemer }, }, ...ownerWallet.utxos.map((utxo) => ({ utxo })), ], outputs: [ new TxOut({ address: ownerWallet.address, value: lockedUtxo.resolved.value, }), ], collaterals: [ ownerWallet.utxos[0] ], changeAddress: ownerWallet.address, requiredSigners: [ ownerWallet.pkh ], // satisfies tx.signatories.includes(owner) }); tx.signWith(ownerPrivateKey); return await provider.submitTx(tx); } ``` ## Uses - [`contract`](../onchain/Contract%20Statements.md), [`state`](../onchain/State.md), [`spend`](../onchain/concepts/Validators%20101.md) - [`assert`](../onchain/Syntax/Control%20Flow%20Statements.md), `tx.signatories.includes(...)` - [`string.toBytes`](../onchain/Prelude/String.mdx), `bytes` equality via `==` - buildooor: `TxBuilder.buildSync`, `DataConstr`, `DataB`, `Credential.script` ## See also - [Validators 101](../onchain/concepts/Validators%20101.md) — `context` shape and script-purpose dispatch - [State](../onchain/State.md) — the keyword used for the datum - [Simple Order Book DEX](./Simple%20order%20book%20DEX.md) — same patterns at protocol scale === examples/Simple minting policy.md === --- sidebar_position: 1 --- # Simple minting policy A native token's supply is controlled by its **minting policy** — a Plutus script invoked under the `Mint` purpose whenever tokens with that policy's hash are minted or burned. This example shows two policies that illustrate the spectrum: 1. **AllowAnyMint** — a permissive policy that accepts every mint and burn. Useful as a teaching example, never in production. 2. **OneShot** — a tightly-constrained policy that can only mint a fixed token name **once**, by requiring a specific UTxO to be consumed in the same transaction. This pattern is how NFTs get their uniqueness on Cardano. ## On-chain ### AllowAnyMint ```ts contract AllowAnyMint { mint allowAnything() {} } ``` The body is empty: no `assert`, no `fail`, so the script always succeeds. Anyone with this policy hash can mint or burn arbitrary amounts. ### OneShot (parameterised by a UTxO ref) ```ts contract OneShot { param seedRef: TxOutRef; param tokenName: TokenName; mint mintOnce() { const { tx, purpose } = context; // 1. The "seed" UTxO must be one of the transaction's inputs. // Once it's spent, the same transaction can never be replayed, // so the mint can never happen again. assert tx.inputs.some((i) => i.ref == seedRef); // 2. Exactly one mint entry: this policy, our token name, amount = 1. const Mint{ policyId: ownPolicy } = purpose; assert tx.mint.amountOf(ownPolicy, tokenName) == 1; // 3. No other asset under this policy is minted. // (Defends against minting an extra side-token in the same tx.) const mintedUnderUs = tx.mint .toMap() .lookup(ownPolicy); match mintedUnderUs { Some{ value: assets }: assert assets.length() == 1, None{}: fail "policy missing from mint map" } } } ``` ### Why this shape - **`OneShot(seedRef, tokenName)` takes contract parameters.** At compile time you bake in the specific UTxO that gates the mint; the resulting validator has a unique policy hash. See [Contract Statements](../onchain/Contract%20Statements.md). - **`purpose` carries the policy ID** when the script runs as a mint. Destructure with `match purpose` or `const Mint{ policyId } = purpose`. - **Both checks together** (input present + exactly one mint entry) make this an **NFT primitive**: minting can happen at most once, and only the named token is created. ## Off-chain: TypeScript with `@harmoniclabs/buildooor` ```ts import { Address, Credential, Hash28, Value, DataConstr, DataB, DataI, TxBuilder, TxOut, UTxO, } from "@harmoniclabs/buildooor"; import { readFile } from "fs/promises"; import { provider } from "./provider"; ``` ### Mint under `OneShot` ```ts async function mintOneShot({ oneShotScriptCbor, // Uint8Array — compiled with the seedRef + tokenName baked in oneShotPolicyHash, // Hash28 of the script above tokenName, // Uint8Array seedUtxo, // UTxO matching the seedRef baked into the script receiverAddress, // Address — who gets the freshly minted token minterWallet, // { address, utxos } minterPrivateKey, }) { const txBuilder = new TxBuilder(await provider.getProtocolParameters()); // mintOnce() takes no arguments — redeemer is Constr(0, []). const redeemer = new DataConstr(0, []); // The freshly minted token, paid to the receiver. const mintedToken = Value.singleAsset(oneShotPolicyHash, tokenName, 1n); const tx = txBuilder.buildSync({ inputs: [ { utxo: seedUtxo }, // <- satisfies the seedRef check ...minterWallet.utxos.map((utxo) => ({ utxo })), ], mints: [ { value: mintedToken, script: { inline: oneShotScriptCbor, redeemer, }, }, ], outputs: [ new TxOut({ address: receiverAddress, // The output must carry the minted token plus enough ADA // to meet the protocol's min-UTxO requirement. value: Value.merge( mintedToken, Value.lovelaces(1_500_000n), ), }), ], collaterals: [ minterWallet.utxos[0] ], changeAddress: minterWallet.address, }); tx.signWith(minterPrivateKey); return await provider.submitTx(tx); } ``` ### Burn (sketch) To burn, mint a **negative** quantity. The policy script runs the same `mintOnce()` body, so an `OneShot` script can't burn — only the looser `AllowAnyMint` can. A more realistic production policy adds a `mint burn()` method that checks the burner's signature and that the burn quantity is negative. ## Uses - [`contract`](../onchain/Contract%20Statements.md) with parameters, `mint` method-kind - `purpose` destructuring via [`Mint{ policyId }`](../onchain/API/types/ScriptContext/ScriptPurpose.md) - [`List.some`](../onchain/Prelude/List.mdx), [`Value.amountOf`](../onchain/Prelude/Value.mdx) - [`Optional`](../onchain/Prelude/Optional.mdx) match on a `LinearMap.lookup` result - buildooor: `mints` field of `buildSync`, `Value.singleAsset`, `Value.merge`, `Value.lovelaces` ## See also - [Validators 101](../onchain/concepts/Validators%20101.md) — how `purpose` is bound for a `mint` script - [Failures](../onchain/reference/Failures.md) — when destructuring `Mint{ ... }` can fail - [Pitfalls](../onchain/reference/Pitfalls.md) — minting more than you intended === examples/Simple order book DEX.md === --- sidebar_position: 4 --- # Simple Order Book DEX A worked example of a single-asset order book on Cardano. Each order is its own UTxO at the contract's address; a counterparty fills an order by spending it and paying the owner the agreed amount. Cancellation is permitted by the owner. The on-chain code uses the **`contract`** and **`state`** keywords introduced in Pebble v0.3.0. The off-chain code uses **[`@harmoniclabs/buildooor`](https://github.com/HarmonicLabs/buildooor)** to construct and submit transactions. ## What we're building Three actions on each order UTxO: 1. **Place** — wallet creates an output at the contract address, datum = the order's terms. 2. **Fill** — anyone spends the order, sending the requested payment to the order's owner. 3. **Cancel** — the owner spends the order back to themselves. The contract has a single `state` (`Simple`) whose fields capture the order's terms. The `spend fill(...)` method enforces the fill rules; a fallback `spend cancelOrder()` outside the state block handles cancellation. ## On-chain: `simple_order_book.pebble` ```ts contract SimpleOrderBook { state Simple { // hash that's allowed to cancel and that receives the payment on fill ownerHash: bytes // token to be paid in policy: PolicyId tokenName: TokenName // minimum amount of (policy, tokenName) the owner must receive minReceiveAmount: int spend fill(inputIdx: int, outputIdx: int) { const { tx, spendingInputRef: purposeRef, state: { ownerHash, policy, tokenName, minReceiveAmount } } = context; // The input named in the redeemer is this script's invocation const { ref: spendingInputRef, resolved: spendingInput } = tx.inputs[inputIdx]; assert purposeRef == spendingInputRef; const ownAddr = spendingInput.address; const ownScriptHash = ownAddr.credential.hash(); // Prevent double satisfaction: at most one of *this script's* // UTxOs may be spent in the same transaction. const ownInputs = tx.inputs.filter( (i) => i.resolved.address.credential.hash() == ownScriptHash ); assert ownInputs.length() == 1; // The named output must pay the owner the agreed token amount, // routed to an address that pays to `ownerHash`. const userOutput = tx.outputs[outputIdx]; assert userOutput.address.credential.hash() == ownerHash; assert userOutput.value.amountOf(policy, tokenName) >= minReceiveAmount; } } spend cancelOrder() { const { tx, state: { ownerHash } } = context; assert tx.signatories.includes(ownerHash); } } ``` ### Why this shape - **`state Simple { ... }`** declares a datum type. The compiler synthesizes a sum-type with one constructor for `Simple`; the order's UTxO carries a CBOR-encoded `Simple{ ownerHash, policy, tokenName, minReceiveAmount }` as its datum. - **`spend fill` inside the state** binds `state` automatically — every state field is in scope after destructuring `context`. - **`spend cancelOrder` outside the state** is the fallback path. When the redeemer doesn't match `fill`, the contract falls through to `cancelOrder`. See [Contract Statements](../onchain/Contract%20Statements.md) for the dispatch rules. - **Double-satisfaction defence**: `tx.inputs.filter(...).length() == 1` ensures one fill output can't satisfy two orders. See [Pitfalls — Double-satisfaction](../onchain/reference/Pitfalls.md). ## Off-chain: TypeScript with `@harmoniclabs/buildooor` ```ts import { Address, Credential, Hash28, Value, DataConstr, DataB, DataI, TxBuilder, TxOut, UTxO, } from "@harmoniclabs/buildooor"; import { readFile } from "fs/promises"; // Your provider — Blockfrost, Koios, Maestro, Ogmios, ... import { provider } from "./provider"; // The compiled contract: load `simple_order_book.uplc` as a Cardano script const scriptCbor = await readFile("./simple_order_book.uplc"); const orderBookScript = /* parse scriptCbor to a Cardano Script value */; const orderBookHash = new Hash28(/* blake2b_224(scriptCbor) */); const orderBookAddr = new Address( "mainnet", Credential.script(orderBookHash) ); ``` ### 1. Placing an order Build a UTxO at `orderBookAddr` whose datum encodes a `Simple{ ... }` state: ```ts async function placeOrder({ ownerPkh, // Hash28 payPolicy, // Hash28 (a Cardano script hash) payTokenName, // Uint8Array minReceive, // bigint lockValue, // Value — what the order itself contains wallet, // { address, utxos } privateKey, }) { // The state datum: a single-constructor sum, hence Constr(0, [...]) const orderDatum = new DataConstr(0, [ new DataB(ownerPkh.toBuffer()), new DataB(payPolicy.toBuffer()), new DataB(payTokenName), new DataI(minReceive), ]); const txBuilder = new TxBuilder(await provider.getProtocolParameters()); const tx = txBuilder.buildSync({ inputs: wallet.utxos.map((utxo) => ({ utxo })), outputs: [ new TxOut({ address: orderBookAddr, value: lockValue, datum: orderDatum, }), ], changeAddress: wallet.address, }); tx.signWith(privateKey); return await provider.submitTx(tx); } ``` ### 2. Filling an order The filler spends the order UTxO with the `fill` redeemer (`inputIdx` of the order in `tx.inputs`, `outputIdx` of the payment in `tx.outputs`), pays the owner the agreed amount, and keeps the locked value. ```ts async function fillOrder({ orderUtxo, // UTxO at orderBookAddr orderState, // { ownerHash, policy, tokenName, minReceiveAmount } fillerWallet, // { address, utxos } privateKey, scriptRefUtxo, // UTxO carrying the orderBook script as reference }) { const txBuilder = new TxBuilder(await provider.getProtocolParameters()); // Build the predicted input/output order so we can quote indices in the // redeemer. buildooor preserves the order you provide. const orderInput = { utxo: orderUtxo, referenceScript: { refUtxo: scriptRefUtxo, redeemer: new DataConstr(0, [ // Constr(0, [...]) = `fill(...)` new DataI(0), // inputIdx: position of orderUtxo new DataI(0), // outputIdx: position of payment ]), }, }; const paymentOutput = new TxOut({ address: Address.fromCredentials( "mainnet", Credential.keyHash(orderState.ownerHash), ), value: Value.singleAsset( new Hash28(orderState.policy), orderState.tokenName, BigInt(orderState.minReceiveAmount), ), }); // The filler also keeps whatever was locked in the order, minus the // payment they had to source from their own UTxOs. const lockedKeptByFiller = new TxOut({ address: fillerWallet.address, value: orderUtxo.resolved.value, }); const tx = txBuilder.buildSync({ inputs: [ orderInput, ...fillerWallet.utxos.map((utxo) => ({ utxo })) ], outputs: [ paymentOutput, lockedKeptByFiller ], collaterals: [ fillerWallet.utxos[0] ], changeAddress: fillerWallet.address, }); tx.signWith(privateKey); return await provider.submitTx(tx); } ``` ### 3. Cancelling an order The owner spends the order back to themselves under the `cancelOrder` purpose: ```ts async function cancelOrder({ orderUtxo, ownerWallet, // { address, utxos, pkh } — must be the order's owner ownerPrivateKey, scriptRefUtxo, }) { const txBuilder = new TxBuilder(await provider.getProtocolParameters()); const orderInput = { utxo: orderUtxo, referenceScript: { refUtxo: scriptRefUtxo, // Constr(1, []) selects the second `spend` method (`cancelOrder`) // since `fill` is Constr(0, [...]). redeemer: new DataConstr(1, []), }, }; const tx = txBuilder.buildSync({ inputs: [ orderInput, ...ownerWallet.utxos.map((utxo) => ({ utxo })) ], outputs: [ new TxOut({ address: ownerWallet.address, value: orderUtxo.resolved.value, }), ], collaterals: [ ownerWallet.utxos[0] ], changeAddress: ownerWallet.address, requiredSigners: [ ownerWallet.pkh ], // satisfies `tx.signatories.includes(ownerHash)` }); tx.signWith(ownerPrivateKey); return await provider.submitTx(tx); } ``` ## Uses (for code-gen tools) Pebble symbols this example exercises: - `contract`, `state`, `spend` — see [Contract Statements](../onchain/Contract%20Statements.md), [State](../onchain/State.md) - [`tx.inputs`](../onchain/API/types/Tx/TxIn.md), [`tx.outputs`](../onchain/API/types/Tx/TxOut.md), `tx.signatories` - [`Address`](../onchain/API/types/Address/Address.md), [`Credential`](../onchain/API/types/Address/Credential.md) - [`Value.amountOf`](../onchain/Prelude/Value.mdx) - [`List.filter`](../onchain/Prelude/List.mdx), [`List.length`](../onchain/Prelude/List.mdx) - [`spendingInputRef`](../onchain/concepts/Validators%20101.md) - [Pitfalls — Double-satisfaction](../onchain/reference/Pitfalls.md) Buildooor APIs used: - `TxBuilder`, `buildSync({ inputs, outputs, ... })` - `TxOut`, `UTxO`, `Address.fromCredentials`, `Credential.keyHash`, `Credential.script` - `Value.singleAsset` - `DataConstr`, `DataB`, `DataI` — to encode the datum and redeemer - Reference-script input form with `{ refUtxo, redeemer }` ## See also - [State](../onchain/State.md) — the keyword used for the order's datum shape - [Contract Statements](../onchain/Contract%20Statements.md) — `spend`/`mint`/`certify`/`withdraw`/`propose`/`vote` dispatch - [Validators 101](../onchain/concepts/Validators%20101.md) — script-purpose model - [Pitfalls](../onchain/reference/Pitfalls.md) — double-satisfaction defence, redeemer-ordering hazards - [`@harmoniclabs/buildooor`](https://github.com/HarmonicLabs/buildooor) — the off-chain library === examples/Vesting.md === --- sidebar_position: 3 --- # Vesting A vesting contract locks funds until a beneficiary becomes eligible to claim them after a deadline (POSIX time). Two parties, two phases: 1. **Lock** — the depositor pays funds to the contract address with a datum naming the `beneficiary` and the `deadline`. 2. **Unlock** — after `deadline`, the beneficiary spends the UTxO. Before `deadline`, every unlock attempt aborts. The on-chain logic checks the transaction's validity-interval lower bound against `deadline`, plus the beneficiary's signature. ## On-chain: `vesting.pebble` ```ts contract Vesting { state Locked { beneficiary: PubKeyHash deadline: int // POSIX milliseconds spend claim() { const { tx, state: { beneficiary, deadline } } = context; assert tx.signatories.includes(beneficiary); // tx.validityInterval.from.boundary is the earliest the tx is // allowed to be on-chain. If the lower bound is >= deadline, // we *know* the deadline has passed when this script runs. const Finite{ n: txEarliest } = tx.validityInterval.from.boundary; assert txEarliest >= deadline; } } } ``` ### Why this shape - **Validity-interval check, not "now"**. There is no "now" inside a validator — scripts are deterministic. Instead, the transaction promises an interval during which it's valid, and the script reads that interval. The off-chain builder picks the bound. - **`Finite` destructuring** asserts the bound is concrete (not `-∞`). If a malicious submitter set the bound to negative infinity, the destructure would fail. See [Pitfalls](../onchain/reference/Pitfalls.md). - **One state**, so the datum is `Locked{ beneficiary, deadline }` — `Constr(0, [B, I])`. ## Off-chain: TypeScript with `@harmoniclabs/buildooor` ```ts import { Address, Credential, Hash28, DataConstr, DataB, DataI, TxBuilder, TxOut, } from "@harmoniclabs/buildooor"; import { readFile } from "fs/promises"; import { provider } from "./provider"; const scriptCbor = await readFile("./vesting.uplc"); const vestingHash = new Hash28(/* blake2b_224(scriptCbor) */); const vestingAddr = new Address("mainnet", Credential.script(vestingHash)); ``` ### Lock ```ts async function lockVesting({ beneficiaryPkh, // Hash28 deadlineMillis, // bigint (POSIX ms) lockValue, // Value wallet, privateKey, }) { const datum = new DataConstr(0, [ new DataB(beneficiaryPkh.toBuffer()), new DataI(deadlineMillis), ]); const txBuilder = new TxBuilder(await provider.getProtocolParameters()); const tx = txBuilder.buildSync({ inputs: wallet.utxos.map((utxo) => ({ utxo })), outputs: [ new TxOut({ address: vestingAddr, value: lockValue, datum, }), ], changeAddress: wallet.address, }); tx.signWith(privateKey); return await provider.submitTx(tx); } ``` ### Claim (after the deadline) ```ts async function claimVesting({ vestingUtxo, deadlineMillis, beneficiaryWallet, beneficiaryPrivateKey, scriptRefUtxo, }) { const txBuilder = new TxBuilder(await provider.getProtocolParameters()); // claim() takes no arguments, so its redeemer is Constr(0, []). const redeemer = new DataConstr(0, []); // Set the transaction's validity *lower bound* at or after the deadline. // buildooor exposes posixToSlot for this — pass `deadlineMillis` and the // builder picks the right slot. const invalidBefore = txBuilder.posixToSlot(Number(deadlineMillis)); const tx = txBuilder.buildSync({ inputs: [ { utxo: vestingUtxo, referenceScript: { refUtxo: scriptRefUtxo, redeemer }, }, ...beneficiaryWallet.utxos.map((utxo) => ({ utxo })), ], outputs: [ new TxOut({ address: beneficiaryWallet.address, value: vestingUtxo.resolved.value, }), ], collaterals: [ beneficiaryWallet.utxos[0] ], changeAddress: beneficiaryWallet.address, requiredSigners: [ beneficiaryWallet.pkh ], invalidBefore, // sets tx.validityInterval.from }); tx.signWith(beneficiaryPrivateKey); return await provider.submitTx(tx); } ``` ## Uses - [`contract`](../onchain/Contract%20Statements.md), [`state`](../onchain/State.md), `spend` - [`Finite`](../onchain/API/types/Interval/IntervalBoundary.md), [`Interval`](../onchain/API/types/Interval/Interval.md), `tx.validityInterval` - `tx.signatories` - buildooor: `TxBuilder.posixToSlot`, `invalidBefore`, `DataConstr`, `DataB`, `DataI` ## See also - [Validators 101](../onchain/concepts/Validators%20101.md) — why we use the validity interval instead of a "now" - [Pitfalls](../onchain/reference/Pitfalls.md) — destructuring an interval boundary safely - [Hello World](./Hello%20World.md) — the same pattern without the time check === Installation.md === --- sidebar_position: 2 --- # Installation ``` bun install -g @harmoniclabs/pebble-cli ``` :::info `bun` or `npm` We suggest using `bun` for installation if you have it available on your machine. This is because [`bun`](https://bun.sh/) offers some good performance improvements for the compiler. Alternatively, if you don't want to use `bun`, you can use any javascript package manager of your choice, since the source of the compiler is present on the npm public registry. `npm` is the package manager use by `NodeJS`. Most of the times you'll find `npm` already present on your machine. If that is not the case, you can install `node` and `npm` from the [`NodeJS` website](https://nodejs.org/en/) ::: ### executable from source :::warning `bun` required Building an executable assumes `bun` is present on your machine ::: ``` git clone https://github.com/HarmonicLabs/pebble cd pebble/packages/pebble-cli npm i # install dependencies npm run build-exe ``` :::info `pebble` executable if the build was successful, you should see an executable at the `out/pebble` path. ::: ## Editor support — `Pebble Language` extension The official VS Code extension ships syntax highlighting, on-save type checking, and Language-Server-Protocol features (go-to-definition, hover types, diagnostics) for `.pebble` files. ### From inside VS Code 1. Open the Extensions view: `Ctrl+Shift+X` (or `Cmd+Shift+X` on macOS). 2. Search for **`Pebble Language`**. 3. Install the entry published by `HarmonicLabs`. ### From the command line ```bash code --install-extension HarmonicLabs.pebble-lsp ``` Or, for VS Codium / OSS builds that use the Open VSX registry: ```bash codium --install-extension HarmonicLabs.pebble-lsp ``` ### Marketplace links - VS Code Marketplace: [`HarmonicLabs.pebble-lsp`](https://marketplace.visualstudio.com/items?itemName=HarmonicLabs.pebble-lsp) - Open VSX (VS Codium, Cursor, etc.): [`HarmonicLabs/pebble-lsp`](https://open-vsx.org/extension/HarmonicLabs/pebble-lsp) :::info `pebble` on `PATH` recommended The extension's LSP shells out to the `pebble` CLI for compilation diagnostics. Install it first (see the section at the top of this page) so the editor can drive real type-checks rather than just colourise the file. ::: ## Sanity check If everything proceeded smoothly, you should have the `pebble` command available in your terminal. You can make sure everything is working properly by running ```bash pebble --version ``` which should output something like ``` pebble-cli version: 0.3.1 pebble language version: 0.3.1 commit hash: c991db9a357a7d214a7a17f867047ab7a3418e2f ``` you can also run ```bash pebble help ``` for more info on the command === onchain/API/types/Address/Address.md === # Address `Address` type represents a Cardano address, encompassing both payment and optional staking credentials. ## Fields | Field | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `payment` | Represents the payment credential associated with the address. | | `stake` | Represents the staking credential associated with the address. This is optional and may be `null` if no staking credential is provided. | === onchain/API/types/Address/Credential.md === # Credential `Credential` type (payment or staking); containing either public key hash (in the case of a user) and script based credentials. ## Fields | Variant | Description | | -------- | --------------------------------------------------------------------------- | | `PubKey` | Represents a public key credential, identified by a hash of the public key. | | `Script` | Represents a script credential, identified by a hash of the script. | === onchain/API/types/Governance/GovernanceAction.md === --- sidebar_label: Governance Action --- # GovAction The `GovAction` type represents different kinds of governance actions in the system. ## `ParameterChange` Represents a change in governance parameters. | Field | Description | | ------------------------ | ------------------------------------------------------------- | | `govActionId` | Optional reference to the governance action. | | `changedParameters` | Map of changed parameters. | | `constitutionScriptHash` | Optional constitution script hash associated with the change. | ## `HardForkInitiation` Initiates a protocol version upgrade (hard fork). | Field | Description | | --------------------- | -------------------------------------------- | | `govActionId` | Optional reference to the governance action. | | `nextProtocolVersion` | The next protocol version for the hard fork. | ## `TreasuryWithdrawals` Handles withdrawals from the treasury. | Field | Description | | ------------------------ | ------------------------------------------------------------------ | | `withdrawals` | Map of credentials to withdrawal amounts. | | `constitutionScriptHash` | Optional constitution script hash associated with the withdrawals. | ## `NoConfidence` Signals a no-confidence vote. | Field | Description | | ------------- | -------------------------------------------- | | `govActionId` | Optional reference to the governance action. | ## `UpdateCommittee` Updates committee membership and quorum. | Field | Description | | ------------- | ----------------------------------------------------- | | `govActionId` | Optional reference to the governance action. | | `removed` | List of credentials being removed from the committee. | | `newMembers` | Map of new members and their weights. | | `newQuorum` | New quorum ratio as a rational number. | ## `NewConstitution` Introduces a new constitution. | Field | Description | | ------------- | -------------------------------------------- | | `govActionId` | Optional reference to the governance action. | | `info` | Constitution information. | ## `InfoAction` Represents a purely informational governance action. :::info Not yet defined ::: === onchain/API/types/Governance/ProposalProcedure.md === # ProposalProcedure Represents a governance proposal including its deposit, proposer credential, and associated action. ## Fields | Field | Description | | ------------ | ------------------------------------------------------ | | `deposit` | Amount of funds deposited for the proposal. | | `credential` | Credential of the proposer (who submits the proposal). | | `action` | The governance action associated with the proposal. | === onchain/API/types/Governance/ProtocolVersion.md === # ProtocolVersion The `ProtocolVersion` type represents the major and minor version numbers of the protocol. ## Fields | Field | Description | | ------- | ----------------------------------------- | | `major` | The major version number of the protocol. | | `minor` | The minor version number of the protocol. | === onchain/API/types/Governance/Rational.md === # Rational The `Rational` type represents a rational number as a fraction. ## Fields | Field | Description | | ------------- | --------------------------------------- | | `numerator` | The numerator of the rational number. | | `denominator` | The denominator of the rational number. | === onchain/API/types/Governance/Vote.md === # Vote Represents a vote choice in governance. ## `No` Represents a `No` vote ## `Yes` Represents a `Yes` vote. ## `Abstain` Represents an abstention. === onchain/API/types/Governance/Voter.md === # Voter `Voter` type represents the different kinds of entities that can participate in governance. ## `Committee` Represents a committee member voter. | Field | Description | | ------------ | -------------------------------------------- | | `credential` | Credential identifying the committee member. | ## `DRep` Represents a decentralized representative voter. | Field | Description | | ------------ | -------------------------------------------------------- | | `credential` | Credential identifying the decentralized representative. | ## `StakePool` Represents a stake pool voter. | Field | Description | | ------------ | -------------------------------------------------------- | | `credential` | Credential identifying the decentralized representative. | === onchain/API/types/Interval/ExtendedInteger.md === # ExtendedInteger Represents an integer with optional infinite boundaries. ## `NegInf` Represents negative infinity. ## `Finite` Represents negative infinity. | Field | Description | | ----- | ----------------- | | `n` | The integer value | ## `PosInf` Represents positive infinity. === onchain/API/types/Interval/IntervalBoundary.md === # IntervalBoundary Represents a boundary of a validity interval. ## Fields | Field | Description | | ----------- | ------------------------------------ | | `boundary` | The boundary value (ExtendedInteger) | | `isInclusive` | Whether the boundary is inclusive | === onchain/API/types/Interval/Interval.md === # Interval Represents a validity interval. ## Fields | Field | Description | | ----- | ----------------- | | `from` | Starting boundary | | `to` | Ending boundary | === onchain/API/types/ScriptContext/Certificate.md === # Certificate It represents any possible certificate that can be included in a transaction. ## `StakeRegistration` Stake credential registration certificate | Field | Description | | -------- | -------------------------------- | | `stakeKey` | The credential of the stake key. | | `deposit` | Optional deposit in lovelaces. | ## `StakeDeRegistration` Stake credential de-registration certificate | Field | Description | | -------- | -------------------------------- | | `stakeKey` | The credential of the stake key. | | `refund` | Optional refund in lovelaces. | ## `Delegation` Delegation of the founds controlled by the specified `PCredential` to the specified poolId | Field | Description | | --------- | ----------------------------------- | | `delegator` | The credential of the delegator. | | `delegatee` | The delegatee (stake pool or DRep). | ## `RegistrationAndDelegation` Combined registration and delegation certificate. | Field | Description | | ---------------- | ----------------------------------- | | `delegator` | The credential of the delegator. | | `delegatee` | The delegatee (stake pool or DRep). | | `lovelacesDeposit` | Deposit in lovelaces. | ## `DRepRegistration` Delegated representative (DRep) registration certificate. | Field | Description | | ---------------- | ----------------------- | | `drep` | Credential of the DRep. | | `lovelacesDeposit` | Deposit in lovelaces. | ## `DRepUpdate` Update certificate for an existing DRep. | Field | Description | | ----- | ----------------------- | | `drep` | Credential of the DRep. | ## `DRepDeRegistration` DRep de-registration certificate. | Field | Description | | ------- | ---------------------------- | | `poolId` | Public key hash of the pool. | | `poolVRF` | VRF key of the pool. | ## `PoolRegistration` Pool registration certificate | Field | Description | | ------- | ---------------------------- | | `poolId` | Public key hash of the pool. | | `poolVRF` | VRF key of the pool. | ## `PoolRetire` Pool de-registration certificate | Field | Description | | ------ | ---------------------------- | | `poolId` | Public key hash of the pool. | | `epoch` | Epoch at which to retire. | ### `CommitteeHotAuthorization` Authorization certificate for committee hot keys | Field | Description | | ----- | --------------------------- | | `cold` | Credential of the cold key. | | `hot` | Credential of the hot key. | ### `CommitteeResignation` Resignation certificate for committee members. | Field | Description | | ----- | --------------------------- | | `cold` | Credential of the cold key. | === onchain/API/types/ScriptContext/Delegatee.md === # Delegatee Represents the entity to which voting power is delegated. ## `StakePool` Delegation to a stake pool. | Field | Description | | ------------- | -------------------------------- | | `poolKeyHash` | The public key hash of the pool. | ## `DRep` Delegation to a DRep (delegated representative). | Field | Description | | ------- | --------------------------- | | `drep` | The credential of the DRep. | ## `PoolAndDRep` Delegation to both a stake pool and a DRep. | Field | Description | | ------------- | -------------------------------- | | `poolKeyHash` | The public key hash of the pool. | | `drep` | The credential of the DRep. | === onchain/API/types/ScriptContext/ScriptContext.md === # ScriptContext Represents the context of script execution. | Field | Description | | -------- | --------------------------------- | | `tx` | Transaction being validated | | `redeemer` | Redeemer data associated | | `purpose` | Script purpose (see `ScriptInfo`) | === onchain/API/types/ScriptContext/ScriptInfo.md === # ScriptInfo Represents information related to a specific script execution purpose. ## `Mint` Information for a minting script. | Field | Description | | -------- | ----------------------------- | | `policy` | Policy ID associated with the minting. | ## `Spend` Information for a spending script. | Field | Description | | ------------- | ------------------------------------------------ | | `ref` | Reference to the transaction output being spent. | | `optionalDatum` | Optional datum associated with the output. | ## `Withdraw` Information for a withdrawal script. | Field | Description | | ------------ | ---------------------------------- | | `credential` | Credential authorized to withdraw. | ## `Certificate` Information for a certificate-related script. | Field | Description | | ---------------- | ------------------------- | | `certificateIndex` | Index of the certificate. | | `certificate` | The certificate details. | ## `Vote` Information for a voting script. | Field | Description | | ----- | ---------------------- | | `voter` | The voter information. | ## `Propose` Information for a proposal script. | Field | Description | | ------------- | --------------------------- | | `proposalIndex` | Index of the proposal. | | `proposal` | Proposal procedure details. | === onchain/API/types/ScriptContext/ScriptPurpose.md === # ScriptPurpose Represents the purpose of a script execution in the blockchain context. ## `Mint` Represents a minting action. | Field | Description | | -------- | ----------------------------- | | `policy` | Policy ID governing the mint. | ## `Spend` Represents spending of an output. | Field | Description | | ----- | ------------------------------------------------ | | `ref` | Reference to the transaction output being spent. | ## `Withdraw` Represents a withdrawal action. | Field | Description | | ------------ | ---------------------------------- | | `credential` | Credential authorized to withdraw. | ## `Certificate` Represents a certificate-related action. | Field | Description | | ------------- | ------------------------------------------- | | `index` | Index of the certificate. | | `certificate` | Credential associated with the certificate. | ## `Vote` Represents a governance voting action. | Field | Description | | ------- | ------------------------------------ | | `voter` | The voter participating in the vote. | ## `Propose` Represents a governance proposal action. | Field | Description | | ---------- | -------------------------------------- | | `index` | Index of the proposal. | | `proposal` | The proposal procedure being proposed. | === onchain/API/types/ScriptContext/StakeCredential.md === # StakeCredential Represents a staking credential used in various on-chain operations. ## `Credential` Represents a standard credential-based stake. | Field | Description | | ---------- | ----------------------------------------- | | `credential` | The credential associated with the stake. | ## `Ptr` Represents a pointer-based stake credential. | Field | Description | | ----- | -------------------------------- | | `a` | First component of the pointer. | | `b` | Second component of the pointer. | | `c` | Third component of the pointer. | === onchain/API/types/Tx/OutputDatum.md === # OutputDatum Represents the datum associated with a transaction output. ## `NoDatum` Indicates there is no datum attached. ## `DatumHash` Represents a datum referenced by its hash. | Field | Description | | ----- | ------------------------- | | `hash` | 32-byte hash of the datum | ## `InlineDatum` Represents a datum provided inline. Represents a datum provided inline. === onchain/API/types/Tx/TxIn.md === # TxIn Represents a transaction input. ## Fields | Field | Description | | -------- | ------------------------------- | | `txOutRef` | Reference to the previous TxOut | | `resolved` | The resolved TxOut being spent | === onchain/API/types/Tx/Tx.md === # Tx Represents a complete transaction. ## Fields | Field | Description | | ---------------- | --------------------------------------- | | `inputs` | List of transaction inputs (`TxIn`) | | `refInputs` | List of reference inputs (`TxIn`) | | `outputs` | List of transaction outputs (`TxOut`) | | `fee` | Transaction fee | | `mint` | Minted value | | `certificates` | List of certificates (`Certificate`) | | `withdrawals` | Map of credential to withdrawal amount | | `validityInterval` | Validity interval (`Interval`) | | `requiredSigners` | List of required public key hashes | | `redeemers` | Map of script purposes to redeemer data | | `datums` | Map of hash32 to datum data | | `hash` | Transaction hash | | `votes` | Map of voter to votes | | `proposals` | List of proposal procedures | | `currentTreasury` | Optional current treasury amount | | `treasuryDonation` | Optional treasury donation amount | === onchain/API/types/Tx/TxOut.md === # TxOut Represents a transaction output. ## Fields | Field | Description | | --------------- | -------------------------------- | | `address` | Address receiving the output | | `value` | Value contained in the output | | `datum` | Output datum (see `OutputDatum`) | | `referenceScript` | Optional reference script hash | === onchain/API/types/Tx/TxOutRef.md === # TxOutRef The `TxOutRef` type represents a reference to a specific transaction output on the blockchain. ## Fields | Field | Description | | ------- | ----------------------------------------------------------------------- | | `id` | The transaction hash identifying the transaction containing the output. | | `index` | The index of the output within the referenced transaction. | === onchain/Builtin Interfaces.md === --- sidebar_position: 8 sidebar_label: Builtin Interfaces --- # Builtin Interfaces *Pebble 0.3.1 · all symbols on this page are stable.* Pebble's type-class machinery is exposed via **interfaces** — sets of methods a type can implement. Two are built into the prelude and auto-derived for every type that supports them. User-defined interfaces use the same dispatch mechanism but require explicit `implements` blocks. ```ts type Foo implements Show { show( self ): bytes { return "Foo".toBytes(); } } ``` The compiler resolves interface methods at **monomorphisation time** — when a generic function constrained on `T: SomeInterface` is called with a concrete type, the compiler looks up that type's impl (either user-defined or built-in factory) and inlines the right method IR. There is no on-chain dictionary-passing overhead. ## `ToData` Defines the canonical conversion of a value to Plutus `data`. Every concrete Pebble type that can be encoded as `data` automatically has a `ToData` impl — primitives, `List`, `Array`, `LinearMap` (when K and V are themselves `ToData`), `Optional`, structs and enums, and the native `Value`. ```ts interface ToData { toData( self ): data; } ``` The auto-derivation is provided by the compiler's built-in factory in [`populateBuiltinInterfaces.ts`](https://github.com/HarmonicLabs/pebble/blob/main/packages/pebble/src/compiler/tir/program/stdScope/populateBuiltinInterfaces.ts). For each concrete type, the factory returns the IR closure that performs the right encoding: | Type | Lowering | |------------------|---------------------------------------------------------------| | `int` | `iData` | | `bytes` | `bData` | | `bool` | `Constr(0/1, [])` | | `List` | `listData(map(toData, xs))` | | `LinearMap` | `mapData` with `mkPairData` + `toData`/`toData` | | `Optional` | `Constr(0, [toData(x)])` for `Some`, `Constr(1, [])` for `None` | | struct | `Constr(0, [toData(field_1), toData(field_2), ...])` | | enum / SoP | `Constr(variantIndex, [toData(field_1), ...])` | | native `Value` | the `valueData` builtin | ### Calling it Every value has a `.toData()` method: ```ts const n: int = 42; const d: data = n.toData(); // iData(42) const xs: List = [1, 2, 3]; const dl: data = xs.toData(); // listData([iData(1), iData(2), iData(3)]) ``` ### Implementing it on your own type Most user types don't need a hand-rolled `ToData` — the auto-derivation covers them. Reach for an explicit impl only when you need a non-canonical encoding (e.g. a legacy on-chain shape from a previous protocol version). ```ts struct LegacyRecord { a: int, b: bytes, } type LegacyRecord implements ToData { toData( self ): data { // Hand-rolled to match an older protocol's wire format. return std.builtins.constrData(7, [ self.b.toData(), // legacy order: b first, then a self.a.toData(), ]); } } ``` ### Using it as a constraint Stdlib generics that need to encode `T` use a `T: ToData` constraint. `std.linearMap.prepend` is the canonical example: ```ts // signature: prepend(k: K, v: V, m: LinearMap): LinearMap const m: LinearMap = {}; const m2 = std.linearMap.prepend(#01, 100, m); ``` ## `Show` Defines a human-readable byte representation. Built-in for primitives; user-overridable. ```ts interface Show { show( self ): bytes; } ``` | Type | Default impl | |----------|--------------------------------------------------------------| | `int` | Decimal digits (`42` → `#3432`) | | `bytes` | Hex digits, lower-case, no `0x` prefix (`#deadbeef` → `#6465616462656566`) | | `bool` | `#74727565` (`"true"`) or `#66616c7365` (`"false"`) | | `string` | UTF-8 bytes (essentially `.toBytes()`) | ### Calling it ```ts const n: int = 42; trace(n.show()); // emits "42" to the trace log ``` ### Implementing it on your own type Useful for adding debug-friendly trace output without writing a hand-rolled formatter every time: ```ts struct Account { name: bytes, balance: int, } type Account implements Show { show( self ): bytes { return self.name.concat(": ").toBytes() .concat(self.balance.show()); } } const a = Account{ name: "alice".toBytes(), balance: 100 }; trace(a.show()); // "alice: 100" ``` ### Using it as a constraint If you write a generic helper that traces its argument: ```ts function debugIdentity( x: T ): T { trace(x.show()); return x; } ``` ## Implementation note Both interfaces are registered in the prelude via the same `populateBuiltinInterfaces` entry point. At call time, dispatch goes through `resolveInterfaceImpl(concreteType, interfaceName)` which: 1. **First** checks the type's `methodsNamesPtr` / `methodNamesPtr` for a user-supplied impl. 2. **Then** falls back to the built-in factory map (`program.builtinInterfaceImpls`). 3. **Otherwise** raises a `"type X does not implement Y"` compile error. This means **user impls always win** over the built-in derivation. If you write a `type int implements ToData` (which would be unusual but legal), your version replaces the default `iData` lowering for every call site that monomorphises against `int`. ## See also - [`data`](./Prelude/Data.mdx) — the type `ToData` produces - [`std.builtins`](./Standard%20Library/std.builtins.mdx) — the underlying data constructors (`constrData`, `iData`, `bData`, …) - [Datum & Redeemer Flow](./concepts/Datum%20and%20Redeemer%20Flow.md) — when and where `ToData` is invoked at the on-chain ↔ off-chain boundary === onchain/Builtin Values and Literals.md === --- sidebar_position: 9 sidebar_label: Builtin Values and Literals --- # Builtin Values and Literals *Pebble 0.3.1 · all symbols on this page are stable.* Every literal form Pebble's tokenizer and parser recognise, plus the small set of implicit bindings the compiler injects into method bodies. ## Integer literals | Form | Example | Notes | |----------------------|-----------------|----------------------------------------------------| | Decimal | `42`, `-7`, `0` | Arbitrary precision — no overflow. | | Underscore separator | `1_000_000` | Cosmetic; ignored at parse time. | | Hexadecimal | `0xCAFE` | Case-insensitive after `0x`. | All resolve to type [`int`](./Prelude/Int.mdx). ```ts const big: int = 1_000_000; const hex: int = 0xCAFE; const neg: int = -7; ``` ## Byte-string literals | Form | Example | Notes | |--------------|-----------------|------------------------------------------------------------------| | Hex literal | `#deadbeef` | Each pair of hex digits is one byte. Case-insensitive after `#`. | | Empty | `#""` | Zero-length byte string. | | From string | `"x".toBytes()` | UTF-8 encode a `string` literal (via the [`string`](./Prelude/String.mdx) method). | All resolve to type [`bytes`](./Prelude/Bytes.mdx). ```ts const policy: PolicyId = #cafedead; const empty: bytes = #""; const utf8: bytes = "hello".toBytes(); ``` ## String literals Double-quoted UTF-8 text. Strings exist primarily for [`trace`](./Syntax/Control%20Flow%20Statements.md) messages and `fail` reasons — avoid storing large strings on-chain (every byte costs CPU and memory in the script budget). ```ts const msg: string = "Order filled"; trace(msg); ``` Escapes (`\n`, `\t`, `\"`, `\\`, etc.) are recognised inside double quotes. There are **no** template strings — concatenate with `+`. ## Boolean literals `true`, `false` — type [`bool`](./Prelude/Boolean.mdx). ```ts const ok: bool = true; if( ok ) { trace("ok"); } ``` ## Void / unit literal `()` is the canonical inhabitant of `void`. Most users never need to spell it explicitly; it appears as the return type of statement-like expressions (`trace(...)`, `assert ...`). ```ts const nothing: void = (); ``` ## Array (list) literals Square-bracketed comma-separated elements: ```ts const empty: List = []; const xs: List = [1, 2, 3]; const nested: List> = [[1, 2], [3, 4]]; ``` The element type is inferred from the declared type, or from the first element if it's unambiguous. An empty literal `[]` requires an explicit type annotation. These produce values of type [`List`](./Prelude/List.mdx). To get an [`Array`](./Prelude/Array.mdx) (the random-access form), pass a list through [`std.array.fromList`](./Standard%20Library/std.array.mdx). ## Map literals Brace-enclosed `key: value` pairs: ```ts const empty: LinearMap = {}; const m: LinearMap = { #01: 10, #02: 20 }; ``` Produces a [`LinearMap`](./Prelude/LinearMap.mdx). Order of entries is preserved; duplicates are allowed (matching the underlying assoc-list semantics). ## Struct / variant constructors Named constructors use `Name{ field: value, … }`: ```ts struct Order { owner: PubKeyHash, policy: PolicyId, amount: int, } const o: Order = Order{ owner: #cafedead, policy: #beef1234, amount: 100, }; ``` For enums and sum types (including `Optional`), name the specific variant: ```ts const some: Optional = Some{ value: 42 }; const none: Optional = None{}; ``` `Some{ value }` shorthand (where the variable `value` is in scope) works inside both expressions and `match`-arm patterns. ## Implicit bindings inside method bodies The compiler injects two implicit names into the body of any `contract` method (`spend`, `mint`, `certify`, `withdraw`, `propose`, `vote`): ### `context: ScriptContext` The full script context for the current invocation. Destructure it for the fields you actually need: ```ts spend handler() { const { tx, purpose, redeemer } = context; // tx — the full Tx // purpose — the ScriptPurpose discriminator // redeemer — this invocation's redeemer (typed to the method's params) } ``` For `spend` methods, additional fields are available: | Field | Type | Notes | |--------------------|-----------------------|----------------------------------------------------------| | `tx` | `Tx` | The full transaction. | | `purpose` | `ScriptPurpose` | `Spend{ ref: TxOutRef }` for `spend` methods. | | `spendingInputRef` | `TxOutRef` | The reference of the UTxO being spent (purpose-ref). | | `optionalDatum` | `Optional` | The raw datum, when no `state` is in scope. | | `state` | `` | Present only inside a `spend` method declared on a [`state`](./State.md). | See [Validators 101](./concepts/Validators%20101.md) for the full per-purpose breakdown. ### `this` Reserved for future use in method-on-type forms (e.g. user-defined `impl Foo { ... }`). Not generally available today. ## `fail` expression `fail` is both a statement and an expression. As a statement it aborts the script unconditionally with an optional trace message: ```ts fail; // bare abort fail "missing datum"; // abort with a string message in the trace fail "expected: " + expected.show(); // abort with a dynamic message ``` The expression form (i.e. `fail` appearing in expression position) has type `void` — it never produces a value because it never returns. ## `undefined` `undefined` is a reserved identifier that produces a value of any type. It's used only by the compiler in synthesised IR; user code should not reference it. Reading from it at runtime aborts the script. ## See also - [Prelude Types](/category/prelude-types) — what each literal form produces - [Syntax / Control Flow Statements](./Syntax/Control%20Flow%20Statements.md) — `if`, `for`, `while`, `match`, `return`, `assert`, `trace`, `fail` - [Contract Statements](./Contract%20Statements.md) — where `context` and per-purpose extras come from === onchain/CLI.md === --- sidebar_position: 15 --- # CLI Pebble comes with a **command-line interface**(CLI) for working with smart contracts. The `pebble` command-line tool is the official utility for working with Pebble projects. Now, let's see how you can use the Pebble CLI. ```bash # if installed globally npm install -g @harmoniclabs/pebble-cli # then pebble # or, use `npx` / direct node invocation if you prefer not to install globally npx pebble # if the package is published that way ``` ## Commands ### `help` Display help for pebble commands #### Usage ```bash pebble help ``` ### `version` Output the pebble CLI and language versions Alternative: `--version` #### Usage ```bash pebble version pebble --version ``` ### `init` Creates a new directory with a fresh pebble project #### Usage ```bash pebble init ``` ### `compile` Compiles a Pebble project #### Usage ```bash pebble compile [options] ``` #### Options - `-c ` : The config file path. Default: `./pebble.config.json`. Alternative: `--config` - `--entry ` : The entry file path (a `.pebble` file). Used only if it is missing from the configuration. Default: `./index.pebble`. - `-o ` : The output file path. Used only if it is missing from the configuration. Default: `./out.flat`. Alternative: `--output` See [Compiler Configuration](./Compiler%20Configuration.md) for the full `pebble.config.json` schema. #### Example ```bash pebble compile --config ./pebble.config.json # or with explicit entry and output pebble compile --entry ./src/contracts/MyContract.pebble -o ./build/MyContract.flat ``` ### `test` Run all tests in the project. #### Usage ```bash pebble test [options] ``` #### Options - `--config ` : The config file path. Default: `./pebble.config.json`. - `--testPathPattern ` : Only run files whose path matches the regex. - `--testNamePattern ` : Only run tests whose name matches the regex. - `--property-runs ` : Iterations per property-based test. Default: `100`. - `--seed ` : Seed for the property-based PRNG. Default: `0`. #### Example ```bash pebble test --testNamePattern "add" --property-runs 1000 --seed 42 ``` See [Testing](./Testing.md) for how to write unit and property-based tests. === onchain/Compiler Configuration.md === --- sidebar_position: 16 --- # Compiler Configuration The Pebble compiler reads its options from a `pebble.config.json` file at the root of your project. The [CLI](./CLI.md) resolves it relative to the current working directory and defaults to `./pebble.config.json`. You can point to a different file with the `--config` flag. ## Example ```json { "compilerVersion": "^0.3.1", "entry": "./src/index.pebble", "outDir": "./out", "removeTraces": true } ``` `pebble init` generates a working `pebble.config.json` for you. ## Fields ### `compilerVersion` **Required since `0.2.0`.** Type: `string` (npm-style semver range). The compiler validates this range against its own version at startup and throws if it does not satisfy it. A missing or empty value is treated as invalid. ```json { "compilerVersion": "^0.3.1" } ``` See [Supported semver syntax](#supported-semver-syntax) below for the accepted range forms. :::caution Migration from 0.1.x → 0.2.0 Projects created on Pebble `0.1.x` did not include `compilerVersion`. Add it to your `pebble.config.json` before upgrading the compiler, otherwise you will see: ``` Error: Pebble compiler config is missing "compilerVersion". Starting from @harmoniclabs/pebble@0.2.0 this field is required and must be an npm-style semver range (e.g. "^0.2.0"). ``` If your compiler version does not satisfy the configured range: ``` Error: Pebble compiler version 0.2.0 does not satisfy the configured "compilerVersion" range "^0.3.0". ``` ::: ### `entry` Type: `string`. Default: `"./src/index.pebble"`. Path to the entry `.pebble` file, relative to `root`. ### `root` Type: `string`. Default: `"."`. Path to the project root. All other relative paths in the config are resolved against this. ### `outDir` Type: `string`. Default: `"./out"`. Directory where the compiler writes its output (the compiled `out.flat` and related artefacts). ### `silent` Type: `boolean`. Default: `false`. When `true`, suppresses compiler stdout output. Useful for embedding the compiler programmatically. ### `targetUplcVersion` Type: `UPLCVersion`. Default: the current default UPLC version exported by `@harmoniclabs/uplc`. The UPLC version that gets encoded into the compiled script. Most users should leave this at the default. ### `removeTraces` Type: `boolean`. Default: `true`. When `true`, strips `trace` statements from the compiled output. Set to `false` during debugging to keep them in the script. ### `delayHoists` Type: `boolean`. Default: `true`. When `true`, hoisted closed terms are converted to `letted` terms instead of being unconditionally added to the script's roots. Hoisted terms are blindly included as part of the script's initialization, so unused hoists in some branches (common in multi-purpose scripts) still pay the initialization cost. Lettings move that cost into the branches that actually use the term, at the price of slightly higher compilation time. Recommended `true` for production builds. ### `uplcOptimizations` Type: `object`. A bag of fine-grained UPLC optimization flags. Default fields: - `groupApplications: boolean` (default `true`) — group sequential applications into single applications where possible. - `inlineSingleUse: boolean` (default `true`) — inline terms that are used exactly once. - `simplifyWrappedPartialFuncApps: boolean` (default `true`) — simplify partial function applications wrapped inside other terms. - `removeForceDelay: boolean` (default `true`) — collapse `force (delay x)` pairs. You can override individual flags: ```json { "uplcOptimizations": { "inlineSingleUse": false } } ``` The other flags fall back to their defaults. ### `addMarker` Type: `boolean`. Default: `true`. When `true`, the compiler emits a script marker (used by tooling to identify Pebble-generated scripts). Disable for size-sensitive deployments where the marker is not needed. ## Supported semver syntax `compilerVersion` accepts a subset of the npm semver range grammar, sufficient for pinning the compiler in real projects: | Form | Example | Matches | | --- | --- | --- | | Exact | `0.2.0` | only `0.2.0` | | Comparator | `>=0.2.0`, `>0.2.0`, `<=0.3.0`, `<0.3.0`, `=0.2.0` | versions satisfying the comparator | | Caret | `^0.2.0` | `>=0.2.0 <0.3.0` (pre-1.0 caret pins the minor; `^1.2.3` → `>=1.2.3 <2.0.0`) | | Tilde | `~0.2.0` | `>=0.2.0 <0.3.0` | | Wildcards | `*`, `x`, `0.x`, `0.2.x` | any matching the wildcard pattern | | Hyphen range | `0.2.0 - 0.3.0` | `>=0.2.0 <=0.3.0` | | AND | `>=0.2.0 <0.4.0` | all comparators must hold (separated by whitespace) | | OR | `^0.2.0 \|\| ^0.3.0` | any sub-range must hold (separated by `\|\|`) | :::info Prerelease and build-metadata suffixes (`0.2.0-beta`, `0.2.0+build.1`) are accepted but ignored — only the numeric `MAJOR.MINOR.PATCH` core is compared. The Pebble compiler ships plain `MAJOR.MINOR.PATCH` releases. ::: === onchain/concepts/Datum and Redeemer Flow.md === --- sidebar_position: 3 sidebar_label: Datum & Redeemer Flow --- # Datum & redeemer flow Validators in Pebble communicate with their off-chain partners through two channels: the **datum** (per-UTxO state) and the **redeemer** (per-action intent). Understanding which goes where, and when, is the difference between code that "compiles" and code that actually validates the transactions you intend. ## The data path ``` ┌──────────────┐ ┌──────────────┐ │ Off-chain │ ── places UTxO with datum ──→ │ Ledger │ │ (TxBuilder) │ │ (chain state)│ └──────────────┘ └──────────────┘ │ ┌──────────────┐ ── spends UTxO + redeemer ──→ │ │ Off-chain │ ──────────────────────────────────────────┘ │ (TxBuilder) │ ↓ └──────────────┘ ┌──────────────────────┐ │ Validator script │ │ reads: state, │ │ redeemer, │ │ context.tx │ └──────────────────────┘ ``` Datum is attached to a UTxO **when it is created**. Redeemer is attached to an input **when that UTxO is spent**. The script sees both, plus the transaction itself, and either returns void (accept) or fails (reject). ## Datum | Aspect | Where it lives | Set by | When | |--------------------|------------------------------------|------------------------------------------|-------------------------------------| | Datum | A `TxOut` | The transaction that created the output | Once, at output-creation time | | Inline vs hash | Either the full datum or its hash | The output's creator | At output-creation time | | Pebble surface | `context.optionalDatum: Optional` or, for stateful contracts, `context.state` | | | When you write a `state` in Pebble, the compiler synthesizes a **sum type** with one constructor per `state` declaration. The datum on a UTxO at the contract's address is a CBOR-encoded instance of that sum. The compiler also bakes the decode logic into the validator, so `context.state` is already typed by the time you see it. If you don't use `state`, you can read the raw datum: ```ts spend handler() { const { optionalDatum } = context; match optionalDatum { Some{ value: rawData }: useIt(rawData), None{}: fail "missing datum" } } ``` ## Redeemer | Aspect | Where it lives | Set by | When | |--------------------|------------------------------------|------------------------------------------|-------------------------------------| | Redeemer | An entry in `tx.witnesses.redeemers` | The transaction submitter | When the action is attempted | | Per-purpose | Each spend / mint / certify / withdraw / propose / vote has its own redeemer | Submitter | Same | | Pebble surface | The method's **parameters** | | | The redeemer that picks **which** method to dispatch is the redeemer's constructor index. The redeemer **payload** is the destructured fields. If `OrderBook` declares two `spend` methods: ```ts contract OrderBook { state Simple { /* ... */ spend fill(inputIdx: int, outputIdx: int) { /* ... */ } } spend cancel() { /* ... */ } } ``` then off-chain you pick: - `DataConstr(0, [DataI(i), DataI(o)])` → dispatches to `fill(i, o)` - `DataConstr(1, [])` → dispatches to `cancel()` The constructor index is the **method's declaration order** for that script purpose. ## Building these in buildooor Given a Pebble method like ```ts spend handler(payload: MyRedeemer) { /* ... */ } ``` and an off-chain shape ```ts import { DataConstr, DataI, DataB } from "@harmoniclabs/buildooor"; ``` the rules are: 1. **Top-level**: every redeemer is a `Constr(methodIndex, [...])`. 2. **`int`**: `DataI(value)` where `value` is `bigint`. 3. **`bytes`**: `DataB(uint8array)`. 4. **`string`**: encode as UTF-8 bytes, then `DataB(...)`. 5. **`bool`**: `DataConstr(0, [])` for `false`, `DataConstr(1, [])` for `true` (UPLC convention). 6. **`Optional`**: `DataConstr(0, [DataT])` for `Some{ value }`, `DataConstr(1, [])` for `None{}`. 7. **`List` / `Array`**: `DataList([t0, t1, ...])` using `DataList` from buildooor. 8. **`LinearMap`**: `DataMap([[k0, v0], [k1, v1], ...])` using `DataMap`. 9. **`struct Foo { a, b, c }`**: `DataConstr(0, [a, b, c])` — one constructor, fields in declaration order. 10. **A `state` constructor**: `DataConstr(index, [field0, field1, ...])` where `index` is the state's position among the contract's `state` declarations. Failing to match these is the most common cause of "the script ran and rejected for no obvious reason". When in doubt, log the buildooor-computed CBOR and compare to what the validator pattern-matches. ## A worked round-trip Pebble side: ```ts contract Counter { state Running { count: int owner: PubKeyHash spend increment(by: int) { const { tx, state: { count, owner } } = context; assert tx.signatories.includes(owner); assert by > 0; // (a fuller example would also assert that the continuing output // carries the updated state datum and the same value) } } } ``` Off-chain side: ```ts import { DataConstr, DataI, DataB } from "@harmoniclabs/buildooor"; // Building the *output* datum for a fresh counter: const initialState = new DataConstr(0, [ new DataI(0n), // count new DataB(ownerPubKeyHash.toBuffer()), // owner ]); // Building the *spend* redeemer to increment by 5: const incRedeemer = new DataConstr(0, [ new DataI(5n), // by ]); ``` When the transaction is submitted, the validator receives `context.state == Running{ count: 0, owner: }` and the redeemer decodes as `(by: 5)`. The `spend increment` body runs. ## Common confusions - **"The datum is the redeemer"** — no. Datum is on the UTxO, set at creation. Redeemer is on the input, set at spend. They serve different purposes and live in different parts of the transaction. - **"The redeemer is just the method arguments"** — yes, but with a constructor tag selecting **which** method. Don't forget the `Constr(index, [...])` wrapper. - **"`fail` in a redeemer aborts the transaction"** — the redeemer is data, it doesn't run anything. The validator runs and may abort. "Sending a fail redeemer" isn't a thing. - **"I can read the datum of another input"** — yes: every `TxIn` in `tx.inputs` carries its resolved `TxOut`, including the datum. Patterns like "verify the order at input N still holds" read `tx.inputs[N].resolved.datum`. ## See also - [Validators 101](./Validators%20101.md) — how `context` carries everything - [State](../State.md) — how `state` synthesizes the datum sum type - [Contract Statements](../Contract%20Statements.md) — `spend` / `mint` / ... method-kind dispatch - [Simple Order Book DEX](../../examples/Simple%20order%20book%20DEX.md) — datum and redeemer in a non-trivial example === onchain/concepts/UTxO Model.md === --- sidebar_position: 1 sidebar_label: UTxO Model --- # The Cardano UTxO model Cardano's ledger is a set of **unspent transaction outputs (UTxOs)**. A transaction consumes some UTxOs (inputs) and produces fresh ones (outputs). That's the entire state model. There's no global "account balance"; "balance" is just the sum of UTxOs you can spend. ## A UTxO has four parts | Field | Type | Role | |------------|---------------------|-------------------------------------------------------------------------------| | Address | `Address` | Where the funds live. Determines who can spend. | | Value | `Value` | What's in the UTxO — ADA, native tokens, NFTs. | | Datum | `OutputDatum` | Optional Datum, can be either missing, a hash of the datum (backwards compatibility only), or inline with the actual data present, effectively acts as a `Optional` although with different contstructors | | Reference script | `Optional` | If present (`Some` constructor) it specifies the 28-length hash of the ref script attached to the utxo | Outputs that pay to a **script address** (an address whose payment credential is a `ScriptHash`) typically carry a datum — that's how the script remembers what state it's guarding across transactions. Since Plutus V3, the datum is no longer mandatory: a spend script can validate a UTxO with `NoDatum` provided its logic doesn't need any persisted state (for instance, when the decision depends only on the current transaction's inputs, outputs, and signatories). When a datum *is* present, the validator is responsible for treating it as input that must be checked, not as a trusted fact — past validation only guarantees the datum was accepted by whichever script wrote it. ## What spending looks like A transaction `tx` has: - `inputs: List` — UTxOs being consumed, by reference. - `outputs: List` — fresh UTxOs being produced. - `mint: Value` — tokens being minted or burned. - `signatories: List` — who has signed. - ... plus fees, validity interval, certificates, governance, etc. The transaction is **valid** if: 1. Every script protecting an input or controlling a mint approves it. 2. The values balance: `sum(inputs.value) + mint == sum(outputs.value) + fee`. 3. Other ledger rules (signatures, time, slot limits, ...) are satisfied. If any single rule fails — including any single script saying "no" — the whole transaction is rejected. There is no partial state on chain. ## Where Pebble fits Pebble compiles a `contract` declaration into a **Plutus script**: a small program that the Cardano ledger evaluates with the relevant context to produce `true`, `false`, or a failure. A script in Pebble can guard: - **Spending** a UTxO at a script address (`spend` methods). - **Minting / burning** tokens under a policy (`mint`). - **Certifying** delegations, registrations (`certify`). - **Withdrawing** rewards (`withdraw`). - **Governance**: `propose`, `vote`. These six **script purposes** are the six method-kinds inside `contract { ... }`. One Pebble contract can compile to a single script that handles several or all of them. ## Datums vs redeemers | Concept | Lives in | Set by | When | Pebble term | |-----------|-------------------|----------------|----------------------------|---------------| | Datum | A UTxO (output) | The output's creator | At UTxO creation time | `state` fields, or raw `data` in `OutputDatum` | | Redeemer | A spend / mint / ... action | The transaction submitter | When the action is attempted | Method arguments | A datum is **per-UTxO state**: "this order wants 100 USDC at minimum". A redeemer is **per-action intent**: "fill the order with input index 2 and output index 3". Together they pin down exactly what the script must check. ## Reference inputs A transaction can include **reference inputs**: UTxOs it reads but does not consume. They're how you publish on-chain data once and have many transactions read it without paying to spend it. Oracle outputs, registry entries, and stored scripts (`reference scripts`) all use them. In a Pebble validator, you can read reference inputs via `tx.referenceInputs: List`. ## Determinism Cardano transactions are **deterministic**: given a transaction and ledger state, every node reaches the same accept/reject decision and the same script budget. There's no on-chain randomness, no concurrency, no network calls. A validator that runs today will run identically tomorrow against the same inputs. This determinism is why off-chain code (e.g. `@harmoniclabs/buildooor`) can construct a transaction, predict the exact script execution, and submit it with confidence. ## See also - [Validators 101](./Validators%20101.md) — what a `contract` actually does at runtime - [State](../State.md) — Pebble's stateful-UTxO abstraction - [Tx, TxIn, TxOut](../API/types/Tx/Tx.md) — the API shapes you read in validator bodies - [ScriptContext](../API/types/ScriptContext/ScriptContext.md) — what `context` carries === onchain/concepts/Validators 101.md === --- sidebar_position: 2 sidebar_label: Validators 101 --- # Validators 101 A **validator** is a function `(context) -> void | fail` that the Cardano ledger calls when a UTxO is spent, a token is minted, or a similar event happens. If the call returns, the action is allowed; if it `fail`s, the action is rejected. In Pebble, you don't write the validator function directly — you write a `contract` declaration. The compiler synthesizes the validator from your methods. ## How a `contract` becomes a validator A Pebble file like: ```ts contract Vault { param owner: PubKeyHash; spend withdraw() { const { tx } = context; assert tx.signatories.includes(owner); } } ``` compiles to (conceptually): ```ts func main(owner: PubKeyHash, context: ScriptContext): void { const { tx, purpose, redeemer } = context; match purpose { when Spend{}: { const { /* redeemer fields */ } = redeemer; const tx_local = context.tx; assert tx_local.signatories.includes(owner); } else: fail; } } ``` Three things to notice: 1. **The contract parameters become curried arguments** to the validator. When you compile, you bake them in: `Vault(myOwnerHash)` produces a no-parameter validator that knows the owner. 2. **The script purpose drives dispatch**. The validator matches on `context.purpose` and runs the right method. 3. **An unmatched purpose fails**. If you only define `spend` methods and the script is invoked as a mint, it rejects. ## The six script purposes | Purpose | Triggered by | Pebble method-kind | |--------------|-----------------------------------------------|--------------------| | `Spend` | Spending a UTxO at the script's address | `spend` | | `Mint` | Minting or burning under a policy | `mint` | | `Certify` | A certificate (stake registration, etc.) | `certify` | | `Withdraw` | Withdrawing staking rewards | `withdraw` | | `Propose` | Proposing a governance action | `propose` | | `Vote` | Voting on a governance action | `vote` | A single contract can define methods for any subset. Most contracts only need one or two. ## What `context` carries Inside any method body, `context` is bound and ready to destructure: ```ts spend handler() { const { tx, purpose, redeemer } = context; // tx: Tx — every field of the transaction // purpose: ScriptPurpose — which purpose is firing right now // redeemer: — the redeemer for *this* invocation } ``` For `spend` methods, `context` also exposes: - `spendingInputRef: TxOutRef` — the reference of the UTxO being spent (the "purpose ref"). - `optionalDatum: Optional` — the datum on that UTxO, if any. - For stateful contracts, `state` — the typed datum (see [State](../State.md)). For `mint` methods, you get the minting policy hash via `purpose`. For `certify`/`withdraw`/`propose`/`vote`, the purpose carries the relevant payload (the certificate, the credential, the action ID, the vote). ## The redeemer Each invocation of a script carries a **redeemer** — arbitrary `data` that the transaction submitter supplied. In Pebble, the method's parameters **are** the redeemer: ```ts spend fillOrder(inputIdx: int, outputIdx: int) { ... } ``` means "this script expects a redeemer that decodes as a two-field record `(int, int)`". At call time, the off-chain code (e.g. `buildooor`) supplies that record. ## What "returning" means A Pebble method returns `void`. The validator accepts if every `assert` in the method passes and no `fail` is hit. There's no return value to inspect — the contract is "succeeded" or "failed". This is unlike Plutus's older "true/false" convention. Pebble is closer to "throw on rejection". ## Off-chain partner A validator alone doesn't make a useful dApp — you need code that **constructs and submits the transactions** that meet the validator's expectations. Use `@harmoniclabs/buildooor` for that. The orderbook example shows both sides side by side. ## Compiling and deploying ```bash pebble build my_contract.pebble -o my_contract.uplc ``` emits the UPLC binary. From there, attach it to a transaction as a script (or as a reference script for later reuse) using your off-chain builder. ## See also - [UTxO Model](./UTxO%20Model.md) — the substrate validators run on - [Contract Statements](../Contract%20Statements.md) — full `contract` grammar - [State](../State.md) — typed datums via the `state` keyword - [ScriptContext](../API/types/ScriptContext/ScriptContext.md) — the field-by-field reference - [Simple Order Book DEX](../../examples/Simple%20order%20book%20DEX.md) — a worked example with both on-chain and off-chain code === onchain/Contract Statements.md === --- sidebar_position: 4 sidebar_label: Contract Statements --- # Contract A **contract** in Pebble is the top-level unit of on-chain logic. It defines one or more **validators** (entry points), which are compiled into Plutus Core scripts. Contracts are declared using the `contract` keyword. Inside a contract you declare **validators** — functions that define the on-chain logic. Syntax: ```ts contract ContractName { // one or more validator statements spend validatorName(...) { ... } mint validatorName(...) { ... } ... } ``` - `ContractName` must be unique within a compilation unit. - Each validator compiles into a separate script that can be deployed on-chain. ## Context Inside a contract, Pebble automatically provides a context binding called `context`. This represents the current script execution context and gives access to transaction information. :::info `context` is not a normal variable, it is a special keyword. It’s a synthetic binding automatically injected by the compiler to let you access the current validator execution environment. The compiler internally uses a construct called contextVarsMapping to resolve the fields that can be extracted from context. You cannot reassign or shadow it — it’s purely a read-only view into the validator’s execution context. ::: The shape of context depends on the validator purpose. Inside each validator, a context object provides: - `tx` : Transaction - `purpose` : Purpose reference depending on the validator kind (`spendingInputRef`, `mintingPolicyHash` etc.) - `datum`, `redeemer` - optional fields Each validator function inside a contract is independent — compilation produces one Plutus script per validator. Context is immutable — you can destructure it, but not reassign. The available fields in context vary depending on the validator purpose. Pebble enforces correct context usage at compile time. #### Example: ```ts contract OnlySpend { spend allowSpending() { const { tx, spendingInputRef } = context; // simple always-valid contract assert true; } } ``` Here, the contract is `OnlySpend`. It has a single `spend` validator `allowSpending`. `context` is a keyword bound to the spending validator context. In the contract body, the `context` is destructured to access the transaction (`tx`) and spending input (`spendingInputRef`). ## Contract Parameters Pebble contracts can take **parameters** at declaration time. These are values that are **fixed at contract instantiation** and become part of the compiled script hash. This is useful when you want to bind a contract to a specific constant, like an owner’s public key or a fixed configuration (e.g., fee rate) ```ts contract ContractName { param param1: Type; param param2: Type; // ... // validator definitions } ``` Each parameter is declared on its own line inside the contract body using the `param` keyword, followed by a name, a `:` type, and a terminating `;`. Parameters must appear before the validator (`spend` / `mint` / etc.) declarations. They are available inside every validator of the contract. Parameters are immutable once compiled, i.e. they cannot be changed without recompiling the contract. Hence, it is ideal to use parameters for constants known at deployment time, and not for values that change per transaction. Contract parameters are especially useful for Single-owner scripts (binding a contract to a fixed PubKeyHash), Policy scripts (tying to a fixed minting policy). ## Validators Functions that define the on-chain logic. Supported validator types: - `spend` – Spending validator - `mint` – Minting policy - `certify` – Certificate validator - `withdraw` – Withdrawal validator - `propose` – Governance proposal validator - `vote` – Governance vote validator ## Context for each Validator The shape of `context depends on the validator purpose: ### For `spend` validators ```ts { tx: Tx, spendingInputRef: TxInputRef, optionalDatum: Option } ``` ### For `mint` validators: ```ts { tx: Tx, mintingPolicyHash: MintingPolicyHash } ``` ### For `certify` validators: ```ts { tx: Tx, certificate: Certificate } ``` ### For `withdraw` validators: ```ts { tx: Tx, withdrawal: Withdrawal } ``` ### For `propose` and `vote` validators (used in governance features): ```ts { tx: Tx, proposal: Proposal, vote: Vote } ``` === onchain/Prelude/Array.mdx === --- sidebar_position: 7 sidebar_label: Array --- # `Array` *Pebble 0.3.1 · all symbols on this page are stable.* A fixed-size, indexed container. Built from a `List` via [`std.array.fromList`](../Standard%20Library/std.array.mdx) and consumed via `at(i)` or the index operator. `Array` is **not** the right answer to "I need fast indexed reads on a `List`". The on-chain cost model bills [`List` indexing](./List.mdx) as effectively constant for practical indices, and `std.array.fromList` itself is O(n) in the list length — so the conversion almost always costs more than the reads you were optimising. See [Cost and Complexity](../reference/Cost%20and%20Complexity.md) for the measured numbers. Reach for `Array` only when: - You receive one from elsewhere (an external API, a builtin) and need to read it. - You're doing many indexed reads against a buffer that you'll keep across many call sites and the amortised conversion is genuinely cheap. For sequential traversal (`map`, `foldl`, `filter`, ...), use `List` directly. import RunnableExample from '@site/src/components/RunnableExample'; = std.array.fromList([10, 20, 30, 40]);\n\nassert std.array.length(xs) == 4 else "wrong length";\nassert std.array.at(xs, 2) == 30 else "at(2) wrong";`} height="240px" /> ## Methods | Method | Description | |----------------------------------------------|----------------------------------------------------------------------------| | `length(): int` | Number of elements. O(1). | | `at(i: int): T` | Element at index `i`. Fails on out-of-bounds. | ### Examples (one per method) ```ts const xs: Array = std.array.fromList([10, 20, 30]); const n: int = xs.length(); // 3 const v: int = xs.at(1); // 20 ``` ## Indexing `xs[i]` is sugar for `xs.at(i)`. Both are O(1). ## Constructing `Array` has no literal form. Build one from a list: ```ts const xs: Array = std.array.fromList([1, 2, 3]); ``` ## See also - [`std.array`](../Standard%20Library/std.array.mdx) — function-call surface - [`List`](./List.mdx) — what most pipelines operate on. Indexed reads on a list are already cheap; don't convert unless you have a specific reason - [Cost and Complexity](../reference/Cost%20and%20Complexity.md) — measured numbers for list-vs-array access === onchain/Prelude/Boolean.mdx === --- sidebar_position: 4 sidebar_label: bool --- # `bool` *Pebble 0.3.1 · all symbols on this page are stable.* Logical truth value: `true` or `false`. Maps to Plutus Core `Bool`. ```ts const signed: bool = tx.signatories.includes(ownerHash); if( signed && tx.outputs.length() > 0 ) { trace("OK"); } else { fail "Unauthorized"; } ``` ## Literals `true`, `false`. ## Operators | Category | Operators | |-------------|--------------------------------------------------------------------------| | Logical | `&&` (short-circuit AND), `\|\|` (short-circuit OR), `!` (NOT) | | Equality | `==`, `!=` | :::tip Short-circuit vs strict `&&` and `\|\|` are short-circuit: the right operand is only evaluated if the left doesn't decide the result. If you need strict evaluation (rare — usually for higher-order use), see [`std.boolean.strictAnd`](../Standard%20Library/std.boolean.mdx) and `strictOr`. ::: ## Methods `bool` has no method-call surface. Use operators or the [`std.boolean`](../Standard%20Library/std.boolean.mdx) namespace. ## See also - [`std.boolean`](../Standard%20Library/std.boolean.mdx) — function-call surface - [`std.builtins`](../Standard%20Library/std.builtins.mdx#polymorphic-intrinsics) — `ifThenElse` is the strict polymorphic conditional === onchain/Prelude/Bytes.mdx === --- sidebar_position: 2 sidebar_label: bytes --- # `bytes` *Pebble 0.3.1 · all symbols on this page are stable.* Raw byte string. Maps to Plutus Core `ByteString`. Used for hashes, policy IDs, token names, signatures, and anything else that's "just bytes on the wire". import RunnableExample from '@site/src/components/RunnableExample'; ## Literals | Form | Example | Notes | |---------------------|------------------------|-------------------------------------------------------------| | Hex literal | `#1a2b3c` | Each pair of hex digits is one byte. Case-insensitive. | | Empty | `#""` | Zero-length byte string. | | From string | `"MyToken".toBytes()` | UTF-8 encode a string literal. | ## Operators | Category | Operators | |-------------|--------------------------------------| | Equality | `==`, `!=` | | Concatenation | `+` | | Ordering | `<`, `<=`, `>`, `>=` (lexicographic) | ## Methods | Method | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `length(): int` | Number of bytes. | | `slice(start: int, len: int): bytes` | `len`-byte sub-string starting at `start`. Clips on out-of-range. | | `prepend(byteVal: int): bytes` | Returns a new byte string with `byteVal` (0–255) prepended. | | `concat(other: bytes): bytes` | Concatenate; same as `a + other`. | | `indexAt(i: int): int` | Byte at index `i` as `0–255`. Fails on out-of-range. | | `equals(other: bytes): bool` | Same as `==`. | | `lessThan(other: bytes): bool` | Same as `<`. | | `lessThanEquals(other: bytes): bool` | Same as `<=`. | | `greaterThan(other: bytes): bool` | Same as `>`. | | `greaterThanEquals(other: bytes): bool` | Same as `>=`. | | `toInt(): int` | Parse as big-endian unsigned integer. | ### Examples (one per method) ```ts const policy: bytes = #deadbeef; const n: int = policy.length(); // 4 const sub: bytes = policy.slice(1, 2); // #adbe const pre: bytes = policy.prepend(0xff); // #ffdeadbeef const cat: bytes = #dead.concat(#beef); // #deadbeef const at: int = policy.indexAt(0); // 222 (0xde) const eq: bool = policy.equals(#deadbeef); // true const lt: bool = #ab.lessThan(#cd); // true const le: bool = #ab.lessThanEquals(#ab); // true const gt: bool = #cd.greaterThan(#ab); // true const ge: bool = #ab.greaterThanEquals(#ab); // true const i: int = #04d2.toInt(); // 1234 ``` ## Type aliases The standard library defines several `bytes` aliases for clarity at usage sites: | Alias | Width | Used for | |----------------|-------|-------------------------------------------| | `Hash28` | 28 | Generic 224-bit hashes. | | `Hash32` | 32 | Generic 256-bit hashes. | | `PubKeyHash` | 28 | Blake2b-224 of a payment public key. | | `ScriptHash` | 28 | Blake2b-224 of a serialized script. | | `TxHash` | 32 | Blake2b-256 of a serialized transaction. | | `PolicyId` | 28 | The script hash of a minting policy. | | `TokenName` | 0–32 | An asset name within a policy. | These are aliases, not nominal types — you can freely pass a `bytes` where any of them is expected and vice versa. ## See also - [`std.bytes`](../Standard%20Library/std.bytes.mdx) — function-call surface - [`std.crypto`](../Standard%20Library/std.crypto.mdx) — hash functions produce `bytes` - [`std.builtins`](../Standard%20Library/std.builtins.mdx) — bitwise operations and `consByteString`, `replicateByte`, etc. === onchain/Prelude/Data.mdx === --- sidebar_position: 5 sidebar_label: data --- # `data` *Pebble 0.3.1 · all symbols on this page are stable.* The Plutus Core `Data` type: a serialization format that every on-chain type can be encoded into and decoded from. Datums, redeemers, and the script context all flow through `data` at the system boundary. You rarely write `data` directly; the compiler does it for you whenever you use `.toData()` or pattern-match a datum. Reach for raw `data` only when you need to construct or inspect the CBOR shape yourself. `data` has five constructors (CBOR major types in parentheses): | Constructor | Shape | Builtin builder | |-------------|----------------------------------------|-------------------------------------------------| | Constr | `(index: int, fields: List)` | [`std.builtins.constrData`](../Standard%20Library/std.builtins.mdx) | | Map | `LinearMap` | [`std.builtins.mapData`](../Standard%20Library/std.builtins.mdx) | | List | `List` | [`std.builtins.listData`](../Standard%20Library/std.builtins.mdx) | | I (integer) | `int` | [`std.builtins.iData`](../Standard%20Library/std.builtins.mdx) | | B (bytes) | `bytes` | [`std.builtins.bData`](../Standard%20Library/std.builtins.mdx) | ## Operators | Category | Operators | |----------|-----------------| | Equality | `==`, `!=` | Equality is structural CBOR equality, implemented by `equalsData`. ## Working with `data` ```ts using { constrData, unConstrData, iData, bData, listData } = std.builtins; using { strToData } = std.data; // Build: const d: data = constrData(0, [iData(42), bData(#cafe)]); // Destructure: const RawConstr{ index, fields } = unConstrData(d); match index { 0: trace("Some"), _: fail "unknown constructor" } ``` For most use cases prefer the `.toData()` method (via the [`ToData`](../Builtin%20Interfaces.md#todata) interface) and pattern-matching on the typed value, rather than touching raw `data`. ## See also - [`std.builtins`](../Standard%20Library/std.builtins.mdx) — `data` constructors and destructors, `serialiseData`, `equalsData` - [`std.data`](../Standard%20Library/std.data.mdx) — `string ↔ data` helpers - [`ToData`](../Builtin%20Interfaces.md#todata) — auto-derived `.toData()` for every encodable type - [`LinearMap`](./LinearMap.mdx), [`List`](./List.mdx) — the containers `data` is built from === onchain/Prelude/Int.mdx === --- sidebar_position: 1 sidebar_label: int --- # `int` *Pebble 0.3.1 · all symbols on this page are stable.* Arbitrary-precision signed integer. Maps to Plutus Core's `Integer`. Does not overflow. import RunnableExample from '@site/src/components/RunnableExample'; = 0 else "underflow";\nassert left == 999_500 else "math off";`} height="220px" /> ## Literals | Form | Example | Notes | |---------------------|------------------------|---------------------------------------------| | Decimal | `42`, `-7`, `0` | The default form. | | Underscore separator| `1_000_000` | Cosmetic; ignored at parse time. | | Hexadecimal | `0xCAFE` | Case-insensitive after the `0x`. | ## Operators | Category | Operators | |-------------|--------------------------------------------------------------------------| | Arithmetic | `+`, `-`, `*`, `/` (floor division), `%` (floor modulo) | | Comparison | `<`, `<=`, `>`, `>=`, `==`, `!=` | | Unary | `-` (negation) | :::note Division semantics `/` and `%` round toward **negative infinity** (matching `std.int.divide` / `std.int.mod`). For C-style truncated division (toward zero), use [`std.int.quotient`](../Standard%20Library/std.int.mdx) / [`std.int.remainder`](../Standard%20Library/std.int.mdx). ::: ## Methods `int` has no method-call surface. All operations are via operators or the [`std.int`](../Standard%20Library/std.int.mdx) namespace. Common idioms: | Want… | Use | |------------------------------------|---------------------------------------------------------------------------| | Modular exponentiation | [`std.builtins.expModInteger(base, exp, m)`](../Standard%20Library/std.builtins.mdx) | | `int → bytes` (smallest fit) | [`std.bytes.fromInt(n)`](../Standard%20Library/std.bytes.mdx) | | `int → bytes` (fixed width) | [`std.builtins.integerToByteString(true, size, n)`](../Standard%20Library/std.builtins.mdx) | | `int → bool` | [`std.int.toBoolean(n)`](../Standard%20Library/std.int.mdx) | | Encoded as `data` | `n.toData()` ([`ToData`](../Builtin%20Interfaces.md#todata)) — uses `iData` | ## See also - [`std.int`](../Standard%20Library/std.int.mdx) — function-call surface - [`bytes`](./Bytes.mdx) — conversion target - [`ToData`](../Builtin%20Interfaces.md#todata) — auto-derived `.toData()` === onchain/Prelude/LinearMap.mdx === --- sidebar_position: 8 sidebar_label: LinearMap --- # `LinearMap` *Pebble 0.3.1 · all symbols on this page are stable.* An association list — Cardano's on-chain "map". Structurally a `List>` where each entry is a `(K, V)` pair. Lookups are O(n); choose it because the underlying Plutus representation is what the chain speaks, not because it's fast. ```ts const m: LinearMap = { #01: 10, #02: 20 }; match m.lookup(#01) { Some{ value: amount }: trace(amount), None{}: fail "missing" } ``` ## Literals ```ts const empty: LinearMap = {}; const m: LinearMap = { #01: 10, #02: 20 }; ``` The key/value types are inferred from context. ## Methods | Method | Description | |---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `length(): int` | Number of entries. O(n). | | `isEmpty(): bool` | True iff there are no entries. | | `head(): LinearMapEntry` | First `(K, V)` pair. Fails on empty. | | `tail(): LinearMap` | Map without its first entry. Fails on empty. | | `lookup(k: K): Optional` | First value bound to `k`, wrapped in `Some`, or `None`. | | `prepend(k: K, v: V): LinearMap` (requires `K: ToData`, `V: ToData`) | Add `(k, v)` at the front. Both type parameters must implement `ToData`. | :::note Duplicate keys `LinearMap` allows duplicates. `lookup` returns the **first** match. To enforce uniqueness, do so at insertion or de-duplicate before lookup. ::: ### Examples (one per method) ```ts const m: LinearMap = { #01: 10, #02: 20 }; const n: int = m.length(); // 2 const e: bool = m.isEmpty(); // false const h: LinearMapEntry = m.head(); // (#01, 10) const t: LinearMap = m.tail(); // { #02: 20 } const got: Optional = m.lookup(#02); // Some{ value: 20 } const m2: LinearMap = m.prepend(#03, 30); // { #03:30, #01:10, #02:20 } ``` ## `LinearMapEntry` The entry returned by `head()`. Has two fields: | Field | Type | Description | |----------|------|-------------------| | `fst` | `K` | The key. | | `snd` | `V` | The value. | ## Iteration `LinearMap` is structurally a list, so anything that works on a list works here. To iterate, use `for...of` or list helpers: ```ts for( const entry of m ) { trace(entry.fst); trace(entry.snd); } ``` ## See also - [`std.linearMap`](../Standard%20Library/std.linearMap.mdx) — function-call surface - [`List`](./List.mdx) — structural relative - [`std.builtins`](../Standard%20Library/std.builtins.mdx#data-destructors) — `mapData` / `unMapData` === onchain/Prelude/List.mdx === --- sidebar_position: 6 sidebar_label: List --- # `List` *Pebble 0.3.1 · all symbols on this page are stable.* A singly-linked list of elements of type `T`. Maps to Plutus Core's built-in list. Despite being a cons list at the interpreter level, **indexed access `xs[i]` is essentially free in the on-chain cost model**: it lowers to `headList (dropList i xs)`, and `dropList` is billed by the bit-width of `i` (constant for any practical index), not by how many cons cells get walked. So unlike with arrays in other languages, you don't need to convert to [`Array`](./Array.mdx) for cheap indexed reads — see [Cost and Complexity](../reference/Cost%20and%20Complexity.md) for the measured numbers. import RunnableExample from '@site/src/components/RunnableExample'; = [1, 2, 3, 4, 5];\n\nconst total = std.list.foldl((acc, n) => acc + n, 0, xs);\nconst evens = std.list.filter((n) => n % 2 == 0, xs);\n\nassert total == 15 else "sum wrong";\nassert std.list.length(evens) == 2 else "filter wrong";`} height="280px" /> ## Literals ```ts const empty: List = []; const xs: List = [1, 2, 3]; ``` The element type is inferred from context (or from the first element if obvious). ## Methods | Method | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `length(): int` | Element count. Walks the spine; O(n) in actual work, charged O(n) by the cost model. | | `isEmpty(): bool` | True if the list has zero elements. | | `head(): T` | First element. Fails on empty. | | `tail(): List` | Everything after the head. Fails on empty. | | `prepend(x: T): List` | New list with `x` at the front. | | `drop(n: int): List` | List with the first `n` elements removed. | | `map(f: (T) -> A): List` | Transform every element. Method-only — there's no `std.list.map`. | | `filter(p: (T) -> bool): List` | Keep only elements satisfying `p`. | | `foldr(f: (T, A) -> A, init: A): A` | Right fold. | | `foldl(f: (A, T) -> A, init: A): A` | Left fold. Tail-recursive; prefer for large lists. | | `some(p: (T) -> bool): bool` | True if any element satisfies `p`. Short-circuits. | | `every(p: (T) -> bool): bool` | True if all elements satisfy `p`. Short-circuits. | | `find(p: (T) -> bool): Optional` | First match wrapped in `Some`, or `None`. | | `equals(eq: (T, T) -> bool, other: List): bool` | Element-wise equality using `eq`. | ### Examples (one per method) ```ts const xs: List = [1, 2, 3, 4, 5]; const n: int = xs.length(); // 5 const e: bool = xs.isEmpty(); // false const h: int = xs.head(); // 1 const t: List = xs.tail(); // [2,3,4,5] const ys: List = xs.prepend(0); // [0,1,2,3,4,5] const dr: List = xs.drop(2); // [3,4,5] const dbl: List = xs.map((n) => n * 2); // [2,4,6,8,10] const ev: List = xs.filter((n) => n % 2 == 0); // [2,4] const sumR: int = xs.foldr((a, acc) => a + acc, 0); // 15 const sumL: int = xs.foldl((acc, a) => acc + a, 0); // 15 const any: bool = xs.some((n) => n > 4); // true const all: bool = xs.every((n) => n > 0); // true const got: Optional = xs.find((n) => n == 3); // Some{ value: 3 } const same: bool = xs.equals(std.int.equals, xs); // true ``` ### Pipeline example ```ts struct Input { policy: bytes, name: bytes, amount: int } const inputs: List = [/* ... */]; const total = inputs .filter((i) => i.policy == myPolicy) .foldl((acc, i) => acc + i.amount, 0); assert total > 0; ``` ## Indexing `xs[i]` is sugar for accessing the i-th element. It lowers to `headList(dropList(i, xs))`. The interpreter does O(i) work walking the spine, but the **on-chain cost model bills `dropList` by the bit-width of `i`**, so for any practical index the charged cost is essentially constant. **Don't convert to `Array` just for indexed reads** — `std.array.fromList` itself is linear in the list length, and that conversion is almost always more expensive than however many list-indexing reads you were trying to optimise. Reserve the conversion for scenarios where you genuinely need many random-access reads against the *same* compact buffer. See [Cost and Complexity](../reference/Cost%20and%20Complexity.md) for the measured numbers. ## Operators | Category | Operators | |----------|------------------------------------------------------| | Equality | `==`, `!=` (when `T` implements equality) | ## See also - [`std.list`](../Standard%20Library/std.list.mdx) — function-call surface - [`Array`](./Array.mdx) — random-access counterpart - [`LinearMap`](./LinearMap.mdx) — structurally a `List>` - [`std.builtins`](../Standard%20Library/std.builtins.mdx#polymorphic-intrinsics) — `mkCons`, `headList`, `tailList`, `nullList`, `chooseList` === onchain/Prelude/Optional.mdx === --- sidebar_position: 9 sidebar_label: Optional --- # `Optional` *Pebble 0.3.1 · all symbols on this page are stable.* A value that may be absent. Two constructors: | Constructor | Payload | Meaning | |---------------|------------|------------------------| | `Some{ value: T }` | `value: T` | The value is present. | | `None{}` | — | The value is absent. | ```ts const maybe: Optional = Some{ value: 42 }; match maybe { Some{ value }: trace(value), None{}: fail "missing" } ``` `Optional` is returned by every "lookup-shaped" operation in the standard library: [`List.find`](./List.mdx), [`LinearMap.lookup`](./LinearMap.mdx), and so on. ## Constructing ```ts const present: Optional = Some{ value: 7 }; const absent: Optional = None{}; ``` ## Destructuring with `match` `match` is the canonical way to consume an `Optional`: ```ts match maybe { Some{ value }: doSomething(value), None{}: defaultBehavior() } ``` Bind the inner value with `Some{ value }` (or any field-rename syntax). ## Destructuring with `const` When you know statically that an `Optional` is `Some`, you can destructure directly: ```ts const Some{ value: x } = maybe; ``` This compiles to an assertion: if `maybe` is `None{}` at runtime, the script fails. ## See also - [`List.find`](./List.mdx), [`LinearMap.lookup`](./LinearMap.mdx) — produce `Optional` - [Builtin Values and Literals](../Builtin%20Values%20and%20Literals.md) — pattern-matching syntax === onchain/Prelude/String.mdx === --- sidebar_position: 3 sidebar_label: string --- # `string` *Pebble 0.3.1 · all symbols on this page are stable.* UTF-8 text. Maps to Plutus Core `String`. Primarily for logging via `trace` and for `fail` messages. Avoid storing large strings on-chain — every character costs CPU/memory in the script budget. ```ts const msg: string = "Order filled"; trace(msg); ``` ## Literals | Form | Example | Notes | |---------------|-----------------|--------------------------------------------------| | Double-quoted | `"hello"` | UTF-8. | | Empty | `""` | | ## Operators | Category | Operators | |-------------|----------------------| | Equality | `==`, `!=` | | Concatenation | `+` | ## Methods | Method | Description | |------------------------------|--------------------------------------------------------------| | `toBytes(): bytes` | UTF-8 encode. Equivalent to [`std.builtins.encodeUtf8`](../Standard%20Library/std.builtins.mdx). | To go from `bytes` back to `string`, use [`std.builtins.decodeUtf8`](../Standard%20Library/std.builtins.mdx) (it fails on invalid UTF-8). ## Examples ```ts const greeting: string = "hello"; const u8: bytes = greeting.toBytes(); // #68656c6c6f // Round-trip: const back: string = std.builtins.decodeUtf8(u8); // "hello" // Concatenation and equality: const full: string = greeting + ", world"; assert full == "hello, world"; ``` ## See also - [`std.builtins`](../Standard%20Library/std.builtins.mdx) — `appendString`, `equalsString`, `encodeUtf8`, `decodeUtf8` - [`std.data`](../Standard%20Library/std.data.mdx) — `strToData` / `strFromData` - [`Show`](../Builtin%20Interfaces.md#show) — interface every type can implement to produce a `string` representation === onchain/Prelude/Value.mdx === --- sidebar_position: 10 sidebar_label: Value --- # `Value` *Pebble 0.3.1 · all symbols on this page are stable.* A multi-asset bundle: zero or more `(PolicyId, TokenName) → amount` entries. Maps to Plutus Core's native value type. ADA appears under a special "ada/lovelace" policy. Every `TxOut.value`, `Tx.mint`, and `Tx.fee` is a `Value`. ```ts const out = tx.outputs[0]; assert out.value.lovelaces() >= 2_000_000; assert out.value.amountOf(myPolicy, myToken) == 1; ``` ## Methods | Method | Description | |---------------------------------------------------------------------|------------------------------------------------------------------------------| | `amountOf(policy: PolicyId, name: TokenName): int` | Amount of `(policy, name)`. `0` if absent. | | `lovelaces(): int` | Amount of ADA, in lovelace. | | `insert(policy: PolicyId, name: TokenName, amount: int): Value` | Return a new `Value` with `(policy, name)` set to `amount`. `0` removes. | | `union(other: Value): Value` | Asset-wise sum. | | `contains(other: Value): bool` | True iff `self ≥ other` for every asset. | | `scale(k: int): Value` | Multiply every amount by `k`. | | `toData(): data` | CBOR-encode. Auto-derived from [`ToData`](../Builtin%20Interfaces.md#todata). | ### Examples (one per method) ```ts const v: Value = mempty.insert(myPolicy, myToken, 5); const a: int = v.amountOf(myPolicy, myToken); // 5 const l: int = tx.outputs[0].value.lovelaces(); // ADA in lovelace const v2: Value = v.insert(myPolicy, myToken, 10); // overwrite -> 10 const u: Value = v.union(v); // 2× v const ok: bool = u.contains(v); // true const s: Value = v.scale(3); // 3× v const d: data = v.toData(); ``` ### Bundled example ```ts const need = mempty .insert(myPolicy, myToken, 1) .insert(adaPolicy, adaName, 2_000_000); assert tx.outputs[0].value.contains(need); ``` ## Operators | Category | Operators | |----------|------------------------------------------------------| | Equality | `==`, `!=` (structural) | ## Building values There's no `Value` literal — build values up from an empty starting point (`mempty` from the prelude) using `insert` and `union`, or destructure them out of a `TxOut`. The underlying primitives are in [`std.builtins`](../Standard%20Library/std.builtins.mdx#native-value-operations) (`insertCoin`, `unionValue`, etc.); the friendlier names live in [`std.value`](../Standard%20Library/std.value.mdx). ## See also - [`std.value`](../Standard%20Library/std.value.mdx) — function-call surface - [`PolicyId`, `TokenName`](./Bytes.mdx#type-aliases) — the keys into a `Value` - [`TxOut`](../API/types/Tx/TxOut.md) — the canonical place a `Value` shows up === onchain/reference/Cost and Complexity.md === --- sidebar_position: 4 sidebar_label: Cost and Complexity --- # Cost and complexity On-chain Cardano scripts are bounded by a **CPU and memory budget** (`exUnits`). A transaction whose validators exceed the budget is rejected — there is no "ran slowly" outcome. When generating Pebble code, you have to keep cost in your head from the first line. This page explains how cost shows up in the docs, where to find it programmatically, and the most common high-cost patterns to recognise and replace. ## Where to find per-symbol cost The machine-readable index at [`/symbols.json`](/symbols.json) has a `complexity` field on every method and builtin. It uses standard big-O notation: | Complexity | Meaning | |-------------------|------------------------------------------------------------------------------------------| | `O(1)` | Constant. Pay once. | | `O(n)` | Linear in the size of the relevant input (string length, list spine, value entries). | | `O(n*m)` | Pairwise — e.g. multiplying two `n`- and `m`-digit ints. | | `O(n+m)` | Linear in the combined size — e.g. concatenation. | | `O(len)` | Linear in the requested slice length, not the source. | | `O(log(exp) * n^2)` | Modular exponentiation: log in the exponent, quadratic in the modulus width. | Where a method's cost depends on a runtime argument (e.g. `slice(start, len, b)`), the variable in the complexity is named in the entry. ## Three cost classes that matter on-chain ### A. Linear-in-list operations Most operations that walk a `List` or `LinearMap` spine are **O(n)** in the cost model where `n` is the number of entries. A few list operations that *look* O(n) are actually billed as effectively constant — those are noted below. | Operation | Charged cost | Avoidable? | |------------------------------------------|--------------|---------------------------------------------------------------------------| | `xs.length()` | O(n) | Use `xs.isEmpty()` if you only need "any?". | | `xs[i]` (list indexing) | **≈ O(1)** | **Don't avoid it.** Lowers to `headList (dropList i xs)`; `dropList` is billed by the bit-width of `i`, not by spine length. Converting via `std.array.fromList` is itself O(n) and almost always more expensive than the reads it replaces. | | `m.lookup(k)` | O(n) | Pin uniqueness at insertion; or pre-sort and binary-search off-chain. | | `xs.filter(p)`, `xs.map(f)` | O(n) | Inevitable when you genuinely need every element. | | `xs.find(p)` | O(n) | Short-circuits at the first match. Prefer over `filter().head()`. | | `std.array.fromList(xs)` | O(n) | Inherent. Only pay this if you have a strong reason — see below. | | `std.array.at(arr, i)` | O(1) | Already optimal. | ### B. `Value`-walking operations `Value` is a nested association list: top-level `(PolicyId → (TokenName → amount))`. Every operation walks both levels. | Operation | Cost | Notes | |------------------------------------------|-------|------------------------------------------------------------------------| | `v.amountOf(p, n)` | O(n) | Walks the policy list then the token list. | | `v.lovelaces()` | O(n) | Same; "ada" is just a policy entry. | | `v.union(other)` | O(n+m) | Merge of two assoc-list-of-assoc-lists. | | `v.contains(other)` | O(n+m) | Each entry of `other` requires a lookup in `v`. | If you query the same `(policy, name)` multiple times in one validator, **bind it once**: ```ts const got = tx.outputs[0].value.amountOf(myPolicy, myToken); assert got >= minimum; assert got <= maximum; ``` See [Pitfalls — Re-deriving the same value](./Pitfalls.md). ### C. Arbitrary-precision integer arithmetic `int` is Plutus's `Integer` — arbitrary precision. Most operations are linear in the digit count: | Operation | Cost | |------------------------------------------|----------| | `a + b`, `a - b` | O(n) | | `a * b` | O(n*m) | | `a / b`, `a % b`, `quotient`, `remainder`| O(n*m) | | `expModInteger(base, exp, m)` | O(log(exp) · m²) | For small constants the cost is negligible. For values that come from user input (redeemers), there's no implicit bound — `2^1000` and `1` look identical in source. Bound them explicitly when reading from data. ## Indexed access: list vs array The intuition that "linked lists need O(n) walks for random access, arrays give you O(1)" is **misleading on-chain**, because the Plutus cost model bills `dropList` (which `xs[i]` lowers through) by the bit-width of the integer index, not by the number of cons cells walked. The interpreter still does O(i) work, but the script's exec budget is charged near-constant. Measured `cpu` budget (from `bench.listVsArrayAccess.test.ts` in the compiler repo): | n | i | `xs[i]` (list) | `at(arr, i)` (already array) | `at(fromList(xs), i)` (convert + index) | |-----|-------|---------------:|-----------------------------:|----------------------------------------:| | 1 | 0 | 441,918 | 424,110 | 497,948 | | 16 | 15 | 441,918 | 424,110 | 870,518 | | 64 | 63 | 441,918 | 424,110 | 2,062,742 | | 256 | 255 | 443,875 | 424,110 | 10,035,740 | - List indexing is **1.04× more expensive** than indexing an already-built array — essentially the same. - Converting via `std.array.fromList` to index *once* is **~22× more expensive** than `xs[i]` at n=256. The conversion `std.array.fromList` only pays for itself if you're going to make so many indexed reads that the per-read difference makes up for the linear conversion cost. Given the per-read difference is ~18,000 budget units and the conversion at n=256 costs ~9,600,000 units, that break-even point is roughly **500 indexed reads on the same list**. If you're not doing that many, stay on the list. ## Strict vs short-circuit The operators `&&` and `||` short-circuit — they only evaluate the right operand if the left didn't decide the result. The named forms `std.boolean.strictAnd` and `std.boolean.strictOr` evaluate **both** operands every time, and `std.builtins.ifThenElse` evaluates **both** branches. In hot paths, prefer the short-circuit operators. Reach for the strict forms only when you need them as values (e.g. as a callback). ## When you can ignore cost - **Contract parameters** are baked in at compile time. `param seedRef: TxOutRef;` inside a contract body doesn't cost anything per call — the value lives in the script as a constant. - **Pattern-matching a `state`** is a fixed-shape destructure. Field access is O(1) per field once the state is bound. - **`hash`, `equality`, and signature verification** are O(n) in the input length, not in any data structure depth. Even with a 32-byte input, that's a few hundred CPU units — usually negligible. ## How to estimate a script's budget consumption off-chain `@harmoniclabs/buildooor` calls the Plutus machine in-process to compute `exUnits` for each redeemer before submission. If a script blows the budget, the build itself fails — you don't need a separate dry-run. See the orderbook example for the call site. ## See also - [`/symbols.json`](/symbols.json) — machine-readable cost fields on every symbol - [Pitfalls](./Pitfalls.md) — anti-patterns that inflate cost - [Laws](./Laws.md) — rewrite rules you can use to fuse passes (e.g. `xs.map(f).map(g) == xs.map(g ∘ f)`) === onchain/reference/Failures.md === --- sidebar_position: 1 sidebar_label: Failures --- # Failure catalog Every condition under which a Pebble builtin, prelude method, or stdlib function aborts the script. Aborting means the entire transaction is rejected — there is no partial state on-chain. Use this catalog when generating defensive code: validate inputs before calling, or arrange the call so failure is unreachable. The columns: - **Symbol** — fully qualified name (e.g. `List.head()`). - **Fails when** — preconditions that, if violated, abort. - **Safer alternative** — when one exists. ## Aborting builtins ### Integer division / modulo | Symbol | Fails when | Safer alternative | |-----------------------------------|-------------------------------------|--------------------------------------------| | `a / b` (operator) | `b == 0` | Guard with `b != 0` before dividing. | | `a % b` (operator) | `b == 0` | Same. | | `std.int.divide(a, b)` | `b == 0` | Same. | | `std.int.mod(a, b)` | `b == 0` | Same. | | `std.int.quotient(a, b)` | `b == 0` | Same. | | `std.int.remainder(a, b)` | `b == 0` | Same. | | `std.builtins.divideInteger(a,b)` | `b == 0` | Same. | | `std.builtins.quotientInteger` | `b == 0` | Same. | | `std.builtins.remainderInteger` | `b == 0` | Same. | | `std.builtins.modInteger` | `b == 0` | Same. | | `std.builtins.expModInteger(b,e,m)` | `m == 0` | Guard `m != 0`. | | `std.int.exponentiate(b, e)` | `e < 0` | Check exponent sign before calling. | ### Byte-string indexing | Symbol | Fails when | Safer alternative | |-----------------------------------------|--------------------------------------------------|--------------------------------------------| | `bytes.indexAt(i)` | `i < 0` or `i >= length()` | Check `i < b.length()` first. | | `std.bytes.indexAt(b, i)` | Same. | Same. | | `std.builtins.indexByteString(b, i)` | Same. | Same. | | `std.builtins.consByteString(n, b)` | `n < 0` or `n > 255` | Ensure byte value in 0–255. | | `std.builtins.decodeUtf8(b)` | `b` is not valid UTF-8 | Use raw bytes if validity can't be assured.| ### List operations | Symbol | Fails when | Safer alternative | |-------------------------------------|-------------------------------------|--------------------------------------------| | `List.head()` | List is empty | Guard with `xs.isEmpty()` or use `find`. | | `List.tail()` | List is empty | Same. | | `std.list.head(xs)` | Same. | Same. | | `std.list.tail(xs)` | Same. | Same. | | `std.builtins.headList(xs)` | Same. | Same. | | `std.builtins.tailList(xs)` | Same. | Same. | | `xs[i]` (list indexing) | `i < 0` or `i >= length()` | Check `i >= 0 && i < xs.length()` before indexing. The list-vs-array conversion is *not* a safety win — both forms fail on out-of-range; see [Cost and Complexity](./Cost%20and%20Complexity.md). | ### Array operations | Symbol | Fails when | Safer alternative | |-------------------------------------|-------------------------------------|--------------------------------------------| | `Array.at(i)` | `i < 0` or `i >= length()` | Check bounds before calling. | | `std.array.at(xs, i)` | Same. | Same. | | `xs[i]` (array indexing) | Same. | Same. | ### LinearMap operations | Symbol | Fails when | Safer alternative | |-------------------------------------|-------------------------------------|--------------------------------------------| | `LinearMap.head()` | Map is empty | Guard `isEmpty()`. | | `LinearMap.tail()` | Map is empty | Same. | | `LinearMap.lookup(k)` | **Does not fail.** Returns `None{}` when absent. | — | ### `Optional` destructuring | Symbol | Fails when | Safer alternative | |-----------------------------------------------------|-------------------------------------|--------------------------------------------| | `const Some{ value } = opt;` | `opt` is `None{}` at runtime | Pattern-match with `match opt { ... }`. | ### `data` destructors | Symbol | Fails when | Safer alternative | |-------------------------------------|--------------------------------------------------|--------------------------------------------| | `std.builtins.unConstrData(d)` | `d` is not a `Constr` | Use `chooseData` to dispatch by tag first. | | `std.builtins.unMapData(d)` | `d` is not a `Map` | Same. | | `std.builtins.unListData(d)` | `d` is not a `List` | Same. | | `std.builtins.unIData(d)` | `d` is not an `I` | Same. | | `std.builtins.unBData(d)` | `d` is not a `B` | Same. | | `std.data.strFromData(d)` | `d` is not `B` *or* its bytes aren't valid UTF-8 | Round-trip through `bData(encodeUtf8(s))`. | ### BLS12-381 | Symbol | Fails when | Safer alternative | |-------------------------------------------------|--------------------------------------------------|--------------------------------------------| | `std.crypto.bls12_381.g1Uncompress(b)` | `b` isn't a valid 48-byte compressed G1 point | Only feed it points produced by `g1Compress` or validated upstream. | | `std.crypto.bls12_381.g2Uncompress(b)` | `b` isn't a valid 96-byte compressed G2 point | Same. | ### Assertion forms | Symbol | Fails when | |-------------------------------------------------|----------------------------------------------------------------------------------| | `assert ` | `` evaluates to `false`. | | `fail ""` | Always — this is the explicit-failure form. The `` is recorded in trace.| | Destructuring const binding with wrong variant | E.g. `const Some{ value } = none_value;`. The runtime constructor check fails. | ## Non-aborting "soft failure" surfaces Worth knowing because they look like failures but aren't: | Symbol | Behaviour | |-----------------------------------------------------|-----------------------------------------------------------------------------| | `std.crypto.verifyEd25519Signature(pk, m, s)` | Returns `false` on **any** malformed input — the script does not abort. | | `std.crypto.verifyEcdsaSecp256k1Signature` | Same. | | `std.crypto.verifySchnorrSecp256k1Signature` | Same. | | `Value.amountOf(policy, name)` | Returns `0` if `(policy, name)` is absent. | | `std.builtins.lookupCoin` | Returns `0` if absent. | | `std.list.find(p, xs)` | Returns `None{}` when nothing matches. | | `std.linearMap.lookup(k, m)` | Returns `None{}` when `k` is absent. | ## Patterns **Guard before destructure**. Prefer ```ts match opt { Some{ value: x }: useIt(x), None{}: fallback() } ``` over ```ts const Some{ value: x } = opt; // aborts if None ``` **Dispatch on `data` tag**. When the shape isn't statically known: ```ts const tag = std.builtins.chooseData(d, "Constr", "Map", "List", "I", "B"); ``` then call the matching destructor. **`assert` after extraction**. Pull values out, validate, then proceed. Each `assert` adds one branch to the budget — group conditions with `&&` when they share evaluation cost. === onchain/reference/Laws.md === --- sidebar_position: 2 sidebar_label: Laws and Invariants --- # Laws and invariants Algebraic properties you can rely on when generating Pebble code or its tests. Every law below holds for **all** values of its free variables unless a precondition is stated. This page is the place to look when: - You need a round-trip identity for serialization/deserialization tests. - You're choosing between two equivalent ways of writing something and want to know they really are equivalent. - You're generating property-based tests and want canonical properties to use as oracles. ## `data` round trips ``` unIData (iData(n)) == n for all int n unBData (bData(b)) == b for all bytes b unListData (listData(xs)) == xs for all xs: List unMapData (mapData(m)) == m for all m: LinearMap let RawConstr{ index: i, fields: fs } = unConstrData(constrData(idx, fields)); i == idx && fs == fields for all idx: int, fields: List ``` The opposite direction also holds, but only on values produced by the corresponding wrapping or known to have that shape: ``` iData(unIData(d)) == d when d is an I data value bData(unBData(d)) == d when d is a B data value listData(unListData(d))== d when d is a List data value ``` Untyped destructors fail on the wrong shape (see [Failures](./Failures.md)) — these laws assume the precondition is met. ## UTF-8 round trip ``` decodeUtf8(encodeUtf8(s)) == s for all valid string s encodeUtf8(decodeUtf8(b)) == b when b is valid UTF-8 ``` `decodeUtf8` fails on invalid UTF-8, so the second identity has a precondition. ## Integer ↔ bytes round trips For unsigned big-endian (the common case): ``` byteStringToInteger(true, integerToByteString(true, 0, n)) == n for all int n >= 0 std.bytes.toInt(std.bytes.fromInt(n)) == n for all int n >= 0 ``` For fixed-width encodings, the round trip holds **if** the integer fits in the chosen width: ``` byteStringToInteger(true, integerToByteString(true, size, n)) == n when n >= 0 && n < 256^size ``` ## Equality and hashing ``` equalsData(a, b) == true if a and b have the same CBOR encoding equalsByteString == == as operator equalsInteger == == as operator equalsString == == as operator std.equals(a, b) == true if a and b are equal under T's natural equality ``` A `bytes` digest from a deterministic hash is itself in canonical big-endian: ``` sha2_256(b).length() == 32 sha3_256(b).length() == 32 blake2b_256(b).length() == 32 blake2b_224(b).length() == 28 keccak_256(b).length() == 32 ripemd_160(b).length() == 20 ``` ## `Value` algebra `Value` forms a commutative monoid under `union`, with `mempty` as the identity: ``` v.union(mempty) == v mempty.union(v) == v a.union(b) == b.union(a) commutative (a.union(b)).union(c) == a.union(b.union(c)) associative ``` Scaling distributes over union: ``` v.scale(0) == mempty v.scale(1) == v v.scale(k1 + k2) == v.scale(k1).union(v.scale(k2)) (a.union(b)).scale(k) == a.scale(k).union(b.scale(k)) ``` Lookup is left-inverse of insert under non-conflicting keys: ``` v.insert(p, n, x).amountOf(p, n) == x for any p, n, x v.insert(p, n, 0).amountOf(p, n) == 0 (insert with 0 removes) ``` `contains` is a partial order: ``` v.contains(mempty) == true mempty is bottom v.contains(v) == true reflexive a.contains(b) && b.contains(a) ⇒ a == b antisymmetric a.contains(b) && b.contains(c) ⇒ a.contains(c) transitive ``` ## `List` laws Folds and traversals: ``` xs.foldr(f, z) ≡ classical right fold xs.foldl(f, z) ≡ classical left fold xs.length() == xs.foldl((acc, _) => acc + 1, 0) xs.isEmpty() == (xs.length() == 0) xs.map(id) == xs identity preservation xs.map(f).map(g) == xs.map((x) => g(f(x))) fusion xs.filter((_) => true) == xs xs.filter((_) => false) == [] ``` Construction and decomposition: ``` xs.prepend(x).head() == x xs.prepend(x).tail() == xs [x] ++ xs == xs.prepend(x) conceptual [1,2,3].length() == 3 [].isEmpty() == true ``` Universal/existential duality: ``` xs.every(p) == !xs.some((x) => !p(x)) xs.some(p) == !xs.every((x) => !p(x)) ``` ## `Optional` laws ``` Some{ value: x }.value == x match Some{ value: x } { Some{ value: y }: y, None{}: _ } == x ``` `find` produces an `Optional`: ``` xs.find(p) == Some{ value: x } if there exists x in xs with p(x), and x is the first such element xs.find(p) == None{} if no element satisfies p ``` ## `LinearMap` laws ``` m.prepend(k, v).lookup(k) == Some{ value: v } most-recent-prepend wins m.isEmpty() == (m.length() == 0) m.length() == m.foldl((acc, _) => acc + 1, 0) ``` Lookup is **not** stable under map equality across reorderings — `LinearMap` is a list, and `{ k: a, k: b }` and `{ k: b, k: a }` differ. Don't rely on order-insensitive equality. ## BLS12-381 laws In each group: ``` g1Add(p, g1Neg(p)) == identityG1 g1Add(p, q) == g1Add(q, p) g1ScalarMul(0, p) == identityG1 g1ScalarMul(1, p) == p g1ScalarMul(a + b, p) == g1Add(g1ScalarMul(a, p), g1ScalarMul(b, p)) g1Uncompress(g1Compress(p)) == p ``` Same shape for G2. The Miller-loop pairing is bilinear: ``` millerLoop(g1ScalarMul(a, P), Q) ~ millerLoop(P, g2ScalarMul(a, Q)) (equal up to MlResult mul; verify with finalVerify) ``` ## Crypto verification All three signature verifiers are **non-aborting**: ``` verifyEd25519Signature(pk, m, s) ∈ { true, false } verifyEcdsaSecp256k1Signature ∈ { true, false } verifySchnorrSecp256k1Signature ∈ { true, false } ``` They return `false` on malformed input rather than aborting — chain a check after the call. ## Identity and friends ``` id(x) == x trace(msg, x) == x (with msg emitted to the trace log) ifThenElse(true, a, b) == a ifThenElse(false, a, b) == b ``` `trace` is strict in both arguments — use it only when both are cheap or already evaluated. ## How to use these laws when generating code 1. **Refactoring**: when you replace `unIData(iData(n))` with `n`, this page is the citation. 2. **Property tests**: pick a law, generate random inputs, assert it. Most laws above translate directly. 3. **Code review**: if a snippet violates a law (e.g. `xs.map(id) != xs`), it's a bug in the snippet or your understanding of the input type. 4. **Optimization**: the fusion law `xs.map(f).map(g) == xs.map((x) => g(f(x)))` saves an allocation pass. === onchain/reference/Pitfalls.md === --- sidebar_position: 3 sidebar_label: Pitfalls --- # Pitfalls and anti-patterns Common mistakes when writing Pebble, grouped by symptom. If the code you're generating matches one of these shapes, replace it with the safer form. Each entry has: - **Smell** — the syntactic shape to recognise. - **Why it's wrong** — the failure mode or hidden cost. - **Better** — the replacement. ## Wrong rounding for integer division **Smell** ```ts const half = -7 / 2; // expected -3, but this is -4 ``` **Why** — `/` and `%` round toward **negative infinity** (matching `std.int.divide` / `std.int.mod`). For negative numerators this differs from C-style truncated division. **Better** — use [`std.int.quotient`](../Standard%20Library/std.int.mdx) / [`std.int.remainder`](../Standard%20Library/std.int.mdx) when you want truncation toward zero. ## Using `strictAnd` / `strictOr` where you meant `&&` / `||` **Smell** ```ts if( std.boolean.strictAnd(canSpend(), expensiveCheck()) ) { ... } ``` **Why** — `strictAnd` evaluates **both** operands. `expensiveCheck()` runs even when `canSpend()` is false. On-chain code pays for that work. **Better** ```ts if( canSpend() && expensiveCheck() ) { ... } ``` Reach for `strictAnd` only when you specifically need to force both operands (rare — typically only when passing as a value to higher-order code). ## `LinearMap.lookup` for uniqueness checks **Smell** ```ts if( m.lookup(k) == None{} ) { /* "key is unique" */ } ``` **Why** — `LinearMap` is a list. `lookup` returns the **first** match. The key could appear later in the map and you'd never know. **Better** — enforce uniqueness at insertion time, or scan with `every`: ```ts const isUnique = m.every((entry) => entry.fst != k); ``` ## `xs[i]` inside a loop **Smell** ```ts for( let i = 0; i < xs.length(); i = i + 1 ) { use(xs[i]); } ``` **Why** — the loop pays for the indexed access **and** also pays `xs.length()` once per iteration (it's O(n) every time). The work is `O(n²)` even though each individual `xs[i]` is cheap in the cost model. **Better** — iterate directly with `for...of`. You get the elements in order without any indexed work and without re-walking the list every iteration: ```ts for( const x of xs ) { use(x); } ``` :::note Don't reach for `Array` here A common mistake is "convert to `Array` once" to make the indexing cheap. But the loop's cost is already dominated by repeatedly calling `xs.length()`, and `std.array.fromList` is itself O(n) — converting buys you nothing over `for...of`, which is the right structure regardless. See [Cost and Complexity](./Cost%20and%20Complexity.md). ::: ## `xs.length() == 0` instead of `xs.isEmpty()` **Smell** ```ts if( xs.length() == 0 ) { ... } ``` **Why** — `length()` walks the entire list. `isEmpty()` is O(1). **Better** ```ts if( xs.isEmpty() ) { ... } ``` ## Destructuring `Optional` without `match` **Smell** ```ts const Some{ value: x } = maybe; ``` **Why** — this aborts the script if `maybe` is `None{}`. The failure has no message and no fallback path. **Better** ```ts match maybe { Some{ value: x }: useIt(x), None{}: fallback() } ``` If you genuinely know it's `Some` and `None` is a programmer error, prefer an explicit assertion with a message: ```ts match maybe { Some{ value: x }: useIt(x), None{}: fail "maybe was None — invariant broken" } ``` ## `decodeUtf8` on opaque bytes **Smell** ```ts const s = std.builtins.decodeUtf8(redeemer.payload); ``` **Why** — `decodeUtf8` aborts on invalid UTF-8. If `redeemer.payload` is attacker-controlled, this is a denial-of-service vector against your own validator (any malformed input crashes the transaction). **Better** — only decode bytes you produced yourself, or that an upstream protocol guarantees are valid. For arbitrary user payloads, work with `bytes` directly. ## Storing large strings on-chain **Smell** ```ts const description: string = "a long human-readable explanation ..."; ``` **Why** — every character costs CPU/memory in the script budget. `string` exists for `trace` messages and `fail` reasons, not for application data. **Better** — keep large blobs off-chain. On-chain, store a hash: ```ts const descriptionHash: bytes = #cafedead...; ``` ## Forgetting that script `purpose` must be matched **Smell** ```ts contract Foo { spend bar() { ... } } // — but the script is deployed for use as a minting policy ``` **Why** — when a contract is invoked under a script purpose with no matching method, it falls through to `fail`. A minting transaction against a contract that only defines `spend` methods will be rejected. **Better** — define methods for every purpose the script is deployed under. If you intend to expose just one purpose, also intend to deploy only as that purpose. ## Confusing `Value.contains` direction **Smell** ```ts // Intended: "tx pays at least the required value" assert requiredValue.contains(tx.outputs[0].value); ``` **Why** — `a.contains(b)` is "a is at least as big as b", not the other way around. The smell asserts the **required** value contains the **paid** value, which is backwards. **Better** ```ts assert tx.outputs[0].value.contains(requiredValue); ``` ## Double-satisfaction: not checking the input count **Smell** ```ts spend fillOrder(/* ... */) { const { tx } = context; const userOutput = tx.outputs[outputIdx]; assert userOutput.value.contains(requiredPayment); } ``` **Why** — if a transaction spends **two** orders that both require payment to the same address, a single output can satisfy both — the protocol is drained. This is the [double-satisfaction problem](https://library.iog.io/preprints/Native_Custom_Tokens_in_the_Extended_UTXO_Model.pdf). **Better** — require that exactly one input from the contract's script address is being spent: ```ts const ownHash = spendingInput.address.credential.hash(); const ownInputs = tx.inputs.filter((i) => i.resolved.address.credential.hash() == ownHash); assert ownInputs.length() == 1; ``` ## Reading the wrong input **Smell** (in a `spend` method) ```ts const { tx } = context; const myInput = tx.inputs[0]; // assume the first input is mine ``` **Why** — input order in a transaction is **canonical, not user-controlled**. The "first input" is whichever has the lexicographically smallest `(tx_id, index)`. A user can craft a transaction where your input is not at index 0. **Better** — match on `purposeRef`: ```ts const { tx, spendingInputRef: purposeRef } = context; const myInput = tx.inputs.find((i) => i.ref == purposeRef); match myInput { Some{ value: input }: useIt(input), None{}: fail "purpose input not in tx.inputs" } ``` ## Iterating to count when you can short-circuit **Smell** ```ts const count = xs.filter(p).length(); if( count >= 1 ) { ... } ``` **Why** — `filter` allocates a new list, then `length` walks it. You only wanted to know "any?". **Better** ```ts if( xs.some(p) ) { ... } ``` ## Re-deriving the same value across `assert`s **Smell** ```ts assert tx.outputs[0].value.amountOf(myPolicy, myToken) >= 100; assert tx.outputs[0].value.amountOf(myPolicy, myToken) <= 200; ``` **Why** — `tx.outputs[0]` walks the outputs list, and `amountOf` walks the value's assets, twice. **Better** ```ts const got = tx.outputs[0].value.amountOf(myPolicy, myToken); assert got >= 100; assert got <= 200; ``` ## Forgetting to drop `LinearMap` duplicates **Smell** ```ts const totalForKey = m.foldl((acc, e) => if e.fst == target { acc + e.snd } else { acc }, 0); // ... then assert totalForKey == ... ``` **Why** — if duplicates are illegal in your protocol, this code accepts them silently and sums them. The protocol is being abused. **Better** — first check uniqueness, then read a single value: ```ts const matches = m.filter((e) => e.fst == target); assert matches.length() == 1; const value = matches.head().snd; ``` ## See also - [Failures](./Failures.md) — the canonical list of what each builtin fails on - [Laws](./Laws.md) — the identities you can lean on to refactor === onchain/Standard Library/std.array.mdx === --- sidebar_position: 6 sidebar_label: std.array --- # `std.array` *Pebble 0.3.1 · all symbols on this page are stable.* Polymorphic helpers over `Array` — the native, random-access variant of a list. Unlike `List`, `Array` supports O(1) indexed access via `at`. Use `std.array.fromList` to convert a `List` once and then index into the result repeatedly. import RunnableExample from '@site/src/components/RunnableExample'; ## Methods | Function | Description | |-------------------------------------------------------------------------|----------------------------------------------------------------------------| | `length(xs: Array): int` | Number of elements. | | `at(xs: Array, i: int): T` | Element at index `i`. Fails on out-of-bounds. | | `fromList(xs: List): Array` | Convert a `List` to an `Array`. O(n). | ## Examples ```ts using { length, at, fromList } = std.array; const xs: Array = fromList([10, 20, 30]); // 3-element array const n: int = length(xs); // 3 const v: int = at(xs, 1); // 20 ``` ## See also - [`Array`](../Prelude/Array.mdx) — method-call surface (`xs.at(i)`, `xs.length()`) - [`std.list`](./std.list.mdx) — most transformations live on `List`; convert with `fromList` when you need random access === onchain/Standard Library/std.boolean.mdx === --- sidebar_position: 9 sidebar_label: std.boolean --- # `std.boolean` *Pebble 0.3.1 · all symbols on this page are stable.* Monomorphic helpers over `bool`. Prefer the operator forms (`&&`, `||`, `!`) in regular code — these exist for partial application and higher-order use. import RunnableExample from '@site/src/components/RunnableExample'; = [true, true, true, false];\n\nconst allPositive: boolean = std.list.foldl(std.boolean.strictAnd, true, signs);\nassert !allPositive else "expected one false";`} height="240px" /> ## Methods | Function | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `not(b: bool): bool` | Logical NOT. | | `strictAnd(a: bool, b: bool): bool` | Logical AND. **Strict in both arguments** — unlike `&&` which short-circuits, both `a` and `b` are evaluated. | | `strictOr(a: bool, b: bool): bool` | Logical OR. **Strict in both arguments** — unlike `\|\|`. | | `equals(a: bool, b: bool): bool` | True iff `a` and `b` have the same value. | | `toInt(b: bool): int` | `0` for `false`, `1` for `true`. | :::warning Strict vs short-circuit `strictAnd` / `strictOr` evaluate **both** operands. If either argument can fail or perform an expensive computation, use the operator forms `&&` / `||` which short-circuit. ::: ## Examples ```ts using { not, strictAnd, strictOr, equals, toInt } = std.boolean; const n: bool = not(true); // false const a: bool = strictAnd(true, false); // false const o: bool = strictOr(true, false); // true const eq: bool = equals(true, true); // true const i: int = toInt(true); // 1 ``` ## See also - [`bool`](../Prelude/Boolean.mdx) — operator forms - [`std.builtins`](./std.builtins.mdx#polymorphic-intrinsics) — `ifThenElse` is the strict polymorphic conditional === onchain/Standard Library/std.builtins.mdx === --- sidebar_position: 3 sidebar_label: std.builtins --- # `std.builtins` *Pebble 0.3.1 · all symbols on this page are stable.* Direct exposure of Plutus Core builtins, plus a handful of polymorphic intrinsics. Prefer the per-type namespaces ([`std.int`](./std.int.mdx), [`std.bytes`](./std.bytes.mdx), etc.) or operator forms (`+`, `==`, ...) for everyday code — `std.builtins` is for when you need a Plutus-Core-shaped function as a value, or when only the raw builtin gives you the semantics you want (e.g. `quotientInteger` vs `divideInteger` rounding). ## Type exports ### `RawConstr` The shape returned by `unConstrData`: a pair `(index: int, fields: List)`. Field access is `.index` and `.fields`. ```ts const { unConstrData } = std.builtins; const RawConstr{ index, fields } = unConstrData(d); match index { 0: trace("Some"), 1: trace("None"), _: fail "unknown constructor" } ``` ## Integer arithmetic and comparison | Function | Description | |-----------------------------------------------------|----------------------------------------------------------------------| | `addInteger(a: int, b: int): int` | `a + b`. | | `subtractInteger(a: int, b: int): int` | `a - b`. | | `multiplyInteger(a: int, b: int): int` | `a * b`. | | `divideInteger(a: int, b: int): int` | Floor division. `divideInteger(-7, 2) == -4`. | | `quotientInteger(a: int, b: int): int` | Truncated division (rounds toward zero). `quotientInteger(-7, 2) == -3`. | | `remainderInteger(a: int, b: int): int` | Remainder paired with `quotientInteger`. Sign matches `a`. | | `modInteger(a: int, b: int): int` | Modulo paired with `divideInteger`. Sign matches `b`. | | `equalsInteger(a: int, b: int): bool` | `a == b`. | | `lessThanInteger(a: int, b: int): bool` | `a < b`. | | `lessThanEqualInteger(a: int, b: int): bool` | `a <= b`. | | `expModInteger(base: int, exp: int, m: int): int` | Modular exponentiation `base^exp mod m`. | ## Byte-string operations | Function | Description | |-----------------------------------------------------------------------|----------------------------------------------------------------------------| | `appendByteString(a: bytes, b: bytes): bytes` | Concatenate. | | `consByteString(byteVal: int, b: bytes): bytes` | Prepend a single byte (0–255). Fails if `byteVal` is out of range. | | `sliceByteString(start: int, len: int, b: bytes): bytes` | Sub-string. Out-of-range indices are clipped. | | `lengthOfByteString(b: bytes): int` | Length in bytes. | | `indexByteString(b: bytes, i: int): int` | Byte at index `i` as an int 0–255. Fails on out-of-range. | | `equalsByteString(a: bytes, b: bytes): bool` | Equality. | | `lessThanByteString(a: bytes, b: bytes): bool` | Lexicographic less-than. | | `lessThanEqualsByteString(a: bytes, b: bytes): bool` | Lexicographic less-than-or-equal. | ## Byte-string bitwise (Plutus V3) | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `andByteString(padShorter: bool, a: bytes, b: bytes): bytes` | Bitwise AND. `padShorter=true` zero-pads the shorter; `false` truncates. | | `orByteString(padShorter: bool, a: bytes, b: bytes): bytes` | Bitwise OR. | | `xorByteString(padShorter: bool, a: bytes, b: bytes): bytes` | Bitwise XOR. | | `complementByteString(b: bytes): bytes` | Bitwise NOT. | | `readBit(b: bytes, bitIdx: int): bool` | Read a single bit at `bitIdx` (MSB-of-byte 0). | | `writeBits(b: bytes, positions: List, value: bool): bytes` | Set or clear every bit in `positions` to `value`. | | `replicateByte(n: int, byteVal: int): bytes` | A byte string of `n` copies of `byteVal`. | | `shiftByteString(b: bytes, k: int): bytes` | Bit shift; positive `k` shifts left, negative right. | | `rotateByteString(b: bytes, k: int): bytes` | Bit rotation. | | `countSetBits(b: bytes): int` | Population count. | | `findFirstSetBit(b: bytes): int` | Index of the first 1-bit, or -1 if `b` is all zero. | ## Int ↔ bytes conversion | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `integerToByteString(bigEndian: bool, size: int, n: int): bytes` | Encode `n` to a `size`-byte string. `size=0` means "smallest fit". | | `byteStringToInteger(bigEndian: bool, b: bytes): int` | Decode a byte string as an unsigned integer. | ## String operations | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `appendString(a: string, b: string): string` | Concatenate. | | `equalsString(a: string, b: string): bool` | Equality. | | `encodeUtf8(s: string): bytes` | Serialize a string to UTF-8 bytes. | | `decodeUtf8(b: bytes): string` | Parse UTF-8 bytes. Fails on invalid encoding. | ## Data constructors | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `constrData(idx: int, fields: List): data` | Construct a `Constr` data value. | | `mapData(m: LinearMap): data` | Wrap an association list as a `Map` data value. | | `listData(xs: List): data` | Wrap a list as `List` data. | | `iData(n: int): data` | Wrap an int as `I` data. | | `bData(b: bytes): data` | Wrap bytes as `B` data. | | `mkNilData(): List` | The empty `List` literal. | ## Data destructors | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `unConstrData(d: data): RawConstr` | Decompose `Constr` data. Fails on the wrong shape. | | `unMapData(d: data): LinearMap` | Extract the underlying assoc-list of a `Map` data value. | | `unListData(d: data): List` | Extract a `List` data. | | `unIData(d: data): int` | Extract an `I` data. | | `unBData(d: data): bytes` | Extract a `B` data. | | `equalsData(a: data, b: data): bool` | Structural equality. | | `serialiseData(d: data): bytes` | CBOR-encode. | ## Native `Value` operations These work on the native `Value` type (see [Value](../Prelude/Value.mdx)). They're aliased into [`std.value`](./std.value.mdx) with friendlier names. | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `insertCoin(policy: bytes, name: bytes, amount: int, v: Value): Value` | Set the amount of `(policy, name)` in `v`. | | `lookupCoin(policy: bytes, name: bytes, v: Value): int` | Read the amount of `(policy, name)`. Returns 0 if absent. | | `unionValue(a: Value, b: Value): Value` | Add two values. | | `valueContains(a: Value, b: Value): bool` | True iff `a ≥ b` for every asset. | | `valueData(v: Value): data` | Serialize `Value` to `data`. | | `unValueData(d: data): Value` | Parse `data` as `Value`. Fails on the wrong shape. | | `scaleValue(k: int, v: Value): Value` | Multiply every amount in `v` by `k`. | ## Polymorphic intrinsics | Function | Description | |--------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `trace(msg: bytes, x: T): T` | Emit `msg` to the trace log, then return `x`. Strict in `x`. | | `ifThenElse(cond: bool, then: T, else: T): T` | Strict if-then-else. Both branches are evaluated. | | `chooseUnit(u: void, x: T): T` | Force evaluation of `u`, then return `x`. | | `mkCons(head: T, tail: List): List` | Prepend; the underlying `MkCons` builtin. | | `headList(xs: List): T` | First element. Fails on empty. | | `tailList(xs: List): List` | Everything after the head. Fails on empty. | | `nullList(xs: List): bool` | True iff `xs` is empty. | | `chooseList(xs: List, caseNil: B, caseCons: B): B` | Pattern match a list. The cons branch does **not** bind head/tail. | | `chooseData(d: data, caseConstr: T, caseMap: T, caseList: T, caseI: T, caseB: T): T` | Pattern match a `data` value by constructor tag. | ## Examples ### Integer arithmetic & comparison ```ts using { addInteger, subtractInteger, multiplyInteger, divideInteger, quotientInteger, remainderInteger, modInteger, equalsInteger, lessThanInteger, lessThanEqualInteger, expModInteger } = std.builtins; const s: int = addInteger(2, 3); // 5 const d: int = subtractInteger(5, 2); // 3 const p: int = multiplyInteger(4, 3); // 12 const q: int = divideInteger(-7, 2); // -4 (floor) const qt: int = quotientInteger(-7, 2); // -3 (toward zero) const r: int = remainderInteger(-7, 2); // -1 const m: int = modInteger(-7, 2); // 1 const eq: bool = equalsInteger(3, 3); // true const lt: bool = lessThanInteger(2, 3); // true const le: bool = lessThanEqualInteger(3, 3); // true const mp: int = expModInteger(5, 3, 13); // 5^3 mod 13 = 8 ``` ### Byte-string operations ```ts using { appendByteString, consByteString, sliceByteString, lengthOfByteString, indexByteString, equalsByteString, lessThanByteString, lessThanEqualsByteString } = std.builtins; const cat: bytes = appendByteString(#ab, #cd); // #abcd const cons: bytes = consByteString(0xff, #00); // #ff00 const sub: bytes = sliceByteString(1, 2, #aabbccdd); // #bbcc const len: int = lengthOfByteString(#aabbcc); // 3 const at: int = indexByteString(#aabbcc, 1); // 187 (0xbb) const bEq: bool = equalsByteString(#ab, #ab); // true const bLt: bool = lessThanByteString(#ab, #cd); // true const bLe: bool = lessThanEqualsByteString(#ab, #ab); // true ``` ### Byte-string bitwise (v3) ```ts using { andByteString, orByteString, xorByteString, complementByteString, readBit, writeBits, replicateByte, shiftByteString, rotateByteString, countSetBits, findFirstSetBit } = std.builtins; const a: bytes = andByteString(true, #f0, #0f); // #00 const o: bytes = orByteString(true, #f0, #0f); // #ff const x: bytes = xorByteString(true, #f0, #ff); // #0f const inv: bytes = complementByteString(#0f); // #f0 const bit: bool = readBit(#80, 0); // true (MSB-of-byte 0) const w: bytes = writeBits(#00, [1, 3, 5], true); // set bits 1,3,5 const rep: bytes = replicateByte(4, 0xab); // #abababab const sh: bytes = shiftByteString(#01, 4); // shift left 4 bits const ro: bytes = rotateByteString(#01020304, 8); // rotate left 8 bits const cnt: int = countSetBits(#ff00); // 8 const fst: int = findFirstSetBit(#0f); // 4 ``` ### Int ↔ bytes ```ts using { integerToByteString, byteStringToInteger } = std.builtins; const enc: bytes = integerToByteString(true, 4, 1234); // 4-byte BE const dec: int = byteStringToInteger(true, #04d2); // 1234 ``` ### Strings ```ts using { appendString, equalsString, encodeUtf8, decodeUtf8 } = std.builtins; const greet: string = appendString("Hello, ", "world"); // "Hello, world" const same: bool = equalsString("ok", "ok"); // true const u8: bytes = encodeUtf8("café"); // UTF-8 bytes const back: string = decodeUtf8(u8); // "café" ``` ### `data` constructors ```ts using { constrData, mapData, listData, iData, bData, mkNilData } = std.builtins; const dI: data = iData(42); const dB: data = bData(#cafe); const dL: data = listData([iData(1), iData(2)]); const dC: data = constrData(0, [dI, dB]); const nilD: List = mkNilData(); const dM: data = mapData({ dI: dB }); ``` ### `data` destructors ```ts using { unConstrData, unMapData, unListData, unIData, unBData, equalsData, serialiseData } = std.builtins; const RawConstr{ index, fields } = unConstrData(dC); // index=0, fields=[dI,dB] const m_kv: LinearMap = unMapData(dM); const xs: List = unListData(dL); const n: int = unIData(dI); // 42 const bs: bytes = unBData(dB); // #cafe const dEq: bool = equalsData(dI, iData(42)); // true const cbor: bytes = serialiseData(dC); ``` ### Native `Value` ```ts using { expModInteger, insertCoin, lookupCoin, unionValue, valueContains, valueData, unValueData, scaleValue } = std.builtins; const v1: Value = insertCoin(myPolicy, myToken, 5, emptyValue); const n: int = lookupCoin(myPolicy, myToken, v1); // 5 const v2: Value = unionValue(v1, v1); // 2× v1 const ok: bool = valueContains(v2, v1); // true const d: data = valueData(v1); const v3: Value = unValueData(d); const v4: Value = scaleValue(3, v1); // 3× v1 const m: int = expModInteger(2, 10, 1000); // 1024 mod 1000 = 24 ``` ### Polymorphic intrinsics ```ts using { trace, ifThenElse, chooseUnit, mkCons, headList, tailList, nullList, chooseList, chooseData } = std.builtins; const traced: int = trace(#6f6b, 42); // logs "ok", returns 42 const pick: int = ifThenElse(true, 1, 2); // 1 (strict) const after: int = chooseUnit((), 100); // 100 const xs: List = mkCons(0, [1, 2, 3]); // [0,1,2,3] const h: int = headList(xs); // 0 const t: List = tailList(xs); // [1,2,3] const empty: bool = nullList(xs); // false const which: string = chooseList(xs, "empty", "has values"); const tag: string = chooseData(dC, "Constr", "Map", "List", "I", "B"); ``` ## See also - [`std.int`](./std.int.mdx), [`std.bytes`](./std.bytes.mdx), [`std.boolean`](./std.boolean.mdx), [`std.data`](./std.data.mdx) — friendlier-named monomorphic equivalents - [`std.list`](./std.list.mdx), [`std.linearMap`](./std.linearMap.mdx), [`std.array`](./std.array.mdx) — polymorphic container helpers - [`std (top-level)`](./std.mdx) — `id` and `equals` === onchain/Standard Library/std.bytes.mdx === --- sidebar_position: 7 sidebar_label: std.bytes --- # `std.bytes` *Pebble 0.3.1 · all symbols on this page are stable.* Monomorphic helpers over `bytes`, exposing every byte-string operation as a first-class function. Use these for partial application or as arguments to higher-order code; prefer operator forms (`==`, `<`, `+`) and methods (`b.length()`) in regular code. import RunnableExample from '@site/src/components/RunnableExample'; ## Methods | Function | Description | |---------------------------------------------------------------------------|----------------------------------------------------------------------------| | `length(b: bytes): int` | Number of bytes. | | `slice(start: int, len: int, b: bytes): bytes` | `len`-byte sub-string starting at `start`. Clips on out-of-range. | | `prepend(byteVal: int, b: bytes): bytes` | Prepend a single byte (0–255). Fails if `byteVal` is out of range. | | `concat(a: bytes, b: bytes): bytes` | Concatenate two byte strings. | | `indexAt(b: bytes, i: int): int` | Byte at index `i` as an int 0–255. Fails on out-of-range. | | `equals(a: bytes, b: bytes): bool` | Equality. | | `lessThan(a: bytes, b: bytes): bool` | Lexicographic less-than. | | `lessThanEquals(a: bytes, b: bytes): bool` | Lexicographic less-than-or-equal. | | `greaterThan(a: bytes, b: bytes): bool` | Lexicographic greater-than. | | `greaterThanEquals(a: bytes, b: bytes): bool` | Lexicographic greater-than-or-equal. | | `toInt(b: bytes): int` | Decode big-endian unsigned integer. | | `fromInt(n: int): bytes` | Encode `n` as the shortest big-endian unsigned byte string. | ## Examples ```ts using { length, slice, prepend, concat, indexAt, equals, lessThan, lessThanEquals, greaterThan, greaterThanEquals, toInt, fromInt } = std.bytes; const n: int = length(#deadbeef); // 4 const sub: bytes = slice(1, 2, #deadbeef); // #adbe const pre: bytes = prepend(0xff, #beef); // #ffbeef const cat: bytes = concat(#dead, #beef); // #deadbeef const at: int = indexAt(#deadbeef, 0); // 222 (0xde) const eq: bool = equals(#ab, #ab); // true const lt: bool = lessThan(#ab, #cd); // true const le: bool = lessThanEquals(#ab, #ab); // true const gt: bool = greaterThan(#cd, #ab); // true const ge: bool = greaterThanEquals(#ab, #ab); // true const i: int = toInt(#04d2); // 1234 const enc: bytes = fromInt(1234); // #04d2 ``` ## See also - [`bytes`](../Prelude/Bytes.mdx) — method-call form - [`std.builtins`](./std.builtins.mdx) — finer-grained byte string and bitwise builtins - [`std.crypto`](./std.crypto.mdx) — hash functions consume `bytes` === onchain/Standard Library/std.crypto.bls12_381.mdx === --- sidebar_position: 2 sidebar_label: std.crypto.bls12_381 --- # `std.crypto.bls12_381` *Pebble 0.3.1 · all symbols on this page are stable.* BLS12-381 pairing-friendly curve operations. Use this namespace for zero-knowledge proofs, BLS signatures, and any protocol that needs efficient bilinear pairings on-chain. import RunnableExample from '@site/src/components/RunnableExample'; The three native types — `G1`, `G2`, `MlResult` — are exported **only** through this namespace. Either fully qualify them or pull them into scope with `using`. ## Types | Type | Description | |-------------|--------------------------------------------------------------------| | `G1` | A point on the G1 subgroup (the "fast" group, 48-byte compressed). | | `G2` | A point on the G2 subgroup (96-byte compressed). | | `MlResult` | A result of `millerLoop`; an element of the target group `GT`. | ## G1 operations | Function | Returns | Description | |---------------------------------------------------------|---------|--------------------------------------------------------------| | `g1Add(a: G1, b: G1): G1` | `G1` | Group addition (curve point addition). | | `g1Neg(a: G1): G1` | `G1` | Negation: returns `-a`. | | `g1ScalarMul(k: int, p: G1): G1` | `G1` | Scalar multiplication: `k · p`. | | `g1Equal(a: G1, b: G1): bool` | `bool` | Point equality. | | `g1HashToGroup(msg: bytes, dst: bytes): G1` | `G1` | Hash-to-curve with domain-separation tag `dst`. | | `g1Compress(p: G1): bytes` | `bytes` | Serialize to 48-byte compressed form. | | `g1Uncompress(b: bytes): G1` | `G1` | Parse a 48-byte compressed point. Fails if not on-curve. | ## G2 operations Same shape as G1 but over the G2 subgroup. Compressed form is 96 bytes. | Function | Returns | Description | |---------------------------------------------------------|---------|------------------------------------------| | `g2Add(a: G2, b: G2): G2` | `G2` | Group addition. | | `g2Neg(a: G2): G2` | `G2` | Negation. | | `g2ScalarMul(k: int, p: G2): G2` | `G2` | Scalar multiplication. | | `g2Equal(a: G2, b: G2): bool` | `bool` | Point equality. | | `g2HashToGroup(msg: bytes, dst: bytes): G2` | `G2` | Hash-to-curve with domain-separation tag.| | `g2Compress(p: G2): bytes` | `bytes` | Serialize to 96-byte compressed form. | | `g2Uncompress(b: bytes): G2` | `G2` | Parse a 96-byte compressed point. | ## Pairing operations | Function | Returns | Description | |---------------------------------------------------------|--------------|----------------------------------------------------------------------| | `millerLoop(g1: G1, g2: G2): MlResult` | `MlResult` | Compute the Miller loop. The result lives in the target group. | | `mulMlResult(a: MlResult, b: MlResult): MlResult` | `MlResult` | Multiplication in the target group. | | `finalVerify(a: MlResult, b: MlResult): bool` | `bool` | Final exponentiation + equality check. The terminal step of a pairing-equation verification. | ## Example: BLS signature verification ```ts using { G1, G2, MlResult, millerLoop, mulMlResult, finalVerify, g1Neg } = std.crypto.bls12_381; const lhs: MlResult = millerLoop(signature, hashedMsgG2); const rhs: MlResult = millerLoop(g1Neg(generatorG1), pubKeyG2); assert finalVerify(mulMlResult(lhs, rhs), oneMlResult); ``` ## Examples ```ts using { G1, G2, MlResult, g1Add, g1Neg, g1ScalarMul, g1Equal, g1HashToGroup, g1Compress, g1Uncompress, g2Add, g2Neg, g2ScalarMul, g2Equal, g2HashToGroup, g2Compress, g2Uncompress, millerLoop, mulMlResult, finalVerify } = std.crypto.bls12_381; // G1 ops const sum1: G1 = g1Add(p1, q1); const neg1: G1 = g1Neg(p1); const scaled1: G1 = g1ScalarMul(7, p1); const same1: bool = g1Equal(p1, q1); const hashed1: G1 = g1HashToGroup(msg, #426c734457535431); // "BlsDWST1" const c1: bytes = g1Compress(p1); const back1: G1 = g1Uncompress(c1); // G2 ops const sum2: G2 = g2Add(p2, q2); const neg2: G2 = g2Neg(p2); const scaled2: G2 = g2ScalarMul(7, p2); const same2: bool = g2Equal(p2, q2); const hashed2: G2 = g2HashToGroup(msg, #426c734457535432); const c2: bytes = g2Compress(p2); const back2: G2 = g2Uncompress(c2); // Pairing const ml1: MlResult = millerLoop(p1, p2); const ml2: MlResult = millerLoop(q1, q2); const product: MlResult = mulMlResult(ml1, ml2); const verified: bool = finalVerify(product, identityMl); ``` ## See also - [`std.crypto`](./std.crypto.mdx) — non-pairing cryptographic primitives - [`std.builtins`](./std.builtins.mdx) — `serialiseData` and byte-level helpers used to prepare messages === onchain/Standard Library/std.crypto.mdx === --- sidebar_position: 1 sidebar_label: std.crypto --- # `std.crypto` *Pebble 0.3.1 · all symbols on this page are stable.* Cryptographic hashes and signature verification primitives. Every function maps one-to-one to a Plutus Core builtin and costs the script's CPU/memory budget accordingly. import RunnableExample from '@site/src/components/RunnableExample'; ## Hash functions All hash functions take `bytes` and return `bytes`. | Function | Output size | Notes | |-------------------------------------|-------------|--------------------------------------------------------| | `sha2_256(b: bytes): bytes` | 32 | SHA-2 family. Standard for general use. | | `sha3_256(b: bytes): bytes` | 32 | SHA-3 family (Keccak with NIST padding). | | `blake2b_256(b: bytes): bytes` | 32 | Blake2b. Used for Cardano transaction IDs. | | `blake2b_224(b: bytes): bytes` | 28 | Blake2b. Used for pubkey hashes (`PubKeyHash`) and script hashes (`ScriptHash`). | | `keccak_256(b: bytes): bytes` | 32 | Keccak with Ethereum-style padding. | | `ripemd_160(b: bytes): bytes` | 20 | RIPEMD-160. Bitcoin-compatible. | ### Example ```ts using { blake2b_224 } = std.crypto; const pkh: bytes = blake2b_224(pubKey); assert pkh.length() == 28; ``` ## Signature verification All three verifiers have the same shape `(pubKey: bytes, message: bytes, signature: bytes) -> bool` and return `false` on any malformed input rather than failing the script. | Function | Curve / scheme | |----------------------------------------------------------------------------|---------------------------| | `verifyEd25519Signature(pubKey, msg, sig): bool` | Ed25519 | | `verifyEcdsaSecp256k1Signature(pubKey, msg, sig): bool` | ECDSA over secp256k1 | | `verifySchnorrSecp256k1Signature(pubKey, msg, sig): bool` | Schnorr over secp256k1 | ### Example ```ts using { verifyEd25519Signature } = std.crypto; assert verifyEd25519Signature( ownerPubKey, serialiseData(tx.id.toData()), redeemer.signature ); ``` ## Examples One runnable snippet per function. Each assumes a `bytes` value `msg`, `pubKey`, and `sig` in scope. ```ts using { sha2_256, sha3_256, blake2b_256, blake2b_224, keccak_256, ripemd_160, verifyEd25519Signature, verifyEcdsaSecp256k1Signature, verifySchnorrSecp256k1Signature } = std.crypto; const h_sha2: bytes = sha2_256(msg); // 32-byte SHA-2 const h_sha3: bytes = sha3_256(msg); // 32-byte SHA-3 const h_blk256: bytes = blake2b_256(msg); // 32-byte Blake2b const h_blk224: bytes = blake2b_224(msg); // 28-byte Blake2b (PubKeyHash) const h_keccak: bytes = keccak_256(msg); // 32-byte Keccak (Ethereum) const h_ripemd: bytes = ripemd_160(msg); // 20-byte RIPEMD-160 const ok_ed25519: bool = verifyEd25519Signature(pubKey, msg, sig); const ok_ecdsa: bool = verifyEcdsaSecp256k1Signature(pubKey, msg, sig); const ok_schnorr: bool = verifySchnorrSecp256k1Signature(pubKey, msg, sig); ``` ## See also - [`std.crypto.bls12_381`](./std.crypto.bls12_381.mdx) — pairing-friendly curve operations - [`std.builtins`](./std.builtins.mdx) — `serialiseData`, `encodeUtf8`, byte-level helpers commonly chained with these functions === onchain/Standard Library/std.data.mdx === --- sidebar_position: 10 sidebar_label: std.data --- # `std.data` *Pebble 0.3.1 · all symbols on this page are stable.* Helpers for converting between `data` and the Pebble-level `string` type. For converting other types to and from `data`, use the built-in [`ToData`](../Builtin%20Interfaces.md#todata) interface (`.toData()` method) and the destructors in [`std.builtins`](./std.builtins.mdx#data-destructors). import RunnableExample from '@site/src/components/RunnableExample'; ## Methods | Function | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `strToData(s: string): data` | Encode a UTF-8 string as `data` (via `encodeUtf8` and then `bData`). | | `strFromData(d: data): string` | Decode `data` containing UTF-8 bytes back to a string. Fails on bad input. | ## See also - [`data`](../Prelude/Data.mdx) — the type itself - [`std.builtins`](./std.builtins.mdx) — `constrData`, `mapData`, `listData`, `iData`, `bData` and their destructors - [`ToData`](../Builtin%20Interfaces.md#todata) — the auto-implemented interface every encodable type satisfies === onchain/Standard Library/std.int.mdx === --- sidebar_position: 8 sidebar_label: std.int --- # `std.int` *Pebble 0.3.1 · all symbols on this page are stable.* Monomorphic helpers over `int`. Use these when you need an `int`-shaped function as a value — for instance `xs.some(std.int.isZero)` is cleaner than constructing a one-off lambda. `int` is arbitrary-precision signed; none of these operations overflow. import RunnableExample from '@site/src/components/RunnableExample'; = [10, 20, 30, 40];\nconst expected: int = 100;\n\nconst total = std.list.foldl(std.int.add, 0, amounts);\nassert std.int.isZero(total - expected) else "sum off";`} height="240px" /> ## Arithmetic | Function | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `add(a: int, b: int): int` | `a + b`. | | `subtract(a: int, b: int): int` | `a - b`. | | `multiply(a: int, b: int): int` | `a * b`. | | `divide(a: int, b: int): int` | Floor division. `divide(-7, 2) == -4`. | | `quotient(a: int, b: int): int` | Truncated division (toward zero). `quotient(-7, 2) == -3`. | | `remainder(a: int, b: int): int` | Remainder paired with `quotient`. Sign matches `a`. | | `mod(a: int, b: int): int` | Modulo paired with `divide`. Sign matches `b`. | | `negate(n: int): int` | `-n`. | | `increment(n: int): int` | `n + 1`. | | `decrement(n: int): int` | `n - 1`. | | `exponentiate(base: int, exp: int): int` | `base^exp` for non-negative `exp`. Fails if `exp < 0`. | ## Comparison | Function | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `equals(a: int, b: int): bool` | `a == b`. | | `lessThan(a: int, b: int): bool` | `a < b`. | | `lessThanEquals(a: int, b: int): bool` | `a <= b`. | | `greaterThan(a: int, b: int): bool` | `a > b`. | | `greaterThanEquals(a: int, b: int): bool` | `a >= b`. | | `isZero(n: int): bool` | `n == 0`. Tiny perf edge over `equals(n, 0)`. | ## Conversion | Function | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `toBoolean(n: int): bool` | `false` if `n == 0`, `true` otherwise. | :::tip Floor vs truncated division `divide` / `mod` round toward negative infinity (mathematical modulo). `quotient` / `remainder` round toward zero (C-style). The two agree for non-negative operands but differ in sign of the result for negative ones. If your code mixes them, write a comment explaining which behaviour you depend on. ::: ## Examples ```ts using { add, subtract, multiply, divide, quotient, remainder, mod, negate, increment, decrement, exponentiate, equals, lessThan, lessThanEquals, greaterThan, greaterThanEquals, isZero, toBoolean } = std.int; const s: int = add(2, 3); // 5 const d: int = subtract(5, 2); // 3 const p: int = multiply(4, 3); // 12 const q: int = divide(-7, 2); // -4 (floor) const qt: int = quotient(-7, 2); // -3 (toward zero) const r: int = remainder(-7, 2); // -1 const m: int = mod(-7, 2); // 1 const ng: int = negate(7); // -7 const inc:int = increment(7); // 8 const dec:int = decrement(7); // 6 const e: int = exponentiate(2, 10); // 1024 const eq: bool = equals(3, 3); // true const lt: bool = lessThan(2, 3); // true const le: bool = lessThanEquals(3, 3); // true const gt: bool = greaterThan(3, 2); // true const ge: bool = greaterThanEquals(3, 3); // true const z: bool = isZero(0); // true const b: bool = toBoolean(0); // false ``` ## See also - [`int`](../Prelude/Int.mdx) — operator forms and method-call surface - [`std.builtins`](./std.builtins.mdx) — the underlying `addInteger`, `expModInteger`, etc. === onchain/Standard Library/std.linearMap.mdx === --- sidebar_position: 5 sidebar_label: std.linearMap --- # `std.linearMap` *Pebble 0.3.1 · all symbols on this page are stable.* Polymorphic helpers over `LinearMap` — Cardano's on-chain "association list" representation of a map (an ordered list of `(K, V)` pairs). Lookups are O(n); choose `LinearMap` over a hash map because the underlying representation is what Plutus speaks. Every function here is also available as a method (`m.lookup(k)`, `m.prepend(k, v)`, ...) — see [`LinearMap`](../Prelude/LinearMap.mdx). import RunnableExample from '@site/src/components/RunnableExample'; ```ts using { lookup, prepend } = std.linearMap; const m0: LinearMap = {}; const m1 = prepend(#01, 100, m0); const result: Optional = lookup(#01, m1); ``` ## Methods | Function | Description | |----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `length(m: LinearMap): int` | Number of entries. O(n). | | `isEmpty(m: LinearMap): bool` | True iff `m` has zero entries. | | `head(m: LinearMap): LinearMapEntry` | First `(K, V)` pair. Fails on empty. | | `tail(m: LinearMap): LinearMap` | Map without its first entry. Fails on empty. | | `lookup(k: K, m: LinearMap): Optional` | First value bound to `k`, wrapped in `Some`, or `None`. | | `prepend(k: K, v: V, m: LinearMap): LinearMap` where `K implements ToData, V implements ToData` | Add a `(k, v)` entry at the front. Both `K` and `V` must implement `ToData`. | :::note `lookup` returns the first match `LinearMap` is a list, not a set — duplicate keys are allowed and `lookup` returns the first one. If you care about uniqueness, enforce it at insertion or de-duplicate before lookup. ::: ## Examples ```ts using { length, isEmpty, head, tail, lookup, prepend } = std.linearMap; const m: LinearMap = { #01: 10, #02: 20 }; const n: int = length(m); // 2 const e: bool = isEmpty(m); // false const h: LinearMapEntry = head(m); // (#01, 10) const rest: LinearMap = tail(m); // { #02: 20 } const got: Optional = lookup(#02, m); // Some{ value: 20 } const m2: LinearMap = prepend(#03, 30, m); // { #03:30, #01:10, #02:20 } ``` ## See also - [`LinearMap`](../Prelude/LinearMap.mdx) — method-call surface - [`std.list`](./std.list.mdx) — `LinearMap` is structurally a `List>` - [`ToData`](../Builtin%20Interfaces.md#todata) — the constraint required by `prepend` === onchain/Standard Library/std.list.mdx === --- sidebar_position: 4 sidebar_label: std.list --- # `std.list` *Pebble 0.3.1 · all symbols on this page are stable.* Polymorphic helpers over `List`. Everything here is also available as a method (`xs.length()`, `xs.map(f)`, etc.) — use the namespace form when you need to pass the helper itself as a value, e.g. `std.list.some(std.int.isZero, xs)`. See [`List`](../Prelude/List.mdx) for the method-call surface (which additionally includes `map`, intentionally omitted here — see note at the bottom). import RunnableExample from '@site/src/components/RunnableExample'; n % 2 == 0, [1, 2, 3, 4, 5]);\nconst sum = foldl((acc, n) => acc + n, 0, evens);\n\nassert sum == 6 else "expected 2+4 = 6";`} height="240px" /> ## Methods | Function | Description | |----------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `length(xs: List): int` | Number of elements. Walks the list — O(n). | | `isEmpty(xs: List): bool` | True iff `xs` has zero elements. | | `head(xs: List): T` | First element. Fails on empty. | | `tail(xs: List): List` | Everything after the head. Fails on empty. | | `prepend(x: T, xs: List): List` | New list with `x` at the front. | | `drop(n: int, xs: List): List` | List with the first `n` elements removed. Returns empty if `n >= length`. | | `foldr(f: (T, A) -> A, init: A, xs: List): A` | Right fold. `foldr(f, z, [a, b, c]) = f(a, f(b, f(c, z)))`. | | `foldl(f: (A, T) -> A, init: A, xs: List): A` | Left fold. Tail-recursive; preferred for large lists. | | `filter(p: (T) -> bool, xs: List): List` | Keep elements where `p` returns true. | | `some(p: (T) -> bool, xs: List): bool` | True iff any element satisfies `p`. Short-circuits. | | `every(p: (T) -> bool, xs: List): bool` | True iff all elements satisfy `p`. Short-circuits. | | `find(p: (T) -> bool, xs: List): Optional` | First matching element wrapped in `Some`, or `None`. | | `equals(eq: (T, T) -> bool, a: List, b: List): bool` | Element-wise equality using `eq`. | :::note `map` is method-only `xs.map(f)` is available as a method on every `List`, but is intentionally **not** exposed in `std.list`. The namespace-level form would need TIR-level composition (constructing a nil literal of the target element type) that the namespace helpers don't currently support. Keep using `xs.map(f)`. ::: ## Examples ```ts using { length, isEmpty, head, tail, prepend, drop, foldr, foldl, filter, some, every, find, equals } = std.list; const xs: List = [1, 2, 3, 4, 5]; const n: int = length(xs); // 5 const e: bool = isEmpty(xs); // false const h: int = head(xs); // 1 const rest: List = tail(xs); // [2,3,4,5] const cons: List = prepend(0, xs); // [0,1,2,3,4,5] const dr: List = drop(2, xs); // [3,4,5] const sumR: int = foldr((a, acc) => a + acc, 0, xs);// 15 const sumL: int = foldl((acc, a) => acc + a, 0, xs);// 15 const ev: List = filter((n) => n % 2 == 0, xs); // [2,4] const any: bool = some((n) => n > 4, xs); // true const all: bool = every((n) => n > 0, xs); // true const got: Optional= find((n) => n == 3, xs); // Some{ value: 3 } const same: bool = equals(std.int.equals, xs, xs); // true ``` ## See also - [`List`](../Prelude/List.mdx) — method-call form including `map` - [`std.linearMap`](./std.linearMap.mdx) — analogous helpers over association lists - [`std.array`](./std.array.mdx) — `Array` is the random-access cousin (but `xs[i]` on a list is already cheap; see [Cost and Complexity](../reference/Cost%20and%20Complexity.md)) === onchain/Standard Library/std.mdx === --- sidebar_position: 12 sidebar_label: std (top-level) slug: /onchain/standard-library/std --- # `std` (top level) *Pebble 0.3.1 · all symbols on this page are stable.* A handful of polymorphic helpers live directly on the `std` namespace itself, alongside the sub-namespaces ([`std.crypto`](./std.crypto.mdx), [`std.list`](./std.list.mdx), etc.). import RunnableExample from '@site/src/components/RunnableExample'; ## Methods | Function | Description | |-------------------------------------------------------|----------------------------------------------------------------------------| | `id(x: T): T` | The polymorphic identity. Useful as a default callback or "do-nothing" higher-order argument. | | `equals(a: T, b: T): bool` | Compile-time-dispatched equality. The compiler picks `equalsInteger`, `equalsByteString`, `equalsData`, etc. based on the instantiation of `T`. For data-encoded structs and most non-primitive types, falls back to `equalsData` (structural CBOR equality). | :::tip Polymorphic equality vs `==` `equals` exists for higher-order use (`xs.some((x) => equals(x, target))`). In direct code, the `==` operator already dispatches the same way and is more readable. ::: ## See also - [`std.builtins`](./std.builtins.mdx) — the underlying `equalsInteger`, `equalsByteString`, `equalsData` - The sub-namespaces — [`crypto`](./std.crypto.mdx), [`builtins`](./std.builtins.mdx), [`list`](./std.list.mdx), [`linearMap`](./std.linearMap.mdx), [`array`](./std.array.mdx), [`bytes`](./std.bytes.mdx), [`int`](./std.int.mdx), [`boolean`](./std.boolean.mdx), [`data`](./std.data.mdx), [`value`](./std.value.mdx) === onchain/Standard Library/std.value.mdx === --- sidebar_position: 11 sidebar_label: std.value, std.valueMap, std.credential --- # `std.value`, `std.valueMap`, `std.credential` *Pebble 0.3.1 · all symbols on this page are stable.* Function-shaped views of the methods on the native [`Value`](../Prelude/Value.mdx), the legacy `ValueMap` representation, and `Credential`. The method-call forms (`v.amountOf(...)`, `cred.hash()`) are what you'll use day-to-day — these namespaces exist so the same operations can be passed as values. import RunnableExample from '@site/src/components/RunnableExample'; ```ts using { amountOf, lovelaces, union } = std.value; const adaPaid = lovelaces(tx.outputs[0].value); const totalOut = std.list.foldl(union, mempty, tx.outputs.map((o) => o.value)); ``` ## `std.value` | Function | Description | |-----------------------------------------------------------------------------------|----------------------------------------------------------------------------| | `amountOf(policy: PolicyId, name: TokenName, v: Value): int` | Amount of `(policy, name)` in `v`. `0` if absent. | | `lovelaces(v: Value): int` | Amount of ADA (the `ada/lovelace` entry). | | `insert(policy: PolicyId, name: TokenName, amount: int, v: Value): Value` | Set the amount of `(policy, name)` in `v`. Pass `0` to remove. | | `union(a: Value, b: Value): Value` | Asset-wise sum. | | `contains(a: Value, b: Value): bool` | True iff `a ≥ b` for every asset (`b` is a sub-value of `a`). | | `scale(k: int, v: Value): Value` | Multiply every amount in `v` by `k`. | | `toData(v: Value): data` | CBOR-encode (mirrors [`ToData`](../Builtin%20Interfaces.md#todata)). | ## `std.valueMap` Methods on the legacy `ValueMap` representation (an assoc-list of assoc-lists, used by older script contexts and on-chain types). Native `Value` is preferred for new code. | Function | Description | |---------------------------------------------------------------------------------------|--------------------------------------------------------------------------| | `amountOf(policy: PolicyId, name: TokenName, vm: ValueMap): int` | Amount of `(policy, name)` in `vm`. `0` if absent. | | `lovelaces(vm: ValueMap): int` | Amount of ADA. | ## `std.credential` | Function | Description | |---------------------------------------------------------------------------------------|--------------------------------------------------------------------------| | `hash(c: Credential): bytes` | The underlying hash of a `Credential` (pubkey-hash or script-hash). | ## Examples ### `std.value` ```ts using { amountOf, lovelaces, insert, union, contains, scale, toData } = std.value; const v: Value = insert(myPolicy, myToken, 5, mempty); // {myPolicy/myToken: 5} const a: int = amountOf(myPolicy, myToken, v); // 5 const l: int = lovelaces(tx.outputs[0].value); // ADA in lovelace const u: Value = union(v, v); // 2× v const ok: bool = contains(u, v); // true const s: Value = scale(3, v); // 3× v const d: data = toData(v); ``` ### `std.valueMap` ```ts using { amountOf, lovelaces } = std.valueMap; const a: int = amountOf(myPolicy, myToken, legacyValueMap); const l: int = lovelaces(legacyValueMap); ``` ### `std.credential` ```ts using { hash } = std.credential; const h: bytes = hash(tx.outputs[0].address.paymentCredential); ``` ## See also - [`Value`](../Prelude/Value.mdx) — method-call surface and the type definition - [`Address`](../API/types/Address/Address.md), [`Credential`](../API/types/Address/Credential.md) — the structures that carry a `Credential` - [`std.builtins`](./std.builtins.mdx#native-value-operations) — the underlying `insertCoin`, `lookupCoin`, `unionValue`, etc. === onchain/State.md === --- sidebar_position: 5 sidebar_label: State --- # `state` *Pebble 0.3.1 · all symbols on this page are stable.* The `state` keyword introduces a **typed datum** inside a `contract` declaration. Each `state` block defines one variant of an on-chain sum type whose underlying CBOR encoding is what the contract's UTxOs carry as their datum. Inside a `spend` method declared on a state, `context.state` is destructured to a strongly-typed record of that state's fields — you never write `unConstrData` or `fromData` by hand. A contract may declare zero or more `state` blocks. If at least one `state` exists, the contract's UTxOs carry a datum of the corresponding sum type. If none exists, the contract is "stateless" and UTxOs carry no datum (or an opaque `data` you handle yourself via `context.optionalDatum`). ## Grammar ``` contract Name [( params… )] { state Variant { field_1: Type field_2: Type ... spend method_1( args… ) { body } spend method_2( args… ) { body } ... } // more states allowed spend fallback( args… ) { body } // optional bare spend } ``` Rules: 1. **Fields precede `spend` methods.** Within a `state` block, every `field: Type` declaration must come before any `spend` method. 2. **Only `spend` is allowed inside `state`.** `mint`, `certify`, `withdraw`, `propose`, `vote` go on the contract level, not inside a state. Their datum-shape is determined by the script purpose, not by the state. 3. **No field initialisers.** `state` fields are inputs from the datum — they aren't initialised at declaration time. The off-chain transaction that creates the UTxO supplies their values. 4. **Names must be unique across states.** Two `state Foo` blocks in the same contract is an error. 5. **`spend` method names are scoped to their state.** Two different states can both declare `spend fill(...)` without collision. ## Single-state contracts When a contract declares exactly one `state`, the compiler enables the `shortcutSingleConstructor` lowering: the datum on-chain is the **raw record** (not wrapped in a `Constr(0, ...)` tag). This saves a few bytes per UTxO at the CBOR level. ```ts contract HelloWorld { state Locked { owner: PubKeyHash spend greet(message: bytes) { const { tx, state: { owner } } = context; assert tx.signatories.includes(owner); assert message == "Hello pebble".toBytes(); } } } ``` The on-chain datum here is simply `B ` (no constructor wrapper). ## Multi-state contracts When a contract declares two or more `state` blocks, the compiler emits an explicit sum type — one CBOR `Constr` constructor per state, in declaration order. The off-chain transaction that creates the UTxO picks which constructor to encode. ```ts contract SimpleOrderBook { state Simple { ownerHash: bytes policy: PolicyId tokenName: TokenName minReceiveAmount: int spend fill(inputIdx: int, outputIdx: int) { ... } } state Partial { ownerHash: bytes policy: PolicyId tokenName: TokenName minReceiveAmountPerTx: int remaining: int spend fill(inputIdx: int, outputIdx: int) { ... } spend topUp(amount: int) { ... } } } ``` Datum CBOR: `Constr(0, [B ownerHash, B policy, B tokenName, I minReceiveAmount])` for `Simple`, `Constr(1, ...)` for `Partial`. At dispatch time, the compiler inserts a `match purpose, datum` over the script context's datum tag, routes execution into the matching state's `spend` method, and only then binds `context.state` to the typed record. ## Destructuring `state` in a method body Inside any `spend` method declared on a state, `context.state` is bound to the state's record type: ```ts state Live { counter: int owner: PubKeyHash spend bump() { const { tx, state: { counter, owner } } = context; assert tx.signatories.includes(owner); trace(counter); } } ``` The `state` field of `context` is **statically narrowed** to `Live` here — the compiler knows we're in a method declared on `Live`, so accessing `state.counter` and `state.owner` is a typed field read, not a runtime decode. ## Combining `state` with bare `spend` methods A contract can mix state-scoped `spend` methods with bare ones at the contract level. The bare `spend` is a **fallback** — it runs when the script is invoked with a datum that doesn't match any declared state's constructor, OR with no datum at all. ```ts contract SimpleOrderBook { state Simple { /* ... */ } state Partial { /* ... */ } spend cancelOrder() { const { tx, state: { ownerHash } } = context; // ← `state` here is the union; narrow with match assert tx.signatories.includes(ownerHash); } } ``` If a contract has at least one `state` AND no bare `spend` fallback, the script rejects every UTxO whose datum doesn't decode to a known state variant. If you want a fallback for legacy datums or admin escape hatches, declare a bare `spend` outside the state blocks. ## State is read-only in validators A `spend` method **cannot mutate `context.state`**. State is decoded from the input UTxO's datum, which is fixed at the moment the UTxO was created. To "update" state, the spending transaction must: 1. Spend the existing UTxO (running this `spend` method). 2. Produce a fresh UTxO at the same script address with the new state encoded as its datum. 3. Have this `spend` method validate that the new output's datum is a legal successor of the old state. That third step is the canonical "state machine on-chain" pattern. The validator reads the *current* state from `context.state` and the *next* state from `tx.outputs[i].datum` (decoded back to the state type via `.toData()` round-trip or pattern-match). :::tip Stateless reference-input pattern A `state` with **no `spend` methods** is a valid declaration. It defines the datum shape but disallows spending. Such UTxOs are used as **reference inputs** — read by other validators via `tx.referenceInputs`, never consumed. Useful for oracles, registries, and protocol parameter publications. ::: ## Off-chain: encoding the datum To create a UTxO at a stateful contract, off-chain code (e.g. [`@harmoniclabs/buildooor`](https://github.com/HarmonicLabs/buildooor)) constructs the datum as the matching CBOR shape. ```ts import { DataConstr, DataB, DataI } from "@harmoniclabs/buildooor"; // SimpleOrderBook.Simple const simpleDatum = new DataConstr(0, [ new DataB(ownerHash.toBuffer()), new DataB(policyId.toBuffer()), new DataB(tokenName), new DataI(minReceiveAmount), ]); // SimpleOrderBook.Partial const partialDatum = new DataConstr(1, [ new DataB(ownerHash.toBuffer()), new DataB(policyId.toBuffer()), new DataB(tokenName), new DataI(minReceiveAmountPerTx), new DataI(remaining), ]); ``` For a single-state contract (where the `shortcutSingleConstructor` optimisation kicks in), the datum is the **raw fields** — no `DataConstr` wrapper: ```ts // HelloWorld.Locked (single state) const lockedDatum = new DataB(ownerPubKeyHash.toBuffer()); ``` ## See also - [Contract Statements](./Contract%20Statements.md) — the `contract` declaration that hosts `state` blocks - [Validators 101](./concepts/Validators%20101.md) — how `spend` methods dispatch - [Datum & Redeemer Flow](./concepts/Datum%20and%20Redeemer%20Flow.md) — the encoding rules for crossing the on-chain / off-chain boundary - [Simple Order Book DEX](../examples/Simple%20order%20book%20DEX.md) — a multi-state worked example - [Hello World](../examples/Hello%20World.md) — a single-state worked example === onchain/Syntax/Block Statements.md === --- sidebar_position: 10 --- # Block Statements Block statements are the basic building blocks of Pebble code. They control variable bindings, runtime assertions, failure modes, and debugging. ## Variable and Constant Declarations Pebble supports both **mutable** (`let`) and **immutable** (`const`) bindings. ```ts const threshold = 100; let counter = tx.inputs.length(); ``` - `const` values cannot be reassigned - `let` values can be mutated (though mutation in Pebble compiles into SSA-style assignments internally) ## Assert `assert` checks a condition at runtime. If it fails, the transaction is invalid. ```ts assert userOutput.value.amountOf(policy, tokenName) >= minReceiveAmount; ``` Assertions are the _primary way to enforce validator rules_ in Pebble. ## Fail `fail` immediately aborts execution with an error. ```ts fail "Insufficient token balance"; ``` Useful when explicit error reporting is preferable to silent assertion failures. ## Trace `trace` allows _debug logging_ during simulation. (Traces are generally stripped or limited in on-chain execution.) ```ts trace("Processing order..."); trace(userOutput.address); ``` This is especially helpful for _off-chain testing_ to understand contract flow. === onchain/Syntax/Control Flow Statements.md === --- sidebar_position: 11 --- # Control Flow Statements Pebble supports a variety of control-flow mechanisms, inspired by both functional and imperative styles. They allow expressing branching logic, looping, and pattern matching directly in contracts. ## If / Else Standard conditional branching: ```ts if( amount > minReceiveAmount ) { trace("Valid amount"); } else { fail "Amount too low"; } ``` Internally, this compiles to conditional UPLC expressions. ## Match / Case Pattern matching is a core feature in Pebble, especially for Cardano-specific types like `Credential`, `Option`, or `Datum`. ```ts assert case ownerPaymentCreds is PubKeyHash{ hash } => tx.signatories.includes(hash) is Validator{ hash } => tx.inputs.some(({ resolved }) => resolved.credential.hash() === hash ); ``` ## For Loops Pebble allows imperative `for` loops. They are compiled into functional recursion under the hood. ```ts for( let i = 0; i < tx.inputs.length(); i = i + 1 ) { trace(tx.inputs[i]); } ``` ## While Loops Pebble also supports `while` loops are also supported ```ts let i = 0; while( i < tx.outputs.length() ) { trace(tx.outputs[i]); i = i + 1; } ``` :::tip On-chain, both `for` and `while` are _bounded by transaction data size_. They cannot introduce unbounded computation, as UPLC enforces resource limits. ::: === onchain/Syntax/Namespaces.md === --- sidebar_position: 12 --- # Namespaces A **namespace** groups related declarations (functions, structs, constants, other namespaces) under a single name. Members are reached through dotted access, and visibility can be controlled with `private`. Namespaces are declared at the top level of a `.pebble` file. ## Declaring a namespace ```ts namespace M { function add( a: int, b: int ): int { return a + b; } } function useAdd( a: int, b: int ): int { return M.add( a, b ); } ``` ## `using` — destructuring members into scope `using` brings selected namespace members directly into the current scope. ```ts namespace M { function add( a: int, b: int ): int { return a + b; } } function useAdd( a: int, b: int ): int { using { add } = M; return add( a, b ); } ``` ## `using` — aliasing `using` can also create a shorter alias for the whole namespace. ```ts namespace M { function add( a: int, b: int ): int { return a + b; } } function useAdd( a: int, b: int ): int { using m = M; return m.add( a, b ); } ``` ## Nested namespaces Namespaces nest, and members of inner namespaces are reached by chaining dotted access. ```ts namespace Outer { namespace Inner { function id( x: int ): int { return x; } } } function useInner( x: int ): int { return Outer.Inner.id( x ); } ``` ## `private` members A member declared `private` is visible inside its namespace but cannot be accessed from outside. ```ts namespace M { private function secret( a: int ): int { return a; } function pub( a: int ): int { return secret( a ); } } function callPub( a: int ): int { return M.pub( a ); // ok — pub is reachable } ``` Accessing a private member from outside the namespace is a compile-time error: ```ts namespace M { private function secret( a: int ): int { return a; } } function callSecret( a: int ): int { return M.secret( a ); // error: secret is private to M } ``` ## Exporting a namespace across files Prefix a top-level namespace with `export` to make it importable from another file. ```ts // lib.pebble export namespace M { function inc( n: int ): int { return n + 1; } } ``` ```ts // main.pebble import { M } from "./lib"; function useInc( n: int ): int { return M.inc( n ); } ``` Cross-file imports follow the rules described in [Top Level Statements](./Top%20Level%20Statements.md#import-statements). ## `import * as` — file-as-namespace A whole file's exports can be imported as a single namespace using `import * as`. ```ts // lib.pebble export function add( a: int, b: int ): int { return a + b; } export function sub( a: int, b: int ): int { return a - b; } ``` ```ts // main.pebble import * as Lib from "./lib"; function combined( a: int, b: int ): int { return Lib.add( a, b ) + Lib.sub( a, b ); } ``` The resulting `Lib` is a normal namespace and can also be destructured with `using`: ```ts import * as Lib from "./lib"; function useAdd( a: int, b: int ): int { using { add } = Lib; return add( a, b ); } ``` === onchain/Syntax/Top Level Statements.md === --- sidebar_position: 9 --- # Top-Level Statements Pebble allows certain declarations at the **file scope** (outside of a contract). These help define reusable data structures or bring in external functionality. ## Import Statements :::caution Experimental At present, the feature is incomplete and should be considered **experimental**. ::: Pebble includes an import mechanism, but its syntax and semantics are still evolving. It is expected to allow: - Reusing code across `.pebble` files - Bringing in common utility structs or functions ## Struct Declaration `struct` is used to define **user-defined types** (similar to TypeScript interfaces or Rust structs). Structs are *first-class citizens* in Pebble, and can be passed around, stored in datums, and destructured. ### Syntax ```ts struct StructName { field1: Type1, field2: Type2, ... } ``` ### Example ```ts struct Order { ownerPaymentCreds: Credential, policy: bytes, tokenName: bytes, minReceiveAmount: int } ``` You can use destructuring when working with struct values: ```ts const { ownerPaymentCreds, minReceiveAmount } as Order = myDatum; ``` === onchain/Testing.md === --- sidebar_position: 17 --- # Testing Pebble has a built-in test runner. You write tests directly inside `.pebble` files using the `test` keyword, and run them with `pebble test`. Tests with parameters are automatically property-based and fed pseudo-random inputs. ## Writing a unit test A test is a top-level declaration with the `test` keyword, an identifier name, an empty parameter list, and a block body. ```ts function add( a: int, b: int ): int { return a + b; } test addWorks() { trace add( 1, 2 ); assert add( 1, 2 ) == 3; } ``` Test names are **identifiers**, not strings. Tests live at the top level alongside `function`, `struct`, `namespace`, and `contract` declarations. Inside the body you can use any Pebble expression. Common helpers: - `assert ;` — fails the test if the condition is false. - `trace ;` — emits a log line attached to the test result. - `fail;` — unconditionally fails the test. ## Running tests ```bash pebble test ``` `pebble test` discovers `.pebble` files recursively starting from the project root and runs every `test` declaration it finds. ### Flags - `--config ` — path to the `pebble.config.json` (default `./pebble.config.json`). See [Compiler Configuration](./Compiler%20Configuration.md). - `--testPathPattern ` — only run files whose path matches the regex. - `--testNamePattern ` — only run tests whose name matches the regex. - `--property-runs ` — iterations per property-based test (default `100`). - `--seed ` — seed for the property-based PRNG (default `0`). Same seed reproduces the same sequence. Example: ```bash pebble test --testNamePattern "add" --property-runs 1000 --seed 42 ``` ## Output format ```text src/math.pebble PASS addWorks [cpu: 150000, mem: 1000] PASS addIsCommutative (1000 iterations, seed=42, total cpu=15000000, mem=10000) FAIL divNonZero (failed at iteration 47, seed=42) inputs: a=12345, b=0 error: division by zero Tests: 2 passed, 1 failed, 0 skipped, 3 total ``` `trace` output from the test body is shown indented under each test. ## Property-based tests Any `test` that declares parameters automatically becomes a **property-based test**. The runner uses a seedable PRNG (`mulberry32`) to generate inputs and runs the body once per iteration. ```ts test addIsCommutative( a: int, b: int ) { assert add( a, b ) == add( b, a ); } ``` When a property test fails, the output reports the iteration index, the seed, and the inputs that triggered the failure. Re-run with the same `--seed` to reproduce. ### Supported parameter types The built-in fuzzer currently supports: - `int` - `bool` Other parameter types are reported as unsupported at compile time. ### `via` — custom fuzzers (experimental) The grammar reserves the `via ` syntax so you can supply your own fuzzer for a parameter: ```ts test inRange( n: int via smallInts() ) { assert n >= 0 && n < 100; } ``` :::caution Experimental The `via` syntax is parsed but not yet executable. Using it currently raises a compile-time "fuzzer not implemented" diagnostic. The shape shown here is the planned API; the syntax may evolve before it lands. ::: === onchain/Types.md === --- sidebar_position: 1 sidebar_label: Standard types --- # Types Pebble supports a few built-in types. Each of these builtin types maps directly a corresponding UPLC type under the hood. ## Primitives Pebble provides four primitive types that map directly to Plutus Core: - `int` - `bool` - `bytes` - `string` These types form the foundation for all Pebble contracts. ### `int` Arbitrary-precision signed integer. Backed by Plutus `Integer`. #### Example ```ts const supply: int = 1_000_000; let spent: int = 500; assert (supply - spent) >= 0; ``` #### Operators - Arithmetic: `+`, `-`, `*`, `/`, `%` - Comparison: `<`, `<=`, `>`, `>=`, `==`, `!=` ### `bytes` Raw byte arrays. Commonly used for hashes, policy IDs, token names, and low-level identifiers. #### Example ```ts const policy: bytes = #deadbeef; const token: bytes = "MyToken".toBytes(); assert tx.outputs[0].value.amountOf(policy, token) > 0; ``` Pebble represents token names as bytes `const token: bytes = "MyToken".toBytes()`. This ensures compatibility with Plutus, where token names are byte arrays. #### Common Operators - Equality: `==`, `!=` - Concatenation: `a + b` - Conversion: `"hello".toBytes()` - Hashing: `hash(bytesVal)` ### `string` UTF-8 encoded text. Primarily for logging and off-chain metadata. Avoid storing large strings on-chain due to cost. #### Example ```ts const msg: string = "Order filled"; trace(msg); ``` ### `bool` Logical true/false values. Derived from comparisons or logical expressions. #### Example ```ts const signed: bool = tx.signatories.includes(ownerHash); if( signed && tx.outputs.length() > 0 ) { trace("Valid transaction"); } else { fail "Unauthorized"; } ``` #### Operators - Logical: `&&`, `||`, `!` - Comparisons return `bool` ## Cardano-Specific See [`Types`](../category/types) for all cardano-specific types supported in Pebble. Refer [stdScope.ts](https://github.com/HarmonicLabs/pebble/blob/302786a75c231648b375e91ae7253c293284a2da/packages/pebble/src/compiler/tir/program/stdScope/stdScope.ts#L34) for details regarding their definition. ## Optional / Sum Types ### `Option` Use `Option` to represent values that may or may not exist. #### Example ```ts const maybeDatum: Option = Some{ value: 10 }; ``` ### `case`/`match` used for destructuring ```ts match maybeDatum { Some{ value }: trace(value), None{} : fail "missing datum" } ``` Destructuring - ```ts const InlineDatum{ datum: { owner, token, minAmount } as Order } = spendingInput.datum; ``` ## Structs Defined using `struct` declarations. `struct` defines custom composite type with named fields. ```ts struct Order { ownerPaymentCreds: Credential, policy: bytes, tokenName: bytes, minReceiveAmount: int } ``` === Project Setup.md === --- sidebar_position: 3 --- # Project Setup you can create a new pebble project by simply running ```bash pebble init ``` The program will then ask a few question, and generate a new repository. `pebble init` generates a `pebble.config.json` — see [Compiler Configuration](./onchain/Compiler%20Configuration.md) for the full schema. In your new repository you can run ```bash pebble compile ``` to generate the contract uplc. Once you have tests in your project, run them with `pebble test` — see [Testing](./onchain/Testing.md). ## Examples Have a look at [Examples](./category/examples) for some example project === Welcome to Pebble.mdx === --- sidebar_position: 1 sidebar_label: Welcome to Pebble slug: / --- # Pebble Built with ❤️ by [Harmonic Laboratories](https://www.harmoniclabs.tech/) This documentation is for `pebble`. Feel free to ask for help in the [Harmonic Labs' discord server](https://discord.gg/CGKNcG7ade) ## Introduction Pebble is a **strongly-typed domain-specific language (DSL)** for writing **Cardano smart contracts**. A simple, yet rock solid, functional language with an imperative bias, targeting UPLC. import RunnableExample from '@site/src/components/RunnableExample'; At its heart, Pebble is: - A **language**: with its own parser, type checker, and compiler. - A **toolchain**: including a CLI, compiler, and integrations for DApp workflows. - A **bridge**: between human-readable smart contract code and low-level Plutus Core. Pebble is **statement-oriented** (with constructs like `let`, `if`, `while`, `match`, `return`), but under the hood, **everything still compiles down to Plutus Core expressions**, maintaining full functional correctness. This makes Pebble feel familiar for developers coming from JavaScript, TypeScript, Rust, or Solidity, while retaining the strict guarantees of Plutus. To sum it all up, `pebble` is a first-class language with its own syntax and compiler, but still exposes onchain/offchain type ecosystems for contract and DApp integrations. # Getting started See [Installation](./Installation) to begin your journey in pebble ## Why Pebble? Cardano contracts require precision, and writing them directly in Plutus Core or Haskell is error-prone. Pebble takes the next step: - **Readable syntax** → close to common c-like programming languages - **Composable abstractions** → structs, options, pattern matching - **Direct blockchain access** → via on-chain types (Tx, ScriptContext, PubKeyHash, etc.) - **Modern tooling** → integrated CLI, compilation, and test workflows