> ## Documentation Index
> Fetch the complete documentation index at: https://whiterail.io/llms.txt
> Use this file to discover all available pages before exploring further.

# WhiterailSDK

> 이메일 OTP 인증과 Non-custodial 지갑 관리를 위한 메인 클라이언트 클래스

Whiterail SDK의 메인 클라이언트 클래스로 계정 관리, 인증, 결제 처리를 위한 포괄적인 기능을 제공합니다.

## Example

```typescript theme={null}
import { WhiterailSDK } from "@whiterail/sdk";

const sdk = new WhiterailSDK();

// 계정 생성 및 인증
const account = await sdk.createAccount({
  email: "user@example.com",
});

const auth = await sdk.initAuth({
  email: "user@example.com",
});

const result = await sdk.completeAuth({
  email: "user@example.com",
  otpCode: "123456",
});

// 결제 전송
const payment = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  currency: "USDC",
  amount: 100,
  network: "devnet",
});
```

## Properties

| Property  | Modifier  | Type                  |
| --------- | --------- | --------------------- |
| `baseUrl` | `private` | `string`              |
| `session` | `private` | `AuthSession \| null` |

## Methods

### Account Management

#### createAccount()

```typescript theme={null}
createAccount(params): Promise<CreateAccountResult>;
```

사용자를 위한 Turnkey sub-organization을 생성합니다. 이 메서드는 멱등성(idempotent)이므로 동일한 이메일로 여러 번 호출하면 기존 계정을 반환합니다.

##### Parameters

| Parameter      | Type                  | Description |
| -------------- | --------------------- | ----------- |
| `params`       | `CreateAccountParams` | 계정 생성 파라미터  |
| `params.email` | `string`              | 사용자 이메일 주소  |

##### Returns

`Promise<CreateAccountResult>`

생성된 계정 정보를 반환합니다.

```typescript theme={null}
{
  success: boolean;
  accountExists?: boolean;      // 계정이 이미 존재하면 true
  subOrganizationId?: string;   // Turnkey sub-org ID
  solanaAddress?: string;       // Solana 지갑 주소 (계정과 함께 생성됨)
  email?: string;               // 사용자 이메일
  error?: string;
}
```

Note: 계정 생성 시 Solana 지갑 주소도 함께 생성되어 반환됩니다.

##### Example

```typescript theme={null}
const result = await sdk.createAccount({
  email: "user@example.com",
});

if (result.success) {
  if (result.accountExists) {
    console.log("계정이 이미 존재합니다");
  } else {
    console.log("새 계정이 생성되었습니다");
  }
  console.log("Sub-Organization ID:", result.subOrganizationId);
  console.log("Solana 주소:", result.solanaAddress);
}
```

***

#### getAccount()

```typescript theme={null}
getAccount(identifier): Promise<AccountResult>;
```

이메일, subOrganizationId 또는 Solana 주소로 계정 정보를 조회합니다.

##### Parameters

| Parameter    | Type     | Description                         |
| ------------ | -------- | ----------------------------------- |
| `identifier` | `string` | 이메일, subOrganizationId 또는 Solana 주소 |

##### Returns

`Promise<AccountResult>`

계정 정보를 반환합니다.

```typescript theme={null}
{
  success: boolean;
  account?: {
    email: string;
    turnkeyId: string;
    solanaAddress: string;
    subOrganizationId: string;
  };
  error?: string;
}
```

##### Examples

**이메일로 조회**

```typescript theme={null}
const result = await sdk.getAccount("user@example.com");
```

**subOrganizationId로 조회**

```typescript theme={null}
const result = await sdk.getAccount("01234567-89ab-cdef-0123-456789abcdef");
```

**Solana 주소로 조회**

```typescript theme={null}
const result = await sdk.getAccount(
  "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
);
```

### Authentication

#### initAuth()

```typescript theme={null}
initAuth(params): Promise<InitAuthResult>;
```

사용자의 이메일로 OTP 코드를 전송합니다. 계정이 이미 존재해야 합니다(`createAccount`로 생성).

##### Parameters

