Skip to main content
Ctrl+ArcZ is a pnpm monorepo. Everything below runs from the repo root unless noted.

Layout

packages/sdk

@ctrl-arcz/sdk, the published TypeScript SDK: the risk firewall, protected transfers, the payer-side shield, and the wallet-side CCTP and Gateway bridge.

packages/contracts

The Foundry project: the protected-transfer contract, the spend-policy account and factory, the vault, and the stealth announcer.

packages/demo-kit

The shared session and design system, plus the server-only helpers the API calls (cosignServer, relayServer, gaslessServer, investigator).

apps/sender

The web app on port 5173. Everything the browser does, including the bridge, which is non-custodial and signs from the user’s own wallet.

apps/api

@ctrl-arcz/api on port 8787: the co-signer, the gasless claim, the relayer routes, and the investigator. Every key in the system lives here.

apps/keeper

An autonomous agent that reclaims expired transfers and pays for its own gas out of a PULL box. See The keeper.
There is no apps/receiver any more, and no Expo or React Native app in this repository. Receiving happens in the sender app’s receive mode. The mobile client is a separate native Kotlin project, held to this repo by shared test vectors rather than by shared code.

Install

Requires Node 20+ and pnpm (the repo pins pnpm@9.15.9). The contracts submodule also needs Foundry.

Build the SDK

Most work needs the SDK built first, since the apps consume it as a workspace package.
To rebuild on change while you work on the SDK:
You can also build every package at once with pnpm build (runs each package’s build).

Run the apps

Two processes, two terminals. The web app is useful on its own; the API is what the co-signer, the gasless claim and the relayer routes need.
The keeper is optional and runs on demand:

Environment

Every signing key lives in apps/api/.env. Copy apps/api/.env.example, which annotates each one:
CORS_ORIGINS is fail-closed. An empty list allows no browser origin; it does not mean “any”. Leaving it blank does not open the API up, it answers 403 to every web app, which is the safe direction to be wrong in. Running locally means adding http://localhost:5173 to it, or the relayer-funded flows return 403 from a page that otherwise looks like it is working.
The web app’s own apps/sender/.env.local holds no secret in production. In development it takes dev-only, throwaway Arc-testnet values:
A VITE_-prefixed value is inlined into the client bundle whenever client code reads it through import.meta.env, so a signing key must never be one. A production build that would inline VITE_DEMO_PK, VITE_RELAYER_PK or VITE_CLIENT_KEY is refused by an assertNoLeakedSecrets build guard unless you explicitly set VITE_ALLOW_DEMO_KEYS=1, and then only with throwaway testnet keys.
The bridge does not need a key. It is non-custodial and signs from the user’s own wallet in the browser, so nothing about it lives on the server. See Bridge USDC. The co-signer, the gasless claim and the relayer routes do run server-side; see The co-signer and The API.

Unit tests

The SDK’s unit suite runs under vitest and needs no chain.
Run the whole monorepo’s tests with pnpm test, or watch with pnpm --filter @ctrl-arcz/sdk test:watch.

Integration tests against a local anvil

The integration suite is the end-to-end proof that the SDK’s digest and signatures are accepted by the deployed contracts on a real EVM. It is excluded from the default unit run and is gated behind INTEGRATION=1, so it is skipped unless anvil is up and the flag is set.
1

Build the contracts

The tests deploy from Foundry’s out/ artifacts, so build them first.
2

Start anvil

Run a local anvil node (Foundry). Keep it running in its own terminal.
3

Run the integration suite

In another terminal, run the gated suite. The test:integration script sets the flag for you.
The suite uses anvil’s default accounts as owner, co-signer, merchant, and vault, deploys USDC and the factory, then drives createEphemeral, readAccount, and settlePrivatePayment against them.

Contract tests

The Solidity tests run under Foundry, independent of the TypeScript side.

Quality checks

Next