IPrivacy.transfer
transfer(PrivacyTransferRequest request) external returns (bool success) 하나 이상의 셸디드 노트를 소비하고 Groth16 증명으로 새 노트를 생성합니다. msg.sender가 effective sender이며, PCL은 호출자를 기준으로 정책을 평가합니다. 실패는 IPrivacy에 선언된 타입 지정 커스텀 오류(요청 만료, nullifier/커밋먼트 충돌, Merkle 용량, 트랜잭션당 한도) 또는 IPrecompile에서 상속된 오류로 revert됩니다. PCL 정책 실패는 그 대신 IPcl의 PCL ReasonCode로 노출됩니다.
파라미터
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
request | PrivacyTransferRequest | ✓ | 셸디드 transfer 페이로드입니다. 증명, Merkle 루트, nullifier들, 새 커밋먼트들, 암호문들, 뷰 태그들, 공개 필드, expiresAtUnix를 포함합니다. expiresAtUnix는 필수이며 현재 블록 시간보다 커야 합니다. 0이거나 과거이면 PrivacyRequestExpired(expiresAtUnix)로 revert됩니다. |
반환값
타입:
bool 성공 시 true를 반환합니다. 실패는 false 반환이 아니라 타입 지정 커스텀 오류로 revert됩니다.
에러
| 코드 | 이름 | 설명 |
|---|---|---|
PrivacyNonPayable | PrivacyNonPayable | msg.value가 0이 아닐 때 revert됩니다. value를 받는 메서드는 deposit뿐입니다. 페이로드는 메서드 이름입니다. |
PrivacyRequestExpired | PrivacyRequestExpired | request.expiresAtUnix가 0이거나 이미 지났을 때 revert됩니다. 페이로드에는 unix 타임스탬프가 담깁니다. |
PrivacyNullifierAlreadySpent | PrivacyNullifierAlreadySpent | 요청의 nullifier가 이미 온체인에 기록되어 있을 때 revert됩니다(이중 지출 시도). |
PrivacyCommitmentAlreadyExists | PrivacyCommitmentAlreadyExists | 요청의 새 커밋먼트가 이미 Merkle 트리에 존재할 때 revert됩니다. |
PrivacyMerkleCapacityExceeded | PrivacyMerkleCapacityExceeded | 새 커밋먼트를 추가하면 셸디드 풀 Merkle 트리 용량을 초과할 때 revert됩니다. 페이로드는 출력 개수입니다. |
PrivacyTxLimitExceeded | PrivacyTxLimitExceeded | 현재 트랜잭션이 이미 너무 많은 stateful privacy 호출을 수행했을 때 revert됩니다(전역 한도). |
PrivacySenderTxLimitExceeded | PrivacySenderTxLimitExceeded | effective sender가 이 트랜잭션에서 이미 너무 많은 stateful privacy 호출을 수행했을 때 revert됩니다. 페이로드는 (sender, limit)입니다. |
InvalidNumberOfArgs | InvalidNumberOfArgs | 공유 IPrecompile 오류 집합에 InvalidNumberOfArgs(uint256 expected, uint256 got)로 선언되어 있지만, 이 함수는 인자 개수가 틀려도 이 오류로 revert되지 않습니다. calldata가 요청을 담기에 짧으면 ABISetupFailed로 revert되고, 요청 뒤에 붙은 바이트는 무시됩니다. |
ABISetupFailed | ABISetupFailed | IPrecompile의 ABISetupFailed(string reason)을 상속합니다. calldata를 프리컴파일 ABI로 디코딩할 수 없을 때 revert됩니다. 알 수 없는 4바이트 selector이면 사유가 no method with id: 0x…이고(보통 ABI가 오래된 경우), calldata가 인자를 담기에 짧으면 디코더의 메시지(예: abi: cannot marshal in to go type: length insufficient 32 require 64)가 사유가 됩니다. 프리컴파일에 UnknownMethod도 선언되어 있지만, 알 수 없는 selector는 이 오류로 revert되지 않습니다. |
예제
직접 셸디드 transfer
PCL과 sender별 rate-limit 집계에서 msg.sender가 effective 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` is prepared off-chain; expiresAtUnix must be in the future.
await wallet.writeContract({
address: PRIVACY,
abi: iPrivacyAbi,
functionName: "transfer",
args: [request],
}); 셸디드 풀 충돌 디코드
두 오류 모두 클라이언트의 노트 집합이 온체인 트리에 비해 오래되었다는 뜻입니다. 재시도 전에 갱신합니다.
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: "transfer",
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 === "PrivacyNullifierAlreadySpent") {
// The user tried to spend a note that is already gone — refresh their note
// set from the indexer and rebuild the proof.
console.warn("note already spent — reload note set");
} else if (decoded.errorName === "PrivacyMerkleCapacityExceeded") {
const [count] = decoded.args as [bigint];
console.warn(`shielded tree is full (${count} outputs requested)`);
} else {
throw err;
}
}