> ## Documentation Index
> Fetch the complete documentation index at: https://daehan-base.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 스펜드 퍼미션 사용

> 신뢰할 수 있는 지출자가 사용자 자산을 지출할 수 있도록 스펜드 퍼미션을 사용하는 방법 알아보기

## 개요

스펜드 퍼미션을 통해 신뢰할 수 있는 `spender`를 지정하여 사용자를 대신해 Base Account에서 자산을 이동할 수 있습니다.

사용자가 퍼미션에 서명하면, `spender`는 정의한 한도 내에서 추가 프롬프트, 팝업, 사용자 서명 없이 전송을 시작할 수 있습니다. 이를 통해 구독 갱신, 알고리즘 거래, 자동화된 지불과 같은 원활한 경험이 가능합니다.

Spend Permission Manager 컨트랙트와 지원 체인에 대해 자세히 알아보려면 [GitHub](https://github.com/coinbase/spend-permissions)을 참조하세요.

<Callout type="info">
  Base App 앱을 위한 스펜드 퍼미션은 곧 출시될 예정이며 향후 업데이트에서 지원될 것입니다.
</Callout>

<Note>
  서브 어카운트를 사용하고 있다면, Base Account가 서브 어카운트에 자동으로 자금을 지원하고 선택적으로 승인 프롬프트를 건너뛸 수 있는 [자동 스펜드 퍼미션](/base-account/improve-ux/sub-accounts#auto-spend-permissions)을 확인해 보세요.
</Note>

## 사용법

### 스펜드 퍼미션 요청

퍼미션을 설명하는 EIP-712 페이로드를 생성하고 사용자에게 서명을 요청합니다. 나중에 온체인에서 퍼미션을 등록할 수 있도록 퍼미션 데이터와 함께 서명 결과를 저장합니다. 아래 SDK 헬퍼가 구성과 서명을 처리해 줍니다.

| 필드명         | 타입        | 설명                                       |
| ----------- | --------- | ---------------------------------------- |
| `account`   | `address` | 이 스펜드 퍼미션이 유효한 스마트 계정                    |
| `spender`   | `address` | `account`의 토큰을 지출할 수 있는 주체               |
| `token`     | `address` | 토큰 주소 (ERC-7528 네이티브 토큰 또는 ERC-20 컨트랙트)  |
| `allowance` | `uint160` | 각 `period` 내에서 지출할 수 있는 최대 허용 값          |
| `period`    | `uint48`  | 사용된 `allowance`를 반복적으로 초기화하는 시간 기간 (초)   |
| `start`     | `uint48`  | 이 스펜드 퍼미션이 유효하기 시작하는 타임스탬프 (포함, Unix 초)  |
| `end`       | `uint48`  | 이 스펜드 퍼미션이 유효한 마지막 타임스탬프 (미포함, Unix 초)   |
| `salt`      | `uint256` | 동일한 필드를 가진 고유한 스펜드 퍼미션을 구별하기 위한 임의 데이터   |
| `extraData` | `bytes`   | `spender`가 소비할 수 있는 스펜드 퍼미션에 첨부되는 임의 데이터 |

```tsx theme={null}
import { requestSpendPermission } from "@base-org/account/spend-permission";
import { createBaseAccountSDK } from "@base-org/account";
import { base } from "viem/chains";

const sdk = createBaseAccountSDK({
  appName: 'Base Account SDK 데모',
  appLogoUrl: 'https://base.org/logo.png',
  appChainIds: [base.id],
});

const permission = await requestSpendPermission({
  account: "0x...",
  spender: "0x...",
  token: "0x...",
  chainId: 8453, // 또는 지원되는 다른 체인
  allowance: 1_000_000n,
  periodInDays: 30,
  provider: sdk.getProvider(),
});

console.log("Spend Permission:", permission);
```

### 스펜드 퍼미션 사용

퍼미션을 사용하는 것은 2단계입니다:

1. **콜 준비** — 퍼미션과 요청된 `amount`로 `prepareSpendCallData`를 호출합니다.
2. **콜 제출** — 앱의 spender 계정을 사용하여 콜을 제출합니다.

`prepareSpendCallData`는 토큰을 지출하는 데 필요한 콜 배열을 반환합니다:

* `approveWithSignature` — 퍼미션이 아직 온체인에 등록되지 않은 경우, 이 콜이 `spend` 콜 앞에 추가됩니다.
* `spend` — 사용자의 Base Account에서 토큰을 지출하는 콜.

```tsx theme={null}
import { prepareSpendCallData } from "@base-org/account/spend-permission";

// [approveWithSignatureCall, spendCall] 반환
const spendCalls = await prepareSpendCallData({
  permission,
  amount, // 선택사항; 생략하면 남은 한도를 지출
});

// 앱 spender 계정이 wallet_sendCalls를 지원하는 경우, wallet_sendCalls를 사용하여 배치로 제출
// 이것은 provider 인터페이스에서 wallet_sendCalls를 사용하는 예입니다
await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: spendCalls,
    },
  ],
});

// 앱 spender 계정이 wallet_sendCalls를 지원하지 않는 경우, eth_sendTransaction을 사용하여 순서대로 제출
// 이것은 provider 인터페이스에서 eth_sendTransaction을 사용하는 예입니다
await Promise.all(
  spendCalls.map((call) =>
    provider.request({
      method: "eth_sendTransaction",
      params: [
        {
          ...call,
          from: spender,
        },
      ],
    })
  )
);
```

<Note>
  **`spendCalls` 배열에 대하여**

  이 배열은 *처음으로* 온체인에 퍼미션을 제출할 때 2개의 콜을 가집니다.
  퍼미션이 이미 온체인에 등록된 경우, 이 배열에는 1개의 콜(`spend` 콜)만 있습니다.

  대부분의 사용 사례에서는 이에 대해 걱정할 필요가 없습니다.
</Note>

### 스펜드 퍼미션 취소

두 가지 방법으로 퍼미션을 취소할 수 있습니다:

* `requestRevoke`를 사용하여 사용자의 Base Account에 요청을 통해 사용자 승인을 요청합니다.
* `prepareRevokeCallData`에서 반환된 콜을 제출하여 앱의 spender에서 자동으로 취소합니다.

```tsx theme={null}
import {
  requestRevoke,
  prepareRevokeCallData,
} from "@base-org/account/spend-permission";

// 옵션 A: 사용자 시작 취소 (지갑 팝업)
try {
  const hash = await requestRevoke(permission);
  console.log("취소 성공", hash);
} catch {
  console.warn("취소가 거부되었거나 실패했습니다");
}

// 옵션 B: 앱의 spender 계정에 의한 자동 취소
const revokeCall = await prepareRevokeCallData(permission);

// 앱의 spender 계정을 사용하여 취소 콜 제출
// 이것은 provider 인터페이스에서 wallet_sendCalls를 사용하는 예입니다
await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: [revokeCall],
    },
  ],
});

// 앱 spender 계정이 wallet_sendCalls를 지원하지 않는 경우, eth_sendTransaction을 사용하여 취소 콜 제출
// 이것은 provider 인터페이스에서 eth_sendTransaction을 사용하는 예입니다
await provider.request({
  method: "eth_sendTransaction",
  params: [
    {
      ...revokeCall,
      from: spender,
    },
  ],
});
```

## API 참조

* `requestSpendPermission`
* `prepareSpendCallData`
* `requestRevoke`
* `prepareRevokeCallData`
* `fetchPermissions`
* `fetchPermission`
* `getPermissionStatus`

## 완전한 통합 예제

```typescript theme={null}
import {
  fetchPermissions,
  fetchPermission,
  getPermissionStatus,
  prepareSpendCallData,
  requestSpendPermission,
  requestRevoke,
  prepareRevokeCallData,
} from "@base-org/account/spend-permission";

import { createBaseAccountSDK } from "@base-org/account";
import { base } from "viem/chains";

const sdk = createBaseAccountSDK({
  appName: 'Base Account SDK 데모',
  appLogoUrl: 'https://base.org/logo.png',
  appChainIds: [base.id],
});

const spender = "0xAppSpenderAddress";

// 1) 해시로 특정 퍼미션 가져오기
// 이미 퍼미션 해시를 알고 있을 때 fetchPermission을 사용합니다
// (예: 이전 세션에서 저장했거나 파라미터로 전달됨)
const permission = await fetchPermission({
  permissionHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  provider: sdk.getProvider(),
});

// 대안: spender의 모든 퍼미션 가져오기
// 모든 사용 가능한 퍼미션을 확인하고 사용할 것을 선택할 때 fetchPermissions를 사용합니다
// const permissions = await fetchPermissions({
//   account: "0xUserBaseAccountAddress",
//   chainId: 84532,
//   spender,
//   provider: sdk.getProvider(),
// });
// const permission = permissions.at(0);

// ========================================
// 기존 퍼미션이 있는 경우
// ========================================

// 2. 퍼미션 상태 확인
try {
  const { isActive, remainingSpend } = await getPermissionStatus(permission);
  const amount = 1000n;

  if (!isActive || remainingSpend < amount) {
    throw new Error("사용 가능한 스펜드 퍼미션이 없습니다");
  }
} catch {
  throw new Error("사용 가능한 스펜드 퍼미션이 없습니다");
}

// 3. 콜 준비
const [approveCall, spendCall] = await prepareSpendCallData({
  permission,
  amount,
});

// 4. 앱의 spender 계정을 사용하여 콜 실행
// 이것은 wallet_sendCalls를 사용한 예입니다. 프로덕션에서는 eth_sendTransaction을 사용할 수 있습니다.
await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: [approveCall, spendCall],
    },
  ],
});

// ========================================
// 기존 퍼미션이 없는 경우
// ========================================

// 2. 사용할 스펜드 퍼미션 요청
const newPermission = await requestSpendPermission({
  account: "0xUserBaseAccountAddress",
  spender,
  token: "0xTokenContractAddress",
  chainId: 84532,
  allowance: 1_000_000n,
  periodInDays: 30,
  provider: sdk.getProvider(),
});

// 3. 콜 준비
const spendCalls = await prepareSpendCallData({
  permission: newPermission,
  amount: 1_000n,
});

// 4. 앱의 spender 계정을 사용하여 콜 실행
// 이것은 eth_sendTransaction을 사용한 예입니다. 앱 계정이 wallet_sendCalls를 지원하면, wallet_sendCalls를 사용하여 콜을 배치로 처리하세요.
await Promise.all(
  spendCalls.map((call) =>
    provider.request({
      method: "eth_sendTransaction",
      params: [
        {
          ...call,
          from: spender,
        },
      ],
    })
  )
);

// ========================================
// 사용자에게 스펜드 퍼미션 취소 요청
// ========================================

try {
  const hash = await requestRevoke(permission);
  console.log("취소 성공", hash);
} catch {
  throw new Error("취소 실패");
}

// ========================================
// 백그라운드에서 스펜드 퍼미션 취소
// ========================================

const revokeCall = await prepareRevokeCallData(permission);

await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: [revokeCall],
    },
  ],
});
```

## 활용 사례 예제

Base에서 보안 [스펜드 퍼미션](/base-account/improve-ux/spend-permissions)을 사용하여 자율적으로 [Zora Creator Coins](https://docs.zora.co/coins)를 구매할 수 있는 AI 에이전트를 구축한다고 가정해 보겠습니다.

이 예제는 Base Account의 [스펜드 퍼미션](/base-account/improve-ux/spend-permissions)을 Coinbase Developer Platform(CDP) [서버 지갑](https://docs.cdp.coinbase.com/server-wallets/v2/introduction/quickstart) 및 [Trade API](https://docs.cdp.coinbase.com/trade-api/quickstart)와 결합하여 원활하고 가스 없는 AI 에이전트 트랜잭션을 구현하는 방법을 보여줍니다.

<Frame>
  <img src="https://mintcdn.com/daehan-base/pXKB1jCfk0doXahz/images/base-account/spend-permissions-agent.png?fit=max&auto=format&n=pXKB1jCfk0doXahz&q=85&s=c5309e5effac65850c4033a0bc9d3bc1" alt="스펜드 퍼미션 에이전트 인터페이스" width="2478" height="1510" data-path="images/base-account/spend-permissions-agent.png" />
</Frame>

<CardGroup cols={2}>
  <Card title="라이브 데모" icon="rocket" href="https://base-agent-spend-permissions.vercel.app">
    라이브 애플리케이션을 사용해 보고 스펜드 퍼미션이 작동하는 것을 확인하세요
  </Card>

  <Card title="소스 코드" icon="github" href="https://github.com/base/demos/tree/master/base-account/agent-spend-permissions">
    GitHub에서 완전한 구현을 탐색하세요
  </Card>
</CardGroup>
