IPrivacy.singleProofBatchTransferWithAuthorization
singleProofBatchTransferWithAuthorization(
PrivacySingleProofBatchTransferRequest request,
PrivacyActionAuthorization authorization
) external returns (bool success) singleProofBatchTransfer의 메타 트랜잭션 변형입니다. 릴레이어가 authorization.effectiveSender를 대신하여 배치를 제출하며, 서명자는 요청 해시를 특정 executor·nonce·deadline에 묶어 오프체인에서 서명해 둡니다. authorization.authorizationKind에 따라 EOA 서명, ERC-1271 스마트 계정 서명, EIP-7702 위임 EOA 서명을 지원합니다. 요청 해시 도메인은 이 단일 증명 배치 메서드 전용이라 다른 프라이버시 호출용 인가와 충돌하지 않습니다. 실패는 타입 지정 커스텀 오류로 revert됩니다. IPrivacy가 자체 오류를 선언하고, 공유 프로토콜 오류는 IPrecompile에서 상속하므로, 클라이언트는 사유 문자열을 맞춰보는 대신 4바이트 selector를 디코딩합니다. 한 가지만 여전히 일반 문자열입니다. EOA 서명이 정확히 65바이트가 아니면 타입 오류를 만들기 전에 거부됩니다.
파라미터
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
request | PrivacySingleProofBatchTransferRequest | ✓ | singleProofBatchTransfer와 동일한 배치 페이로드입니다. 메서드 ID와 ABI 인코딩된 요청 튜플로 계산한 요청 해시가 인가 서명에 함께 묶입니다. |
authorization | PrivacyActionAuthorization | ✓ | 오프체인 인가입니다. 필드는 다음과 같습니다. effectiveSender(정책 엔진이 실제 발신자로 취급하는 계정), executor(msg.sender와 같아야 하며 릴레이어 주소입니다), nonce(effectiveSender별로 고유하며 리플레이 보호가 적용됩니다), deadline(unix 초 단위이며 미래여야 합니다), authorizationKind(0=EOA, 1=ERC-1271, 2=EIP-7702이며 그 외 값은 revert됩니다), signature(bytes이며 EOA는 65바이트 (r, s, v)입니다). |
반환값
bool 성공 시 true를 반환합니다. 실패는 타입 지정 커스텀 오류로 revert되므로, IPrecompile에서 상속한 오류까지 포함된 IPrivacy ABI에 대해 4바이트 selector를 디코딩하십시오.
에러
| 코드 | 이름 | 설명 |
|---|---|---|
PrivacyInvalidAuthorizationField | PrivacyInvalidAuthorizationField(string field) | 인증 필드가 비었거나 형식이 맞지 않을 때 revert됩니다. 페이로드가 어느 필드인지 알려줍니다: effectiveSender, executor, nonce, deadline. |
RequesterIsNotMsgSender | RequesterIsNotMsgSender(address msgSender, address requester) | 호출자가 서명이 묶인 executor가 아닐 때 revert됩니다. IPrecompile에서 상속하므로 selector를 다른 프리컴파일과 공유합니다. |
PrivacyAuthorizationExpired | PrivacyAuthorizationExpired(uint64 deadline) | 블록 시간이 authorization.deadline을 지났을 때 revert됩니다. 페이로드는 서명된 deadline입니다. |
PrivacyAuthorizationNonceUsed | PrivacyAuthorizationNonceUsed(address sender, uint256 nonce) | 이 (effectiveSender, nonce) 조합이 이미 사용됐을 때 revert됩니다. 재사용 방지 장치입니다. |
PrivacyAuthorizationSignerMismatch | PrivacyAuthorizationSignerMismatch(address expected, address got) | 서명에서 복원한 주소가 effectiveSender와 다를 때 revert됩니다. EOA와 EIP-7702 경로에 적용되며 페이로드에 두 주소가 모두 담깁니다. |
PrivacyAuthorizationRejected | PrivacyAuthorizationRejected(uint8 authorizationKind) | 계정 형태가 선언한 authorizationKind와 맞지 않거나(코드 있는 EOA, 코드 없는 ERC-1271, 위임 코드 없는 EIP-7702), ERC-1271 검증자가 거부하거나, 알 수 없는 kind일 때 revert됩니다. 페이로드는 시도한 kind입니다. |
PrivacyInvalidAuthorizationMagic | PrivacyInvalidAuthorizationMagic(bytes4 magic) | ERC-1271 검증자가 기대한 magic 값이 아닌 것을 반환했을 때 revert됩니다. 페이로드는 실제 반환값입니다. |
PrivacyRequestExpired | PrivacyRequestExpired(uint64 expiresAtUnix) | request.expiresAtUnix가 0이거나 이미 지났을 때 revert됩니다. 위의 인증 deadline과 별개인, 배치 페이로드 자체의 만료입니다. |
PrivacyDuplicateNullifier | PrivacyDuplicateNullifier() | 이 배치 안의 두 항목이 같은 nullifier를 쓸 때 revert됩니다. |
PrivacyNullifierAlreadySpent | PrivacyNullifierAlreadySpent() | 배치의 nullifier가 이미 온체인에 기록되어 있을 때 revert됩니다(이중지불). |
PrivacyDuplicateCommitment | PrivacyDuplicateCommitment() | 이 배치 안의 두 항목이 같은 출력 commitment를 만들 때 revert됩니다. |
PrivacyCommitmentAlreadyExists | PrivacyCommitmentAlreadyExists() | 배치의 출력 commitment가 이미 머클 트리에 있을 때 revert됩니다. |
PrivacyBatchSizeOutOfRange | PrivacyBatchSizeOutOfRange(uint256 count, uint256 max) | 배치에 항목이 없거나 회로가 지원하는 수를 넘을 때 revert됩니다. 페이로드에 개수와 상한이 담깁니다. |
privacy EOA authorization signature must be 65 bytes | privacy EOA authorization signature must be 65 bytes | 이 메서드에서 유일한 일반 문자열 revert입니다. 길이가 틀린 EOA 서명은 타입 오류를 만들기 전에 거부되므로, 이 건만 문자열로 판별하십시오. |
예제
릴레이어가 인가된 단일 증명 배치를 제출
릴레이어 자신의 주소가 authorization.executor와 대조되며, 불일치하면 증명 검증 이전에 revert됩니다. nonce는 오프체인에서 관리하며, effectiveSender별 단조 증가 카운터로도 충분합니다.
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],
}); 인가 실패 처리
실패는 ABI로 디코딩합니다. viem에 넘기는 ABI에 IPrivacy가 IPrecompile에서 상속한 오류까지 포함시켜야 RequesterIsNotMsgSender가 디코딩됩니다. 타입 오류가 없는 경로가 하나 있어 그것만 문자열로 판별합니다 — 아래 default 분기를 보십시오.
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;
}