Contracts — Custom Hardware from typeCAD

When you design a custom board in typeCAD, a contract is a small JSON file that tells Cuttlefish exactly which pins on the chip are wired to what. Cuttlefish uses it to build a board definition that only exposes the pins your hardware actually uses — so you can’t accidentally write code for a pin that isn’t connected. Use this for any custom PCB (the circuit board), production board, or design that differs from a standard development board.

What’s in a Contract

A contract is a JSON file (.contract.json) exported from a typeCAD project. It captures:

  • Connected pins — which MCU (the main chip on the board) pins are wired to something on the PCB
  • Pin metadata — pin name, type (I/O, power, bidirectional), and net assignment
  • External components — what each pin connects to (resistors, capacitors, connectors, etc.)
  • Available peripherals — which peripheral buses (I2C, SPI, UART) are wired up

The transpiler reads this contract and generates a board package that re-exports only the connected pins from the MCU package. This means your TypeScript firmware can only import and use pins that are physically wired on your board — invalid pin access becomes a compile-time error.

With and Without a Contract

Without a ContractWith a Contract
Import any pin from a full board packageOnly connected pins are available
Hardware/software mismatch caught at runtimeMismatch caught at compile time
Must manually track which pins are wiredtypeCAD design is the source of truth
Works for standard dev boards like Arduino UnoWorks for any custom PCB

Contracts are essential when working with custom hardware — custom PCBs, production boards, or any design where the MCU pins differ from a standard development board.

Contract File Format

A contract file is a JSON document with this structure:

{
  "version": 1,
  "mcu": {
    "symbol": "MCU_Microchip_ATmega:ATmega328-MM",
    "reference": "U1",
    "value": "",
    "footprint": "Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.45mm_EP2.4x2.4mm"
  },
  "connectedPins": {
    "5": {
      "pinName": "XTAL1/PB6",
      "pinType": "bidirectional",
      "net": "net11",
      "externalComponents": [
        {
          "reference": "Y11",
          "symbol": "Device:Crystal",
          "value": "",
          "footprint": "Crystal:Crystal_SMD_5032-2Pin_5.0x3.2mm"
        }
      ]
    },
    "6": {
      "pinName": "XTAL2/PB7",
      "pinType": "bidirectional",
      "net": "net12"
    }
  },
  "availablePeripherals": {
    "i2c": false,
    "spi": false,
    "uart": false
  }
}

Fields

FieldDescription
versionContract format version (currently 1)
mcu.symbolKiCad symbol library identifier for the MCU
mcu.referenceComponent reference designator (e.g. U1, R1 — the part label on the schematic)
connectedPinsMap of pin number to pin info — only pins wired on the PCB
connectedPins[n].pinNameFull pin name from the MCU symbol (may be compound like XTAL1/PB6)
connectedPins[n].pinTypePin direction: bidirectional, input, output, power_in, passive
connectedPins[n].netNet name the pin is connected to
connectedPins[n].externalComponentsOptional list of components connected to this pin
availablePeripheralsWhich peripheral buses are available based on wiring

Pin Name Matching

The transpiler matches contract pin names to MCU package pin names using substring matching. Compound pin names like XTAL1/PB6 are resolved to the MCU port name PB6. This handles the common KiCad convention of combining multiple pin functions into a single name.

Integrating with a Cuttlefish Project

Option 1: Explicit Configuration

Add a contract field to your cuttlefish.config.ts alongside an mcu package:

import type { CuttlefishConfig } from '@typecad/core';

const config: CuttlefishConfig = {
  entry: './src/sketch.ts',
  target: 'avr',
  mcu: '@typecad/mcu-atmega328p',
  contract: './src/my-board.contract.json',
  framework: '@typecad/framework-arduino',
  frameworkData: {
    buildTarget: 'arduino:avr:uno',
  },
  output: {
    framework: 'arduino',
    optimize: 'size',
    outDir: './out',
  },
  toolchain: {
    type: 'arduino-cli',
  },
};

export default config;

The key differences from a standard board-based config:

  • mcu instead of board — specifies the MCU silicon package (e.g., @typecad/mcu-atmega328p)
  • contract — path to the .contract.json file
  • No board field — board and contract are mutually exclusive

