A small, self-documenting Deno CLI that runs a Crossmint regulated stablecoin payout end to end: a company treasury wallet pays out to a KYC-verified recipient, on Crossmint's licensed infrastructure. It uses the Crossmint wallets SDK with a server signer.
The CLI prints a plan, narrates each step, and on any block prints the exact reason plus the relevant docs link - so the output alone explains what a project needs to support the flow. It is staging-first and safe to re-run as a test suite.
AML, KYC, sanctions screening, and Travel Rule are handled by Crossmint. You provide the recipient's details; Crossmint runs the checks at transfer time. See the Regulated Transfers overview.
treasury (COMPANY) wallet --payout--> recipient (KYC-verified user)
- Treasury wallet -
owner: "COMPANY", server signer, fixedalias. Idempotent on the alias, so it is the same address every run (locatorCOMPANY:<alias>). Treasury wallets guide - Recipient user - registered via the REST users API (provide their
userDetails: name, date of birth, country of residence). Users REST API - Recipient wallet - a wallet owned by that user. Create a wallet
- Payout - treasury to the recipient,
transactionType: "regulated-transfer". The transfer triggers the recipient's KYC + sanctions screen automatically. Making a payout
Wallets and the payout use the SDK; the recipient setup (step 2) uses REST, because the SDK does not cover it.
After a run, the treasury, the recipient wallets, and the payout history all show up in the Crossmint Console.
Treasury wallet (Company)
Recipient wallets (Users)
Payout history
You generate a secret (xmsk1_<64 hex>); the SDK derives the wallet address deterministically from
it and signs locally - only the address is sent to Crossmint. Same secret, same wallet. No private
keys to manage and no manual signature handling. For production treasury reserves you can instead
point the admin signer at your own Cloud KMS key; the flow is identical.
Server signer · Custody models
- Deno installed.
- A Crossmint project and a server-side API key. Staging is self-serve - create them in the Console. Production payouts are enabled by Crossmint; reach out to your Crossmint contact to turn them on for your project. API keys overview · Server-side keys · Scopes
Start on staging; everything here runs against staging.crossmint.com. For production, flip ENV,
use a production key, and set a production chain.
Staging vs production
git clone https://github.com/Crossmint/regulated-payouts-quickstart
cd regulated-payouts-quickstart
cp .env.example .env
deno task gen-secret # prints xmsk1_... -> paste into .env as CROSSMINT_SIGNER_SECRET
# set CROSSMINT_API_KEY in .env (a staging server key)
deno task transfer:dry # set up wallets + recipient, PREPARE the payout (no funds move)
deno task transfer # execute the payout
deno task inspect # balances + recent transfers, via the SDKFund the treasury once: send testnet USDC from the Circle faucet to the treasury address printed in step 1. Get staging tokens
The setup steps are idempotent, so it is safe to re-run. The treasury is reused via its alias - fund that one address once, then re-run freely. Each non-dry run sends one fresh payout.
| Task | What it does |
|---|---|
deno task gen-secret |
Print a new server-signer secret |
deno task transfer |
Run the payout (executes) |
deno task transfer:dry |
Same setup, but prepare the payout without executing |
deno task inspect |
Print treasury + recipient balances and recent transfers |
deno task docs |
Run the flow live and write a local docs/REGULATED_TRANSFERS.md |
deno task check |
deno fmt --check + deno check |
deno task fmt |
Format |
Flags: --dry-run (prepare only), --debug (show the SDK's own logs).
RECIPIENT_COUNTRY must be a supported country of residence. An unsupported country is rejected
at payout time.
A freshly created recipient is screened first; while the screen runs the payout returns
"User KYC is in progress, retry in a few seconds". The CLI retries until it clears - seconds for a
clean, supported-country recipient.
deno task docs runs the flow live and writes docs/REGULATED_TRANSFERS.md from the real requests,
responses, addresses, and outcomes of that run. It wraps fetch before the SDK initializes, so the
doc shows the actual Crossmint API calls underneath the SDK, with full untruncated ids. The doc is a
byproduct of execution, so it cannot drift from the API. The API key is never recorded and any
signer secret is redacted.
The generated doc is gitignored: it captures the addresses and tx hashes from your own run, so it
stays a local reference rather than a committed file. See docs/TESTING.md for
the full test matrix.
| Var | Default | Notes |
|---|---|---|
ENV |
staging |
staging or production |
CROSSMINT_API_KEY |
(required) | server key; staging self-serve, production enabled by Crossmint |
CROSSMINT_SIGNER_SECRET |
(required) | server-signer secret (deno task gen-secret) |
CROSSMINT_BASE_URL |
(auto) | override the API base URL |
CHAIN |
polygon-amoy |
EVM chain that supports regulated transfers; use polygon in prod |
TOKEN |
usdc |
currency symbol passed to the SDK (no chain prefix) |
TREASURY_ALIAS |
payouts-treasury |
stable alias, same treasury every run |
RECIPIENT_EMAIL (+ name / DOB / country) |
(sample) | KYC'd recipient user |
AMOUNT |
0.1 |
decimal string |
Tasks run with an explicit Deno permission set - no -A, no prompts:
--allow-env read configuration from the environment / .env
--allow-read read .env and node_modules
--allow-net talk to the Crossmint API
--allow-ffi load the native module the SDK pulls in
Payouts (regulated transfers)
Wallets
- Wallets overview
- Treasury wallets
- Wallets SDK (Node.js)
- Users REST API
- Check balances
- Transfer tokens
- List transfers
- Monitor transfers (webhooks)
Signers & custody
Platform
MIT - see LICENSE.



