콘텐츠로 이동
요금문서EN

위임 결제

Pouchi Guard는 위임과 한도를 다룹니다. 위임은 대리인 하나의 서명과 지갑이 강제하는 한도입니다. 이용자는 승인 화면에서 대리인과 한도를 보고 소유자 서명을 한 번 합니다. 그 뒤 대리인은 자기 키로 서명해 한도 안의 호출을 실행하고, 지갑 컨트랙트가 대리인 서명과 한도를 다시 확인합니다.

Pouchi는 위임 실행에 서명하지 않고, 대리인 키를 보관하지 않으며, 대리인이 될 수 없습니다. 실행을 요청만 하는 소프트웨어(에이전트, 예약 작업, 버튼)는 키를 갖지 않고 대리인에게 부탁만 합니다.

방식 누가 서명 언제 쓰나
소유자 실행 (건별 승인) 이용자 (키 조각 둘) 매번 이용자가 확인해야 하는 호출. useSendTransaction, 서버의 proposeTransaction
위임 실행 대리인 (키 하나) 같은 종류의 호출을 한도 안에서 반복. 정기 결제, 게임, 에이전트 결제

건별 승인은 위임이 아닙니다. 이용자가 그 호출 하나에 소유자 서명을 하는 평소 실행입니다.

AI 에이전트 연결의 URL 하나로 호스팅된 테스트넷 지갑 MCP와 기본 쇼핑 도구를 쓸 수 있습니다. 아래 PC용 지갑 제품을 설치할 필요는 없습니다. PC용 지갑 제품과 새 서버 위임을 만드는 앱 운영자 SDK 함수는 별도로 준비 중입니다. 자체 서비스나 앱 운영자 배포는 business@pouchi.app으로 접근 권한과 설정 절차를 요청하세요.

위임에는 대리인을 두 가지 방법으로 적습니다. 키 주소를 적거나(key delegate), 앱 운영자 레지스트리 항목을 적습니다(tenant delegate).

대리인 적는 것 키를 가진 쪽 쓰는 곳 SDK
브라우저 세션 키 키 주소 이용자 (이 브라우저 탭) 앱 안의 반복 동작 useSessionApproval (사용 가능)
Pouchi 지갑 MCP 키 주소 이용자 (이용자 PC) 이용자 PC의 AI 에이전트 결제 준비 중
앱 서버 앱 운영자 레지스트리 항목 앱 운영자 (KMS, HSM) 정기 결제, 예약 실행, 원격 MCP executeDelegatedPolicy (사용 가능), 위임 만들기 함수는 준비 중

앱 운영자 레지스트리 항목으로 적으면, 앱 운영자가 키를 바꿔도(rotateDeploymentSigner) 이용자가 다시 승인할 필요가 없습니다. 앱 운영자 키를 정지하면 그 앱 운영자가 대리인인 위임만 멈춥니다. 이용자 본인의 실행과 키 주소로 적은 위임에는 영향이 없습니다.

이용자가 서명한 한도는 지갑 컨트랙트에 들어가고, 실행할 때마다 컨트랙트가 확인합니다. 앱 코드나 Pouchi 서버가 한도를 올릴 수 없습니다.

한도 뜻
대상과 함수 컨트랙트 주소(코드 해시 포함)와 함수 셀렉터. 네이티브 송금은 받는 주소
금액 1회 최대, 합계 최대
횟수 남은 실행 횟수
기간 시작과 끝 시각
주기 한도 고정 길이 창마다 횟수와 금액 (정기 결제)
실행당 최대 가스 실행에 허용할 최대 가스. 가스비는 앱이 부담하며 이용자 지갑에서는 차감하지 않음

이용자는 언제든 소유자 서명으로 위임을 끌 수 있습니다. 대리인 키가 유출되면 그 위임의 남은 한도까지 쓰일 수 있으니, 작은 한도와 짧은 기간을 권합니다.

useSessionApproval은 이 브라우저 탭에서 새 키를 만들고, 그 키 주소를 대리인으로 하는 위임을 이용자에게 승인받습니다. 이용자는 사용자 인증(패스키, PIN 등)을 한 번 하고, 그 뒤 세션 안의 호출은 확인 버튼 한 번으로 실행됩니다. 세션 키는 탭 밖으로 나가지 않고 Pouchi도 보지 못합니다.

