IPrivacy.singleProofBatchTransfer

singleProofBatchTransfer(
  PrivacySingleProofBatchTransferRequest request
) external returns (bool success)

Applies a batch join-split (multiple inputs, multiple outputs) validated by a single Groth16 proof. Input count must be in 1..BatchJoinSplitV1MaxInputs, output count must be in 1..BatchJoinSplitV1MaxOutputs; out-of-range counts revert with PrivacyBatchSizeOutOfRange(count, max). Failures surface as typed custom errors on IPrivacy or inherited on IPrecompile.

Parameters

Name Type Required Description
request PrivacySingleProofBatchTransferRequest ✓ The single-proof batch payload — one proof, one root, an array of input nullifiers, an array of output entries (each with commitment + ciphertext + disclosure fields), audit-key metadata, audit disclosure target pubkey, and expiresAtUnix.

Returns

Type: bool

Returns true on success. Failures revert with a typed custom error rather than returning false.

Errors

Code Name Description
PrivacyBatchSizeOutOfRange PrivacyBatchSizeOutOfRange Reverts when input count is outside 1..BatchJoinSplitV1MaxInputs or output count is outside 1..BatchJoinSplitV1MaxOutputs. Payload is (count, max).
PrivacyRequestExpired PrivacyRequestExpired Reverts when request.expiresAtUnix is zero or has already passed.
PrivacyDuplicateNullifier PrivacyDuplicateNullifier Reverts when the same nullifier appears more than once in request.nullifiers.
PrivacyNullifierAlreadySpent PrivacyNullifierAlreadySpent Reverts when any nullifier is already recorded on-chain.
PrivacyDuplicateCommitment PrivacyDuplicateCommitment Reverts when the same output commitment appears more than once in request.outputs.
PrivacyCommitmentAlreadyExists PrivacyCommitmentAlreadyExists Reverts when any output commitment already exists in the Merkle tree.
PrivacyMerkleCapacityExceeded PrivacyMerkleCapacityExceeded Reverts when appending the outputs would exceed the shielded-pool Merkle tree capacity.
PrivacyTxLimitExceeded PrivacyTxLimitExceeded Reverts when the transaction has already exceeded the global stateful-privacy call limit.
InvalidNumberOfArgs InvalidNumberOfArgs Declared in the shared IPrecompile error set as InvalidNumberOfArgs(uint256 expected, uint256 got), but this function does not revert with it for a wrong argument count: calldata too short for the request reverts with ABISetupFailed, and bytes after the request are ignored.
ABISetupFailed ABISetupFailed Inherited from IPrecompile as ABISetupFailed(string reason). Reverts when the calldata cannot be decoded against the precompile's ABI: an unknown 4-byte selector gives the reason no method with id: 0x… (usually a stale ABI), and calldata too short for the arguments gives the decoder's message, for example abi: cannot marshal in to go type: length insufficient 32 require 64. An unknown selector does not revert with UnknownMethod, although the precompile declares it.

Examples

Merge two notes into one

A single proof authorises the whole join-split — cheaper than a multi-proof batchTransfer when all inputs belong to the same sender.

import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { iPrivacyAbi } from "@maroo-chain/contracts/abi/precompiles/privacy/IPrivacy";

const PRIVACY = "0x100000000000000000000000000000000000000b" as const;

const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.SENDER_KEY as `0x${string}`),
  transport: http("https://rpc-testnet.maroo.io"),
});

// request.nullifiers.length in 1..BatchJoinSplitV1MaxInputs;
// request.outputs.length   in 1..BatchJoinSplitV1MaxOutputs.
await wallet.writeContract({
  address: PRIVACY,
  abi: iPrivacyAbi,
  functionName: "singleProofBatchTransfer",
  args: [request],
});

Reacting to a size violation

PrivacyBatchSizeOutOfRange fires for both inputs and outputs; log the offending value against the interface's BatchJoinSplitV1MaxInputs / BatchJoinSplitV1MaxOutputs limits.

import { BaseError, ContractFunctionRevertedError, decodeErrorResult } from "viem";
import { iPrivacyAbi } from "@maroo-chain/contracts/abi/precompiles/privacy/IPrivacy";

try {
  await wallet.writeContract({
    address: PRIVACY,
    abi: iPrivacyAbi,
    functionName: "singleProofBatchTransfer",
    args: [request],
  });
} catch (err) {
  // viem keeps the revert data on the ContractFunctionRevertedError in the cause chain, not on err.
  const revert = err instanceof BaseError ? err.walk((e) => e instanceof ContractFunctionRevertedError) : null;
  if (!(revert instanceof ContractFunctionRevertedError) || !revert.raw) throw err;
  const decoded = decodeErrorResult({ abi: iPrivacyAbi, data: revert.raw });
  if (decoded.errorName === "PrivacyBatchSizeOutOfRange") {
    const [count, max] = decoded.args as [bigint, bigint];
    console.warn(`batch input or output count ${count} exceeds max ${max}`);
  } else {
    throw err;
  }
}
ESC
Type to search