Documentation Index
Fetch the complete documentation index at: https://docs.inco.org/llms.txt
Use this file to discover all available pages before exploring further.
Components
This guide walks you through the key components that handle encryption, minting, and balance display.EncryptedInput Component
This component handles:- Taking user input
- Encrypting the value with Inco SDK
- Creating mint and token accounts if needed
- Minting confidential tokens
Full Implementation
"use client";
import { useState, useEffect, useRef } from "react";
import {
useWallet,
useConnection,
useAnchorWallet,
} from "@solana/wallet-adapter-react";
import { encryptValue } from "@inco/solana-sdk/encryption";
import { hexToBuffer } from "@inco/solana-sdk/utils";
import {
Keypair,
PublicKey,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import {
fetchUserMint,
fetchUserTokenAccount,
getAllowancePda,
extractHandle,
getProgram,
} from "@/utils/constants";
export default function EncryptedInput() {
const { publicKey, connected, sendTransaction } = useWallet();
const { connection } = useConnection();
const wallet = useAnchorWallet();
// State
const [mint, setMint] = useState<string | null>(null);
const [account, setAccount] = useState<string | null>(null);
const [value, setValue] = useState("");
const [encrypted, setEncrypted] = useState("");
const [txHash, setTxHash] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Fetch existing accounts when wallet connects
useEffect(() => {
if (!publicKey) {
setMint(null);
setAccount(null);
return;
}
(async () => {
const m = await fetchUserMint(connection, publicKey);
if (m) {
setMint(m.pubkey.toBase58());
const a = await fetchUserTokenAccount(connection, publicKey, m.pubkey);
setAccount(a?.pubkey.toBase58() ?? null);
}
})();
}, [publicKey, connection]);
// Encrypt the input value
const handleEncrypt = async () => {
if (!value) return;
setLoading(true);
try {
// Convert to smallest unit (6 decimals)
const amount = BigInt(Math.floor(parseFloat(value) * 1e6));
const encryptedHex = await encryptValue(amount);
setEncrypted(encryptedHex);
} catch (e: any) {
setError(e.message || "Encryption failed");
}
setLoading(false);
};
// Mint tokens with the encrypted amount
const handleMint = async () => {
if (!publicKey || !wallet || !encrypted) {
setError("Missing required data");
return;
}
setLoading(true);
setError(null);
try {
const program = getProgram(connection, wallet);
const ciphertext = hexToBuffer(encrypted);
let m = mint;
let a = account;
// Create mint and account if they don't exist
if (!m || !a) {
const signers: Keypair[] = [];
const tx = new Transaction();
if (!m) {
const mintKp = Keypair.generate();
m = mintKp.publicKey.toBase58();
signers.push(mintKp);
tx.add(
await program.methods
.initializeMint(6, publicKey, publicKey)
.accounts({
mint: mintKp.publicKey,
payer: publicKey,
systemProgram: SystemProgram.programId,
})
.instruction()
);
}
if (!a) {
const accountKp = Keypair.generate();
a = accountKp.publicKey.toBase58();
signers.push(accountKp);
tx.add(
await program.methods
.initializeAccount()
.accounts({
account: accountKp.publicKey,
mint: m,
owner: publicKey,
payer: publicKey,
systemProgram: SystemProgram.programId,
})
.instruction()
);
}
// Send initialization transaction
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.feePayer = publicKey;
tx.partialSign(...signers);
await connection.confirmTransaction(
await sendTransaction(tx, connection),
"confirmed"
);
setMint(m);
setAccount(a);
// Wait for account to be available
await new Promise((r) => setTimeout(r, 1000));
}
// Now mint the tokens
const mintPk = new PublicKey(m);
const accPk = new PublicKey(a);
const accounts = {
mint: mintPk,
account: accPk,
mintAuthority: publicKey,
systemProgram: SystemProgram.programId,
};
// Simulate to get the handle
const simTx = await program.methods
.mintTo(ciphertext, 0)
.accounts(accounts)
.transaction();
simTx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
simTx.feePayer = publicKey;
const sim = await connection.simulateTransaction(simTx, undefined, [accPk]);
if (sim.value.err) {
throw new Error(`Simulation failed: ${JSON.stringify(sim.value.err)}`);
}
// Extract handle from simulation
const data = sim.value.accounts?.[0]?.data;
if (!data) throw new Error("No simulation data");
const handle = extractHandle(Buffer.from(data[0], "base64"));
if (!handle) throw new Error("No handle found");
// Derive allowance PDA
const [allowancePda] = getAllowancePda(handle, publicKey);
// Submit the actual transaction with allowance PDA
const sig = await program.methods
.mintTo(ciphertext, 0)
.accounts(accounts)
.remainingAccounts([
{ pubkey: allowancePda, isSigner: false, isWritable: true },
{ pubkey: publicKey, isSigner: false, isWritable: false },
])
.rpc();
setTxHash(sig);
setValue("");
setEncrypted("");
// Notify other components
window.dispatchEvent(new CustomEvent("token-minted"));
} catch (e: any) {
console.error(e);
setError(e.message || "Minting failed");
}
setLoading(false);
};
return (
<div className="mt-8 space-y-4">
{/* Input Field */}
<div>
<label className="block text-sm font-medium mb-2">
Amount to Mint
</label>
<div className="flex space-x-2">
<input
type="number"
placeholder="Enter amount..."
value={value}
onChange={(e) => {
setValue(e.target.value);
setTxHash(null);
setEncrypted("");
setError(null);
}}
className="flex-1 p-3 border rounded-full"
/>
<button
onClick={handleEncrypt}
disabled={loading || !value || !connected}
className="px-6 py-3 bg-blue-600 text-white rounded-full
hover:bg-blue-700 disabled:bg-gray-300"
>
{loading ? "..." : "Encrypt"}
</button>
</div>
</div>
{/* Encrypted Value Display */}
{encrypted && (
<>
<div className="bg-gray-100 p-3 rounded-full flex items-center">
<span className="text-sm font-mono truncate flex-1">
{encrypted.slice(0, 20)}...
</span>
<button
onClick={() => navigator.clipboard.writeText(encrypted)}
className="ml-2 bg-blue-600 text-white px-3 py-1 rounded-full text-xs"
>
Copy
</button>
</div>
<button
onClick={handleMint}
disabled={loading}
className="w-full bg-blue-600 text-white py-2 rounded-full
hover:bg-blue-700 disabled:bg-gray-300"
>
{loading ? "Processing..." : "Mint Tokens"}
</button>
</>
)}
{/* Error Display */}
{error && (
<p className="text-sm text-red-500 bg-red-50 p-3 rounded-xl">
{error}
</p>
)}
{/* Success Display */}
{txHash && (
<div className="bg-green-50 p-3 rounded-xl">
<p className="text-sm text-green-800">
✅ Success!{" "}
<a
href={`https://explorer.solana.com/tx/${txHash}?cluster=devnet`}
target="_blank"
className="underline"
>
View transaction
</a>
</p>
</div>
)}
</div>
);
}
Key Steps Explained
1. Encrypt the Value
import { encryptValue } from "@inco/solana-sdk/encryption";
const amount = BigInt(Math.floor(parseFloat(value) * 1e6));
const encryptedHex = await encryptValue(amount);
2. Create Accounts if Needed
First-time users need a mint and token account:// Create mint
const mintKp = Keypair.generate();
tx.add(
await program.methods
.initializeMint(6, publicKey, publicKey)
.accounts({
mint: mintKp.publicKey,
payer: publicKey,
systemProgram: SystemProgram.programId,
})
.instruction()
);
// Create token account
const accountKp = Keypair.generate();
tx.add(
await program.methods
.initializeAccount()
.accounts({
account: accountKp.publicKey,
mint: mintKp.publicKey,
owner: publicKey,
payer: publicKey,
systemProgram: SystemProgram.programId,
})
.instruction()
);
3. Simulate to Get Handle
Before minting, simulate to extract the handle:const sim = await connection.simulateTransaction(simTx, undefined, [accPk]);
const data = sim.value.accounts?.[0]?.data;
const handle = extractHandle(Buffer.from(data[0], "base64"));
4. Derive Allowance PDA and Mint
const [allowancePda] = getAllowancePda(handle, publicKey);
const sig = await program.methods
.mintTo(ciphertext, 0)
.accounts(accounts)
.remainingAccounts([
{ pubkey: allowancePda, isSigner: false, isWritable: true },
{ pubkey: publicKey, isSigner: false, isWritable: false },
])
.rpc();
Balance Component
This component displays and decrypts the user’s confidential token balance.Full Implementation
"use client";
import { useState, useEffect } from "react";
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
import { decrypt } from "@inco/solana-sdk/attested-decrypt";
import {
fetchUserMint,
fetchUserTokenAccount,
extractHandle,
} from "@/utils/constants";
export default function Balance() {
const { publicKey, connected, signMessage } = useWallet();
const { connection } = useConnection();
const [balance, setBalance] = useState<string>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Read and decrypt balance
const handleReadBalance = async () => {
if (!connected || !publicKey || !signMessage) return;
setLoading(true);
setError(null);
try {
// Find user's mint
const mint = await fetchUserMint(connection, publicKey);
if (!mint) {
setBalance("No mint");
return;
}
// Find user's token account
const acc = await fetchUserTokenAccount(
connection,
publicKey,
mint.pubkey
);
if (!acc) {
setBalance("No account");
return;
}
// Extract the encrypted handle
const handle = extractHandle(acc.data);
// Handle zero balance
if (handle === BigInt(0)) {
setBalance("0");
return;
}
// Decrypt the balance
const result = await decrypt([handle.toString()], {
address: publicKey,
signMessage,
});
// Convert from smallest unit (6 decimals)
const rawBalance = BigInt(result.plaintexts?.[0] ?? "0");
setBalance((Number(rawBalance) / 1e6).toString());
} catch (e) {
console.error(e);
setError(e instanceof Error ? e.message : "Failed to read balance");
} finally {
setLoading(false);
}
};
// Reset on wallet change
useEffect(() => {
setBalance(undefined);
setError(null);
}, [publicKey]);
// Listen for mint events
useEffect(() => {
const onMint = () => setBalance(undefined);
window.addEventListener("token-minted", onMint);
return () => window.removeEventListener("token-minted", onMint);
}, []);
return (
<div className="mt-8 space-y-4">
<div className="flex items-center justify-between">
<div>
<span className="text-sm font-medium">Balance:</span>
<span className="ml-2 font-mono">
{balance ?? "****"}
</span>
</div>
<button
onClick={handleReadBalance}
disabled={loading || !connected}
className="bg-gray-600 text-white py-2 px-4 rounded-full
hover:bg-gray-700 disabled:bg-gray-300"
>
{loading ? "Loading..." : "Reveal"}
</button>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
</div>
);
}
Key Steps Explained
1. Fetch Account Data
const mint = await fetchUserMint(connection, publicKey);
const acc = await fetchUserTokenAccount(connection, publicKey, mint.pubkey);
2. Extract Handle
const handle = extractHandle(acc.data);
The offset in
extractHandle is specific to the Inco Token program. Adjust based on your program’s account layout.3. Decrypt with Wallet Signature
import { decrypt } from "@inco/solana-sdk/attested-decrypt";
const result = await decrypt([handle.toString()], {
address: publicKey,
signMessage, // From useWallet hook
});
const balance = result.plaintexts[0];
The
signMessage function is required for attested reveal. The wallet will prompt the user to sign a message that proves ownership of the address.Component Communication
Components communicate via custom events:// In EncryptedInput - emit after minting
window.dispatchEvent(new CustomEvent("token-minted"));
// In Balance - listen for mint events
useEffect(() => {
const onMint = () => setBalance(undefined);
window.addEventListener("token-minted", onMint);
return () => window.removeEventListener("token-minted", onMint);
}, []);
Summary
| Component | Purpose | SDK Functions Used |
|---|---|---|
EncryptedInput | Encrypt values and mint tokens | encryptValue, hexToBuffer |
Balance | Display and decrypt balance | decrypt |
Header | Wallet connection UI | Wallet adapter hooks |