IPrivacy.transferWithAuthorization
transferWithAuthorization(
PrivacyTransferRequest request,
PrivacyActionAuthorization authorization
) external returns (bool success) Executes a shielded transfer on behalf of an effectiveSender who signed an EIP-712 PrivacyActionAuthorization. The relayer is msg.sender; the authorization designates the sender whose nonce is spent and whose PCL rules apply. Failures now revert with typed custom errors on IPrivacy (authorization checks, request-expiry, nullifier/commitment collisions, batch limits) or inherited errors on IPrecompile (InvalidNumberOfArgs, RequesterIsNotMsgSender, etc.). PCL policy failures still surface as PCL ReasonCodes from IPcl.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
request | PrivacyTransferRequest | ✓ | The shielded transfer payload. Its expiresAtUnix must be strictly greater than the current block time — a stale or zero deadline reverts with PrivacyRequestExpired(expiresAtUnix). |
authorization | PrivacyActionAuthorization | ✓ | The EIP-712-signed permission from the effective sender. effectiveSender, executor, nonce, and deadline must all be set (missing fields revert with PrivacyInvalidAuthorizationField(field)); executor must equal msg.sender (mismatch reverts with RequesterIsNotMsgSender); the signature must recover to effectiveSender (mismatch reverts with PrivacyAuthorizationSignerMismatch(expected, got)). See privacy-authorization-eip712-domain for the exact typeHash preimage. |
Returns
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. Only deposit is payable. Encoded as PrivacyNonPayable(string methodName). |
PrivacyRequestExpired | PrivacyRequestExpired | Reverts when request.expiresAtUnix is zero or has already passed. Payload carries the offending unix timestamp. |
PrivacyInvalidAuthorizationField | PrivacyInvalidAuthorizationField | Reverts when a required authorization field is missing or invalid — the payload is the field name ("effectiveSender", "executor", "nonce", or "deadline"). |
PrivacyAuthorizationExpired | PrivacyAuthorizationExpired | Reverts when the authorization's deadline has passed relative to the current block time. |
PrivacyAuthorizationNonceUsed | PrivacyAuthorizationNonceUsed | Reverts when the nonce has already been consumed for that effective sender. Payload is (sender, nonce). |
PrivacyAuthorizationSignerMismatch | PrivacyAuthorizationSignerMismatch | Reverts when the recovered signer does not match effectiveSender. Payload is (expected, got). |
PrivacyAuthorizationRejected | PrivacyAuthorizationRejected | Reverts when an ERC-1271 or EIP-7702 authorization is rejected by its verifier (bad code shape, verifier revert, or unsupported authorization kind). Payload is the authorization kind. |
PrivacyInvalidAuthorizationMagic | PrivacyInvalidAuthorizationMagic | Reverts when an ERC-1271 smart account returned bytes that are not the 0x1626ba7e magic. Payload is the returned magic bytes. |
PrivacyNullifierAlreadySpent | PrivacyNullifierAlreadySpent | Reverts when a nullifier in the request has already been recorded on-chain. |
PrivacyCommitmentAlreadyExists | PrivacyCommitmentAlreadyExists | Reverts when a new commitment in the request already exists in the Merkle tree. |
PrivacyMerkleCapacityExceeded | PrivacyMerkleCapacityExceeded | Reverts when appending the request's new commitments would exceed the Merkle tree capacity. |
PrivacyTxLimitExceeded | PrivacyTxLimitExceeded | Reverts when the transaction has already exceeded the global stateful-privacy call limit within a single EVM execution. |
PrivacySenderTxLimitExceeded | PrivacySenderTxLimitExceeded | Reverts when the transaction has already exceeded the per-effective-sender stateful-privacy call limit. |
RequesterIsNotMsgSender | RequesterIsNotMsgSender | Inherited from IPrecompile. Reverts when authorization.executor is not the same as msg.sender. Payload is (msgSender, requester). |
InvalidNumberOfArgs | InvalidNumberOfArgs | Inherited from IPrecompile. Reverts when the ABI-encoded call does not carry exactly two arguments. |
UnknownMethod | UnknownMethod | Inherited from IPrecompile. Reverts when the ABI selector does not match any function on the precompile — usually indicates a stale ABI. |
Examples
Relayer submits an authorized transfer
The relayer becomes msg.sender; PCL evaluates rules against authorization.effectiveSender. If any check fails the whole call reverts with the corresponding typed error.
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { privacyAbi } from "@maroo-chain/contracts/abi/IPrivacy";
const PRIVACY = "0x100000000000000000000000000000000000000b" as const;
const relayer = createWalletClient({
account: privateKeyToAccount(process.env.RELAYER_KEY as `0x${string}`),
transport: http("https://rpc-testnet.maroo.io"),
});
// `request` and `authorization` are built off-chain; the effective sender
// signs the EIP-712 typeHash preimage (see privacy-authorization-eip712-domain).
// authorization.executor MUST equal the relayer address.
await relayer.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "transferWithAuthorization",
args: [request, authorization],
}); Decoding authorization / expiry errors
Match on decoded.errorName — each typed error's arg tuple is stable and decodes into typed values (address, bigint) usable directly in your UX.
import { decodeErrorResult } from "viem";
import { privacyAbi } from "@maroo-chain/contracts/abi/IPrivacy";
try {
await relayer.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "transferWithAuthorization",
args: [request, authorization],
});
} catch (err: any) {
if (!err?.data) throw err;
const decoded = decodeErrorResult({ abi: privacyAbi, data: err.data });
switch (decoded.errorName) {
case "PrivacyAuthorizationNonceUsed": {
const [sender, nonce] = decoded.args as [`0x${string}`, bigint];
console.warn(`nonce ${nonce} already consumed for ${sender}`);
break;
}
case "PrivacyRequestExpired": {
const [expiresAtUnix] = decoded.args as [bigint];
console.warn(`request expired at ${expiresAtUnix}`);
break;
}
case "PrivacyAuthorizationSignerMismatch": {
const [expected, got] = decoded.args as [`0x${string}`, `0x${string}`];
console.warn(`bad signature: expected ${expected}, got ${got}`);
break;
}
default:
throw err;
}
}