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 pinspnpm@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.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.Environment
Every signing key lives inapps/api/.env. Copy apps/api/.env.example, which
annotates each one:
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.Unit tests
The SDK’s unit suite runs under vitest and needs no chain.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 behindINTEGRATION=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 The suite uses anvil’s default accounts as owner, co-signer, merchant, and vault,
deploys USDC and the factory, then drives
test:integration script sets the
flag for you.createEphemeral, readAccount, and
settlePrivatePayment against them.Contract tests
The Solidity tests run under Foundry, independent of the TypeScript side.Quality checks
Next
- Try the flows: Quickstart.
- Understand the design: Overview.
- Handle failures: Errors and result shapes.