| Parameter      | Type             | Description |
| -------------- | ---------------- | ----------- |
| `params`       | `InitAuthParams` | 인증 초기화 파라미터 |
| `params.email` | `string`         | 사용자 이메일 주소  |

##### Returns

`Promise<InitAuthResult>`

OTP 세션 정보를 반환합니다.

```typescript theme={null}
{
  success: boolean;
  otpId?: string;               // OTP 세션 ID
  subOrganizationId?: string;   // Turnkey sub-org ID
  error?: string;
}
```

##### Example

```typescript theme={null}
const result = await sdk.initAuth({
  email: "user@example.com",
});

if (result.success) {
  console.log("OTP가 이메일로 전송되었습니다");
  console.log("OTP ID:", result.otpId);
}
```

***

#### completeAuth()

```typescript theme={null}
completeAuth(params): Promise<CompleteAuthResult>;
```

OTP 코드를 검증하고 인증을 완료합니다. 첫 인증 시 Solana 지갑을 생성합니다.

##### Parameters

| Parameter        | Type                 | Description    |
| ---------------- | -------------------- | -------------- |
| `params`         | `CompleteAuthParams` | 인증 완료 파라미터     |
| `params.email`   | `string`             | 사용자 이메일 주소     |
| `params.otpCode` | `string`             | 이메일로 받은 6자리 코드 |

##### Returns

`Promise<CompleteAuthResult>`

인증 세션과 지갑 정보를 반환합니다.

```typescript theme={null}
{
  success: boolean;
  walletCreated?: boolean;      // 지갑이 생성되었으면 true
  turnkeyUserId?: string;       // Turnkey 사용자 ID
  subOrganizationId?: string;   // Turnkey sub-org ID
  solanaAddress?: string;       // Solana 지갑 주소
  authentication?: AuthSession; // 트랜잭션 서명용 세션
  error?: string;
}
```

Note: 세션은 30분 동안 유효합니다. 페이지를 새로고침하면 메모리가 지워지고 재인증이 필요합니다.

##### Example

```typescript theme={null}
const result = await sdk.completeAuth({
  email: "user@example.com",
  otpCode: "123456",
});

if (result.success) {
  console.log("지갑 주소:", result.solanaAddress);
  console.log("지갑 생성됨:", result.walletCreated);
  // 세션이 자동으로 저장됩니다
}
```

### Session Management

#### isAuthenticated()

```typescript theme={null}
isAuthenticated(): boolean;
```

사용자가 유효한 세션으로 인증되었는지 확인합니다. 임시 개인키가 메모리에 존재하고 세션이 만료되지 않은 경우에만 `true`를 반환합니다.

##### Returns

`boolean`

사용자가 트랜잭션에 서명할 수 있으면 `true`, 그렇지 않으면 `false`.

Note: 페이지를 새로고침하거나 30분이 지나면 `false`를 반환합니다. 이는 보안을 위한 설계입니다.

##### Example

```typescript theme={null}
if (sdk.isAuthenticated()) {
  console.log("✅ 사용자가 트랜잭션에 서명할 수 있습니다");
} else {
  console.log("❌ 재인증이 필요합니다");
  // 로그인 화면 표시
}
```

***

#### getSession()

```typescript theme={null}
getSession(): Omit<AuthSession, 'ephemeralPrivateKey'> | null;
```

현재 인증 세션을 반환합니다.

**보안**: 개인키(`ephemeralPrivateKey`)는 보안을 위해 제외됩니다. 개인키는 메모리에만 존재하며 외부로 노출되지 않습니다.

##### Returns

`Omit<AuthSession, 'ephemeralPrivateKey'> | null`

현재 세션 (개인키 제외) 또는 null.

```typescript theme={null}
{
  email: string;
  sessionToken: string;
  // ephemeralPrivateKey는 보안을 위해 반환되지 않음
  ephemeralPublicKeyCompressed: string;
  turnkeyUserId: string;
  subOrganizationId: string;
  expiresAt: number;
}
```

##### Example

```typescript theme={null}
const session = sdk.getSession();
if (session) {
  console.log("이메일:", session.email);
  console.log("만료 시간:", new Date(session.expiresAt));
  // Note: session.ephemeralPrivateKey는 보안을 위해 접근 불가
}
```

***

#### loadSession()

```typescript theme={null}
loadSession(): PersistedSession | null;
```

localStorage에서 세션을 불러옵니다.

**보안**: 개인키 없이 부분 세션만 반환합니다 (XSS 보호).

##### Returns

`PersistedSession | null`

개인키가 제외된 세션 또는 null.

```typescript theme={null}
{
  email: string;
  sessionToken: string;
  turnkeyUserId: string;
  subOrganizationId: string;
  ephemeralPublicKeyCompressed: string;
}
```

Note: 세션에 `ephemeralPrivateKey`가 포함되지 않습니다. 사용자는 이 세션으로 트랜잭션에 서명할 수 없으며, 새 임시 키페어를 얻기 위해 전체 인증 흐름을 완료해야 합니다.

##### Example

```typescript theme={null}
const session = sdk.loadSession();
if (session) {
  console.log("이전에 로그인한 사용자:", session.email);
  // 이메일 미리 채우기, 하지만 재인증 필요
}
```

***

#### saveSession()

```typescript theme={null}
saveSession(session): void;
```

인증 세션을 localStorage에 저장합니다.

**보안**: `ephemeralPrivateKey`는 localStorage에 **절대 저장되지 않습니다**. 개인키는 메모리에만 존재하며, XSS 공격으로부터 보호됩니다. localStorage에는 `PersistedSession` (개인키 제외)만 저장됩니다.

##### Parameters

| Parameter | Type          | Description               |
| --------- | ------------- | ------------------------- |
| `session` | `AuthSession` | 저장할 세션 객체 (개인키는 자동으로 제외됨) |

##### Returns

`void`

Note: 이 메서드는 인증 성공 후 자동으로 호출됩니다.

***

#### clearSession()

```typescript theme={null}
clearSession(): void;
```

현재 세션을 지우고 localStorage에서 제거합니다.

##### Returns

`void`

##### Example

```typescript theme={null}
const handleLogout = () => {
  sdk.clearSession();
  // 로그인 페이지로 리다이렉트
};
```

### Payment Processing

#### sendPayment()

```typescript theme={null}
sendPayment(params): Promise<PaymentResult>;
```

한 번의 메서드 호출로 결제를 전송합니다 (intent 생성 → 서명 → 제출).

##### Parameters

| Parameter             | Type                | Description           |
| --------------------- | ------------------- | --------------------- |
| `params`              | `SendPaymentParams` | 결제 파라미터               |
| `params.destination`  | `string`            | 수신자 Solana 주소         |
| `params.currency?`    | `string`            | 토큰 심볼 (USDC, USDT)    |
| `params.mintAddress?` | `string`            | 직접 민트 주소              |
| `params.amount`       | `number`            | 전송할 금액                |
| `params.decimals?`    | `number`            | 토큰 소수점 (기본값: 6)       |
| `params.feePayer?`    | `string`            | 수수료 지불자 주소 (기본값: 송신자) |
| `params.network?`     | `string`            | 네트워크 (기본값: 'devnet')  |

Note: `currency` 또는 `mintAddress` 중 하나는 반드시 제공해야 합니다.

##### Returns

`Promise<PaymentResult>`

트랜잭션 서명과 탐색기 URL을 반환합니다.

```typescript theme={null}
{
  success: boolean;
  signature?: string;      // 트랜잭션 서명
  explorerUrl?: string;    // Solscan 탐색기 URL
  error?: string;
}
```

##### Examples

**통화 심볼 사용**

```typescript theme={null}
const result = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  currency: "USDC",
  amount: 100,
  network: "devnet",
});
```

**직접 민트 주소 사용**

```typescript theme={null}
const result = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  mintAddress: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
  decimals: 6,
  amount: 100,
  network: "devnet",
});
```

**사용자 정의 수수료 지불자**

```typescript theme={null}
const result = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  currency: "USDC",
  amount: 100,
  feePayer: "FeePayerWalletAddress",
  network: "devnet",
});
```

