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

Example

import { WhiterailSDK } from "@whiterail/sdk";

const sdk = new WhiterailSDK();

// 계정 생성 및 인증
const account = await sdk.createAccount({
  email: "[email protected]",
});

const auth = await sdk.initAuth({
  email: "[email protected]",
});

const result = await sdk.completeAuth({
  email: "[email protected]",
  otpCode: "123456",
});

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

Properties

PropertyModifierType
baseUrlprivatestring
sessionprivateAuthSession | null

Methods

Account Management

createAccount()

createAccount(params): Promise<CreateAccountResult>;
사용자를 위한 Turnkey sub-organization을 생성합니다. 이 메서드는 멱등성(idempotent)이므로 동일한 이메일로 여러 번 호출하면 기존 계정을 반환합니다.
Parameters
ParameterTypeDescription
paramsCreateAccountParams계정 생성 파라미터
params.emailstring사용자 이메일 주소
Returns
Promise<CreateAccountResult> 생성된 계정 정보를 반환합니다.
{
  success: boolean;
  accountExists?: boolean;      // 계정이 이미 존재하면 true
  subOrganizationId?: string;   // Turnkey sub-org ID
  solanaAddress?: string;       // Solana 지갑 주소 (계정과 함께 생성됨)
  email?: string;               // 사용자 이메일
  error?: string;
}
Note: 계정 생성 시 Solana 지갑 주소도 함께 생성되어 반환됩니다.
Example
const result = await sdk.createAccount({
  email: "[email protected]",
});

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

getAccount()

getAccount(identifier): Promise<AccountResult>;
이메일, subOrganizationId 또는 Solana 주소로 계정 정보를 조회합니다.
Parameters
ParameterTypeDescription
identifierstring이메일, subOrganizationId 또는 Solana 주소
Returns
Promise<AccountResult> 계정 정보를 반환합니다.
{
  success: boolean;
  account?: {
    email: string;
    turnkeyId: string;
    solanaAddress: string;
    subOrganizationId: string;
  };
  error?: string;
}
Examples
이메일로 조회
const result = await sdk.getAccount("[email protected]");
subOrganizationId로 조회
const result = await sdk.getAccount("01234567-89ab-cdef-0123-456789abcdef");
Solana 주소로 조회
const result = await sdk.getAccount(
  "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
);

Authentication

initAuth()

initAuth(params): Promise<InitAuthResult>;
사용자의 이메일로 OTP 코드를 전송합니다. 계정이 이미 존재해야 합니다(createAccount로 생성).
Parameters
ParameterTypeDescription
paramsInitAuthParams인증 초기화 파라미터
params.emailstring사용자 이메일 주소
Returns
Promise<InitAuthResult> OTP 세션 정보를 반환합니다.
{
  success: boolean;
  otpId?: string;               // OTP 세션 ID
  subOrganizationId?: string;   // Turnkey sub-org ID
  error?: string;
}
Example
const result = await sdk.initAuth({
  email: "[email protected]",
});

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

completeAuth()

