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. Failures revert with a typed custom error: IPrivacy declares its own, and inherits the shared protocol errors from IPrecompile, so a client decodes the 4-byte selector rather than matching reason text. One case is still a plain string — an EOA signature that is not exactly 65 bytes is rejected before any typed error is built.

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

Type: bool

Returns true on success. Failures revert with a typed custom error — decode the 4-byte selector against the IPrivacy ABI, which includes the errors inherited from IPrecompile.

Errors

Code Name Description
PrivacyInvalidAuthorizationField PrivacyInvalidAuthorizationField(string field) Reverts when a required authorization field is missing or malformed. The payload names the field: effectiveSender, executor, nonce or deadline.
RequesterIsNotMsgSender RequesterIsNotMsgSender(address msgSender, address requester) Reverts when the caller is not the executor the signature was bound to. Inherited from IPrecompile, so the selector is shared with the other precompiles.
PrivacyAuthorizationExpired PrivacyAuthorizationExpired(uint64 deadline) Reverts when the block time has passed authorization.deadline. Payload is the deadline that was signed.
PrivacyAuthorizationNonceUsed PrivacyAuthorizationNonceUsed(address sender, uint256 nonce) Reverts when this (effectiveSender, nonce) pair has already been consumed — the replay guard.
PrivacyAuthorizationSignerMismatch PrivacyAuthorizationSignerMismatch(address expected, address got) Reverts when the address recovered from the signature is not effectiveSender. Applies to the EOA and EIP-7702 paths; the payload carries both addresses.
PrivacyAuthorizationRejected PrivacyAuthorizationRejected(uint8 authorizationKind) Reverts when the account shape does not match the declared authorizationKind (EOA with code, ERC-1271 without code, EIP-7702 without delegated code), when an ERC-1271 verifier rejects, or when the kind is unknown. Payload is the kind that was tried.
PrivacyInvalidAuthorizationMagic PrivacyInvalidAuthorizationMagic(bytes4 magic) Reverts when an ERC-1271 verifier returns a value other than the expected magic. Payload is what it returned.
PrivacyRequestExpired PrivacyRequestExpired(uint64 expiresAtUnix) Reverts when request.expiresAtUnix is zero or already passed. This is the batch payload's own expiry, separate from the authorization deadline above.
PrivacyDuplicateNullifier PrivacyDuplicateNullifier() Reverts when two items inside this batch spend the same nullifier.
PrivacyNullifierAlreadySpent PrivacyNullifierAlreadySpent() Reverts when a nullifier in the batch is already recorded on-chain (double-spend).
PrivacyDuplicateCommitment PrivacyDuplicateCommitment() Reverts when two items inside this batch produce the same output commitment.
PrivacyCommitmentAlreadyExists PrivacyCommitmentAlreadyExists() Reverts when an output commitment in the batch already exists in the Merkle tree.
PrivacyBatchSizeOutOfRange PrivacyBatchSizeOutOfRange(uint256 count, uint256 max) Reverts when the batch carries no items or more than the circuit supports. Payload carries the count and the maximum.
privacy EOA authorization signature must be 65 bytes privacy EOA authorization signature must be 65 bytes The one PLAIN-STRING revert on this method: an EOA signature of the wrong length is rejected before a typed error is constructed, so match this one on text.

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

Failures decode off the ABI. Keep the errors IPrivacy inherits from IPrecompile in the ABI you hand to viem, or RequesterIsNotMsgSender arrives undecoded. One path has no typed error and is matched on text — see the default branch.

import { BaseError, ContractFunctionRevertedError } from "viem";

try {
  await relayer.writeContract({
    address:      PRIVACY,
    abi:          privacyAbi, // must include IPrecompile's inherited errors
    functionName: "singleProofBatchTransferWithAuthorization",
    args:         [request, authorization],
  });
} catch (err) {
  if (err instanceof BaseError) {
    const revert = err.walk(e => e instanceof ContractFunctionRevertedError);
    if (revert instanceof ContractFunctionRevertedError) {
      switch (revert.data?.errorName) {
        case "PrivacyAuthorizationExpired": {
          const [deadline] = revert.data.args as [bigint];
          // ask the effective sender to re-sign past `deadline`
          break;
        }
        case "PrivacyAuthorizationNonceUsed": {
          const [sender, nonce] = revert.data.args as [`0x${string}`, bigint];
          // bump the nonce and re-sign
          break;
        }
        case "RequesterIsNotMsgSender": {
          // inherited from IPrecompile: the signature was bound to a different relayer
          break;
        }
        case "PrivacyInvalidAuthorizationField": {
          const [field] = revert.data.args as [string];
          // "effectiveSender" | "executor" | "nonce" | "deadline"
          break;
        }
        case "PrivacyAuthorizationSignerMismatch": {
          const [expected, got] = revert.data.args as [`0x${string}`, `0x${string}`];
          // the recovered signer is not the effective sender
          break;
        }
        default: {
          // The one plain-string path here: the length check runs before any typed
          // error is built, so match it on PREFIX.
          if ((revert.reason ?? "").startsWith("privacy EOA authorization signature must be 65 bytes")) {
            // re-sign — the signature is the wrong length
          }
        }
      }
    }
  }
  throw err;
}
ESC
Type to search