Skip to main content

Documentation Index

Fetch the complete documentation index at: https://daehan-base.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

신뢰할 수 있는 지출자가 추가 사용자 서명 없이 Base Account에서 자산을 이동할 수 있도록 스펜드 퍼미션을 생성하고 관리하는 방법을 알아보세요.

개요

스펜드 퍼미션을 통해 사용자는 신뢰할 수 있는 지출자에게 일정 기간 한도를 부여하여, 정해진 한도 내에서 사용자를 대신해 자산을 이동할 수 있도록 허용합니다. 이를 통해 정기 결제, 구독, 자동화된 트랜잭션에 대한 원활한 사용자 경험을 만들 수 있습니다.

달성할 내용

이 가이드를 마치면 다음을 할 수 있습니다:
  • 특정 한도와 기간으로 스펜드 퍼미션 생성
  • 기존 스펜드 퍼미션 조회 및 관리
  • 스펜드 퍼미션을 사용하여 트랜잭션 실행
  • React 애플리케이션에 스펜드 퍼미션 기능 구현
이 가이드의 코드 스니펫은 다음 예제 프로젝트를 기반으로 합니다:

설정

설정 가이드를 따라 Base Account와 함께 Privy를 설정하세요.

구현

컴포넌트 설정

"use client";

import { useState, useEffect, useCallback, useMemo } from "react";
import { useBaseAccountSdk, useWallets } from "@privy-io/react-auth";
import {
  requestSpendPermission,
  prepareSpendCallData,
  fetchPermissions,
  getPermissionStatus,
  type SpendPermission,
} from "@base-org/account/spend-permission/browser";
import { base } from "@privy-io/chains";

