🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
⚠️ Malware / Trojaner / VirenLumma Stealer – dllhost.exe Hollowing, C2 Domains & Payload Extraction(01.09.2026 um 17:19 Uhr)
🔧 AI Nachrichten Simcha Kosman AMA: Owning ChatGPT's Secure Sandbox(03.09.2026 um 07:41 Uhr)
⚠️ Malware / Trojaner / VirenThe Gentlemen Ransomware Analysis: Go Obfuscated(04.09.2026 um 12:05 Uhr)
⚠️ Malware / Trojaner / VirenTengu, a Mirai-style Linux and IoT botnet(06.09.2026 um 15:27 Uhr)
🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
⚠️ Malware / Trojaner / VirenLumma Stealer – dllhost.exe Hollowing, C2 Domains & Payload Extraction(01.09.2026 um 17:19 Uhr)
🔧 AI Nachrichten Simcha Kosman AMA: Owning ChatGPT's Secure Sandbox(03.09.2026 um 07:41 Uhr)
⚠️ Malware / Trojaner / VirenThe Gentlemen Ransomware Analysis: Go Obfuscated(04.09.2026 um 12:05 Uhr)
⚠️ Malware / Trojaner / VirenTengu, a Mirai-style Linux and IoT botnet(06.09.2026 um 15:27 Uhr)

🔧 Programmierung 🕛 kürzlich 16 Min Lesezeit
0

[Tutorial] Building a Shielded Token dApp on Midnight: From Compact Contract to React UI

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

