IPrivacy.withdraw
withdraw(PrivacyWithdrawRequest request) external returns (bool success) 셸디드 노트를 소비하고 대응하는 공개된 OKRW를 request.recipient에게 지급합니다. PCL 정책 목적에서 msg.sender가 effective sender입니다. 실패는 IPrivacy에 선언된 타입 지정 커스텀 오류(요청 만료, 네이티브 denom 불일치, nullifier 충돌) 또는 IPrecompile의 상속 오류(InvalidAmount, InvalidAddress 등)로 revert됩니다.
파라미터
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
request | PrivacyWithdrawRequest | ✓ | withdraw 페이로드입니다. 증명, Merkle 루트, nullifier, 금액(SDK coin 문자열, 예: 15 OKRW에 해당하는 aokrw는 "15000000000000000000aokrw" — 네이티브 denom을 사용해야 하며 그렇지 않으면 PrivacyNativeDenomMismatch로 revert), 수신자, 체인 ID, expiresAtUnix를 포함합니다. |
반환값
타입:
bool 성공 시 true를 반환합니다. 실패는 false 반환이 아니라 타입 지정 커스텀 오류로 revert됩니다.
에러
| 코드 | 이름 | 설명 |
|---|---|---|
PrivacyNonPayable | PrivacyNonPayable | msg.value가 0이 아닐 때 revert됩니다. withdraw는 payable이 아닙니다. |
PrivacyRequestExpired | PrivacyRequestExpired | request.expiresAtUnix가 0이거나 이미 지났을 때 revert됩니다. |
PrivacyNativeDenomMismatch | PrivacyNativeDenomMismatch | request.amount가 네트워크의 네이티브 denom(aokrw)이 아닌 denom을 사용할 때 revert됩니다. 페이로드는 (got, expected)입니다. |
PrivacyNullifierAlreadySpent | PrivacyNullifierAlreadySpent | nullifier가 이미 온체인에 기록되어 있을 때 revert됩니다. |
InvalidAmount | InvalidAmount | IPrecompile에서 상속됩니다. request.amount를 coin 문자열로 파싱할 수 없을 때 revert됩니다. InvalidAmount(string amount)로 인코딩되며 페이로드는 원본 amount 문자열입니다. |
InvalidAddress | InvalidAddress | IPrecompile에서 상속됩니다. 호출자를 유효한 계정 주소로 변환할 수 없을 때 revert됩니다. InvalidAddress(string bad)로 인코딩됩니다. |
PrivacyTxLimitExceeded | PrivacyTxLimitExceeded | 트랜잭션이 이미 전역 stateful-privacy 호출 한도를 초과했을 때 revert됩니다. |
PrivacySenderTxLimitExceeded | PrivacySenderTxLimitExceeded | effective sender가 이미 sender별 stateful-privacy 호출 한도를 초과했을 때 revert됩니다. |
InvalidNumberOfArgs | InvalidNumberOfArgs | IPrecompile에서 상속됩니다. 호출이 정확히 하나의 인자를 담고 있지 않을 때 revert됩니다. |
예제
기본 withdraw
amount 문자열은 네트워크의 네이티브 denom인 aokrw를 사용해야 합니다. 다른 denom을 전달하면 PrivacyNativeDenomMismatch로 revert됩니다.
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { privacyAbi } from "@maroo-chain/contracts/abi/IPrivacy";
const PRIVACY = "0x100000000000000000000000000000000000000b" as const;
const wallet = createWalletClient({
account: privateKeyToAccount(process.env.USER_KEY as `0x${string}`),
transport: http("https://rpc-testnet.maroo.io"),
});
// The single withdraw amount fits inside the 64-bit shielded cap.
// TODO: replace with the real payout recipient before production.
const request = {
proof: proofBytes,
root: merkleRoot,
nullifier: noteNullifier,
amount: "15000000000000000000aokrw", // 15 OKRW
recipient: "0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62",
chainId: "maroo-testnet",
expiresAtUnix: BigInt(Math.floor(Date.now() / 1000) + 300),
};
await wallet.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "withdraw",
args: [request],
}); denom / 만료 거절 처리
올바른 aokrw denom 문자열이나 새 마감으로 요청을 다시 만듭니다. 증명 자체는 그대로 유효합니다.
import { decodeErrorResult } from "viem";
import { privacyAbi } from "@maroo-chain/contracts/abi/IPrivacy";
try {
await wallet.writeContract({
address: PRIVACY,
abi: privacyAbi,
functionName: "withdraw",
args: [request],
});
} catch (err: any) {
if (!err?.data) throw err;
const decoded = decodeErrorResult({ abi: privacyAbi, data: err.data });
if (decoded.errorName === "PrivacyNativeDenomMismatch") {
const [got, expected] = decoded.args as [string, string];
console.warn(`use denom ${expected}; got ${got}`);
} else if (decoded.errorName === "PrivacyRequestExpired") {
const [expiresAtUnix] = decoded.args as [bigint];
console.warn(`withdraw request expired at ${expiresAtUnix} — rebuild with a fresh deadline`);
} else {
throw err;
}
}