completeAuth(params): Promise<CompleteAuthResult>;
OTP 코드를 검증하고 인증을 완료합니다. 첫 인증 시 Solana 지갑을 생성합니다.
Parameters
ParameterTypeDescription
paramsCompleteAuthParams인증 완료 파라미터
params.emailstring사용자 이메일 주소
params.otpCodestring이메일로 받은 6자리 코드
Returns
Promise<CompleteAuthResult> 인증 세션과 지갑 정보를 반환합니다.
{
  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
const result = await sdk.completeAuth({
  email: "[email protected]",
  otpCode: "123456",
});

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

Session Management

isAuthenticated()

isAuthenticated(): boolean;
사용자가 유효한 세션으로 인증되었는지 확인합니다. 임시 개인키가 메모리에 존재하고 세션이 만료되지 않은 경우에만 true를 반환합니다.
Returns
boolean 사용자가 트랜잭션에 서명할 수 있으면 true, 그렇지 않으면 false. Note: 페이지를 새로고침하거나 30분이 지나면 false를 반환합니다. 이는 보안을 위한 설계입니다.
Example
if (sdk.isAuthenticated()) {
  console.log("✅ 사용자가 트랜잭션에 서명할 수 있습니다");
} else {
  console.log("❌ 재인증이 필요합니다");
  // 로그인 화면 표시
}

getSession()

getSession(): Omit<AuthSession, 'ephemeralPrivateKey'> | null;
현재 인증 세션을 반환합니다. 보안: 개인키(ephemeralPrivateKey)는 보안을 위해 제외됩니다. 개인키는 메모리에만 존재하며 외부로 노출되지 않습니다.
Returns
Omit<AuthSession, 'ephemeralPrivateKey'> | null 현재 세션 (개인키 제외) 또는 null.
{
  email: string;
  sessionToken: string;
  // ephemeralPrivateKey는 보안을 위해 반환되지 않음
  ephemeralPublicKeyCompressed: string;
  turnkeyUserId: string;
  subOrganizationId: string;
  expiresAt: number;
}
Example
const session = sdk.getSession();
if (session) {
  console.log("이메일:", session.email);
  console.log("만료 시간:", new Date(session.expiresAt));
  // Note: session.ephemeralPrivateKey는 보안을 위해 접근 불가
}

loadSession()

loadSession(): PersistedSession | null;
localStorage에서 세션을 불러옵니다. 보안: 개인키 없이 부분 세션만 반환합니다 (XSS 보호).
Returns
PersistedSession | null 개인키가 제외된 세션 또는 null.
{
  email: string;
  sessionToken: string;
  turnkeyUserId: string;
  subOrganizationId: string;
  ephemeralPublicKeyCompressed: string;
}
Note: 세션에 ephemeralPrivateKey가 포함되지 않습니다. 사용자는 이 세션으로 트랜잭션에 서명할 수 없으며, 새 임시 키페어를 얻기 위해 전체 인증 흐름을 완료해야 합니다.
Example
const session = sdk.loadSession();
if (session) {
  console.log("이전에 로그인한 사용자:", session.email);
  // 이메일 미리 채우기, 하지만 재인증 필요
}

saveSession()

saveSession(session): void;
인증 세션을 localStorage에 저장합니다. 보안: ephemeralPrivateKey는 localStorage에 절대 저장되지 않습니다. 개인키는 메모리에만 존재하며, XSS 공격으로부터 보호됩니다. localStorage에는 PersistedSession (개인키 제외)만 저장됩니다.
Parameters
ParameterTypeDescription
sessionAuthSession저장할 세션 객체 (개인키는 자동으로 제외됨)
Returns
void Note: 이 메서드는 인증 성공 후 자동으로 호출됩니다.

clearSession()

clearSession(): void;
현재 세션을 지우고 localStorage에서 제거합니다.
Returns
void
Example
const handleLogout = () => {
  sdk.clearSession();
  // 로그인 페이지로 리다이렉트
};

Payment Processing

sendPayment()

sendPayment(params): Promise<PaymentResult>;
한 번의 메서드 호출로 결제를 전송합니다 (intent 생성 → 서명 → 제출).
Parameters
ParameterTypeDescription
paramsSendPaymentParams결제 파라미터
params.destinationstring수신자 Solana 주소
params.currency?string토큰 심볼 (USDC, USDT)
params.mintAddress?string직접 민트 주소
params.amountnumber전송할 금액
params.decimals?number토큰 소수점 (기본값: 6)
params.feePayer?string수수료 지불자 주소 (기본값: 송신자)
params.network?string네트워크 (기본값: ‘devnet’)
Note: currency 또는 mintAddress 중 하나는 반드시 제공해야 합니다.
Returns
Promise<PaymentResult> 트랜잭션 서명과 탐색기 URL을 반환합니다.
{
  success: boolean;
  signature?: string;      // 트랜잭션 서명
  explorerUrl?: string;    // Solscan 탐색기 URL
  error?: string;
}
Examples
통화 심볼 사용
const result = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  currency: "USDC",
  amount: 100,
  network: "devnet",
});
직접 민트 주소 사용
const result = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  mintAddress: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
  decimals: 6,
  amount: 100,
  network: "devnet",
});
사용자 정의 수수료 지불자
const result = await sdk.sendPayment({
  destination: "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
  currency: "USDC",
  amount: 100,
  feePayer: "FeePayerWalletAddress",
  network: "devnet",
});
Note: feePayer를 사용할 경우, 수수료 지불자 지갑도 트랜잭션에 서명해야 합니다.

