@to-nexus/crossx-sdk-react
v0.0.0-beta.1
Published
CROSSx React SDK - React Hooks and Components for Embedded Wallet
Downloads
86
Maintainers
Readme
@to-nexus/crossx-sdk-react
CROSSx Embedded Wallet을 React 앱에 통합하기 위한 Provider + Hooks 패키지.
wagmi를 사용하는 경우
@to-nexus/crossx-sdk-wagmi를 대신 사용하세요. 이 패키지는 wagmi 없이 순수 React만으로 CROSSx 지갑을 사용할 때 적합합니다.
패키지 구조
@to-nexus/crossx-sdk-core ← 핵심 SDK (인증, 서명, 트랜잭션)
@to-nexus/crossx-sdk-react ← React Hooks & Provider (이 패키지)
@to-nexus/crossx-sdk-wagmi ← wagmi Connector (wagmi 사용 시)설치
# pnpm (monorepo 내부)
pnpm add @to-nexus/crossx-sdk-react
# npm
npm install @to-nexus/crossx-sdk-reactpeerDependencies로 react ^18.0.0이 필요합니다.
환경 변수
.env 파일에 아래 값을 설정합니다. SDK 내부에서 자동으로 읽습니다.
# OAuth 서비스 URL (로그인용) — 필수
VITE_OAUTH_SERVICE_URL=https://dev-cross-wallet-oauth.crosstoken.io
# JWT 검증 API URL — 필수
VITE_AUTH_API_URL=https://dev-cross-auth.crosstoken.io
# Embedded Wallet Gateway URL (지갑 기능용) — 필수
VITE_WALLET_GATEWAY_URL=https://dev-embedded-wallet-gateway.crosstoken.io/api/v1URL은
SDKConfig에 노출하지 않습니다. 환경 변수로만 전환 가능합니다. (외부 개발사에는 위 설정을 알리지 않을 예정)
Quick Start
1. Provider 설정
앱 최상위에 CROSSxProvider를 배치합니다. config에는 SDKConfig 객체를 전달합니다.
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { CROSSxProvider } from '@to-nexus/crossx-sdk-react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<CROSSxProvider config={{}}>
<App />
</CROSSxProvider>
</React.StrictMode>
);Provider가 마운트되면 내부적으로 createCROSSxSDK(config) → sdk.init()을 자동 호출합니다.
기존 세션이 있으면 자동으로 복원됩니다.
2. 로그인 / 로그아웃 — useAuth
import { useAuth } from '@to-nexus/crossx-sdk-react';
function LoginButton() {
const { signIn, signOut, isAuthenticated, isLoading, error } = useAuth();
if (isLoading) return <p>처리 중...</p>;
return isAuthenticated ? (
<button onClick={signOut}>로그아웃</button>
) : (
<div>
<button onClick={signIn}>로그인</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}3. 서명 / 전송 — useWallet
import { useWallet } from '@to-nexus/crossx-sdk-react';
import { ChainId } from '@to-nexus/crossx-sdk-react';
function WalletActions() {
const { address, signMessage, sendTransaction, isLoading, error } = useWallet();
const handleSign = async () => {
// chainId는 CAIP-2 형식 문자열 (예: 'eip155:612055')
const result = await signMessage(ChainId.CROSS_MAINNET, 'Hello CROSSx!');
console.log('signature:', result.signature);
};
const handleSend = async () => {
const result = await sendTransaction(ChainId.CROSS_MAINNET, {
from: address!,
to: '0x920A31f0E48739C3FbB790D992b0690f7F5C42ea',
value: '0x2386f26fc10000', // 0.01 CROSS (hex wei)
gasLimit: '0x5208',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x3b9aca00',
});
console.log('txHash:', result.txHash);
};
return (
<div>
<p>주소: {address ?? '—'}</p>
<button onClick={handleSign} disabled={isLoading}>메시지 서명</button>
<button onClick={handleSend} disabled={isLoading}>전송</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}4. SDK 직접 접근 — useCROSSx
hooks로 커버되지 않는 기능(잔액 조회, nonce 조회, RPC 요청 등)은
useCROSSx로 SDK 인스턴스에 직접 접근합니다.
import { useCROSSx } from '@to-nexus/crossx-sdk-react';
function AdvancedPanel() {
const { sdk, isInitialized, isAuthenticated, walletAddress } = useCROSSx();
const handleGetBalance = async () => {
if (!sdk) return;
const { formatted } = await sdk.getBalance('eip155:612055');
console.log('잔액:', formatted, 'CROSS');
};
const handleSignTypedData = async () => {
if (!sdk) return;
const result = await sdk.signTypedData('eip155:612055', {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
primaryType: 'Permit',
domain: {
name: 'MyToken',
version: '1',
chainId: 612055,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
message: {
owner: walletAddress,
spender: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
value: '1000000000000000000',
nonce: '0',
deadline: '1234567890',
},
});
console.log('typed data signature:', result.signature);
};
const handleRpcCall = async () => {
if (!sdk) return;
// eth_call로 ERC-20 balanceOf 직접 호출
const data = '0x70a08231000000000000000000000000' +
walletAddress!.slice(2).toLowerCase().padStart(64, '0');
const raw = await sdk.rpcRequest(
'eth_call',
[{ to: '0x9f85c7b5d7637e18f946cc8af9c131318c6833d9', data }, 'latest'],
'eip155:612044'
);
console.log('balanceOf result:', raw);
};
if (!isInitialized) return <p>초기화 중...</p>;
return (
<div>
<p>인증: {isAuthenticated ? '✅' : '❌'}</p>
<p>주소: {walletAddress ?? '—'}</p>
<button onClick={handleGetBalance}>잔액 조회</button>
<button onClick={handleSignTypedData}>EIP-712 서명</button>
<button onClick={handleRpcCall}>eth_call (tUSDT)</button>
</div>
);
}API Reference
<CROSSxProvider>
| Prop | 타입 | 설명 |
|------|------|------|
| config | SDKConfig | SDK 설정 객체 |
| children | ReactNode | 자식 컴포넌트 |
SDKConfig 주요 필드:
| 필드 | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| theme | 'light' \| 'dark' | 'light' | 확인 모달 테마 |
| themeTokens | ConfirmationTokenOverride | — | 모달 색상 커스터마이징 |
| oauthDisplayMode | 'popup' \| 'modal' | 'popup' | OAuth 로그인 표시 방식 |
| useMockWallet | boolean | false | Mock 지갑 사용 (개발용) |
| debug | boolean | true | 디버그 로그 출력 |
ConfirmationTokenOverride 구조:
themeTokens: {
light?: ConfirmationThemeTokens; // 라이트 모드 오버라이드
dark?: ConfirmationThemeTokens; // 다크 모드 오버라이드
}ConfirmationThemeTokens 오버라이드 가능 필드:
| 필드 | Semantic 토큰 | 기본값 (light / dark) |
|------|------|------|
| primary | Primary | #019D92 / #019D92 |
| secondary | Secondary | #E70077 / #E70077 |
| onPrimary | OnPrimary | #FFFFFF / #FFFFFF |
| borderDefault | Border/Default | rgba(18,18,18,0.05) / rgba(255,255,255,0.05) |
| borderSubtle | Border/Subtle | rgba(18,18,18,0.1) / rgba(255,255,255,0.1) |
| textPrimary | TextIcon/Primary | #121212 / #FFFFFF |
| textSecondary | TextIcon/Secondary | rgba(18,18,18,0.7) / rgba(255,255,255,0.7) |
| textTertiary | TextIcon/Tertiary | rgba(18,18,18,0.5) / rgba(255,255,255,0.5) |
| surfaceDefault | Surface/default | rgba(18,18,18,0.05) / rgba(255,255,255,0.05) |
| bg | Surface/BG | #FFFFFF / #121212 |
useAuth()
인증(로그인/로그아웃/탈퇴) 관련 상태와 액션을 제공합니다.
const {
isAuthenticated, // boolean — 현재 인증 상태
isLoading, // boolean — 비동기 작업 진행 중
error, // string | null — 마지막 에러 메시지
signIn, // () => Promise<AuthResult> — OAuth 로그인
signOut, // () => Promise<void> — 로그아웃
withdraw, // () => Promise<void> — 계정 탈퇴 (데이터 삭제)
} = useAuth();signIn() 호출 시 OAuth 팝업/모달이 열립니다.
성공하면 내부적으로 window.location.reload()가 호출되어 Provider 상태가 갱신됩니다.
useWallet()
지갑 주소, 메시지 서명, 트랜잭션 전송 기능을 제공합니다.
const {
address, // string | null — 지갑 주소 (0x...)
isLoading, // boolean — 비동기 작업 진행 중
error, // string | null — 마지막 에러 메시지
signMessage, // (chainId, message) => Promise<SignMessageResult>
sendTransaction, // (chainId, tx) => Promise<TransactionResult>
} = useWallet();signMessage(chainId, message)
| 파라미터 | 타입 | 예시 |
|----------|------|------|
| chainId | string | 'eip155:612055' 또는 ChainId.CROSS_MAINNET |
| message | string | 'Hello CROSSx!' |
반환: { chainId, signature, message, address }
sendTransaction(chainId, tx)
| 파라미터 | 타입 | 설명 |
|----------|------|------|
| chainId | string | CAIP-2 체인 ID |
| tx | EvmTransactionRequest | 트랜잭션 객체 |
EvmTransactionRequest 주요 필드:
| 필드 | 타입 | 필수 | 설명 |
|------|------|:----:|------|
| from | string | O | 보내는 주소 |
| to | string | O | 받는 주소 |
| value | string | | hex wei (예: '0x2386f26fc10000') |
| data | string | | calldata hex |
| gasLimit | string | | gas limit hex |
| maxFeePerGas | string | | EIP-1559 max fee |
| maxPriorityFeePerGas | string | | EIP-1559 priority fee |
| nonce | number | | 트랜잭션 순서 |
반환: { chainId, txHash, status }
useCROSSx()
SDK 인스턴스와 전역 상태에 직접 접근합니다.
const {
sdk, // CROSSxSDK | null — SDK 인스턴스
isInitialized, // boolean — init() 완료 여부
isAuthenticated, // boolean — 인증 상태
walletAddress, // string | null — 지갑 주소
} = useCROSSx();sdk 인스턴스를 통해 호출할 수 있는 주요 메서드:
| 메서드 | 설명 |
|--------|------|
| sdk.signMessage(chainId, message) | EIP-191 메시지 서명 |
| sdk.signTypedData(chainId, typedData) | EIP-712 Typed Data 서명 |
| sdk.signTransaction(chainId, tx) | 트랜잭션 서명 (전송 없음) |
| sdk.sendTransaction(chainId, tx) | 트랜잭션 서명 + 전송 |
| sdk.sendTransactionAndWait(chainId, tx) | 전송 + Receipt 폴링 |
| sdk.getBalance(chainId) | 네이티브 잔액 조회 |
| sdk.getNonce(chainId) | 현재 nonce 조회 |
| sdk.rpcRequest(method, params, chainId) | 범용 JSON-RPC 호출 |
| sdk.getProvider(chainId) | EIP-1193 Provider 반환 |
| sdk.createWallet() | 수동 지갑 생성 |
| sdk.withdraw() | 계정 탈퇴 |
| sdk.setTheme('dark') | 확인 모달 테마 런타임 전환 (themeTokens 오버라이드 유지) |
ChainId 상수
체인 ID 오타 방지를 위한 상수를 제공합니다.
import { ChainId } from '@to-nexus/crossx-sdk-react';
ChainId.CROSS_MAINNET // 'eip155:612055'
ChainId.CROSS_TESTNET // 'eip155:612044'wagmi와의 차이
| | @to-nexus/crossx-sdk-react | @to-nexus/crossx-sdk-wagmi |
|---|---|---|
| 의존성 | React만 필요 | wagmi + viem + @tanstack/react-query |
| 접근 방식 | CROSSx 전용 hooks | wagmi 표준 hooks (useAccount, useConnect 등) |
| EIP-1193 | 필요 시 sdk.getProvider() | 자동 제공 (wagmi connector) |
| 적합 대상 | CROSSx 지갑만 사용하는 앱 | 멀티 지갑(MetaMask 등) 지원 앱 |
wagmi를 사용하는 경우 @to-nexus/crossx-sdk-wagmi의 createCROSSxConnector를 사용하세요.
wagmi 예제는 examples/wagmi-app을 참고하세요.
확인 모달 (Confirmation)
서명/전송 시 자동으로 확인 모달이 표시됩니다.
- Message Sign — "Signature Request" 모달 (Cancel / Confirm)
- EIP-712 Typed Data — 구조화된 key-value 표시
- Transaction Sign/Send — 수신자, 금액, 수수료 표시
모바일(480px 이하)에서는 자동으로 하단 바텀시트로 전환됩니다.
테마 설정
초기화 시 고정:
<CROSSxProvider config={{ theme: 'dark' }}>런타임 전환:
const { sdk } = useCROSSx();
sdk?.setTheme('dark'); // 다음 모달부터 적용. themeTokens 오버라이드는 유지됨색상 커스터마이징
themeTokens.light / themeTokens.dark 로 각 모드를 독립적으로 오버라이드합니다.
지정하지 않은 항목은 해당 모드의 기본값을 유지합니다.
<CROSSxProvider
config={{
theme: 'light',
themeTokens: {
light: {
primary: '#FF6B35', // 버튼·강조색 (기본: #019D92)
bg: '#F5F0EB', // 카드 배경색 (기본: #FFFFFF)
},
dark: {
primary: '#FF6B35',
bg: '#1A0A00', // 카드 배경색 (기본: #121212)
},
},
}}
>특정 모드만 지정하는 것도 가능합니다.
// 라이트 모드만 커스터마이징
themeTokens: { light: { primary: '#FF6B35' } }마이그레이션 (자동)
이전 CROSSx 네이티브 앱 사용자가 로그인하면, 기존 백업이 감지될 경우 PIN 입력 팝업이 자동으로 표시되어 Embedded Wallet로 마이그레이션됩니다.
별도 코드 작성이 필요 없습니다.
트러블슈팅
useCROSSx must be used within CROSSxProvider
앱 최상위에 <CROSSxProvider>가 빠져있습니다.
OAuth 팝업이 열리지 않음
브라우저의 팝업 차단을 확인하세요.
또는 config.oauthDisplayMode = 'modal'로 변경하세요.
로그인 후 상태가 업데이트되지 않음
현재 useAuth().signIn()은 성공 시 window.location.reload()를 호출합니다.
SPA 라우터와 충돌하는 경우 useCROSSx()로 직접 상태를 관리하세요.
라이센스
MIT
