Help us improve
Share bugs, ideas, or general feedback.
From compact-core
This skill should be used when the user asks about Midnight tokens, token types (NIGHT, DUST, shielded, unshielded), minting and burning tokens, token transfers, token colors and domain separators, the zswap protocol, ShieldedCoinInfo, QualifiedShieldedCoinInfo, Kernel mint operations, contract token patterns (FungibleToken, NonFungibleToken, MultiToken), the account model vs UTXO model for tokens, sendShielded, receiveShielded, sendUnshielded, mintShieldedToken, mintUnshieldedToken, unshieldedBalance, OpenZeppelin Compact token contracts, or choosing between shielded and unshielded token approaches.
npx claudepluginhub devrelaicom/midnight-expert --plugin compact-coreHow this skill is triggered — by the user, by Claude, or both
Slash command
/compact-core:compact-tokensThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill covers tokens on Midnight: choosing between shielded and unshielded approaches, using the standard library mint/send/receive functions, understanding token colors and domain separators, and the NIGHT/DUST token model. It does not cover ledger ADT types or state design -- those belong in `compact-ledger`. It does not cover overall contract anatomy or circuit/witness design -- those be...
This skill should be used when the user asks about UTXO vs account models, ledger tokens, shielded/unshielded tokens, nullifiers, coin commitments, the Zswap commitment tree, double-spend prevention, token balances, parallel transaction processing, choosing between token paradigms in Midnight, minting tokens, token type identification, or the ledger structure.
This skill should be used when the user asks about the Compact standard library (CompactStandardLibrary), stdlib types (Maybe, Either, JubjubPoint, MerkleTreeDigest, MerkleTreePath, ContractAddress, ZswapCoinPublicKey, UserAddress), deprecated stdlib names (CurvePoint, NativePoint, CoinInfo), stdlib constructor functions (some, none, left, right), elliptic curve functions (ecAdd, ecMul, ecMulGenerator, hashToCurve, jubjubPointX, jubjubPointY, constructJubjubPoint), Merkle tree path verification (merkleTreePathRoot, merkleTreePathRootNoLeafHash), or when the user needs to verify which functions exist in the standard library, prevent hallucination of non-existent stdlib functions, or search the Midnight MCP for stdlib source code.
This skill should be used when the user asks about the Midnight.js SDK, midnight-js packages, @midnight-ntwrk npm packages, setting up SDK providers, deploying or finding contracts with deployContract or findDeployedContract, calling circuits with callTx or submitCallTx, the transaction lifecycle, SDK provider types (WalletProvider, MidnightProvider, PublicDataProvider, ProofProvider, ZkConfigProvider, PrivateStateProvider), testkit-js testing, observable state subscriptions, contract maintenance and verifier keys, or connecting to the indexer or proof server.
Share bugs, ideas, or general feedback.
This skill covers tokens on Midnight: choosing between shielded and unshielded approaches, using the standard library mint/send/receive functions, understanding token colors and domain separators, and the NIGHT/DUST token model. It does not cover ledger ADT types or state design -- those belong in compact-ledger. It does not cover overall contract anatomy or circuit/witness design -- those belong in compact-structure.
| Need | Approach | Key Functions |
|---|---|---|
| Private balances/transfers | Shielded ledger tokens (zswap UTXO) | mintShieldedToken, sendShielded, receiveShielded |
| Transparent balances/transfers | Unshielded ledger tokens | mintUnshieldedToken, sendUnshielded, unshieldedBalance |
| Programmable fungible token (ERC-20 style) | Contract token with Map state | OpenZeppelin FungibleToken pattern |
| NFTs / multi-token collections | Contract token with ownership Maps | OpenZeppelin NonFungibleToken / MultiToken |
| Gas fees | DUST (generated from NIGHT) | Not contract-programmable |
| Type | Location | Privacy | Model | Key Traits |
|---|---|---|---|---|
| Shielded ledger | Blockchain ledger | Private | UTXO | Native privacy, maximum efficiency, hidden sender/recipient/value |
| Unshielded ledger | Blockchain ledger | Transparent | UTXO | Full transparency, high performance, visible balances |
| Shielded contract | Contract state | Private | Account (via Map) | Private balances via ZK proofs, but no post-issuance spend enforcement — contract cannot freeze, pause, or claw back coins once received (see Known Limitations in references/token-patterns.md). OpenZeppelin ShieldedERC20 is archived; use unshielded contract tokens for custom logic. |
| Unshielded contract | Contract state | Transparent | Account (via Map) | Full programmability, visible operations |
Key types:
| Type | Fields | Purpose |
|---|---|---|
ShieldedCoinInfo | nonce: Bytes<32>, color: Bytes<32>, value: Uint<128> | Newly created coin (this transaction) |
QualifiedShieldedCoinInfo | nonce, color, value, mt_index: Uint<64> | Existing coin on ledger, ready to spend |
ShieldedSendResult | change: Maybe<ShieldedCoinInfo>, sent: ShieldedCoinInfo | Result of send operations |
ZswapCoinPublicKey | bytes: Bytes<32> | User public key for coin output |
Key functions:
| Function | Signature | Returns |
|---|---|---|
mintShieldedToken | (domainSep: Bytes<32>, value: Uint<64>, nonce: Bytes<32>, recipient: Either<ZswapCoinPublicKey, ContractAddress>) | ShieldedCoinInfo |
receiveShielded | (coin: ShieldedCoinInfo) | [] |
sendShielded | (input: QualifiedShieldedCoinInfo, recipient: Either<ZswapCoinPublicKey, ContractAddress>, value: Uint<128>) | ShieldedSendResult |
sendImmediateShielded | (input: ShieldedCoinInfo, target: Either<ZswapCoinPublicKey, ContractAddress>, value: Uint<128>) | ShieldedSendResult |
mergeCoin | (a: QualifiedShieldedCoinInfo, b: QualifiedShieldedCoinInfo) | ShieldedCoinInfo |
mergeCoinImmediate | (a: QualifiedShieldedCoinInfo, b: ShieldedCoinInfo) | ShieldedCoinInfo |
evolveNonce | (index: Uint<128>, nonce: Bytes<32>) | Bytes<32> |
shieldedBurnAddress | () | Either<ZswapCoinPublicKey, ContractAddress> |
ownPublicKey | () | ZswapCoinPublicKey |
export circuit mint(amount: Uint<64>): ShieldedCoinInfo {
counter.increment(1);
const newNonce = evolveNonce(counter.read() as Uint<128>, nonce);
nonce = newNonce;
return mintShieldedToken(
domain, disclose(amount), nonce,
left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey())
);
}
export circuit send(coin: ShieldedCoinInfo, to: ZswapCoinPublicKey, amount: Uint<128>): ShieldedSendResult {
receiveShielded(disclose(coin));
return sendImmediateShielded(disclose(coin), left<ZswapCoinPublicKey, ContractAddress>(disclose(to)), disclose(amount));
}
| Function | Signature | Returns |
|---|---|---|
mintUnshieldedToken | (domainSep: Bytes<32>, value: Uint<64>, recipient: Either<ContractAddress, UserAddress>) | Bytes<32> (color) |
sendUnshielded | (color: Bytes<32>, amount: Uint<128>, recipient: Either<ContractAddress, UserAddress>) | [] |
receiveUnshielded | (color: Bytes<32>, amount: Uint<128>) | [] |
unshieldedBalance | (color: Bytes<32>) | Uint<128> |
unshieldedBalanceLt/Gte/Gt/Lte | (color: Bytes<32>, amount: Uint<128>) | Boolean |
Caveat:
unshieldedBalance()returns the balance at transaction construction time, not at application time. If the balance changes between construction and application, the transaction fails. Prefer the comparison functions (unshieldedBalanceLt,unshieldedBalanceGte, etc.) unless you specifically need an exact-match constraint.
export circuit mintToSelf(domainSep: Bytes<32>, amount: Uint<64>): Bytes<32> {
const color = mintUnshieldedToken(
disclose(domainSep), disclose(amount),
left<ContractAddress, UserAddress>(kernel.self())
);
receiveUnshielded(color, disclose(amount) as Uint<128>);
return color;
}
A token color (type) is a Bytes<32> value derived from the contract address and a domain separator:
| Function | Signature | Purpose |
|---|---|---|
tokenType | (domainSep: Bytes<32>, contract: ContractAddress): Bytes<32> | Compute color for another contract's token |
nativeToken | (): Bytes<32> | Returns the zero value (NIGHT token color) |
Colors are deterministic: the same (domainSep, contractAddress) pair always yields the same color. A contract can issue multiple token types by using different domain separators. The color field in ShieldedCoinInfo identifies which token a coin represents.
NIGHT is Midnight's native utility token. It exists as UTXOs on the ledger with the zero token color (nativeToken()). NIGHT is used for staking, governance, and generating DUST.
DUST is a shielded network resource, not a token. It is generated from NIGHT over time when NIGHT UTXOs are registered for dust generation. Key properties:
On testnet, these are called tNIGHT and tDUST. Contracts cannot mint, send, or manipulate NIGHT or DUST directly through standard library functions.
| Wrong | Correct | Why |
|---|---|---|
kernel.mintShielded(dom, amt) | mintShieldedToken(dom, amt, nonce, recipient) | kernel.mintShielded is the low-level Kernel op; use the standard library wrapper which also returns ShieldedCoinInfo |
unshieldedBalance(color) for conditional logic | unshieldedBalanceGte(color, amount) | unshieldedBalance locks the exact balance at construction time; comparison functions are more robust |
Omitting disclose() on token params | mintShieldedToken(disclose(dom), ...) | Witness-derived values passed to token functions must be disclosed |
Sending shielded to ContractAddress without receiveShielded | Call receiveShielded(coin) in the receiving contract | The receiving contract must explicitly accept the coin |
Uint<64> for shielded amounts in send/receive | Uint<128> | ShieldedCoinInfo.value, sendShielded, and sendImmediateShielded use Uint<128> |
Uint<128> for mintShieldedToken value | Uint<64> | mintShieldedToken accepts Uint<64> for the value parameter, not Uint<128> |
| Minting unshielded to self without receiving | Call receiveUnshielded(color, amount) after mintUnshieldedToken | The contract must receive its own minted unshielded tokens to update its balance |
| Topic | Reference File |
|---|---|
| Token architecture, shielded vs unshielded deep dive, UTXO vs account model | references/token-architecture.md |
| Complete function signatures, detailed parameters, nonce management, merge strategies | references/token-operations.md |
| OpenZeppelin FungibleToken, NonFungibleToken, MultiToken patterns and examples | references/token-patterns.md |
| Example | File |
|---|---|
| ERC-20 style fungible token (non-compilable — requires OpenZeppelin compact-contracts) | examples/FungibleToken.compact |
| Non-fungible token with ownership tracking (non-compilable — requires OpenZeppelin compact-contracts) | examples/NonFungibleToken.compact |
| Multi-token collection with mint/burn per ID (non-compilable — requires OpenZeppelin compact-contracts) | examples/MultiToken.compact |
| Shielded fungible token using zswap coin infrastructure (non-compilable — requires OpenZeppelin midnight-apps) | examples/ShieldedFungibleToken.compact |