IPrivacy.withdraw
withdraw(PrivacyWithdrawRequest request) external returns (bool success) Spends a shielded note and pays the underlying transparent OKRW to request.recipient. msg.sender is the effective sender for PCL policy purposes. Failures revert with typed custom errors declared on IPrivacy (request-expiry, native-denom mismatch, nullifier collision) or inherited from IPrecompile (InvalidAmount, InvalidAddress, etc.).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
request | PrivacyWithdrawRequest | ✓ | The withdraw payload — proof, Merkle root, nullifier, amount (SDK coin string, e.g. "15000000000000000000aokrw" for 15 OKRW-worth of aokrw — must use the native denom or revert with PrivacyNativeDenomMismatch), recipient, chain id, and expiresAtUnix. |
Returns
Type:
bool Returns true on success. Failures revert with a typed custom error rather than returning false.
Errors
| Code | Name | Description |
|---|---|---|
PrivacyNonPayable | PrivacyNonPayable | Reverts when msg.value is non-zero. withdraw is not payable. |
PrivacyRequestExpired | PrivacyRequestExpired | Reverts when request.expiresAtUnix is zero or has already passed. |
PrivacyNativeDenomMismatch | PrivacyNativeDenomMismatch | Reverts when request.amount uses a denom other than the network's native denom (aokrw). Payload is (got, expected). |
PrivacyNullifierAlreadySpent | PrivacyNullifierAlreadySpent | Reverts when the nullifier is already recorded on-chain. |
InvalidAmount | InvalidAmount | Inherited from IPrecompile. Reverts when request.amount fails to parse as a coin string. Encoded as InvalidAmount(string amount) where the payload is the raw amount string. |
InvalidAddress | InvalidAddress | Inherited from IPrecompile. Reverts when the caller cannot be converted to a valid account address. Encoded as InvalidAddress(string bad). |
PrivacyTxLimitExceeded | PrivacyTxLimitExceeded | Reverts when the transaction has already exceeded the global stateful-privacy call limit. |
PrivacySenderTxLimitExceeded | PrivacySenderTxLimitExceeded | Reverts when the effective sender has already exceeded the per-sender stateful-privacy call limit. |
InvalidNumberOfArgs | InvalidNumberOfArgs | Inherited from IPrecompile. Reverts when the call does not carry exactly one argument. |
Examples
Basic withdraw
The amount string must use aokrw — the network's native denom. Passing another denom reverts with PrivacyNativeDenomMismatch.
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { privacyAbi } from "@maroo-chain/contracts/abi/IPrivacy";
const PRIVACY = "0x100000000000000000000000000000000000000b" as const;
const wallet = createWalletClient({
account: privateKeyToAccount(process.env.USER_KEY as `0x${string}`),
transport: http("https://rpc-testnet.maroo.io"),
});
// The single withdraw amount fits inside the 64-bit shielded cap.
// TODO: replace with the real payout recipient before production.
const request = {
proof: proofBytes,
root: merkleRoot,
nullifier: noteNullifier,
amount: "15000000000000000000aokrw", // 15 OKRW
recipient: "0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62",
chainId: "maroo-testnet",
expiresAtUnix: BigInt(Math.floor(Date.now() / 1000) + 300),
};
await wallet.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "withdraw",
args: [request],
}); Handling denom / expiry rejections
Rebuild the request with the correct aokrw denom string or a fresh deadline; the proof itself remains valid.
import { decodeErrorResult } from "viem";
import { privacyAbi } from "@maroo-chain/contracts/abi/IPrivacy";
try {
await wallet.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "withdraw",
args: [request],
});
} catch (err: any) {
if (!err?.data) throw err;
const decoded = decodeErrorResult({ abi: privacyAbi, data: err.data });
if (decoded.errorName === "PrivacyNativeDenomMismatch") {
const [got, expected] = decoded.args as [string, string];
console.warn(`use denom ${expected}; got ${got}`);
} else if (decoded.errorName === "PrivacyRequestExpired") {
const [expiresAtUnix] = decoded.args as [bigint];
console.warn(`withdraw request expired at ${expiresAtUnix} — rebuild with a fresh deadline`);
} else {
throw err;
}
}