IPrivacy.batchTransferWithAuthorization
batchTransferWithAuthorization(
bytes32 batchId,
AuthorizedTransferItem[] items
) external returns (bool success) Submits a multi-proof batch on behalf of one or more effective senders — each items[i] carries a PrivacyTransferRequest and a PrivacyActionAuthorization whose signature covers that specific item's request under the shared batchId and per-item batchItemIndex. The caller (msg.sender) is recorded as the executor on every authorization; a mismatch between the recovered signer and authorization.effectiveSender, an expired deadline, or a reused nonce reverts the entire batch. The 20-item protocol limit (MaxPrivacyMultiProofBatchItems) is enforced identically to the direct batchTransfer path.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
batchId | bytes32 | ✓ | Shared batch identifier that every effective sender must carry in the EIP-712 message they sign (it is a field of that message, not of the PrivacyActionAuthorization ABI tuple). Should be unique per submission; typically random 32 bytes generated by the executor. |
items | AuthorizedTransferItem[] | ✓ | Between 1 and 20 items, each pairing a PrivacyTransferRequest with its signed PrivacyActionAuthorization. Every item's signed EIP-712 message must carry a batchItemIndex that MUST equal its 0-based position in the array — otherwise digest recomputation fails and the batch reverts. |
Returns
bool Returns true when every authorization verifies and every proof applies.
Examples
Submit an authorized batch as an executor
The executor generates batchId first, then hands it to every effective sender so their EIP-712 signatures are bound to the same batch. Client-side index checks catch mislabeled positions before the transaction is sent.
import { createWalletClient, http, toHex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { iPrivacyAbi } from "@maroo-chain/contracts/abi/precompiles/privacy/IPrivacy";
const PRIVACY = "0x100000000000000000000000000000000000000b" as const;
const MAX_BATCH_ITEMS = 20;
// NOT the Solidity `AuthorizedTransferItem` (2 members). This is the client-side envelope
// that also keeps the signed message, which the ABI tuple cannot carry.
type ItemWithSignedMessage = {
request: any; // PrivacyTransferRequest tuple
authorization: any; // PrivacyActionAuthorization ABI tuple: effectiveSender,
// executor, nonce, deadline, authorizationKind, signature
signedMessage: { // the EIP-712 message that produced `authorization.signature`
batchId: `0x${string}`;
batchItemIndex: bigint;
};
};
// `batchId` is agreed BEFORE anyone signs: the executor mints it, hands it to every
// effective sender, and each signature is bound to that value. Minting it here — after
// the items arrive already signed — could never match, so it is a parameter.
async function submitAuthorizedBatch(batchId: `0x${string}`, items: ItemWithSignedMessage[]) {
if (items.length === 0 || items.length > MAX_BATCH_ITEMS) {
throw new Error(`expected 1..${MAX_BATCH_ITEMS} items, got ${items.length}`);
}
// `batchId` and `batchItemIndex` live in the EIP-712 MESSAGE each effective sender
// signed — not in the PrivacyActionAuthorization ABI tuple, which carries only
// effectiveSender, executor, nonce, deadline, authorizationKind and signature.
// The chain re-derives the digest and rejects a mismatch; a client can only check
// the message it holds.
items.forEach((item, i) => {
if (item.signedMessage.batchId !== batchId) throw new Error(`item ${i} batchId mismatch`);
if (item.signedMessage.batchItemIndex !== BigInt(i)) throw new Error(`item ${i} index mismatch`);
});
const executor = createWalletClient({
account: privateKeyToAccount(process.env.EXECUTOR_KEY as `0x${string}`),
transport: http("https://rpc-testnet.maroo.io"),
});
return executor.writeContract({
address: PRIVACY,
abi: iPrivacyAbi,
functionName: "batchTransferWithAuthorization",
// only the two ABI members go on the wire; `signedMessage` is client-side bookkeeping
args: [batchId, items.map(({ request, authorization }) => ({ request, authorization }))],
});
}
// Step 1 — the executor mints the batch id and distributes it to the effective senders,
// who each sign an EIP-712 message carrying it, then return { signature, message }.
const batchId = toHex(crypto.getRandomValues(new Uint8Array(32)));
// Step 2 — collect each sender's { signature, message } and pair it with its request.
declare const items: ItemWithSignedMessage[];
// Step 3 — once every signature is back, submit.
await submitAuthorizedBatch(batchId, items);