src/SessionPlay.tsx
import { useSendTransaction, useSessionApproval } from "@shard-lab/nc-waas-react";
import { encodeFunctionData, parseAbi, parseEther } from "viem";
const GAME = import.meta.env.VITE_GAME_CONTRACT as `0x${string}`;
const abi = parseAbi(["function play(uint256 round) payable"]);
export function SessionPlay() {
const { session, active, startSession, endSession } = useSessionApproval();
const { sendTransaction } = useSendTransaction();
// One PIN or biometric approval: this function only, at most 0.05 OKRW in 10 calls, for 1 hour.
const start = () => startSession({
durationSeconds: 60 * 60,
maxTotalValueWei: parseEther("0.05").toString(),
maxValuePerCallWei: parseEther("0.005").toString(),
maxCalls: 10,
contracts: [{ address: GAME, functions: ["play(uint256)"] }]
});
// Inside the session this is a one-tap confirmation; outside it, a normal approval.
const play = (round: number) => sendTransaction({
to: GAME,
value: parseEther("0.005"),
data: encodeFunctionData({ abi, functionName: "play", args: [BigInt(round)] })
});
if (!active) return <button onClick={start}>1시간 세션 시작</button>;
return (
<div>
<p>남은 횟수 {session?.callsRemaining}, 만료 {new Date(session!.expiresAt).toLocaleTimeString()}</p>
<button onClick={() => play(1)}>플레이</button>
<button onClick={endSession}>세션 끝내기</button>
</div>
);
}
항목 값
필요한 설정 config.approvals.session.clientId: 앱 운영자가 이 앱에 등록한 웹 클라이언트 ID
startSession(input) durationSeconds(60초에서 24시간, 기본 1시간), maxTotalValueWei, maxValuePerCallWei, maxCalls(1에서 1,000), contracts({ address, functions }), nativeRecipients
범위 함수 최대 16개. 함수는 셀렉터(0x12345678)나 시그니처(play(uint256))
넣을 수 없는 함수 transfer, transferFrom, approve, permit, setApprovalForAll처럼 토큰을 옮기거나 허락하는 함수. 세션은 인자를 제한하지 않기 때문에, 이런 호출은 매번 승인합니다
반환 { session, active, startSession, endSession }. session에는 expiresAt, callsRemaining, spentWei, valueAllowanceWei, permissions

세션 안에 드는 호출은 useSendTransaction이 알아서 세션 키로 실행합니다. 세션 밖의 호출(다른 함수, 한도 초과, 만료)은 평소처럼 소유자 승인 화면이 열립니다.

정기 결제처럼 이용자가 앱에 없을 때 실행하려면 앱 서버를 대리인으로 둡니다. 서버는 @nc-waas/tenant-server-sdk의 executeDelegatedPolicy로 실행합니다. Pouchi가 정확한 실행 내용을 준비해 digest를 돌려주면 서버의 대리인 키가 서명하고, Pouchi가 제출합니다. 서버가 뚫려도 한도 밖의 요청은 체인에서 실패합니다.

server/charge.ts
// Tenant server: run one payment inside a delegation whose delegate is this server.
// The wallet contract checks this server's signature and every limit the user signed.
import { executeDelegatedPolicy, type PolicyDelegateSigner } from "@nc-waas/tenant-server-sdk";
import { encodeFunctionData, parseAbi, parseUnits, type Address, type Hex } from "viem";
const erc20 = parseAbi(["function transfer(address to, uint256 amount) returns (bool)"]);
// Production: a KMS or HSM key that signs digests. The private key never leaves it.
declare function kmsSignDigest(keyId: string, digest: Hex): Promise<Hex>;
const signer: PolicyDelegateSigner = {
address: process.env.DELEGATE_ADDRESS as Address,
signDigest: (digest) => kmsSignDigest(process.env.DELEGATE_KMS_KEY_ID!, digest)
};
export async function chargeMonthly(input: { connectionToken: string; policyId: string; invoiceId: string; merchant: Address }) {
const result = await executeDelegatedPolicy({
apiUrl: process.env.POUCHI_API_URL!,
connectionToken: input.connectionToken, // app connection token with scope policy:execute
policyId: input.policyId,
requestId: `invoice-${input.invoiceId}`, // stable per payment: a retry never pays twice
calls: [{
target: process.env.TOKEN_ADDRESS as Address,
data: encodeFunctionData({ abi: erc20, functionName: "transfer", args: [input.merchant, parseUnits("9900", 18)] })
}],
signer
});
if (!result.ok) throw new Error(`charge ${result.status}: ${result.code ?? ""} ${result.failureReason ?? ""}`);
return result.transactionHash;
}
입력 뜻
apiUrl Pouchi API 주소
connectionToken 이용자 지갑에 대한 앱 연결 토큰 (scope policy:execute)
policyId 이용자가 승인한 위임 ID
requestId 결제마다 고정하는 ID. 같은 ID로 다시 보내도 두 번 결제되지 않음
calls { target, value?, data }[]. 위임의 대상과 함수 안이어야 함
signer PolicyDelegateSigner ({ address, signDigest(digest) }). 운영에서는 KMS나 HSM
responseMode "completed"(체인 결과까지 기다림) 또는 "submitted"

결과는 { ok, status, operationId?, transactionHash?, userOperationHash?, failureReason?, code? }입니다.

이용자가 자기 PC에 Pouchi 지갑 MCP를 설치하면, 대리인 키가 그 PC에서 만들어지고 밖으로 나가지 않습니다. 승인 화면에는 “이 PC의 Pouchi 지갑 MCP”로 표시되고, 대리인은 이용자 자신입니다. 도구는 wallet_status, pay_intent, request_budget, payment_status, revoke_mandate입니다. 제품 배포는 준비 중입니다.

연동 코드를 쓰는 개발자용 MCP(문서와 예시)는 이것과 다른 도구입니다. AI 개발 도구 연동