Privacy-preserving transfers on Solana mainnet — commit-reveal scheme, zero custodians.
Program ID: 2akvmbaGhGeAWVCQrpHXvJTZE9EksMk5rpHM4NcMmfKG
D3FAULT is a non-custodial privacy layer for Solana. Funds are locked behind a SHA-256 commitment and released only when the matching secret is presented on-chain — no relayer ever holds your keys.
- SOL and SPL token support (USDC, USDT, etc.)
- Expiry + reclaim — senders can reclaim after a deadline
- Relayer-assisted withdrawal so recipients pay zero gas
- Open-source program — full source in
programs/private_transfer/
Go to https://d3fault.sh and sign in. D3FAULT uses Privy for authentication — you can log in with:
- Email (magic link)
- Google / Apple
- Solana wallet (Phantom, Backpack, etc.)
- Open the Developers page from the top navigation.
- Click New API Key and enter a name for it.
- Copy the key immediately — it is shown once. D3FAULT only stores a SHA-256 hash.
Keys look like:
dfk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Store the key in your secret manager (AWS Secrets Manager, Doppler, HashiCorp Vault, etc.) or as an environment variable. Never commit it to source control.
Each key has a per-minute budget. The default is 60 requests / minute.
Every successful response includes rate limit headers:
RateLimit-Limit: 60
RateLimit-Remaining: 42
RateLimit-Reset: 18RateLimit-Reset is the number of seconds until the budget resets. When you hit a 429, back off using this value instead of a fixed sleep interval.
const BASE = "https://api.d3fault.sh";
const KEY = process.env.DFK_KEY!;
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${KEY}`,
};
// Check server health (no auth required)
const health = await fetch(`${BASE}/api/healthz`);
console.log(await health.json()); // { status: "ok" }
// Get program info
const programRes = await fetch(`${BASE}/api/v1/program`, { headers });
const program = await programRes.json();
console.log(program.programId, program.network, program.storeState);import { randomBytes, createHash } from "crypto";
import { VersionedTransaction, Connection } from "@solana/web3.js";
const BASE = "https://api.d3fault.sh";
const KEY = process.env.DFK_KEY!;
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${KEY}`,
};
// Step 1: Generate a secret client-side
// NEVER send the secret to a server before deposit — only send the commitment
const secretBytes = randomBytes(32);
const secretHex = secretBytes.toString("hex");
const commitment = createHash("sha256").update(secretBytes).digest("hex");
// Step 2: Build the unsigned deposit transaction
const buildRes = await fetch(`${BASE}/api/v1/tx/deposit-sol/build`, {
method: "POST",
headers,
body: JSON.stringify({
depositor: wallet.publicKey.toBase58(),
amount: "1000000", // 0.001 SOL in lamports
expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
commitment,
}),
});
if (!buildRes.ok) {
const err = await buildRes.json();
throw new Error(`Build failed [${err.code}]: ${err.error}`);
}
const { tx } = await buildRes.json();
// Step 3: Sign and broadcast
const connection = new Connection("https://api.mainnet-beta.solana.com");
const vTx = VersionedTransaction.deserialize(Buffer.from(tx, "base64"));
vTx.sign([yourKeypair]);
const sig = await connection.sendTransaction(vTx);
console.log("Deposit signature:", sig);
console.log("Share this secret privately:", secretHex);const withdrawRes = await fetch(`${BASE}/api/v1/tx/withdraw`, {
method: "POST",
headers,
body: JSON.stringify({
secret: secretHex, // 64-char hex
recipient: myWalletPublicKey, // base58 public key
}),
});
if (!withdrawRes.ok) {
const err = await withdrawRes.json();
switch (err.code) {
case "EXPIRED":
console.error("This link has expired. Ask the depositor to reclaim and resend.");
break;
case "COMMITMENT_NOT_FOUND":
console.error("Secret not recognized — it may already have been claimed.");
break;
default:
throw new Error(`Withdraw failed [${err.code}]: ${err.error}`);
}
return;
}
const { signature, explorerUrl } = await withdrawRes.json();
console.log("Funds received:", explorerUrl);// USDC has 6 decimals. To deposit 1.5 USDC:
const amount = String(1.5 * 10 ** 6); // "1500000"
const buildRes = await fetch(`${BASE}/api/v1/tx/deposit-spl/build`, {
method: "POST",
headers,
body: JSON.stringify({
depositor: wallet.publicKey.toBase58(),
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
amount,
decimals: 6,
expiry: Math.floor(Date.now() / 1000) + 86400,
commitment,
}),
});interface D3FAULTError {
error: string;
code: string;
}
async function d3faultFetch(url: string, options: RequestInit): Promise<unknown> {
const res = await fetch(url, options);
if (res.status === 429) {
const resetSeconds = parseInt(res.headers.get("RateLimit-Reset") ?? "60", 10);
await new Promise(r => setTimeout(r, resetSeconds * 1000));
return d3faultFetch(url, options);
}
if (!res.ok) {
const body: D3FAULTError = await res.json().catch(() => ({ error: "Unknown error", code: "UNKNOWN" }));
const err = new Error(`[${body.code}] ${body.error}`);
(err as any).code = body.code;
(err as any).status = res.status;
throw err;
}
return res.json();
}Error code reference:
| Code | Action |
|---|---|
RATE_LIMITED |
Wait RateLimit-Reset seconds, then retry |
EXPIRED |
Inform the user the link has expired |
COMMITMENT_NOT_FOUND |
Secret is wrong or already claimed |
PROGRAM_NOT_DEPLOYED |
Contact D3FAULT support |
RELAYER_NOT_CONFIGURED |
Contact D3FAULT support |
TX_FAILED |
Retry once; if it persists, contact support |
INVALID_KEY |
Check key format; generate a new one if needed |
KEY_REVOKED |
Generate a new key from the dashboard |
const lookupRes = await fetch(
`${BASE}/api/v1/store/lookup/${commitment}`,
{ headers }
);
if (lookupRes.status === 404) {
console.log("Not found — deposit may not have confirmed yet.");
} else if (lookupRes.ok) {
const entry = await lookupRes.json();
const nowSec = Math.floor(Date.now() / 1000);
if (entry.claimed) {
console.log("Already claimed.");
} else if (entry.expiry < nowSec) {
console.log("Expired — depositor can reclaim.");
} else {
console.log("Active — safe to withdraw.");
}
}- Generate secrets client-side. Never transmit the secret to a server before deposit.
- Store API keys in a secret manager, not in
.envfiles committed to git. - Rotate keys regularly. Mint a new key, deploy it, then revoke the old one.
- Set conservative expiries. A few hours gives recipients time to claim.
- Do not log secrets. Ensure request body logging redacts the
secretfield. - Pace your polling. Respect
RateLimit-Remaining— do not exceed one request per second.
The D3FAULT program is deployed on Solana mainnet. The executable hash of the local build has been verified to match the on-chain binary.
| Field | Value |
|---|---|
| Program ID | 2akvmbaGhGeAWVCQrpHXvJTZE9EksMk5rpHM4NcMmfKG |
| Executable Hash | 72a96a60e7aa476a84b9a1e29f274e93f4a32563c9e29a8df2aa5a03052180c1 |
| Network | Solana Mainnet-Beta |
| Verification Record | VERIFICATION.md |
Note on formal badge: The solana-verify tooling uses a Docker image with Rust 1.75. Parts of the dependency tree (via solana-program 1.18.26) require Rust 1.76+, making automated Docker verification currently unavailable for this toolchain. This is a known ecosystem-wide limitation for programs built with the Agave toolchain — the issue is in the verifier infrastructure, not the program code. Formal verification will be submitted once the upstream Docker image is updated.