📁 Full source code: NIGHT tokens

  • A .






    Compiling the compact smart contract



    Install the Compact compiler:




    CODE
    curl --proto '=https' --tlsv1.2 -LsSf \
    https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh






    Then compile:




    CODE
    compact compile contracts/Token.compact src/contracts






    This will generate files and folders such as keys and zkir, all of which are essential for deploying and interacting with the smart contract later.



    .



    First, start by cloning the repository.




    CODE
    git clone https://github.com/0xfdbu/midnight-apps.git






    Run the starter and install dependencies.




    CODE
    cd midnight-apps/dapp-connect
    npm install
    npm run dev









    Building the providers and the TypeScript API



    Before continuing, you need a helper function to build the providers.




    CODE
    // src/hooks/wallet/services/providers.ts

    import type { ConnectedAPI } from '@midnight-ntwrk/dapp-connector-api';
    import type { MidnightProviders } from '@midnight-ntwrk/midnight-js-types';
    import { INDEXER_HTTP, INDEXER_WS, CONTRACT_PATH, PRIVATE_STATE_PASSWORD } from '../wallet.constants';
    import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
    import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
    import type { ZKConfigProvider } from '@midnight-ntwrk/midnight-js-types';
    import { dappConnectorProofProvider } from '@midnight-ntwrk/midnight-js-dapp-connector-proof-provider';
    import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
    import { toHex, fromHex } from '@midnight-ntwrk/midnight-js-utils';
    import { Transaction, CostModel } from '@midnight-ntwrk/ledger-v8';






    Provider builder function:




    CODE
    export async function buildProviders(
    connectedApi: ConnectedAPI,
    coinPublicKey: string,
    encryptionPublicKey: string,
    contractAddress?: string,
    existingPrivateStateProvider?: any
    ): Promise<MidnightProviders> {
    const fetchProvider = new FetchZkConfigProvider(
    `${window.location.origin}${CONTRACT_PATH}`,
    fetch.bind(window)
    );
    const zkConfigProvider = new ArtifactValidatingProvider(fetchProvider);

    const privateStateProvider = existingPrivateStateProvider || levelPrivateStateProvider({
    accountId: coinPublicKey,
    privateStoragePasswordProvider: () => PRIVATE_STATE_PASSWORD,
    });

    if (contractAddress) {
    privateStateProvider.setContractAddress(contractAddress);
    }

    return {
    privateStateProvider,
    publicDataProvider: indexerPublicDataProvider(INDEXER_HTTP, INDEXER_WS),
    zkConfigProvider,
    proofProvider: await dappConnectorProofProvider(connectedApi, zkConfigProvider, CostModel.initialCostModel()),
    walletProvider: {
    getCoinPublicKey: () => coinPublicKey,
    getEncryptionPublicKey: () => encryptionPublicKey,
    async balanceTx(tx: any, _ttl?: Date): Promise<any> {
    const serializedTx = toHex(tx.serialize());
    const received = await connectedApi.balanceUnsealedTransaction(serializedTx);
    return Transaction.deserialize('signature', 'proof', 'binding', fromHex(received.tx));
    },
    },
    midnightProvider: {
    async submitTx(tx: any): Promise<string> {
    await connectedApi.submitTransaction(toHex(tx.serialize()));
    const txIdentifiers = (tx as any).identifiers();
    return txIdentifiers?.[0] ?? '';
    },
    },
    };
    }






    Now proceed to create the hook for the TypeScript API. These are some of the essential imports for the API




    CODE
    // src/hooks/wallet/services/api.ts

    import type { ConnectedAPI } from '@midnight-ntwrk/dapp-connector-api';
    import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
    import { buildProviders } from './providers';
    import { getContract, createInitialPrivateState } from './contract';
    import { INDEXER_HTTP, INDEXER_WS, CONTRACT_PATH, PRIVATE_STATE_ID, PRIVATE_STATE_PASSWORD } from '../wallet.constants';
    import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
    import { CompiledContract } from '@midnight-ntwrk/compact-js';









    Deploying the smart contract



    deployTokenContract builds a CompiledContract instance, binds the localNonce witness, attaches the compiled ZK artifacts, and then calls deployContract with the providers:




    CODE
    // src/hooks/wallet/services/api.ts

    export async function deployTokenContract(
    connectedApi: ConnectedAPI,
    coinPublicKey: string,
    encryptionPublicKey: string
    ): Promise<string> {
    const { deployContract } = await import('@midnight-ntwrk/midnight-js-contracts');
    const privateStateProvider = await ensurePrivateState(coinPublicKey, 'tmp-deploy');
    const providers = await buildProviders(connectedApi, coinPublicKey, encryptionPublicKey, undefined, privateStateProvider);

    const contractModule = await import(`${CONTRACT_PATH}/contract/index.js`);
    const cc: any = CompiledContract.make('shielded-token', contractModule.Contract);
    const withWitnesses = (CompiledContract as any).withWitnesses({
    localNonce: ({ privateState }: any): [any, Uint8Array] => {
    const nonce = crypto.getRandomValues(new Uint8Array(32));
    return [privateState, nonce];
    },
    });
    const withAssets = (CompiledContract as any).withCompiledFileAssets(CONTRACT_PATH);
    const compiledContract = withWitnesses(withAssets(cc));

    const deployed = await deployContract(providers as any, {
    compiledContract,
    privateStateId: PRIVATE_STATE_ID,
    initialPrivateState: createInitialPrivateState(),
    args: [],
    } as any);

    const address = deployed.deployTxData.public.contractAddress;
    localStorage.setItem('shielded_token_contract', address);
    return address;
    }






    Wire deployTokenContract into the frontend




    CODE
    // src/pages/Deploy.tsx
    // Other imports
    import { useWalletStore } from '../hooks/useWallet';
    import { deployTokenContract } from '../hooks/wallet/services/api';

    const handleDeploy = async () => {
    if (!connectedApi || !addresses?.shieldedCoinPublicKey || !addresses?.shieldedEncryptionPublicKey) {
    setError('Wallet not fully connected');
    return;
    }
    setStatus('pending');
    setError(null);

    try {
    const addr = await deployTokenContract(
    connectedApi,
    addresses.shieldedCoinPublicKey,
    addresses.shieldedEncryptionPublicKey
    );
    setContractAddress(addr);
    setStatus('success');
    } catch (err) {
    console.error('[Deploy] Error:', err);
    setError(err instanceof Error ? err.message : 'Deployment failed');
    setStatus('error');
    }
    };












    Minting tokens



    The Mint page has two modes: Mint to Self and Mint & Send.



    Mint to Self calls createShieldedToken and sends the minted coin into the user's shielded coin public key:




    CODE
    const selfRecipient = {
    is_left: true,
    left: { bytes: parseKeyBytes(addresses.shieldedCoinPublicKey) },
    right: { bytes: ZERO_BYTES32 },
    };

    const result = await callCreateShieldedToken(
    connectedApi,
    addresses.shieldedCoinPublicKey,
    addresses.shieldedEncryptionPublicKey,
    value,
    selfRecipient
    );








    When a mint is successful, Nonce, Color, and Value are stored in localStorage so they can be referenced later during the burn phase. This means users won't need to enter the values manually when they are already stored in localStorage.




    Note: The createShieldedToken circuit returns ShieldedCoinInfo, while the mintAndSend circuit returns a ShieldedSendResult containing sent and change. For mintAndSend with exact amounts, change is typically None.







    Coin storage



    Shielded coins are different from unshielded ones: they are private, and the wallet does not expose an API to enumerate them with their nonces, so the DApp stores mint results in localStorage.




    CODE
    export interface StoredCoin {
    id: string;
    nonce: string;
    color: string;
    value: string;
    source: 'mint' | 'mintAndSend' | 'change';
    txId: string;
    createdAt: string;
    }






    Mint page writes using saveStoredCoins and burn page reads using getStoredCoins. Sending tokens from wallet does not require reading or writing.






    Sending tokens



    The send page uses the wallet's native makeTransfer for shielded transfers. The wallet handles everything, including proving; however, you still need to call submitTransaction to broadcast it:




    CODE
    const desiredOutput = {
    kind: 'shielded' as const,
    type: selectedToken,
    value,
    recipient: recipientClean,
    };

    const result = await connectedApi.makeTransfer([desiredOutput]);
    if (result.tx) {
    await connectedApi.submitTransaction(result.tx);
    }






    makeTransfer is the most convenient way of sending shielded tokens using the DApp Connector API.






    Burning tokens



    The Burn page uses the depositAndBurn circuit to destroy stored coins




    CODE
    const coin = {
    nonce: hexToUint8Array(selectedCoin.nonce),
    color: hexToUint8Array(selectedCoin.color),
    value: BigInt(selectedCoin.value),
    };

    const result = await callDepositAndBurn(
    connectedApi,
    addresses.shieldedCoinPublicKey,
    addresses.shieldedEncryptionPublicKey,
    coin,
    BigInt(amount)
    );






    After burning, the coin is removed from localStorage.




    CODE
    const updatedCoins = getStoredCoins().filter((c) => c.id !== selectedCoin.id);
    saveStoredCoins(updatedCoins);














    3. The mint-and-send atomic pattern



    The mintAndSend circuit pattern solves a critical problem in shielded token design.



    The main issue is that a freshly minted shielded coin is not immediately spendable via sendShielded when it has not yet been committed to the Merkle tree. If you mint a coin in transaction X, you cannot spend it in transaction X+1 without waiting for it to be included in the Merkle tree and obtaining its mt_index.



    sendImmediateShielded is different, it bypasses the Merkle qualification by using mt_index: 0.



    The circuit pattern:





    1. mintShieldedToken(..., kernel.self()) — mint shielded coins to the kernel (smart contract)


    2. sendImmediateShielded(coin, recipient, amount) — forward to the recipient



    Either both steps succeed, or the entire transaction fails. The recipient receives a fully qualified shielded coin that is spendable in future transactions with sendShielded once it is committed to the Merkle tree.



    depositAndBurn circuit pattern:





    1. receiveShielded(coin) — deposits user coins into the transaction


    2. sendImmediateShielded(coin, burnAddr, amount) — burn it immediately in the same transaction



    This atomic pattern makes it possible to burn a shielded coin through the smart contract without using sendShielded with mt_index, which requires the commitment of the coin to the Merkle tree.









    4. Key architectural decisions






































    Decision Choice Rationale
    Proving strategy
    dappConnectorProofProvider (wallet-backed)
    Built-in ledger circuits like output are not generated by the Compact compiler; the wallet has them
    Send path Wallet makeTransfer for transfers, smart contract depositAndBurn for burns
    makeTransfer handles change correctly; smart contract burns update totalBurned
    Coin storage
    localStorage via coinStore.ts
    The DApp Connector API does not expose individual coin nonces; storing mint results enables smart contract burns
    Burn default Full burn Partial burns via depositAndBurn lock change in the smart contract
    Network Preprod Testnet with faucet support








    Conclusion



    You have now built a complete shielded token DApp that demonstrates the ability to mint privacy-preserving tokens with mintShieldedToken, atomically forward freshly minted coins with sendImmediateShielded, burn tokens with receiveShielded + sendImmediateShielded, and finally build a React frontend with deploy, mint, send, burn, and balance display.



    It is important to distinguish between sendImmediateShielded (bypasses Merkle path before spending) and sendShielded (requires mt_index). Understanding this correctly determines whether the coins you minted are immediately spendable or locked.






    Next steps




    • Check the full repository source code on GitHub

    • Read the Midnight Compact language docs

    • Experiment with transferShielded by storing mt_index for committed coins

    • Add admin authentication to restrict minting privileges






    Troubleshooting











































    Symptom Cause Fix
    Shielded balance shows 0 after mint Wallet hasn't synced the mint block yet Wait 15s (auto-refresh) or open wallet extension to trigger sync
    Burn page empty dropdown Burn only shows DApp-minted coins, not wallet-received coins Use Send page (makeTransfer to burn address) for wallet balance burns
    Wallet disconnects during proving ZK proof generation timed out in wallet popup Reconnect wallet, ensure extension is active and unlocked

    "Invalid shielded address" on Mint & Send
    Recipient field expects Bech32m, not raw hex Use parseShieldedAddress() to decode the wallet's shielded address

    Invalid Transaction: Custom error: 138 on burn
    1AM wallet dust sponsoring interferes with contract call balancing Turn off dust sponsoring in 1AM wallet settings
    "No compatible wallet found" Extension API version outside 4.x
    Update Lace or 1AM to latest version
    Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
  • Wie bewertest du diesen Beitrag?
    1 Klick Feedback
    Teilen mit Netzwerk & Team:

    Community-Analysen & Experten-Meinungen 0

    Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
    Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
    Community Pulse: Relevanz-Einschätzung
    1 Klick Experten-Votum
    🔴 Akute Relevanz 50%
    🟡 In Evaluierung 23%
    🟢 Keine Auswirkung 18%
    Spannende Innovation 9%
    Verwandte Story-Cluster & Quellen (Vektor-KI)
    Port 8095 Engine
    1 Quelle
    KARR Security vulnerability
    1 Quelle
    How can we detect if Claude in Chrome or other LLM browser agents are accessing/hijacking our web app user authenticated sessions and Block it
    1 Quelle
    OpenAI confirms ChatGPT is down ahead of 'Astra' model launch
    Ähnliche Beiträge
    🔍 Verwandte News

    Auch interessante Nachrichten [Tutorial] Building a Shielded Token dApp on Midnight: From Compact Contract to React UI

    Thematisch verwandte Begriffe: Tutorial, Building, Shielded, Token · 6 Treffer

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...