Audited, copy-pasteable smart-contract templates for the XChain Platform: worked examples of the contracts-as-orchestration model. Each template is a real contract you can fork, paired with a walkthrough and an explicit "attacks we considered" section, and verified by running the actual template through the XChain VM.
The goal is to seed the mental model: an XChain contract is a deterministic JavaScript program that custodies tokens and emits protocol actions. These templates show how to do that safely.
Component overview is available in the xchain-documentation repository:
| Document | Description |
|---|---|
| README | Scope, license posture (MIT vs. platform AGPL), how the library relates to the VM and SDK |
scaffold → customize → lint → deploy.
# 1. Scaffold a template (or print available names with `list`)
npx xchain-contracts list
npx xchain-contracts scaffold escrow my-escrow.js
# 2. ...edit my-escrow.js...
# 3. Lint it: a conservative preflight over the deploy-time rules (needs Node 22 / isolated-vm)
npx xchain-contracts lint my-escrow.js
# 4. Deploy via the SDK (which lints again before spending a transaction)
# sdk.deploy({ CODE: fs.readFileSync('my-escrow.js','utf8'), GAS_LIMIT: '200000' }, encoder)Prefer to stay in JS? The SDK exposes the same library: sdk.scaffold('escrow')
returns the source, sdk.validateContract(source) runs the advisory linter (no
Node-22 requirement), and sdk.deploy(..., { lint }) blocks a guaranteed-to-fail
deploy. See the developer guide.
Reusable building blocks (access control, pausable, safe-transfer, input
validation, state machines) live in patterns/; paste
the helpers you need into your contract.
Most people who reach for a contract actually want a token with rules (royalties, transfer restrictions, a pause switch) and never want to write contract code. Describe the policy in a small JSON file and generate a deploy-ready controller guard contract, no code written:
# describe the policy (see lib/policy.example.json), then:
npx xchain-contracts policy my-policy.json my-guard.js
# → writes my-guard.js and prints the ISSUE v6 bind stepsThe config supports pausable, freeze (denylist), allowlist, a royalty
proceeds split, a maxTakeBps cap, an optional meta (contract identity, generated
from the config when you leave it out), and a permissions manifest, over any of the
transfer/trade/burn/mint/stake/ownership/all action classes. A controller guard
is a contract the indexer runs before a gated native action settles; a token
binds to it with ISSUE v6 (SDK: sdk.controller.bindToken). The generated source
is built to pass the deploy linter clean. See
lib/policy-gen.js.
allowlist direction. allowlistDirection picks which end of a move the
allowlist is checked on: "from" (the default, and what every guard generated
before this key enforced), "to", or "both". The default is the compatible
setting, not the safe one: under "from" an allowlisted holder can move the
token to any address, and because the guard then blocks that address as a
sender, the balance is stranded there. A holder-restricted (security-token
shaped) policy wants "both". The resolved direction is emitted into the
generated header, the policy descriptor and the generator's features, so a
reader can see which one is in force.
No setting is a complete holder restriction. Actions that carry no recipient
(burns, and the escrow-creating order/swap/dispenser/airdrop/dividend actions)
pass an empty to and are exempt, and matched trade settlement invokes no guard
at all, so a matched buyer can still end up holding the token.
royalty scope. The proceeds split is returned for ORDER_CREATE and
SWAP_CREATE only: the indexer records the legs when the order or swap is listed
and applies them when it matches. DISPENSER_CREATE is in the same trade
class, so the guard runs when a holder opens a dispenser, but the dispenser path
honours only a revert; the legs are discarded and dispenser sales pay no split.
A holder can therefore sell a royalty-bearing token through a dispenser
royalty-free, and a dispenser opened before the bind is never guarded at all.
The generated header and the printed bind hints repeat this whenever a
royalty is configured.
| Template | Contract | Guide | Tests | What it teaches |
|---|---|---|---|---|
| Escrow | escrow.js | README | tests | The custody baseline: DEPOSIT funding verified on-chain, conditional release/refund, an arbiter, and a deadline so funds can't be locked forever. |
| Vesting | vesting.js | README | tests | Linear release with a cliff, partial-claim accounting, and optional revocation. |
| Crowdsale | crowdsale.js | README | tests | A capped raise with a soft cap, deadline, and refunds, plus a contract that issues its own token and mints it to buyers. |
| AMM | amm.js | README | tests | A constant-product market maker. LP positions are real, tradeable ticks; 0.3% fee; slippage protection; the k-invariant is fuzz-tested. |
| Treasury | treasury.js | README | tests | A poll-governed treasury hardened against low-turnout governance raids: binding VOTE polls, a timelock between "passed" and "paid", and a guardian veto. |
| Card dispenser | cardDispenser.js | README | tests | A random card-pack dispenser backed by the contract's own token inventory (no mint): stock-weighted rarity, deterministic on-chain randomness and its limits. |
| Price bet | priceBet.js | README | tests | A two-party binary option settled by the PRICE oracle at an agreed round: round-anchored determinism, permissionless settlement, liveness escape hatches. |
| Price bet (timed) | priceBetTimed.js | README | tests | The timestamp variant: the first oracle round at/after a settle time decides, with a gas-capped, cursor-persisted round scan. |
| Stable vault | stableVault.js | README | tests | A mini-MakerDAO: over-collateralized vaults that mint the contract's own stable token, oracle staleness gating, and permissionless liquidation. |
| URL oracle | urlOracle.js | README | tests | Reading off-chain HTTP data without breaking determinism: the ATTEST request/callback round-trip. |
| Escrow (delivery) | escrowDelivery.js | README | tests | Escrow that settles itself: point it at a carrier tracking URL and a marker string, and a delivery attestation pays the seller with nobody having to call release(). |
| English auction | englishAuction.js | README | tests | An ascending-bid auction: each new bid refunds the one it topped in the same transaction, and after the deadline anyone can settle. Custody applied to a contest rather than a single hand-off. |
| Dutch auction | dutchAuction.js | README | tests | A descending-price auction: the price falls linearly per block to a floor, and the first buyer to pay the price in effect at their block takes the item. One purchase, no losing bids to refund. |
| Counterparty bridge | counterpartyBridge.js | README | tests | A burn-to-mint bridge for a single Counterparty asset: an off-chain attestation (the same pattern as urlOracle) confirms an irreversible burn to a well-known unspendable address before minting, so a holder cannot claim the migrated tokens and still sell the original asset. |
Start with escrow: it explains the custody model the others build on.
Each template above exports a meta block as the first key of module.exports, carrying the
human name in the Template column, a one-sentence honest version of the What it teaches
column, and a version string (1.0.0 for every template in this release):
meta: { name: 'Dutch Auction', description: 'Descending-price auction: ...', version: '1.0.0' }This is not advisory metadata like abi. The indexer reads it off the deployed export and
refuses a DEPLOY that carries no name and description, and it is what a wallet history
row and an explorer contract page show beside the contract address (Dutch Auction v1.0.0 · C:DOGE:2154). A fork keeps the block and rewrites it for the contract it has become; the address
stays the identity, since names are not unique. Editing a template means bumping its
meta.version: see CONTRIBUTING.md.
These templates do not reimplement native protocol actions. XChain already has
native ORDER/SWAP (an orderbook DEX), DISPENSER, DIVIDEND, and ISSUE; use
those directly. Templates exist for what native actions can't do: custody with
custom release rules, multi-step state machines, and (in the showcase tier) the
cross-chain, oracle, and attestation primitives.
The AMM is the clearest example of the distinction: there is no native AMM (only an orderbook, which has thin long-tail liquidity), so an AMM-as-a-contract is both genuinely useful and the best proof that the VM's custody model is real.
XChain has no msg.value, so a contract call carries no tokens. Instead:
-
A contract is an address (
C:<CHAIN>:<index>) that holds balances like a wallet. -
Tokens enter via a separate
DEPOSITaction to that address. -
Logic runs via an
EXECUTEaction. -
To fund and act in one transaction, submit both in one
BATCH:BATCH( DEPOSIT(contract, TICK, amount), EXECUTE(contract, "method", ...args) )
The two commands still settle independently: a BATCH is not atomic, so an
EXECUTE that fails does not undo the DEPOSIT before it. The deposit stays in
the contract's custody and is not recoverable by its sender. Where a template
attributes tokens by balance delta (amm, crowdsale, stableVault,
cardDispenser, the auctions) it is folded into the next caller's credited
amount; where a template instead verifies an absolute balance (escrow,
escrowDelivery, vesting) it sits untracked until settlement sweeps the
contract's whole balance to whoever that template pays. Each template's own README
names its case. Size the call to clear before batching the deposit behind
it. ("Atomic" in these templates means the intra-EXECUTE scope, where a method's
state writes and its deferred emissions commit or roll back together.)
A safe contract never trusts a caller-supplied amount: it reads its own balance
with xchain.getBalance(xchain.getContractAddress(), tick). Every template here
follows that rule; escrow's README explains it in full.
xchain-contracts lint runs each contract through the VM's full deploy-time
validation (V8 syntax, the acorn metering pass, reserved identifiers, banned
Math.*, banned BigInt/RegExp literals) plus the logic-level advisories
(crossCallable integrity, unbounded loops, unchecked state.get, …). A clean
result is a conservative preflight, not exact deploy parity: the rule set is a
superset of the live deploy gate (future and mainnet-gated rules are enforced
immediately, and a malformed crossCallable is a linter error the chain itself
accepts), so the linter can still refuse a contract a given chain, network and
block would deploy. It delegates to xchain-vm's linter, so it needs Node 22
/ isolated-vm.
npm run lint # lint every template + pattern
npx xchain-contracts lint my-escrow.js # lint one file
npx xchain-contracts lint my-escrow.js --json # machine-readable
# exit 0 = clean · 1 = errors · warnings print to stderrThis is the CI gate for the library. Authors who want the advisory rules without a
Node-22 install can run sdk.validateContract(source) from the SDK (everything
except the V8 step).
Each template's tests load the real contract and run it through the XChain VM
(xchain-vm, which requires Node 22 / isolated-vm), checked out alongside
this repo:
npm test # all templates + the pattern lint-gate
npx mocha --timeout 0 patterns/patterns.test.js # pattern lint-gate only (runs on any Node)Value-holding contracts require the VM gateway's getBalance / getTokenInfo to
return real data, wired in xchain-indexer. Without it a contract cannot read its
own holdings to verify deposits.
MIT, fork freely, including into closed-source products. (The XChain platform is licensed AGPL-3.0; these templates are intentionally permissive so you can build proprietary contracts on top of them.)