Skip to content

Astor/stablecoinkit

Repository files navigation

StableCoin Kit

Mock stablecoin contracts and a small ethers v6 helper layer so integration tests can exercise real-world quirks (USDT’s missing return value, USDC blacklisting, fee-on-transfer, proxies, pausing, permit, and so on) without hitting mainnet.

What this is: a local / CI testing toolkit.
What this is not: audited custody, issuance, or anything you would ship to a production chain as “the” stablecoin.


Requirements

  • Node.js ≥ 18
  • Hardhat (this repo uses Hardhat for compile + tests)
  • ethers v6 (peer to how you use Hardhat in your project)

Deploy helpers expect a Signer with a provider (e.g. await ethers.getSigner()). A bare provider cannot deploy.


Quick start (this repo)

cd stablecoinkit
npm install
npm run compile    # builds Solidity artifacts under artifacts/
npm run build      # optional: TypeScript → dist/
npm test

deployStablecoins reads compiled artifacts from artifacts/. If you see “Artifact not found”, run npm run compile first.


Use in another project

  1. Add this package to your workspace (git submodule, monorepo path, or copy), or publish it and depend on it like any npm package.
  2. Run npm run compile in stablecoinkit (or ensure artifacts/ is present).
  3. From your Hardhat tests or scripts, import from the built output or source (depending on how you wire TypeScript).

Minimal pattern:

import { deployStablecoins, createFaucet, snapshot } from "stablecoinkit"; // or "../path/to/stablecoinkit/src"

const signer = await ethers.getSigner();
const coins = await deployStablecoins(signer, ["usdc", "usdt", "dai"], {
  owner: await signer.getAddress(),
  seedOwnerAmountHuman: "1_000_000", // optional: seed owner after deploy
  // usdcUseProxy: true,            // optional: USDC behind ERC1967 proxy
});

await coins.usdc!.mint(alice.address, "5000"); // human amount; 6 decimals handled inside
await coins.dai!.mint(alice.address, "5000"); // 18 decimals

await coins.usdc!.blacklist(badActor.address);
await coins.usdt!.setTransferFee(50); // basis points (0.5%)
await coins.usdt!.pause();

const faucet = createFaucet(coins, { amount: "10_000" });
await faucet.drip([wallet1, wallet2]);

const snap = await snapshot(ethers.provider);
if (snap.supported) {
  // ... tests ...
  await snap.restore();
}

Deploy options (DeployStablecoinsOptions)

Option Purpose
owner Receives initial role/supply semantics per mock; defaults to deployer.
seedOwnerAmountHuman After deploy, mint this display amount (per token, per its decimals) to owner.
usdcUseProxy Deploy USDC mock behind an ERC1967 proxy (MockUSDCUpgradeable).

Supported mocks

Kind Decimals Notes (high level)
usdc 6 Blacklist, pause, permit; optional proxy deployment.
usdt 6 No bool return on transfer, fee bps, pause.
dai 18 Permit-style flows, drip-style helpers per contract.
frax 18 Collateral ratio style behavior (see MockFRAX).
lusd 18 Liquity-style mock (tests include drip).
busd 18 Pausable + permit-related surface (see MockBUSD).

Feature flags and metadata live in adapters in src/types.ts (StablecoinAdapter, StablecoinFeature).


API surface (exports)

From src/index.ts:

  • Deploy / faucet: deployStablecoins, createFaucet, types DeployStablecoinsOptions, FaucetOptions
  • Registry: TokenRegistry, StablecoinSet
  • Adapters: TestToken, USDCoin, USDTcoin, DAIcoin, FRAXcoin, LUSDcoin, BUSDcoin
  • Types: adapters map, StablecoinKind, StablecoinFeature, StablecoinAdapter
  • Units: parseHumanAmount
  • Snapshot: snapshot, SnapshotProvider (re-exported as network as well)

Project layout

stablecoinkit/
├── contracts/           # MockUSDC, MockUSDT, MockDAI, MockFRAX, MockLUSD, MockBUSD, upgrades, etc.
├── src/
│   ├── factory.ts       # deployStablecoins, createFaucet
│   ├── registry.ts      # TokenRegistry, StablecoinSet
│   ├── types.ts         # kinds, features, adapter table
│   ├── adapters/        # per-token wrappers on TestToken
│   ├── network/         # snapshot re-exports
│   └── utils/           # units, snapshot (evm_snapshot / evm_revert)
├── test/
└── dist/                # after npm run build

Why bother?

Generic ERC20Mock tests miss failures that only show up with:

  • USDT: transfer without a bool return (SafeERC20 assumptions).
  • USDC: blacklist or pause reverting mid-flow.
  • Decimals: 6 vs 18 mixed in one flow.

This kit makes those paths reproducible in your environment before mainnet.

About

Mock stablecoin contracts and a small **ethers v6** helper layer so integration tests can exercise real-world quirks

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors