> ## 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.

# 서브 어카운트 사용

> Base Account SDK를 사용하여 서브 어카운트를 생성하고 사용하는 방법 알아보기

export const GithubRepoCard = ({title, githubUrl}) => {
  return <a href={githubUrl} target="_blank" rel="noopener noreferrer" className="mb-4 flex items-center rounded-lg bg-zinc-900 p-4 text-white transition-all hover:bg-zinc-800">
      <div className="flex w-full items-center gap-3">
        <svg height="24" width="24" className="flex-shrink-0 dark:fill-white" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
          <path fill="currentColor" fillRule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
        </svg>

        <div className="flex min-w-0 flex-grow flex-col">
          <span className="truncate text-base font-medium">{title}</span>
          <span className="truncate text-xs text-zinc-400">{githubUrl}</span>
        </div>

        <svg className="h-5 w-5 flex-shrink-0 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
        </svg>
      </div>
    </a>;
};

## 서브 어카운트란?

서브 어카운트를 통해 애플리케이션에 직접 임베드된 앱별 지갑 계정을 사용자에게 프로비저닝할 수 있습니다. 생성 후에는 지갑 프로바이더나 wagmi, viem과 같은 인기 온체인 라이브러리를 통해 다른 지갑과 동일하게 상호작용할 수 있습니다.

