npm package: @satoshai/abi-cli
Description: CLI to fetch Stacks contract ABIs and generate TypeScript definitions.
Latest version: 1.0.0

# @satoshai/abi-cli

[![npm version](https://img.shields.io/npm/v/@satoshai/abi-cli)](https://www.npmjs.com/package/@satoshai/abi-cli)
[![CI](https://github.com/satoshai-dev/abi-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/satoshai-dev/abi-cli/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

CLI tool to fetch ABIs from deployed Stacks blockchain contracts and output TypeScript-ready definitions (`as const`).

## Install

```bash
# Global install
npm install -g @satoshai/abi-cli

# Or run directly with npx
npx @satoshai/abi-cli fetch <contract>
```

## Usage

### Basic — fetch and write to file

By default, the CLI writes a TypeScript file named after the contract to the current directory:

```bash
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01
# → writes amm-pool-v2-01.ts
```

The generated file looks like:

```typescript
import type { ClarityAbi } from '@stacks/transactions';

// ABI for SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01
// Generated by @satoshai/abi-cli

export const abi = {
  "functions": [...],
  "variables": [...],
  ...
} as const satisfies ClarityAbi;

export type Abi = typeof abi;
```

### Output to stdout

Use `--stdout` to print the output instead of writing a file:

```bash
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 --stdout
```

### Custom output path

```bash
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 -o ./abis/amm-pool.ts
```

### JSON format

```bash
# Write to file (amm-pool-v2-01.json)
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 --format json

# Print to stdout
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 --format json --stdout
```

### Networks

Defaults to mainnet. Use `-n` / `--network` to switch:

```bash
# Testnet
abi-cli fetch ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.my-contract -n testnet

# Devnet (localhost:3999)
abi-cli fetch ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.my-contract -n devnet

# Custom Stacks API URL
abi-cli fetch SP...contract -n https://my-node.example.com
```

### Multiple contracts

Comma-separate contract IDs to fetch several at once:

```bash
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01,SP2C2YFP12AJZB1KD5HQ4XFRYGEK02H70HVK8GQH.arkadiko-swap-v2-1
# → writes amm-pool-v2-01.ts and arkadiko-swap-v2-1.ts
```

### Sync — config-driven multi-contract sync

For projects with multiple contracts, create a config file to keep ABIs in sync declaratively.

Create `abi.config.json` in your project root:

```json
{
  "outDir": "./src/abis",
  "format": "ts",
  "network": "mainnet",
  "contracts": [
    { "id": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01", "name": "amm-pool" },
    { "id": "SP2C2YFP12AJZB1KD5HQ4XFRYGEK02H70HVK8GQH.arkadiko-swap-v2-1", "name": "arkadiko-swap" },
    { "id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.my-contract", "network": "testnet" }
  ]
}
```

The `name` field controls the output filename. This decouples your imports from the on-chain contract ID — when you upgrade to a new contract version, change the `id` but keep the `name`. Your imports stay the same.

Or use `abi.config.ts` for type-safe config with autocomplete:

```typescript
import type { AbiConfig } from '@satoshai/abi-cli';

export default {
  outDir: './src/abis',
  format: 'ts',
  network: 'mainnet',
  contracts: [
    { id: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01', name: 'amm-pool' },
    { id: 'SP2C2YFP12AJZB1KD5HQ4XFRYGEK02H70HVK8GQH.arkadiko-swap-v2-1', name: 'arkadiko-swap' },
  ],
} satisfies AbiConfig;
```

Then run:

```bash
abi-cli sync
# → reads abi.config.json (or .ts), writes all ABIs to outDir
```

This generates:

```
src/abis/
├── amm-pool.ts
├── arkadiko-swap.ts
└── index.ts          # barrel file with re-exports
```

The barrel file re-exports all ABIs with camelCase names:

```typescript
export { abi as ammPoolAbi } from './amm-pool.js';
export { abi as arkadikoSwapAbi } from './arkadiko-swap.js';
```

Use `--config` / `-c` to point to a custom config path:

```bash
abi-cli sync --config ./configs/my-abis.json
```

#### Config schema

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `outDir` | `string` | yes | — | Output directory for generated files |
| `format` | `"ts" \| "json"` | no | `"ts"` | Output format |
| `network` | `string` | no | `"mainnet"` | Default network for all contracts |
| `contracts` | `ContractEntry[]` | yes | — | List of contracts to sync |
| `contracts[].id` | `string` | yes | — | Contract ID in `address.name` format |
| `contracts[].name` | `string` | no | contract name from ID | Alias for output filename and barrel export |
| `contracts[].network` | `string` | no | top-level `network` | Per-contract network override |

### Check — CI staleness detection

Use `--check` to verify local files are in sync with on-chain ABIs without writing anything. Exits with code 1 if any files are stale or missing.

```bash
# Single contract
abi-cli fetch SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 --check

# All contracts from config
abi-cli sync --check
```

Add it to your CI pipeline to catch stale types:

```yaml
- name: Check ABIs are up-to-date
  run: npx @satoshai/abi-cli sync --check
```

## Flags Reference

### `abi-cli fetch`

| Flag | Alias | Default | Description |
|------|-------|---------|-------------|
| `--network` | `-n` | `mainnet` | Network: `mainnet`, `testnet`, `devnet`, or a custom URL |
| `--output` | `-o` | `<contract-name>.<format>` | Output file path (single contract only) |
| `--format` | `-f` | `ts` | Output format: `ts` or `json` |
| `--stdout` | | `false` | Print to stdout instead of writing a file |
| `--check` | | `false` | Check if local files match on-chain ABI (exit 1 if stale) |
| `--help` | | | Show help |

### `abi-cli sync`

| Flag | Alias | Default | Description |
|------|-------|---------|-------------|
| `--config` | `-c` | auto-discover | Path to config file |
| `--check` | | `false` | Check if local files match on-chain ABIs (exit 1 if stale) |
| `--help` | | | Show help |

## Programmatic API

```typescript
import { fetchContractAbi, generateTypescript, generateJson, parseContractId } from '@satoshai/abi-cli';

// Fetch an ABI
const abi = await fetchContractAbi('mainnet', 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM', 'amm-pool-v2-01');

// Generate TypeScript (as const)
const tsCode = generateTypescript('SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01', abi);

// Generate JSON
const json = generateJson(abi);

// Parse a contract ID string
const { address, name } = parseContractId('SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01');
```

### Config loading

```typescript
import { loadConfig, validateConfig } from '@satoshai/abi-cli';
import type { AbiConfig, ContractEntry } from '@satoshai/abi-cli';

// Load and validate from file (auto-discovers abi.config.json/.ts)
const config = await loadConfig();

// Or from a specific path
const config2 = await loadConfig('./my-config.json');

// Validate a raw object
const validated = validateConfig({ outDir: './abis', contracts: [{ id: 'SP1.token' }] });
```

Types are re-exported from `@stacks/transactions`:

```typescript
import type { ClarityAbi, ClarityAbiFunction, ClarityAbiType } from '@satoshai/abi-cli';
```

## License

MIT