Note: `feePayer`를 사용할 경우, 수수료 지불자 지갑도 트랜잭션에 서명해야 합니다.

***

#### createPaymentIntent()

```typescript theme={null}
createPaymentIntent(params): Promise<PaymentIntentResult>;
```

결제 intent를 생성하고 서명되지 않은 트랜잭션을 가져옵니다.

##### Parameters

| Parameter             | Type                        | Description          |
| --------------------- | --------------------------- | -------------------- |
| `params`              | `CreatePaymentIntentParams` | Payment intent 파라미터  |
| `params.destination`  | `string`                    | 수신자 Solana 주소        |
| `params.currency?`    | `string`                    | 토큰 심볼 (USDC, USDT)   |
| `params.mintAddress?` | `string`                    | 직접 민트 주소             |
| `params.amount`       | `number`                    | 전송할 금액               |
| `params.decimals?`    | `number`                    | 토큰 소수점 (기본값: 6)      |
| `params.feePayer?`    | `string`                    | 수수료 지불자 주소           |
| `params.network?`     | `string`                    | 네트워크 (기본값: 'devnet') |

##### Returns

`Promise<PaymentIntentResult>`

서명되지 않은 트랜잭션과 상세 정보를 반환합니다.

```typescript theme={null}
{
  success: boolean;
  paymentIntentId?: string;
  transaction?: string;     // Base64 서명되지 않은 트랜잭션
  details?: {
    source: string;
    destination: string;
    feePayer: string;
    sourceAta: string;
    destinationAta: string;
    amount: string;
    currency: string;
    mintAddress: string;
    decimals: number;
    needsAtaCreation: boolean;
    nonceAccount: string;
    nonce: string;
  };
  error?: string;
}
```

##### Example

```typescript theme={null}
const intent = await sdk.createPaymentIntent({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  currency: "USDC",
  amount: 100,
  network: "devnet",
});

if (intent.success) {
  console.log("Payment Intent ID:", intent.paymentIntentId);
  console.log("트랜잭션:", intent.transaction);
}
```

***

#### signTransaction()

```typescript theme={null}
signTransaction(unsignedTransaction): Promise<SignResult>;
```

Turnkey API 프록시를 통한 클라이언트 측 서명으로 Solana 트랜잭션에 서명합니다.

**작동 방식:**

1. 임시 개인키로 트랜잭션 본문에 서명 (DER 인코딩 ECDSA)
2. 압축된 공개키 + 서명으로 X-Stamp 헤더 생성
3. SDK 서버 프록시 엔드포인트 호출 (`/transaction/sign`)
4. SDK 서버가 Turnkey API로 요청 전달 (CORS 회피, 보안 향상)
5. 서명된 트랜잭션 반환

##### Parameters

| Parameter             | Type     | Description               |
| --------------------- | -------- | ------------------------- |
| `unsignedTransaction` | `string` | Base64로 인코딩된 서명되지 않은 트랜잭션 |

##### Returns

`Promise<SignResult>`

서명된 트랜잭션을 반환합니다.

```typescript theme={null}
{
  success: boolean;
  signedTransaction?: string;
  error?: string;
}
```

Note: 이 메서드는 `sendPayment()`에서 자동으로 호출됩니다. 대부분의 사용자는 직접 호출할 필요가 없습니다.

##### Example

```typescript theme={null}
const intent = await sdk.createPaymentIntent({
  /* ... */
});
const signed = await sdk.signTransaction(intent.transaction);

if (signed.success) {
  console.log("서명된 트랜잭션:", signed.signedTransaction);
}
```

***

#### submitTransaction()

```typescript theme={null}
submitTransaction(params): Promise<SubmitResult>;
```

서명된 트랜잭션을 네트워크에 제출합니다.

##### Parameters

| Parameter                  | Type                      | Description                 |
| -------------------------- | ------------------------- | --------------------------- |
| `params`                   | `SubmitTransactionParams` | 제출 파라미터                     |
| `params.paymentIntentId`   | `string`                  | createPaymentIntent에서 받은 ID |
| `params.signedTransaction` | `string`                  | Base64 서명된 트랜잭션             |
| `params.network`           | `string`                  | 네트워크                        |

##### Returns

`Promise<SubmitResult>`

트랜잭션 서명과 확인 상태를 반환합니다.

```typescript theme={null}
{
  success: boolean;
  signature?: string;
  explorerUrl?: string;
  confirmationStatus?: string;
  error?: string;
}
```

##### Example

```typescript theme={null}
const result = await sdk.submitTransaction({
  paymentIntentId: intent.paymentIntentId,
  signedTransaction: signed.signedTransaction,
  network: "devnet",
});

if (result.success) {
  console.log("서명:", result.signature);
  console.log("탐색기:", result.explorerUrl);
}
```

### Utilities

#### createEphemeralKeypair()

```typescript theme={null}
static createEphemeralKeypair(): Promise<EphemeralKeypair>;
```

임시 세션 인증을 위한 P-256 ECDSA 키페어를 생성합니다.

##### Returns

`Promise<EphemeralKeypair>`

생성된 키페어를 반환합니다.

```typescript theme={null}
{
  publicKey: string; // 압축되지 않음 (130 hex chars)
  publicKeyCompressed: string; // 압축됨 (66 hex chars) - Turnkey에 사용
  privateKey: string; // 개인키 (PKCS8 형식) - 서버로 절대 전송하지 말 것
}
```

Note: SDK는 `completeAuth()`에서 이를 자동으로 처리합니다. 대부분의 사용자는 직접 호출할 필요가 없습니다.

##### Example

```typescript theme={null}
import { createEphemeralKeypair } from "@whiterail/sdk";

const keypair = await createEphemeralKeypair();
console.log(keypair.publicKeyCompressed); // 백엔드로 전송 (66 hex chars)
console.log(keypair.privateKey); // 클라이언트 측에만 유지
```

## Complete Example

React 애플리케이션에서의 완전한 인증 및 결제 흐름:

```typescript theme={null}
import React, { useState, useEffect } from "react";
import { WhiterailSDK } from "@whiterail/sdk";

const sdk = new WhiterailSDK();

function App() {
  const [email, setEmail] = useState("");
  const [otpCode, setOtpCode] = useState("");
  const [step, setStep] = useState(1);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    // 인증 상태 확인
    if (sdk.isAuthenticated()) {
      setIsAuthenticated(true);
      return;
    }

    // 이전 세션 불러오기
    const session = sdk.loadSession();
    if (session) {
      setEmail(session.email);
      // 재인증 필요
    }
  }, []);

  const handleCreateAccount = async () => {
    // 1단계: 계정 생성
    const createResult = await sdk.createAccount({ email });
    if (!createResult.success) return;

    // 2단계: OTP 전송
    const initResult = await sdk.initAuth({ email });
    if (!initResult.success) return;

    setStep(2);
  };

  const handleVerifyOTP = async () => {
    // 3단계: OTP 검증 및 인증
    const result = await sdk.completeAuth({ email, otpCode });

    if (result.success) {
      console.log("지갑:", result.solanaAddress);
      setIsAuthenticated(true);
    }
  };

  const handleSendPayment = async () => {
    const payment = await sdk.sendPayment({
      destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
      currency: "USDC",
      amount: 10,
      network: "devnet",
    });

    if (payment.success) {
      console.log("결제 성공:", payment.signature);
    }
  };

  if (isAuthenticated) {
    return (
      <div>
        <h2>환영합니다!</h2>
        <button onClick={handleSendPayment}>10 USDC 전송</button>
        <button
          onClick={() => {
            sdk.clearSession();
            setIsAuthenticated(false);
          }}
        >
          로그아웃
        </button>
      </div>
    );
  }

  return (
    <div>
      {step === 1 ? (
        <div>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="이메일 입력"
          />
          <button onClick={handleCreateAccount}>계속</button>
        </div>
      ) : (
        <div>
          <input
            type="text"
            value={otpCode}
            onChange={(e) => setOtpCode(e.target.value)}
            placeholder="6자리 코드"
            maxLength="6"
          />
          <button onClick={handleVerifyOTP}>검증 및 로그인</button>
        </div>
      )}
    </div>
  );
}
```
