Skip to main content

Shielded and Unshielded Token Transfers

For the complete documentation index, see llms.txt

This Compact contract implements send, receive and mint functions for tokens in Compact. It offers demonstration of the following features:

  • Minting and receiving unshielded tokens to the contract
  • Sending unshielded tokens to a user
  • Receiving unshielded tokens
  • Night token operations
  • Shielded token operations
The NIGHT circuits fail on Mainnet

receiveNightTokens and sendNightTokensToUser work on Preview, Preprod, and a local network, but fail on Mainnet. Today most Mainnet NIGHT is held on Cardano as cNIGHT. Registering cNIGHT generates DUST for fees, but it doesn't put NIGHT in a wallet on Midnight, so the wallet has no NIGHT to pay into the contract and the contract holds none to send. Receiving fails in the wallet with Wallet.InsufficientFunds, and the node rejects sending with 1010: Invalid Transaction: Custom error: 194. To check a contract for circuits that move NIGHT, see the NIGHT on Mainnet runbook. For how Mainnet funding works, see Funding and transaction cost.

import CompactStandardLibrary;


// Mint using mintUnshieldedToken() + receiveUnshielded()
export circuit mintAndReceive(amount: Uint<64>): Bytes<32> {
const domain = pad(32, "simple:receive");

const color = mintUnshieldedToken(
disclose(domain),
disclose(amount),
left<ContractAddress, UserAddress>(kernel.self())
);

return color;
}


// Send to user address
export circuit sendToUser(amount: Uint<64>, user_addr: UserAddress): [] {
const domain = pad(32, "simple:receive");
const color = tokenType(disclose(domain), kernel.self());

sendUnshielded(
color,
disclose(amount) as Uint<128>,
right<ContractAddress, UserAddress>(disclose(user_addr))
);
}

export circuit receiveTokens(amount: Uint<128>): [] {
const domain = pad(32, "simple:receive");
const color = tokenType(domain, kernel.self());
receiveUnshielded(color, disclose(amount));
}

export circuit receiveNightTokens(amount: Uint<128>): [] {
receiveUnshielded(default<Bytes<32>>, disclose(amount));
}

export circuit sendNightTokensToUser(amount: Uint<64>, user_addr: UserAddress): [] {
sendUnshielded(
default<Bytes<32>>,
disclose(amount) as Uint<128>,
right<ContractAddress, UserAddress>(disclose(user_addr))
);
}

export circuit receiveShieldedTokens(coin: ShieldedCoinInfo): [] {
receiveShielded(disclose(coin));
}

export circuit sendShieldedToUser(input: QualifiedShieldedCoinInfo, publicKey: ZswapCoinPublicKey, value: Uint<128>): ShieldedSendResult {
return sendShielded(disclose(input), left<ZswapCoinPublicKey, ContractAddress>(disclose(publicKey)), disclose(value));
}

export circuit mintShieldedToSelf(domainSep: Bytes<32>, value: Uint<64>, nonce: Bytes<32>): ShieldedCoinInfo {
return mintShieldedToken(disclose(domainSep), disclose(value), disclose(nonce), right<ZswapCoinPublicKey, ContractAddress>(kernel.self()));
}

export circuit mintAndSendShielded(domainSep: Bytes<32>, mintValue: Uint<64>, mintNonce: Bytes<32>, publicKey: ZswapCoinPublicKey, sendValue: Uint<128>): ShieldedSendResult {
const coin = mintShieldedToken(disclose(domainSep), disclose(mintValue), disclose(mintNonce), right<ZswapCoinPublicKey, ContractAddress>(kernel.self()));
const qualified = QualifiedShieldedCoinInfo { nonce: coin.nonce, color: coin.color, value: coin.value, mt_index: 0 as Uint<64> };
return sendShielded(qualified, left<ZswapCoinPublicKey, ContractAddress>(disclose(publicKey)), disclose(sendValue));
}