Skip to main content
Effective date: 2026-05-26. Use this guide when upgrading an integration that deploys agreements, signs EIP-712 permits, submits agreement inputs, or filters agreement lists with @cns-labs/agreements-api-client@0.3.0.

What changed

Agreements can now be deployed to more than one supported EVM chain in the same hosted API environment. Deployment, signing, and input submission code must carry the selected chainId consistently instead of assuming a single environment chain.
PackageUpgrade targetWhy it matters
@cns-labs/agreements-api-client@0.3.00.3.0Adds multi-chain signing checks and requires chainId for input signing helpers.
@cns-labs/agreements-protocol-evm@0.1.30.1.3Adds protocol deployment registry entries for Base, Base Sepolia, and Ethereum Sepolia.
Supported hosted deployment chains:
API environmentSupported chains
testnetLinea Sepolia (59141), Ethereum Sepolia (11155111), Base Sepolia (84532)
productionLinea Mainnet (59144), Base Mainnet (8453)

Upgrade checklist

  1. Upgrade to @cns-labs/agreements-api-client@0.3.0.
  2. Confirm your lockfile resolves @cns-labs/agreements-protocol-evm@0.1.3 or newer.
  3. Choose a supported chainId before deployment preflight.
  4. Include the same chainId in client.validateDeployment(...), EIP-712 deploy signing, and deploy-with-permit requests.
  5. Create the publicClient and walletClient for the selected chain before signing.
  6. For input signing, read the deployed agreement record and pass agreementRecord.chainId to submitAgreementInputWithPermit(...) or signAgreementInputPermit(...).
  7. Add chainId filters to agreement list views when your UI or job should operate on one chain at a time.
  8. Update error handling for unsupported chains and RPC/client chain mismatches.

Deployment code changes

Before this migration, integrations often treated the API environment as the chain selection:
const validation = await client.validateDeployment({
  agreement,
  initValues,
  participants,
});

const deployed = await deployAgreementWithPermit({
  client,
  walletClient,
  publicClient,
  agreement,
  displayName: 'Consulting Agreement',
  initValues: validation.variables,
  participants,
});
After the migration, select a supported chainId and carry it through the whole deployment flow:
const chainId = 59141;

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

const deployed = await deployAgreementWithPermit({
  client,
  walletClient,
  publicClient,
  chainId,
  agreement,
  displayName: 'Consulting Agreement',
  initValues: validation.variables,
  participants,
});
publicClient.getChainId() must match the selected chainId. If it does not, the SDK rejects before requesting a signature.

Input signing changes

The 0.3.0 TypeScript client makes chainId required for input signing helpers. Use the chain stored on the deployed agreement record, not a hardcoded environment default. Before 0.3.0:
await submitAgreementInputWithPermit({
  client,
  agreementId,
  walletClient,
  publicClient,
  agreementContractAddress,
  agreement,
  inputId: 'partyASignature',
  values,
});
In 0.3.0:
const agreementRecord = await client.getAgreement(agreementId);

await submitAgreementInputWithPermit({
  client,
  agreementId: agreementRecord.id,
  walletClient,
  publicClient,
  chainId: agreementRecord.chainId,
  agreementContractAddress: agreementRecord.address!,
  agreement: agreementRecord.json as AgreementJson,
  inputId: 'partyASignature',
  values,
});
The raw HTTP input request body does not include chainId. The API uses the stored agreement record after lookup. chainId is required by the SDK signing helpers so the EIP-712 domain is built for the deployed agreement’s chain and the RPC client can be checked before signing.

Raw HTTP changes

Deployment preflight and deploy-with-permit requests should include chainId:
{
  "agreement": {},
  "chainId": 59141,
  "initValues": {},
  "participants": []
}
{
  "agreement": {},
  "displayName": "Consulting Agreement",
  "chainId": 59141,
  "signer": "0x1111111111111111111111111111111111111111",
  "deadline": 1776219513,
  "signature": {
    "v": 27,
    "r": "0x...",
    "s": "0x..."
  }
}
For raw EIP-712 signing, use the same chainId in the typed-data domain and use the factory address registered for that chain. For input signing, read GET /v0/agreements/{id} first and use the returned chainId and address.

Agreement list filtering

Agreement lists can now be filtered by chain:
const page = await client.listAgreements({
  chainId: 84532,
  limit: 25,
});
Raw HTTP:
curl -sS "$BASE_URL/v0/agreements?chainId=84532&limit=25" \
  -H "X-API-Key: $API_KEY"
If you omit the filter, list responses can include agreements from every chain visible to the API key.

Protocol registry changes

The protocol SDK registry now includes five deployments. Use registry helpers instead of hardcoding factory addresses:
import { getFactoryConfigByChainId } from '@cns-labs/agreements-protocol-evm';

const factoryConfig = getFactoryConfigByChainId(chainId);
if (!factoryConfig) {
  throw new Error(`Unsupported agreement chain: ${chainId}`);
}
This matters even when two networks share the same factory address. The EIP-712 domain still needs the correct chainId.

Common upgrade failures

SymptomLikely causeFix
Requested chainId ... does not match publicClient chainId ...The RPC client is connected to a different chain than the selected agreement chain.Recreate publicClient for the selected chainId before signing.
No AgreementFactory deployment registered for chain ...The selected chain is not in the installed protocol SDK registry.Upgrade to @cns-labs/agreements-protocol-evm@0.1.3 or choose a supported chain.
Unsupported agreement.chainId ...The API environment does not allow deployments on that chain.Choose one of the chains supported by the target API environment.
Input signature is rejected or does not advance stateThe signature was created for the wrong chain, contract address, payload, signer, nonce, or deadline.Re-read the agreement record and current state, then sign again with agreementRecord.chainId and agreementRecord.address.