createPaymentIntent()

createPaymentIntent(params): Promise<PaymentIntentResult>;
결제 intent를 생성하고 서명되지 않은 트랜잭션을 가져옵니다.
Parameters
ParameterTypeDescription
paramsCreatePaymentIntentParamsPayment intent 파라미터
params.destinationstring수신자 Solana 주소
params.currency?string토큰 심볼 (USDC, USDT)
params.mintAddress?string직접 민트 주소
params.amountnumber전송할 금액
params.decimals?number토큰 소수점 (기본값: 6)
params.feePayer?string수수료 지불자 주소
params.network?string네트워크 (기본값: ‘devnet’)
Returns
Promise<PaymentIntentResult> 서명되지 않은 트랜잭션과 상세 정보를 반환합니다.
{
  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
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()

signTransaction(unsignedTransaction): Promise<SignResult>;
Turnkey API 프록시를 통한 클라이언트 측 서명으로 Solana 트랜잭션에 서명합니다. 작동 방식:
  1. 임시 개인키로 트랜잭션 본문에 서명 (DER 인코딩 ECDSA)
  2. 압축된 공개키 + 서명으로 X-Stamp 헤더 생성
  3. SDK 서버 프록시 엔드포인트 호출 (/transaction/sign)
  4. SDK 서버가 Turnkey API로 요청 전달 (CORS 회피, 보안 향상)
  5. 서명된 트랜잭션 반환
Parameters
ParameterTypeDescription
unsignedTransactionstringBase64로 인코딩된 서명되지 않은 트랜잭션
Returns
Promise<SignResult> 서명된 트랜잭션을 반환합니다.
{
  success: boolean;
  signedTransaction?: string;
  error?: string;
}
Note: 이 메서드는 sendPayment()에서 자동으로 호출됩니다. 대부분의 사용자는 직접 호출할 필요가 없습니다.
Example
const intent = await sdk.createPaymentIntent({
  /* ... */
});
const signed = await sdk.signTransaction(intent.transaction);

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

submitTransaction()

submitTransaction(params): Promise<SubmitResult>;
서명된 트랜잭션을 네트워크에 제출합니다.
Parameters
ParameterTypeDescription
paramsSubmitTransactionParams제출 파라미터
params.paymentIntentIdstringcreatePaymentIntent에서 받은 ID
params.signedTransactionstringBase64 서명된 트랜잭션
params.networkstring네트워크
Returns
Promise<SubmitResult> 트랜잭션 서명과 확인 상태를 반환합니다.
{
  success: boolean;
  signature?: string;
  explorerUrl?: string;
  confirmationStatus?: string;
  error?: string;
}
Example
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()

static createEphemeralKeypair(): Promise<EphemeralKeypair>;
임시 세션 인증을 위한 P-256 ECDSA 키페어를 생성합니다.
Returns
Promise<EphemeralKeypair> 생성된 키페어를 반환합니다.
{
  publicKey: string; // 압축되지 않음 (130 hex chars)
  publicKeyCompressed: string; // 압축됨 (66 hex chars) - Turnkey에 사용
  privateKey: string; // 개인키 (PKCS8 형식) - 서버로 절대 전송하지 말 것
}
Note: SDK는 completeAuth()에서 이를 자동으로 처리합니다. 대부분의 사용자는 직접 호출할 필요가 없습니다.
Example
import { createEphemeralKeypair } from "@whiterail/sdk";

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

Complete Example

React 애플리케이션에서의 완전한 인증 및 결제 흐름:
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>
  );
}