<Note>
  완전한 구현을 찾고 계신가요? [완전한 통합 예제](/base-account/improve-ux/sub-accounts#complete-integration-example)로 바로 이동하세요.
</Note>

<Tip>
  **동영상 콘텐츠를 선호하시나요?**

  이 페이지의 [마지막 섹션](#video-guide)에 구현을 자세히 다루는 동영상 가이드가 있습니다.
</Tip>

## 주요 이점

* **마찰 없는 트랜잭션**: 고빈도 및 에이전틱 사용 사례를 위한 반복적인 서명 프롬프트를 제거하거나 트랜잭션 플로우를 완전히 제어합니다.
* **자금 조달 플로우 불필요**: 스펜드 퍼미션을 통해 서브 어카운트가 범용 Base Account의 잔액에서 직접 지출할 수 있습니다.
* **사용자 제어**: 사용자는 [account.base.app](https://account.base.app)에서 모든 서브 어카운트를 관리할 수 있습니다.

<Note>
  서브 어카운트의 라이브 데모를 보려면 [서브 어카운트 데모](https://sub-accounts-fc.vercel.app)를 확인하세요.
</Note>

<Tip>
  **스펜드 퍼미션**

  서브 어카운트는 앱이 사용자의 기존 Base Account 잔액을 활용할 수 있도록 스펜드 퍼미션과 함께 사용하도록 최적화되어 있습니다. 작동 방식에 대한 자세한 내용은 [스펜드 퍼미션](/base-account/improve-ux/spend-permissions) 가이드를 참조하세요.
</Tip>

## 설치

Base Account SDK를 설치합니다:

<CodeGroup>
  ```bash npm theme={null}
  npm install @base-org/account
  ```

  ```bash pnpm theme={null}
  pnpm add @base-org/account
  ```

  ```bash yarn theme={null}
  yarn add @base-org/account
  ```

  ```bash bun theme={null}
  bun add @base-org/account
  ```
</CodeGroup>

## 빠른 시작

서브 어카운트를 채택하는 가장 빠른 방법은 SDK 설정에서 `creation`을 `on-connect`로, `defaultAccount`를 `sub`로 설정하는 것입니다.

```tsx page.tsx theme={null}
const sdk = createBaseAccountSDK({
  // ...
  subAccounts: {
    creation: 'on-connect',
    defaultAccount: 'sub',
  }
});
```

이렇게 하면 사용자가 Base Account를 연결할 때 자동으로 서브 어카운트가 생성되고, `from` 파라미터를 범용 계정 주소로 지정하지 않는 한 트랜잭션은 자동으로 서브 어카운트에서 전송됩니다. 앱이 필요할 때 서브 어카운트에 대한 스펜드 퍼미션도 자동으로 요청됩니다.

자동 서브 어카운트가 활성화된 상태에서 사용자가 Base Account를 연결할 때 표시되는 화면:

<div style={{ display: 'flex', justifyContent: 'center'}}>
  <img src="https://mintcdn.com/daehan-base/pXKB1jCfk0doXahz/images/base-account/SubAccountCreationConnect.png?fit=max&auto=format&n=pXKB1jCfk0doXahz&q=85&s=b6651288d4162c03182a48904258cb25" alt="서브 어카운트 생성 플로우" style={{ width: '300px', height: 'auto' }} width="532" height="875" data-path="images/base-account/SubAccountCreationConnect.png" />
</div>

<Tip>
  서브 어카운트 통합 시 최상의 사용자 경험을 위해 [페이마스터](/base-account/improve-ux/sponsor-gas/paymasters)를 사용하여 가스를 스폰서하는 것을 권장합니다. SDK 설정에서 `paymasterUrls` 파라미터를 구성하여 모든 트랜잭션에 페이마스터를 사용하도록 설정할 수 있습니다. 이 옵션은 `createBaseAccount` SDK 초기화 설정에서 함께 구성할 수 있습니다.
</Tip>

<Tip>
  **동영상 콘텐츠를 선호하시나요?**

  이 페이지의 [마지막 섹션](#video-guide)에 이 특정 구현을 다루는 동영상 가이드가 있습니다.
</Tip>

## 서브 어카운트 사용

### SDK 초기화

먼저 Base Account SDK를 설정합니다. `appName`과 `appLogoUrl`을 앱에 맞게 커스터마이징하세요. 이는 지갑 연결 팝업과 account.base.app 대시보드에 표시됩니다. 또한 앱이 지원하는 체인으로 `appChainIds`를 커스터마이징할 수 있습니다.

```tsx page.tsx theme={null}
import { createBaseAccountSDK, getCryptoKeyAccount } from '@base-org/account';
import { base } from 'viem/chains';

// 서브 어카운트 설정으로 SDK 초기화
const sdk = createBaseAccountSDK({
  appName: 'Base Account SDK 데모',
  appLogoUrl: 'https://base.org/logo.png',
  appChainIds: [base.id],
});

// EIP-1193 프로바이더 가져오기
const provider = sdk.getProvider()
```

### 서브 어카운트 생성

<Tip>
  서브 어카운트를 생성하기 전에 사용자를 Base Account로 인증해야 합니다.
  다음 중 하나를 선택할 수 있습니다:

  * [사용자 인증](/base-account/guides/authenticate-users) 가이드를 따르세요
  * 간단한 지갑 연결을 위해 `provider.request({ method: 'eth_requestAccounts' });`를 사용하세요
</Tip>

프로바이더의 `wallet_addSubAccount` RPC 메서드를 사용하여 애플리케이션을 위한 서브 어카운트를 생성합니다. `publicKey` 파라미터가 제공되지 않으면 추출 불가능한 브라우저 CryptoKey가 생성되어 서브 어카운트를 대신해 서명합니다.

```tsx page.tsx theme={null}
// 서브 어카운트 생성
const subAccount = await provider.request({
  method: 'wallet_addSubAccount',
  params: [
    {
      account: {
        type: 'create',
      },
    }
  ],
});

console.log('Sub Account created:', subAccount.address);
```

또는 SDK 편의 메서드를 사용할 수 있습니다:

```tsx page.tsx theme={null}
const subAccount = await sdk.subAccount.create();

console.log('Sub Account created:', subAccount.address);
```

서브 어카운트 생성 프롬프트가 표시될 때 사용자에게 보이는 화면:

<div style={{ display: 'flex', justifyContent: 'center'}}>
  <img src="https://mintcdn.com/daehan-base/pXKB1jCfk0doXahz/images/base-account/SubAccountCreation.png?fit=max&auto=format&n=pXKB1jCfk0doXahz&q=85&s=cfd4688f984d4c4c6001ac3d9884bc98" alt="서브 어카운트 생성 플로우" style={{ width: '300px', height: 'auto' }} width="532" height="875" data-path="images/base-account/SubAccountCreation.png" />
</div>

### 기존 서브 어카운트 가져오기

프로바이더의 `wallet_getSubAccounts` RPC 메서드를 사용하여 기존 서브 어카운트를 조회합니다. 앱의 도메인과 연결된 서브 어카운트를 반환하며, 사용자에게 이미 서브 어카운트가 존재하는지 확인하는 데 유용합니다.

```tsx page.tsx theme={null}
// 범용 계정 가져오기
const [universalAddress] = await provider.request({
  method: "eth_requestAccounts",
  params: []
})

// 범용 계정의 서브 어카운트 가져오기
const { subAccounts: [subAccount] } = await provider.request({
  method: 'wallet_getSubAccounts',
  params: [{
    account: universalAddress,
    domain: window.location.origin,
  }]
})

if (subAccount) {
  console.log('Sub Account found:', subAccount.address);
} else {
  console.log('이 앱에 대한 서브 어카운트가 없습니다');
}
```

또는 SDK 편의 메서드를 사용할 수 있습니다:

```tsx page.tsx theme={null}
const subAccount = await sdk.subAccount.get();

console.log('Sub Account:', subAccount);
```

### 트랜잭션 전송

연결된 서브 어카운트에서 트랜잭션을 전송하려면 EIP-5792 `wallet_sendCalls` 또는 `eth_sendTransaction`을 사용합니다. `from` 파라미터를 서브 어카운트 주소로 지정해야 합니다.

<Tip>
  서브 어카운트가 연결되면 `eth_requestAccounts` 또는 `eth_accounts`가 반환하는 배열의 두 번째 계정이 됩니다. `wallet_addSubAccount`는 서브 어카운트를 사용하기 전에 각 세션에서 호출되어야 합니다. 이미 서브 어카운트가 있는 경우 새로운 서브 어카운트가 생성되지 않습니다.

  `mode: 'auto'`를 사용하는 경우 서브 어카운트가 배열의 첫 번째 계정이 됩니다.
</Tip>

먼저, 서브 어카운트가 두 번째 계정이 되는 모든 사용 가능한 계정을 가져옵니다:

```tsx page.tsx theme={null}
const [universalAddress, subAccountAddress] = await provider.request({
  method: "eth_requestAccounts", // 이미 연결된 경우 "eth_accounts"
  params: []
})
```

그런 다음 서브 어카운트에서 트랜잭션을 전송합니다:

**`wallet_sendCalls`**

```tsx page.tsx theme={null}
const callsId = await provider.request({
  method: 'wallet_sendCalls',
  params: [{
    version: "2.0",
    atomicRequired: true,
    from: subAccountAddress, // 서브 어카운트 주소 지정
    calls: [{
      to: '0x...',
      data: '0x...',
      value: '0x...',
    }],
    capabilities: {
      // https://docs.cdp.coinbase.com/paymaster/introduction/welcome
      paymasterUrl: "https://...",
    },
  }]
})

console.log('Calls sent:', callsId);
```

**`eth_sendTransaction`**

```tsx page.tsx theme={null}
const tx = await provider.request({
  method: 'eth_sendTransaction',
  params: [{
    from: subAccountAddress, // 서브 어카운트 주소 지정
    to: '0x...',
    data: '0x...',
    value: '0x...',
  }]
})

console.log('Transaction sent:', tx);
```

최상의 사용자 경험을 위해 페이마스터와 함께 `wallet_sendCalls`를 사용하는 것을 권장합니다. 자세한 내용은 [페이마스터](/base-account/improve-ux/sponsor-gas/paymasters) 가이드를 참조하세요.

## 고급 사용법

### 기존 계정 임포트

이미 배포된 스마트 컨트랙트 계정이 있고 이를 연결된 Base Account의 서브 어카운트로 만들고 싶다면 프로바이더 RPC 메서드를 사용하여 임포트할 수 있습니다:

```tsx page.tsx theme={null}
const subAccount = await provider.request({
  method: 'wallet_addSubAccount',
  params: [
    {
      account: {
        type: 'deployed',
        address: '0xYourSmartContractAccountAddress',
        chainId: 8453 // 계정이 배포된 체인
      },
    }
  ],
});

console.log('Sub Account added:', subAccount.address);
```

<Note>
  서브 어카운트를 임포트하기 전에 Base Account 주소를 서브 어카운트의 소유자로 추가해야 합니다. 현재 이것은 임포트하려는 서브 어카운트의 스마트 컨트랙트에서 [`addOwnerAddress`](https://github.com/coinbase/smart-wallet/blob/a8c6456f3a6d5d2dea08d6336b3be13395cacd42/src/MultiOwnable.sol#L101) 또는 [`addOwnerPublicKey`](https://github.com/coinbase/smart-wallet/blob/a8c6456f3a6d5d2dea08d6336b3be13395cacd42/src/MultiOwnable.sol#L109) 함수를 수동으로 호출하고 Base Account 주소를 소유자로 설정해야 합니다.

  또한 현재 Coinbase 스마트 지갑 컨트랙트만 Base Account의 서브 어카운트로 임포트하는 것을 지원합니다.

  Coinbase 스마트 지갑 컨트랙트 ABI는 [GitHub](https://github.com/base/account-sdk/blob/master/packages/account-sdk/src/sign/base-account/utils/constants.ts#L8)에서 확인할 수 있습니다.
</Note>

### 소유자 계정 추가

서브 어카운트는 서명이 필요할 때 소유권 업데이트가 필요한 경우 자동으로 감지하여 서명 전에 사용자에게 업데이트를 승인하도록 프롬프트합니다. 하지만 SDK 편의 메서드를 사용하여 서브 어카운트에 소유자를 수동으로 추가할 수도 있습니다:

```tsx page.tsx theme={null}
const ownerAccount = await sdk.subAccount.addOwner({
  address: subAccount?.address,
  publicKey: cryptoAccount?.account?.publicKey,
  chainId: base.id,
});

console.log('서브 어카운트에 소유자 추가됨');
```

이는 서브 어카운트의 스마트 컨트랙트에서 `addOwnerAddress` 또는 `addOwnerPublicKey` 함수를 호출하여 소유자를 추가하는 트랜잭션을 생성합니다.

<Note>
  사용자가 새 기기나 브라우저에서 앱에 로그인하면 소유권 변경이 예상됩니다.

  서버(예: Node.js)에서 SDK를 사용할 때 앱의 서브 어카운트 서명자 키를 잃지 않도록 주의하세요. 소유자를 업데이트하려면 사용자의 서명이 필요하며, 이는 서버 컨텍스트에서 요청할 수 없습니다.
</Note>

## 자동 스펜드 퍼미션

자동 스펜드 퍼미션을 통해 서브 어카운트는 트랜잭션 잔액이 부족할 때 상위 Base Account에서 자금을 접근할 수 있습니다. 이 기능은 또한 지속적인 스펜드 퍼미션을 설정하여 미래 트랜잭션이 사용자 승인 프롬프트 없이 실행될 수 있게 하여 앱의 트랜잭션 플로우의 마찰을 줄입니다.

이 기능은 서브 어카운트 사용 시 **기본적으로 활성화**됩니다.

### 작동 방식

**첫 번째 트랜잭션 플로우:**
서브 어카운트가 첫 번째 트랜잭션을 시도하면 Base Account가 사용자 승인을 위한 팝업을 표시합니다. 이 승인 과정에서 Base Account는:

* 트랜잭션에 필요한 누락된 토큰(네이티브 또는 ERC-20)을 자동으로 감지합니다
* 현재 트랜잭션을 이행하기 위해 상위 Base Account에서 서브 어카운트로 필요한 자금 전송을 요청합니다
* 사용자가 선택적으로 해당 토큰에 대한 미래 트랜잭션을 위한 지속적인 스펜드 퍼미션을 부여할 수 있도록 합니다

**이후 트랜잭션:**
사용자가 스펜드 퍼미션을 부여한 경우, 미래 트랜잭션은 다음 우선순위를 따릅니다:

1. 먼저 기존 서브 어카운트 잔액과 부여된 스펜드 퍼미션 사용 시도
2. 부족한 경우 Base Account에서 추가 전송 및/또는 스펜드 퍼미션 승인을 위해 사용자에게 프롬프트 표시

<Warning>
  스펜드 퍼미션 요청은 단일 트랜잭션에 여러 전송이 필요한 경우 첫 번째 토큰으로 제한됩니다. 추가 토큰은 별도의 승인이 필요합니다.
</Warning>

### 설정

사용자의 서브 어카운트가 수동으로 자금이 조달될 경우 SDK 설정에서 `funding`을 `manual`로 설정하여 자동 스펜드 퍼미션을 비활성화할 수 있습니다:

```tsx page.tsx theme={null}
const sdk = createBaseAccountSDK({
  appName: 'Base Account SDK 데모',
  appLogoUrl: 'https://base.org/logo.png',
  appChainIds: [base.id],
  subAccounts: {
    funding: 'manual', // 자동 스펜드 퍼미션 비활성화
  }
});
```

## 기술적 세부사항

Base Account의 자기 보관 설계는 트랜잭션이나 메시지 서명과 같은 각 지갑 상호작용마다 사용자 패스키 프롬프트를 요구합니다. 이를 통해 모든 지갑 상호작용에 대한 사용자 인식과 승인이 보장되지만, 빈번한 지갑 상호작용이 필요한 애플리케이션에서는 사용자 경험에 영향을 줄 수 있습니다.

개발자가 지갑 상호작용을 더 많이 제어할 수 있는 사용자 경험으로 Base Account를 지원하기 위해, 우리는 [ERC-7895](https://eip.tools/eip/7895)와 함께 서브 어카운트를 구축했습니다. 이는 지갑 계정 간 계층적 관계를 생성하는 새로운 지갑 RPC입니다.

이 서브 어카운트들은 온체인 관계를 통해 최종 사용자의 Base Account와 연결됩니다. [스펜드 퍼미션 기능](/base-account/improve-ux/spend-permissions)과 결합하면 앱 계정을 안전하게 프로비저닝하고 자금을 조달하는 강력한 기반을 만들면서, 애플리케이션에 가장 적합한 사용자 경험을 구축하는 데 충분한 제어권을 제공합니다.

## 완전한 통합 예제

서브 어카운트 생성과 사용을 보여주는 전체 React 컴포넌트:

```tsx page.tsx expandable theme={null}
import { createBaseAccountSDK } from "@base-org/account";
import { useCallback, useEffect, useState } from "react";
import { baseSepolia } from "viem/chains";

interface SubAccount {
  address: `0x${string}`;
  factory?: `0x${string}`;
  factoryData?: `0x${string}`;
}

interface GetSubAccountsResponse {
  subAccounts: SubAccount[];
}

interface WalletAddSubAccountResponse {
  address: `0x${string}`;
  factory?: `0x${string}`;
  factoryData?: `0x${string}`;
}

export default function SubAccountDemo() {
  const [provider, setProvider] = useState<ReturnType<
    ReturnType<typeof createBaseAccountSDK>["getProvider"]
  > | null>(null);
  const [subAccount, setSubAccount] = useState<SubAccount | null>(null);
  const [universalAddress, setUniversalAddress] = useState<string>("");
  const [connected, setConnected] = useState(false);
  const [loadingSubAccount, setLoadingSubAccount] = useState(false);
  const [loadingUniversal, setLoadingUniversal] = useState(false);
  const [status, setStatus] = useState("");

  // SDK 및 크립토 계정 초기화
  useEffect(() => {
    const initializeSDK = async () => {
      try {
        const sdkInstance = createBaseAccountSDK({
          appName: "서브 어카운트 데모",
          appChainIds: [baseSepolia.id],
        });

        // 프로바이더 가져오기
        const providerInstance = sdkInstance.getProvider();
        setProvider(providerInstance);

        setStatus("SDK 초기화됨 - 연결 준비 완료");
      } catch (error) {
        console.error("SDK initialization failed:", error);
        setStatus("SDK 초기화 실패");
      }
    };

    initializeSDK();
  }, []);

  const connectWallet = async () => {
    if (!provider) {
      setStatus("프로바이더가 초기화되지 않았습니다");
      return;
    }

    setLoadingSubAccount(true);
    setStatus("지갑 연결 중...");

    try {
      // 지갑 연결
      const accounts = (await provider.request({
        method: "eth_requestAccounts",
        params: [],
      })) as string[];

      const universalAddr = accounts[0];
      setUniversalAddress(universalAddr);
      setConnected(true);

      // 기존 서브 어카운트 확인
      const response = (await provider.request({
        method: "wallet_getSubAccounts",
        params: [
          {
            account: universalAddr,
            domain: window.location.origin,
          },
        ],
      })) as GetSubAccountsResponse;

      const existing = response.subAccounts[0];
      if (existing) {
        setSubAccount(existing);
        setStatus("연결됨! 기존 서브 어카운트 발견");
      } else {
        setStatus("연결됨! 기존 서브 어카운트 없음");
      }
    } catch (error) {
      console.error("Connection failed:", error);
      setStatus("연결 실패");
    } finally {
      setLoadingSubAccount(false);
    }
  };

  const createSubAccount = async () => {
    if (!provider) {
      setStatus("프로바이더가 초기화되지 않았습니다");
      return;
    }

    setLoadingSubAccount(true);
    setStatus("서브 어카운트 생성 중...");

    try {
      const newSubAccount = (await provider.request({
        method: "wallet_addSubAccount",
        params: [
          {
            account: {
              type: 'create',
            },
          }
        ],
      })) as WalletAddSubAccountResponse;

      setSubAccount(newSubAccount);
      setStatus("서브 어카운트가 성공적으로 생성됨!");
    } catch (error) {
      console.error("Sub Account creation failed:", error);
      setStatus("서브 어카운트 생성 실패");
    } finally {
      setLoadingSubAccount(false);
    }
  };

  const sendCalls = useCallback(
    async (
      calls: Array<{ to: string; data: string; value: string }>,
      from: string,
      setLoadingState: (loading: boolean) => void
    ) => {
      if (!provider) {
        setStatus("프로바이더를 사용할 수 없습니다");
        return;
      }

      setLoadingState(true);
      setStatus("콜 전송 중...");

      try {
        const callsId = (await provider.request({
          method: "wallet_sendCalls",
          params: [
            {
              version: "2.0",
              atomicRequired: true,
              chainId: `0x${baseSepolia.id.toString(16)}`, // 16진수로 변환
              from,
              calls,
              capabilities: {
                // https://docs.cdp.coinbase.com/paymaster/introduction/welcome
                // paymasterUrl: "your paymaster url",
              },
            },
          ],
        })) as string;

        setStatus(`콜 전송됨! 콜 ID: ${callsId}`);
      } catch (error) {
        console.error("Send calls failed:", error);
        setStatus("콜 전송 실패");
      } finally {
        setLoadingState(false);
      }
    },
    [provider]
  );

  const sendCallsFromSubAccount = useCallback(async () => {
    if (!subAccount) {
      setStatus("서브 어카운트를 사용할 수 없습니다");
      return;
    }

    const calls = [
      {
        to: "0x4bbfd120d9f352a0bed7a014bd67913a2007a878",
        data: "0x9846cd9e", // yoink
        value: "0x0",
      },
    ];

    await sendCalls(calls, subAccount.address, setLoadingSubAccount);
  }, [sendCalls, subAccount]);

  const sendCallsFromUniversal = useCallback(async () => {
    if (!universalAddress) {
      setStatus("범용 계정을 사용할 수 없습니다");
      return;
    }

    const calls = [
      {
        to: "0x4bbfd120d9f352a0bed7a014bd67913a2007a878",
        data: "0x9846cd9e", // yoink
        value: "0x0",
      },
    ];

    await sendCalls(calls, universalAddress, setLoadingUniversal);
  }, [sendCalls, universalAddress]);

  return (
    <div className="sub-account-demo">
      <h2>서브 어카운트 데모</h2>

      <div className="status">
        <p>
          <strong>상태:</strong> {status}
        </p>
        {universalAddress && (
          <p>
            <strong>범용 계정:</strong> {universalAddress}
          </p>
        )}
        {subAccount && (
          <p>
            <strong>서브 어카운트:</strong> {subAccount.address}
          </p>
        )}
      </div>

      <div className="actions">
        {!connected ? (
          <button
            onClick={connectWallet}
            disabled={loadingSubAccount || !provider}
            className="connect-btn"
          >
            {loadingSubAccount ? "연결 중..." : "지갑 연결"}
          </button>
        ) : !subAccount ? (
          <button
            onClick={createSubAccount}
            disabled={loadingSubAccount}
            className="create-btn"
          >
            {loadingSubAccount ? "생성 중..." : "서브 어카운트 추가"}
          </button>
        ) : (
          <div>
            <button
              onClick={sendCallsFromSubAccount}
              disabled={loadingSubAccount}
              className="sub-account-btn"
            >
              {loadingSubAccount ? "전송 중..." : "서브 어카운트에서 콜 전송"}
            </button>
            <button
              onClick={sendCallsFromUniversal}
              disabled={loadingUniversal}
              className="universal-btn"
            >
              {loadingUniversal
                ? "전송 중..."
                : "범용 계정에서 콜 전송"}
            </button>
          </div>
        )}
      </div>

      <style jsx>{`
        .sub-account-demo {
          max-width: 600px;
          margin: 0 auto;
          padding: 20px;
          font-family: Arial, sans-serif;
        }

        .status {
          border-radius: 8px;
          margin: 20px 0;
        }

        .status p {
          margin: 5px 0;
        }

        .actions {
          margin: 20px 0;
        }

        .connect-btn,
        .create-btn,
        .sub-account-btn,
        .universal-btn {
          background: #0052ff;
          color: white;
          border: none;
          padding: 12px 24px;
          border-radius: 8px;
          cursor: pointer;
          font-size: 16px;
          margin-right: 15px;
          margin-bottom: 10px;
        }

        .connect-btn:disabled,
        .create-btn:disabled,
        .sub-account-btn:disabled,
        .universal-btn:disabled {
          background: #ccc;
          cursor: not-allowed;
        }

        .connect-btn:hover:not(:disabled),
        .create-btn:hover:not(:disabled),
        .sub-account-btn:hover:not(:disabled),
        .universal-btn:hover:not(:disabled) {
          background: #0041cc;
        }
      `}</style>
    </div>
  );
}
```

## 동영상 가이드

<iframe width="560" height="315" src="https://www.youtube.com/embed/M2WQ3astqQo?si=IUiHVmOrPFL-kVCL" title="YouTube 동영상 플레이어" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />
