The testnet faucet flows.
Sepolia only: mint plaintext TTT, mint backed CTTT, read both balances, inspect rate and supply, gate on the chain.
- 1
Mint TTT underlying
TokenopsTestToken (TTT) is a plain 18-decimal ERC-20 with an open mint(to, amount). Anyone on Sepolia can mint plaintext test tokens: no encryptor, no roles, no pause.
- useMintUnderlying().mutate({ amount, to?, gas? }) submits mint(to, amount) on TTT; amount is a plaintext bigint in 18-decimal units, gas an optional fixed limit
- MintUnderlyingResult is { hash, to, amount }, returned once the receipt confirms success
- Invalidate ["tokenops-sdk", "testnet-faucet"] and read it back with useUnderlyingBalance({ account })
- 2
Mint CTTT backed-confidential
ConfidentialTokenopsTestToken (CTTT) is a 6-decimal ERC-7984 wrapper behind a UUPS proxy with an open, fully-backed mint. The amount is public: the faucet mints the TTT behind it, then credits the confidential balance.
- useMintConfidential().mutate({ amount, to?, gas? }) submits mint(to, amount) on CTTT; amount is a plaintext uint64 in 6-decimal units
- MintConfidentialResult is { hash, to, amount, underlyingMinted, handle }, decoded from the ConfidentialMint event
- FaucetSupplyExhaustedError maps the ERC7984TotalSupplyOverflow revert once the backing reaches maxTotalSupply
- 3
Read public + encrypted balances
TTT exposes a plaintext balance; CTTT exposes a euint64 handle. The faucet is the producer side only: it returns the handle as-is, and you decrypt it with your own Zama SDK instance against the CTTT address.
- useUnderlyingBalance({ account, query }) returns the TTT balance as a bigint
- useConfidentialBalance({ account, query }) returns the CTTT balance handle (Hex); a never-credited account reads the zero handle
- Headless: client.underlyingBalanceOf(account) and client.confidentialBalanceOf(account)
- 4
Read faucet metadata, rate + supply
Static and dynamic faucet state in read-only calls: metadata for both tokens, the backing rate, and supply headroom against the CTTT cap. The static reads (metadata, rate, decimals, underlying decimals and address, max supply) already default to staleTime: Infinity; pass query: { staleTime: 0 } to refetch them.
- useFaucetMetadata() returns FaucetMetadata { confidential, underlying, rate, maxTotalSupply }
- useFaucetRate() / useMaxTotalSupply() / useInferredTotalSupply() return the rate and supply headroom as bigints
- useUnderlyingTokenAddress() returns the TTT address backing CTTT, read from the wrapper
- 5
Gate on the chain
The faucet refuses mainnet and every chain outside TESTNET_FAUCET_SUPPORTED_CHAIN_IDS. The hooks catch the constructor error as resolutionError, so the UI never crashes during render; gate the mint button on the chain instead of waiting for the rejection.
- isTestnetFaucetChainId(chainId) tells you up front whether to show the faucet or a switch-network prompt
- On an unsupported chain the read hooks stay disabled and useMintConfidential / useMintUnderlying reject with UnsupportedChainError
- err.context.method is "TestnetFaucetClient" and err.context.hint names the fix: connect the clients to Sepolia