IPrivacy.singleProofBatchTransferWithAuthorization
singleProofBatchTransferWithAuthorization(
PrivacySingleProofBatchTransferRequest request,
PrivacyActionAuthorization authorization
) external returns (bool success) Meta-transaction variant of singleProofBatchTransfer: a relayer submits the batch on behalf of authorization.effectiveSender, who signed an off-chain authorization binding the request hash to a specific executor, nonce, and deadline. Supports EOA signatures, ERC-1271 smart-account signatures, and EIP-7702 delegated-EOA signatures, chosen via authorization.authorizationKind. The request-hash domain is dedicated to the single-proof batch method — it will not collide with authorizations for other privacy calls. All failure modes surface as plain string reverts; no typed custom errors are declared on this precompile.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
request | PrivacySingleProofBatchTransferRequest | ✓ | Same batch payload as singleProofBatchTransfer. Its hash — computed over the method ID and the ABI-encoded request tuple — is bound into the authorization signature. |
authorization | PrivacyActionAuthorization | ✓ | Off-chain authorization. Fields: effectiveSender (the account the policy engine treats as the actual sender), executor (must equal msg.sender — the relayer address), nonce (unique per effectiveSender; replay-protected), deadline (unix seconds; must be in the future), authorizationKind (0=EOA, 1=ERC-1271, 2=EIP-7702; other values revert), and signature (bytes; for EOA a 65-byte (r, s, v) blob). |
Returns
bool Returns true on success. Failures revert with a plain string reason.
Errors
| Code | Name | Description |
|---|---|---|
privacy authorization effectiveSender is required | privacy authorization effectiveSender is required | String revert when authorization.effectiveSender is the zero address. |
privacy authorization executor is required | privacy authorization executor is required | String revert when authorization.executor is the zero address. |
privacy authorization executor mismatch | privacy authorization executor mismatch | String revert when msg.sender does not equal authorization.executor. |
privacy authorization deadline is required | privacy authorization deadline is required | String revert when authorization.deadline is zero. |
privacy authorization expired | privacy authorization expired | String revert when the block time has passed authorization.deadline. |
privacy authorization nonce already used | privacy authorization nonce already used | String revert when authorization.nonce has already been consumed by a prior privacy call for the same effectiveSender. |
unsupported privacy authorization kind %d | unsupported privacy authorization kind %d | String revert when authorizationKind is not one of the supported values (0 EOA, 1 ERC-1271, 2 EIP-7702). |
privacy EOA authorization signer mismatch | privacy EOA authorization signer mismatch | String revert when the recovered EOA signer does not match authorization.effectiveSender. |
privacy EOA authorization signature must be 65 bytes | privacy EOA authorization signature must be 65 bytes | String revert when the EOA signature blob length is not 65 bytes. |
privacy ERC-1271 authorization rejected: %w | privacy ERC-1271 authorization rejected | String revert when an ERC-1271 smart-account signature check fails on effectiveSender. |
single-proof batch transfer payload has expired | single-proof batch transfer payload has expired | String revert when the block time has already reached request.expiresAtUnix. |
privacy single-proof batch nullifier already spent | privacy single-proof batch nullifier already spent | String revert when one of the supplied nullifiers has already been consumed by a prior privacy call. |
privacy single-proof batch output commitment already exists | privacy single-proof batch output commitment already exists | String revert when an output's commitment is already present in the on-chain commitment index. |
Examples
Relayer submits an authorized single-proof batch
The relayer's own address is checked against authorization.executor; any mismatch reverts before the proof is evaluated. Pin the nonce off-chain (per-effectiveSender monotonic counter is fine).
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const PRIVACY = "0x100000000000000000000000000000000000000b" as const;
// ABI snippet — reuse the `PrivacySingleProofBatchTransferRequest` shape
// documented on `contract-privacy-single-proof-batch-transfer`.
const privacyAbi = [{
type: "function",
name: "singleProofBatchTransferWithAuthorization",
stateMutability: "nonpayable",
inputs: [
{ name: "request", type: "tuple", components: [ /* … */ ] },
{ name: "authorization", type: "tuple", components: [
{ name: "effectiveSender", type: "address" },
{ name: "executor", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint64" },
{ name: "authorizationKind", type: "uint8" },
{ name: "signature", type: "bytes" },
]},
],
outputs: [{ type: "bool" }],
}] as const;
// The relayer's key. Must equal `authorization.executor`.
// TODO: replace with the real relayer address before production.
const relayer = createWalletClient({
account: privateKeyToAccount(process.env.RELAYER_KEY as `0x${string}`),
transport: http("https://rpc-testnet.maroo.io"),
});
// `request` and `authorization` come from the effective sender's wallet.
// `authorization.signature` is over the EIP-712 domain documented on
// `privacy-authorization-eip712-domain`, using the single-proof batch
// request-hash.
await relayer.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "singleProofBatchTransferWithAuthorization",
args: [request, authorization],
}); Handling authorization rejections
The authorization surface produces string reverts only. Branch on revert.reason substrings for UX; never call decodeErrorResult here.
import { BaseError, ContractFunctionRevertedError } from "viem";
try {
await relayer.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "singleProofBatchTransferWithAuthorization",
args: [request, authorization],
});
} catch (err) {
if (err instanceof BaseError) {
const revert = err.walk(e => e instanceof ContractFunctionRevertedError);
if (revert instanceof ContractFunctionRevertedError) {
// Match on the verbatim reason substring — the privacy precompile
// does not use typed custom errors.
const r = revert.reason ?? "";
if (r.includes("privacy authorization expired")) {
// ask the effective sender to re-sign with a later deadline
} else if (r.includes("privacy authorization nonce already used")) {
// bump the nonce and re-sign
} else if (r.includes("privacy authorization executor mismatch")) {
// the signature was bound to a different relayer
}
}
}
throw err;
}