Skip to main content
Use this quickstart when you are building a TypeScript app or service with Shodai Agreements. You will authenticate with the Agreements API, validate a complete agreement JSON artifact, preflight deployment values, and sign the deploy permit locally without creating a live agreement. For the agent/MCP path, use Quickstart with MCP. To compare the two first-run paths, start with Choose an integration surface.
This quickstart stops before deployWithPermit(...). The final signing step proves that your app can reach API auth, validation, deployment preflight, chain/RPC context, and EIP-712 signing without performing a live write.

Prerequisites

  • Node.js >=18.
  • A Shodai testnet API key. Create one in the Developer Portal.
  • A Linea Sepolia RPC URL for chainId: 59141.
  • A local package or temporary directory where you can install npm packages.

Make the first-flight script

1

Create an ESM project

mkdir shodai-sdk-quickstart
cd shodai-sdk-quickstart
npm init -y
npm pkg set type=module
2

Install the SDK and signing dependencies

npm install @cns-labs/agreements-api-client viem
npm install --save-dev tsx
The API client handles typed Agreements API calls. viem provides the test wallet, public client, and EIP-712 signing primitives used by the SDK signing helpers.
3

Set environment variables

export API_KEY="api_key_replace_me"
export RPC_URL="linea_sepolia_rpc_url_replace_me"
RPC_URL must point at Linea Sepolia because this quickstart uses chainId: 59141.
4

Save the example agreement

Copy the complete simple-agreement.json code block from Simple Agreement into:
simple-agreement.json
Use the complete JSON artifact from the example page, not an abbreviated API request body.
5

Create the quickstart script

Create quickstart.ts:
import { readFile } from 'node:fs/promises';
import {
  ApiClient,
  computeDefaultDeadlineSeconds,
  signDeployWithPermit,
} from '@cns-labs/agreements-api-client';
import { createPublicClient, createWalletClient, http } from 'viem';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { lineaSepolia } from 'viem/chains';

const apiKey = process.env.API_KEY;
const rpcUrl = process.env.RPC_URL;

if (!apiKey) throw new Error('Set API_KEY before running this script.');
if (!rpcUrl) throw new Error('Set RPC_URL to a Linea Sepolia RPC endpoint.');

const chainId = 59141;
const client = new ApiClient({
  environment: 'testnet',
  apiKey,
});

const health = await client.getHealth();
console.log('Health:', health.status, health.service);

const agreementsPage = await client.listAgreements({ limit: 25 });
console.log('Visible agreements:', agreementsPage.data.length);

const agreement = JSON.parse(
  await readFile(new URL('./simple-agreement.json', import.meta.url), 'utf8'),
);

const templateValidation = await client.validateTemplate(agreement);
console.log('Template validation:', {
  participantVariableKeys: templateValidation.participantVariableKeys,
  inputIds: templateValidation.inputIds,
  stateIds: templateValidation.stateIds,
  warnings: templateValidation.warnings,
});

const partyA = privateKeyToAccount(generatePrivateKey());
const partyB = privateKeyToAccount(generatePrivateKey());

const publicClient = createPublicClient({
  chain: lineaSepolia,
  transport: http(rpcUrl),
});

const walletClient = createWalletClient({
  account: partyA,
  chain: lineaSepolia,
  transport: http(rpcUrl),
});

const participants = [
  {
    variableKey: 'partyAEthAddress',
    walletAddress: partyA.address,
  },
  {
    variableKey: 'partyBEthAddress',
    walletAddress: partyB.address,
  },
];

const deploymentValidation = await client.validateDeployment({
  agreement,
  chainId,
  participants,
});

console.log('Deployment preflight:', {
  variables: deploymentValidation.variables,
  participants: deploymentValidation.participants,
  observers: deploymentValidation.observers,
  contributors: deploymentValidation.contributors,
  warnings: deploymentValidation.warnings,
});

type ProtocolInitValue = string | bigint | boolean | `0x${string}`;
const deploymentInitValues = deploymentValidation.variables as Record<string, ProtocolInitValue>;

const deadline = computeDefaultDeadlineSeconds();
const deployPermit = await signDeployWithPermit({
  walletClient,
  publicClient,
  chainId,
  agreement,
  deadline,
  permitOptions: {
    initValues: deploymentInitValues,
  },
});

console.log('Deploy permit signed but not submitted:', {
  signerAddress: deployPermit.signerAddress,
  deadline: deployPermit.deadline,
  signatureV: deployPermit.signature.v,
  signatureR: deployPermit.signature.r,
  signatureS: deployPermit.signature.s,
});
6

Run the script

npx tsx quickstart.ts
The generated Party A wallet signs the deploy permit. The generated wallets are for this local test only; do not persist generated private keys or use this pattern for production custody.

Completion state

You have completed this quickstart when the script prints:
  • a healthy API response
  • an authenticated agreement list response
  • template validation output for the Simple Agreement
  • deployment preflight output
  • signatureV, signatureR, and signatureS from signDeployWithPermit(...)
At that point your TypeScript integration has reached API auth, agreement validation, deployment preflight, chain/RPC context, and EIP-712 signing readiness. It has not deployed an agreement.

Continue from here

Run an end-to-end agreement workflow

Continue from first-flight readiness to deployment, signed input submission, state reads, and input history.

TypeScript client reference

Reference constructor options, methods, signing helpers, diagnostics, path helpers, and exports.

Author agreement JSON

Model terms, variables, participants, states, inputs, issuers, and transitions.

Deploy an agreement

Learn the live deployment workflow after preflight and signing readiness are working.