Option 2: Auto-Discovery

If your config specifies mcu without board or contract, the transpiler will auto-discover a contract file by searching for *.contract.json in:

  1. The project root directory
  2. The src/ subdirectory

If exactly one contract file is found, it’s used automatically.

⚙️ Advanced details — what the contract generates

What Gets Generated

When a contract is present, the transpiler generates a .cuttlefish/board.ts file that re-exports only the connected pins:

// .cuttlefish/board.ts — Auto-generated, do not edit

export {
  HIGH, LOW, INPUT, OUTPUT, INPUT_PULLUP,
  delay, millis, micros, delayMicroseconds,
  map, constrain,
  abs, min, max, Num,
  pulseIn, pulseInLong, Pulse,
  shiftIn, shiftOut, Shift,
  randomSeed, random, Random,
  noInterrupts, interrupts, attachInterrupt, detachInterrupt,
  ADC, AsyncClass, Async
} from '@typecad/hal';

// Re-export ONLY connected pins from MCU package
export {
  PB6,
  PB7,
  PC6
} from '@typecad/mcu-atmega328p';

Your firmware code imports from the generated board:

import { PB6, PB7, OUTPUT } from './.cuttlefish/board';

const xtal1 = PB6.asOutput();

Or more commonly, through the virtual @typecad/board module:

import { PB6, PB7, OUTPUT } from '@typecad/board';

cuttlefish-env.d.ts

The transpiler also generates a cuttlefish-env.d.ts file that declares the @typecad/board ambient module. When using a contract, it points to the generated board:

declare module '@typecad/board' {
  export * from './.cuttlefish/board';
}

typeCAD Integration

Exporting a Contract from typeCAD

A contract file is generated from your typeCAD PCB project. When you define your hardware in typeCAD:

import { PCB } from '@typecad/typecad';
import { Resistor, LED } from '@typecad/passives/0603';

let typecad = new PCB('my-board');
let r1 = new Resistor({ value: '330' });
let d1 = new LED();

typecad.net(mcu.PB5, r1.pin(1));
typecad.net(r1.pin(2), d1.pin(2));

typecad.create(r1, d1);

The typeCAD tooling can export a .contract.json that describes which MCU pins are connected and what they’re connected to. Place this file in your Cuttlefish project’s src/ directory.

Typical Project Setup

For a project that uses both typeCAD (hardware) and Cuttlefish (firmware), the recommended structure is:

my-project/
  hardware/                  # typeCAD project
    src/
      board.ts               # PCB design
    kiCAD/
      my-board.kicad_sch     # Generated schematic
    package.json
  firmware/                  # Cuttlefish project
    src/
      sketch.ts              # Firmware code
      my-board.contract.json # Exported from hardware/
    cuttlefish.config.ts
    package.json

Workflow

  1. Design hardware in typeCAD — define the schematic, place components, wire nets
  2. Export contract — generate a .contract.json from the typeCAD project
  3. Write firmware in Cuttlefish — the contract ensures you only use physically connected pins
  4. Transpile and compilecuttlefish build --compile --upload

Board vs Contract

The board and contract config fields are mutually exclusive:

ScenarioConfig
Standard dev board (Arduino Uno, ESP32 DevKit)board: '@typecad/board-arduino-uno'
Custom PCB designed in typeCADmcu: '@typecad/mcu-atmega328p' + contract: './src/board.contract.json'
Bare MCU with no board definitionmcu: '@typecad/mcu-atmega328p' (all pins available)

CLI Flags

The contract path can also be set via the CLI:

cuttlefish build --mcu @typecad/mcu-atmega328p --contract ./src/my-board.contract.json

Troubleshooting

“MCU package does not export TypeCADManifest”

The MCU package must export a TypeCADManifest object with pinNames and peripheralNames for contract-based board generation to work. Ensure you’re using a supported MCU package (e.g., @typecad/mcu-atmega328p, @typecad/mcu-esp32).

“Specifying both ‘board’ and ‘contract’ is not allowed”

Remove one from your cuttlefish.config.ts. Use board for standard development boards, or mcu + contract for custom hardware.

Contract file not found

If using auto-discovery, ensure the .contract.json file is in the project root or src/ directory. For explicit paths, use a relative path from the config file location.