docs(tokens): top-level Tokens category with shielded/unshielded token tutorials#1101
docs(tokens): top-level Tokens category with shielded/unshielded token tutorials#1101Olanetsoft wants to merge 39 commits into
Conversation
Convert the Tokens section into a generated-index category with Overview as the first page (moved from index.mdx), and add two tutorials: - Create and transfer an unshielded token - Create and transfer a shielded token Doc URLs in the source are rewritten as relative paths and the two tutorials cross-link each other.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary
|
🚀 Preview Deployment✅ Deployment Ready 🔗 Preview URL: https://pr-1101-midnight-docs.vercel.app 📝 Latest commit: This preview updates automatically when you push new commits to this PR. |
Rewrite passive-voice sentences flagged by Midnight.Readability into active voice, and replace a 'the user' phrasing flagged by Midnight.SecondPerson. The SPDX license header is unchanged.
oduameh
left a comment
There was a problem hiding this comment.
Nice work on this PR. These are substantial, well-structured tutorials that walk developers through both unshielded and shielded tokens end to end. The progression from transparent balances to immutable coins is well motivated and the code examples are thorough.
Left inline suggestions that can be batch committed.
Top recurring issues:
- Em-dashes appear throughout both tutorials. The Midnight docs style guide uses commas, periods, colons, or restructured sentences instead. Each instance below has an inline fix.
- Compiler version: both tutorials reference compactc 0.31.0. Worth verifying against the current compatibility matrix before merging.
- Intro scope: the unshielded tutorial intro says it covers both tokens, but only this file covers the unshielded one.
Additional notes (not inlined):
- The shielded tutorial's deploy script duplicates the wallet-funding flow from the unshielded tutorial nearly verbatim (~160 lines). Consider extracting the shared setup into a partial or collapsible block to reduce maintenance surface.
- "Midnight has no native token live on the network yet" (unshielded-token.mdx, line 21): with Mainnet launched, this may need qualifying (e.g. 'native mNIGHT is not yet live').
- The genesis wallet seed
0x00...001is shown in the run command. Consider adding a note that this seed is for local use only.
27 suggestions, 1 comment across 2 files.
oduameh
left a comment
There was a problem hiding this comment.
Source-verified technical review. Every Compact standard library claim was checked against the actual source at LFDT-Minokawa/compact/compiler/standard-library.compact.
All function signatures confirmed (mintUnshieldedToken, sendUnshielded, unshieldedBalanceGte, mintShieldedToken, sendShielded, sendImmediateShielded, evolveNonce, shieldedBurnAddress, pad, persistentHash, all Either type wrapping). One field name discrepancy and two protocol-level claims that cannot be verified from the standard library source are flagged below.
Structural notes:
- The shielded tutorial's deploy script duplicates ~160 lines of wallet-funding flow from the unshielded tutorial. The PR notes this is intentional ("included here in full so it runs on its own"), but it creates a maintenance burden. Consider a collapsible block or partial.
- Renaming
index.mdxtooverview.mdxand switching togenerated-indexchanges the URL from/tokensto/tokens/overview. Any existing links to/tokensnow land on the auto-generated index page. Flagged inline on_category_.yaml.
4 suggestions, 7 comments across 3 files.
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
Co-authored-by: Emmanuel Ameh <oduameh@gmail.com> Signed-off-by: Idris Olubisi <heedris2olubisi@gmail.com>
…eld name - Reframe the unshielded tutorial's intro as part one of a pair and link the companion shielded tutorial, instead of claiming it covers both. - Use mt_index (snake_case) consistently for the QualifiedShieldedCoinInfo Merkle index field. Verified by compiling against the Compact stdlib (compact 0.4.0, lang 0.23) and confirmed in upstream source (LFDT-Minokawa/compact standard-library.compact); the camelCase mtIndex belongs to the separate QualifiedDustOutput type. - Repoint the renamed 'Go further' section anchor.
- Note that sendImmediateShielded returns ShieldedSendResult (same as sendShielded); the circuit ignores it because it sends the full value, but a partial send returns a change coin to handle. - Remove all em dashes from the token tutorials, replacing them with commas, colons, semicolons, and periods. The ownPublicKey() authorization caveat is left as-is: verified against the compiler runtime (runtime/src/zswap.ts returns the prover's local ZswapLocalState.coinPublicKey) and the compiler changelog, which both classify ownPublicKey as a witness-like external value, not bound to the transaction signer.
Match customProps.description to the generated-index description so the two no longer diverge.
The generated-index default permalink is /category/<name>, which left /tokens returning 404 (it previously served the hand-authored index). Pin the generated index to slug: /tokens so the URL keeps working.
All fixed. For the snippet that seem to be verbatim, its intentional. Managing should be easy because update to tokens should cover both shielded and unshielded. |
| node: 'https://rpc.preview.midnight.network', | ||
| nodeWS: 'wss://rpc.preview.midnight.network', | ||
| proofServer: process.env['MIDNIGHT_PROOF_SERVER'] ?? 'http://127.0.0.1:6300', | ||
| faucet: 'https://faucet.preview.midnight.network/api/drips', |
There was a problem hiding this comment.
These are the faucet links that are being deprecated -- please update to nethermind faucets
| right<ContractAddress, UserAddress>(disclose(recipient)) | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
This circuit either needs auth or needs a warning that anyone can call this and drain it to any address they like.
While this tutorial is about Tokens specifically, it would be too easy for a human (or an AI) to copy and paste this code assuming that it is safe. My preference would be to add auth as if this were a real token, rather than having this all be permissionless
| ); | ||
| ``` | ||
|
|
||
| `withVacantWitnesses` applies because the contract has no witnesses; its state is entirely public. `zkConfigPath` points the proof server at the keys the compiler generated under `src/managed/unshielded-token`. |
There was a problem hiding this comment.
Witness !== private state
| } else { | ||
| logger.info('NIGHT is already registered for DUST generation.'); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Is this checking for a NIGHT UTxO specifically? This crashes on a re-run with a wallet that already holds the test token. Likely because this is taking any UTxO instead of filtering for NIGHT first
|
|
||
| ## How tokens work on Midnight | ||
|
|
||
| Midnight has no native token live on the network yet. NIGHT exists on Cardano and bridges to Midnight later. Until that lands, a smart contract creates every token on Midnight, which is exactly what you do here. |
There was a problem hiding this comment.
This is only true for mainnet. The tests below demonstrate working with NIGHT on other networks.
|
|
||
| You reuse the unshielded tutorial's environment. In particular: | ||
|
|
||
| - The toolchain (compactc 0.31.0), the local network (midnight-local-dev), and the proof server. See the [project setup](/tokens/unshielded-token#set-up-your-project). |
There was a problem hiding this comment.
We don't recommend this version due to the security flaw -- should be 0.31.1
| .update('tutorial:shielded:token:seed') | ||
| .digest(), | ||
| minterSecret: Uint8Array = createHash('sha256') | ||
| .update('tutorial:shielded:minter:secret') |
There was a problem hiding this comment.
With the high likelihood of a user (or AI) copy / pasting this code, we should avoid printing secrets or seeds like this. Other examples call randomBytes(32)
| } | ||
| ``` | ||
|
|
||
| Unlike the unshielded tutorial, the private state is not empty. It carries the nonce seed. You pass the real value at deploy time in the next step. |
There was a problem hiding this comment.
It carries the nonce seed and minter secret
|
|
||
| On the local network the genesis wallet is already funded and registered, so the NIGHT wait returns immediately, registration reports it is already done, and DUST appears within a few seconds. | ||
|
|
||
| Now the shielded-specific part. Build the providers, deploy the contract with the nonce-seed private state, and read the initial state: |
There was a problem hiding this comment.
| Now the shielded-specific part. Build the providers, deploy the contract with the nonce-seed private state, and read the initial state: | |
| Now the shielded-specific part. Build the providers, deploy the contract with the private state, and read the initial state: |
| logger.info(`initialized at deploy: ${(await readLedger()).initialized}`); // false | ||
| ``` | ||
|
|
||
| The one difference from the unshielded tutorial is `initialPrivateState` instead of `{}`: you pass `createShieldedTokenPrivateState()`, which seeds the witnesses. |
There was a problem hiding this comment.
It doesn't seed the witnesses, it populates the private state. Witness !== private state. In this case, the witnesses are just off-chain functions being used to access the private state.
This is not entirely necessary, but offers one example with witnesses and one without. We need to be more clear about the purpose and power of witness functions
Summary
Turns Tokens into a top-level, expandable sidebar category and adds two tutorials for creating and transferring custom tokens in Compact.
Changes
docs/tokens/_category_.yamlnow useslink: type: generated-index(same pattern as Guides), so Tokens renders as a real expandable category with Overview as its explicit first entry.index.mdx→overview.mdx(sidebar_position: 1), body unchanged aside from a pointer to the two new tutorials.unshielded-token.mdx, position 2) — transparent token: on-chain balances,mintUnshieldedToken/sendUnshielded, full deploy-and-test harness.shielded-token.mdx, position 3) — privacy-preserving token: value in immutable coins, witnesses,mintShieldedToken/sendImmediateShielded, confirmed via the wallet's shielded balance.