export const SpendPermissions = () => {
  const { baseAccountSdk } = useBaseAccountSdk();
  const { wallets } = useWallets();
  const [permissions, setPermissions] = useState<SpendPermission[]>([]);
  const [selectedPermission, setSelectedPermission] = useState<SpendPermission | null>(null);
  const [loading, setLoading] = useState(false);
  
  // 설정
  const spenderAddress = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
  const tokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Base의 USDC

  // Base Account 지갑 찾기
  const baseAccount = useMemo(() => {
    return wallets.find((wallet) => wallet.walletClientType === 'base_account');
  }, [wallets]);

  const provider = baseAccountSdk?.getProvider();
  const account = baseAccount?.address;

  const loadPermissions = useCallback(async () => {
    if (!account || !provider || !spenderAddress) return;
    
    try {
      setLoading(true);
      const fetchedPermissions = await fetchPermissions({
        account,
        chainId: base.id,
        spender: spenderAddress,
        provider,
      });
      setPermissions(fetchedPermissions);
    } catch (error) {
      console.error("Failed to load permissions:", error);
    } finally {
      setLoading(false);
    }
  }, [account, provider, spenderAddress]);

  const handleRequestSpendPermission = async () => {
    if (!account || !provider || !spenderAddress || !tokenAddress) return;

    try {
      setLoading(true);
      
      const permission = await requestSpendPermission({
        account,
        spender: spenderAddress,
        token: tokenAddress,
        chainId: base.id,
        allowance: BigInt(1) * BigInt(10 ** 6), // 1 USDC (소수점 6자리)
        periodInDays: 1, // 1일
        provider,
      });

      setPermissions([...permissions, permission]);
    } catch (error) {
      console.error("Failed to create Spend Permission:", error);
    } finally {
      setLoading(false);
    }
  };

  const handleUseSpendPermission = async () => {
    if (!selectedPermission || !provider || !spenderAddress) return;

    try {
      setLoading(true);
      
      // 퍼미션 상태 확인
      const { isActive, remainingSpend } = await getPermissionStatus(selectedPermission);
      
      if (!isActive) {
        console.error("Selected permission is not active");
        return;
      }

      const spendAmount = BigInt(100) * BigInt(10 ** 6); // 100 USDC
      
      if (remainingSpend < spendAmount) {
        console.error("Insufficient remaining allowance");
        return;
      }

      // 스펜드 콜 준비
      const spendCalls = await prepareSpendCallData(selectedPermission, spendAmount);
      console.log("Spend calls prepared:", spendCalls);
      
    } catch (error) {
      console.error("Failed to use Spend Permission:", error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="space-y-4">
      <div className="flex gap-4">
        <button 
          onClick={handleRequestSpendPermission}
          disabled={loading || !account}
          className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
        >
          스펜드 퍼미션 생성
        </button>
        <button 
          onClick={loadPermissions}
          disabled={loading || !account}
          className="px-4 py-2 bg-green-600 text-white rounded disabled:opacity-50"
        >
          퍼미션 불러오기
        </button>
        <button 
          onClick={handleUseSpendPermission}
          disabled={loading || !selectedPermission}
          className="px-4 py-2 bg-purple-600 text-white rounded disabled:opacity-50"
        >
          퍼미션 사용
        </button>
      </div>

      {/* 설정 표시 */}
      <div className="p-4 rounded-lg">
        <h4 className="font-semibold mb-3">설정</h4>
        <div className="text-sm space-y-1">
          <div><strong>지출자:</strong> {spenderAddress}</div>
          <div><strong>토큰:</strong> {tokenAddress} (USDC)</div>
          <div><strong>한도:</strong> 하루 $1 USDC</div>
        </div>
      </div>

      {/* 퍼미션 목록 */}
      {permissions.length > 0 && (
        <div className="p-4 rounded-lg">
          <h4 className="font-semibold mb-3">기존 퍼미션</h4>
          <div className="space-y-2">
            {permissions.map((permission, index) => (
              <div
                key={index}
                className={`p-3 border rounded-md cursor-pointer ${
                  selectedPermission === permission
                    ? "border-blue-500"
                    : "border-gray-300"
                }`}
                onClick={() => setSelectedPermission(permission)}
              >
                <div className="text-sm">
                  <div><strong>지출자:</strong> {permission.permission.spender?.slice(0, 10)}...</div>
                  <div><strong>토큰:</strong> {permission.permission.token?.slice(0, 10)}...</div>
                  <div><strong>한도:</strong> {permission.permission.allowance?.toString()}</div>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

주요 메서드

스펜드 퍼미션 생성

requestSpendPermission을 사용하여 새 스펜드 퍼미션을 생성합니다:
const permission = await requestSpendPermission({
  account,
  spender: spenderAddress,
  token: tokenAddress,
  chainId: base.id,
  allowance: BigInt(1) * BigInt(10 ** 6), // 1 USDC
  periodInDays: 1, // 1일
  provider,
});

퍼미션 조회

fetchPermissions를 사용하여 기존 퍼미션을 조회합니다:
const permissions = await fetchPermissions({
  account,
  chainId: base.id,
  spender: spenderAddress,
  provider,
});

퍼미션 사용

퍼미션 상태를 확인하고 스펜드 콜을 준비합니다:
// 퍼미션이 활성 상태인지 확인
const { isActive, remainingSpend } = await getPermissionStatus(permission);

// 스펜드 트랜잭션 데이터 준비
const spendCalls = await prepareSpendCallData(permission, spendAmount);

설정 옵션

한도 및 기간

지출 한도와 기간을 설정합니다:
{
  allowance: BigInt(100) * BigInt(10 ** 6), // 100 USDC (소수점 6자리)
  periodInDays: 7, // 7일
}

토큰 주소

Base의 주요 토큰 주소:
  • USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
  • ETH: 네이티브 토큰 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
  • DAI: 0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb

활용 사례

스펜드 퍼미션이 적합한 경우:
  • 구독: 사용자 상호작용 없는 정기 결제
  • DeFi 프로토콜: 자동화된 거래 및 수익 농업
  • 게임: 게임 내 구매 및 보상
  • 커머스: 간소화된 결제 경험