npm.io
1.4.2 • Published 1 week ago

@nexus-cross/dapp-ui

Licence
MIT
Version
1.4.2
Deps
7
Size
5.5 MB
Vulns
0
Weekly
0

@nexus-cross/dapp-ui

CROSS 생태계 DApp 공통 UI 컴포넌트 패키지.

공통 DApp 기능을 제공한다. 주요 진입점:

  • AppLauncher: 글로벌 서비스 메뉴
  • WalletInfo: 지갑 잔액·가격·등락률 조회 UI
  • WalletConnectModal: 지갑 연결 모달/드로어 UI
  • WalletPortfolio: 리워드·DEX 포지션 포트폴리오 UI
  • GetOneUsd: Swap·Bridge·Relay를 묶은 ONEUSD 획득 플로팅/버튼 위젯

이전 패키지 이름: cross-app-launcher (@nexus-cross/dapp-ui@0.0.1+ 부터 rename).

Install

npm install @nexus-cross/dapp-ui

Peer Dependencies

소비자 프로젝트에 아래 패키지가 이미 설치되어 있어야 한다.

  • react >= 18
  • react-dom >= 18
  • @tanstack/react-query >= 5
  • @radix-ui/react-popover >= 1
  • vaul >= 1

Import

모든 기능은 루트 경로에서 import한다.

import {
  AppLauncher,
  AppLauncherTrigger,
  AppLauncherContent,
  WalletInfo,
  WalletConnectModal,
  WalletPortfolio,
  GetOneUsd,
} from "@nexus-cross/dapp-ui";

AI-assisted integration

Give an AI coding agent AI_INTEGRATION.md as the implementation contract. It contains the canonical Get ONEUSD mount location, route-persistent floating transaction status rule, mode behavior, and the boundary between CrossGetOneUsd and the low-level GetOneUsd ports.


AppLauncher

S3에 호스팅된 글로벌 메뉴 메타데이터를 fetch하여 일관된 UI로 렌더링한다. Desktop에서는 Popover, Mobile에서는 오른쪽 Drawer로 자동 전환된다.

Quick Start
import {
  AppLauncher,
  AppLauncherTrigger,
  AppLauncherContent,
} from "@nexus-cross/dapp-ui";

function MyNav() {
  return (
    <AppLauncher env="stage">
      <AppLauncherTrigger />
      <AppLauncherContent />
    </AppLauncher>
  );
}

모든 prop은 optional이며, 별도의 CSS import 없이 스타일이 자동 적용된다.

<AppLauncher>

Root 컴포넌트. Context Provider + Popover/Drawer Root 역할.

Prop Type Default Description
env 'dev' | 'stage' | 'production' 'production' 환경에 따라 메뉴 아이템의 URL이 결정된다
theme 'dark' | 'light' 'dark' 다크/라이트 테마
mobileBreakpoint number 768 이 px 이하에서 Drawer로 전환
domain string window.location.hostname 내부/외부 링크 판별 기준 도메인
<AppLauncherTrigger>

Trigger 버튼. children 없이 사용하면 기본 dots grid 아이콘 버튼이 렌더링된다.

Prop Type Default Description
asChild boolean true true이면 자식 엘리먼트를 trigger로 사용 (Radix 패턴)
<AppLauncherContent>

메뉴 콘텐츠. 2열 그리드 레이아웃. 패키지가 디자인을 통제한다.

Prop Type Default Description
align 'start' | 'center' | 'end' 'end' 팝오버 정렬 위치 (Desktop only)
sideOffset number 12 trigger와의 간격 px (Desktop)
className string - 추가 CSS 클래스
  • 같은 도메인 → window.location.href로 이동 (풀 URL 사용)
  • 다른 도메인 → window.open으로 새 창 열기
  • 현재 페이지 → 클릭 무시 (Active State)
Hooks
import { useGlobalMenu } from "@nexus-cross/dapp-ui";

const { data, isLoading } = useGlobalMenu();

WalletInfo

지갑 주소를 받아 CROSS·BSC 두 체인의 토큰 잔액, 가격, 24시간 등락률을 조회하고 표시하는 Compound Component. Desktop에서는 Popover, Mobile에서는 Drawer로 자동 전환된다.

Dot notation 패턴(WalletInfo.Trigger, WalletInfo.Content 등)으로 사용한다.

Quick Start
import { WalletInfo } from "@nexus-cross/dapp-ui";

function MyWallet() {
  return (
    <WalletInfo
      env="stage"
      theme="dark"
      walletAddress="0xe0c8d120...c279"
      onCopyAddress={(address, success) => {
        if (success) toast.success("주소가 복사되었습니다");
      }}
    >
      <WalletInfo.Trigger />
      <WalletInfo.Content />

      {/* 선택: dapp별 네비게이션 메뉴 */}
      <WalletInfo.Nav position="top">
        <nav>
          <a href="/about">About</a>
          <a href="/faq">FAQ</a>
        </nav>
      </WalletInfo.Nav>

      {/* 선택: dapp별 푸터 (Disconnect 등) */}
      <WalletInfo.Footer>
        <button onClick={() => disconnect()}>Disconnect</button>
      </WalletInfo.Footer>
    </WalletInfo>
  );
}
<WalletInfo> (Root)

Context Provider + Popover/Drawer Root.

Prop Type Default Description
env 'dev' | 'stage' | 'production' 'production' API URL 및 chainId 결정
theme 'dark' | 'light' 'dark' 다크/라이트 테마
walletAddress string (필수) 토큰 잔액을 조회할 지갑 주소
mobileBreakpoint number 768 이 px 이하에서 Drawer로 전환
drawerDirection 'right' | 'bottom' | 'left' 'right' 모바일 Drawer 방향
modal boolean false true이면 배경 overlay 표시 (Drawer, Popover 공통)
showBalance boolean false 토큰 잔고 리스트 표시 여부
showForgeToken boolean false Forge(본딩커브) 토큰 보유 리스트 추가 표시 여부
showGameToken boolean true game 카테고리 토큰 표시 여부 (false이면 숨김)
showQR boolean true QR 코드 버튼/뷰 활성화 여부
onCopyAddress (address: string, success: boolean) => void - 주소 복사 콜백 (토스트 표시용)
onBuy () => void - 기본 액션 row의 Buy 클릭 핸들러
showOnePop boolean true 액션 row의 ONEpop 카드 + ONEpop 화면 노출 (아래 참조)
onDisconnect () => void - 지정 시 하단에 기본 Disconnect(Log Out) 버튼 렌더
disconnectLabel string 'Disconnect' 기본 Disconnect 버튼 라벨
open boolean - 외부에서 열림 상태를 제어 (controlled)
onOpenChange (open: boolean) => void - 열림 상태 변경 콜백
style WalletInfoStyle - CSS 커스텀 변수 오버라이드 (자동완성 지원)
<WalletInfo.Trigger>

Trigger 버튼. children 없이 사용하면 지갑 아이콘 + 축약 주소가 렌더링된다.

Prop Type Default Description
asChild boolean true true이면 자식 엘리먼트를 trigger로 사용
className string - 추가 CSS 클래스 (배경색 커스터마이징 등)
// 기본 트리거 (지갑 아이콘 + 축약 주소)
<WalletInfo.Trigger />

// 배경색 커스텀 (Tailwind 예시)
<WalletInfo.Trigger className="bg-emerald-900!" />

// 완전 커스텀 트리거
<WalletInfo.Trigger>
  <button>My Wallet</button>
</WalletInfo.Trigger>
<WalletInfo.Content>

지갑 정보 콘텐츠. 헤더, 주소 바, 토큰 리스트(가격·등락률·보유 수량·USD 환산가)를 렌더링한다.

Prop Type Default Description
align 'start' | 'center' | 'end' 'end' 팝오버 정렬 위치 (Desktop only)
sideOffset number 12 trigger와의 간격 px (Desktop)
className string - 추가 CSS 클래스 (배경색 등)
<WalletInfo.Nav>

dapp별 네비게이션 메뉴 슬롯. 스크롤 영역 외부에 고정 배치된다.

Prop Type Default Description
position 'top' | 'bottom' 'top' 토큰 리스트 위/아래 배치
children ReactNode (필수) 네비게이션 콘텐츠
<WalletInfo.Footer>

하단 고정 영역 슬롯. My Profile·Settings 같은 임의의 커스텀 버튼을 배치한다. wallet·QR 뷰에서만 노출되고 portfolio·send 뷰에서는 숨겨진다. 전용 버튼 배열 prop은 없고 children으로 직접 구성한다.

Log Out 처리는 레이아웃 목표에 따라 택일한다: (A) 단독 Disconnect만 필요하면 onDisconnect(본문 하단·우측 정렬 기본 버튼), (B) Profile/Settings/Log Out을 한 덩어리로 묶으려면 onDisconnect를 생략하고 세 버튼을 모두 Footer에 넣는다. 둘은 위치·정렬이 달라 함께 쓰면 Log Out만 떨어져 보이므로 동시에 쓰지 않는다.

Prop Type Default Description
children ReactNode (필수) 푸터 콘텐츠
ONEpop 화면

기본 액션 row는 Buy / Bridge / ONEpop / Portfolio 이고, Send는 총자산 금액 우측의 아이콘 버튼이다. ONEpop 카드를 누르면 소셜 핸들 드롭 진입 화면이 열린다.

이 화면은 렌더링만 한다 — dapp-ui는 @nexus-cross/pop(viem 필수)에 의존할 수 없고, one-pop-api /drops·/histories는 SIWE 서명을 요구하기 때문이다. 표시할 상태와 액션 목적지는 주입받는다.

Prop Type Description
showOnePop boolean ONEpop 카드/화면 노출 (기본 true)
onePopSummary OnePopSummary 수령 대기 수·합계, replies 카운트, 최근 활동. 미주입 시 온보딩 화면
onOnePopOpen () => void 화면 진입 시 호출 — 조회를 이 시점까지 미루는 트리거
onOnePopSend () => void Send POP 카드 + "Send your first POP!"
onOnePopClaim () => void Claim POPs 카드 + 수령 대기 배너
onOnePopActivity () => void Activity 카드 + "See all activity" + replies 배너
onePopLogoSrc string 브랜드 워드마크 이미지 (미지정 시 내장 SVG)
onePopHeroSrc string 히어로 일러스트 이미지 (미지정 시 내장 SVG)

미주입 콜백의 버튼은 비활성으로 보인다. Available 카드는 지갑이 이미 불러온 CROSS 체인 ONEUSD 잔액을 그대로 쓰고, Get ONEUSD ›는 onBuy(on-ramp)를 재사용한다.

@nexus-cross/connect-kit-react를 쓰고 onePopEnabled: true면 onePopSummary가 자동 주입된다. 자세한 내용은 docs/dapp-ui/onepop.md.

<WalletInfo
  walletAddress={address}
  showBalance
  onePopSummary={summary}
  onOnePopSend={() => router.push('/onepop/send')}
  onOnePopClaim={() => router.push('/onepop/claim')}
  onOnePopActivity={() => router.push('/onepop/activity')}
>

OnePopSummary의 금액 필드는 raw wei-scale 문자열(amountRaw / claimableTotalRaw) + decimals 로 받는다 — 표시 시 소수점 2자리 버림.

토큰 노출 정책
규칙 설명
필터링 보유 수량 > 0인 토큰만 표시
정렬 USD 환산가 내림차순
고정 노출 CROSSD(CROSS), CROSS(CROSS), CROSS(BSC)는 잔고 0이어도 항상 최상단 배치
Forge 토큰 showForgeToken={true} 시 Forge 토큰이 기존 리스트 아래에 추가 표시. balance API와 중복되는 토큰은 자동 제거 (balance 우선)
카테고리 필터 showGameToken={false} 시 game 카테고리 토큰 숨김 (pinned 토큰은 영향 없음)
숫자 포맷팅
대상 >= 1 < 1 처리
가격 소수점 2자리 소수점 4자리 버림(floor)
보유 수량 소수점 2자리 소수점 4자리 버림(floor)
USD 환산가 소수점 2자리 소수점 4자리 버림(floor)
등락률(양수) - - 소수점 2자리 버림(floor)
등락률(음수) - - 소수점 2자리 올림(ceil)
등락률(0) - - 부호 없이 0% 표시
Hooks
import {
  useTokenBalance,
  useTokenStats,
  USER_BALANCE_QUERY_KEY,
  TOKEN_STATS_QUERY_KEY,
} from "@nexus-cross/dapp-ui";

// 토큰 잔액 (CROSS + BSC 두 체인 병렬 fetch)
const { tokens, isLoading } = useTokenBalance("stage", "0xe0c8...");

// 토큰 가격/등락률
const { statsMap } = useTokenStats("stage");

// 트랜잭션 후 잔액 갱신
queryClient.invalidateQueries({ queryKey: [USER_BALANCE_QUERY_KEY] });
API

토큰 잔액과 가격은 CROSS Wallet API에서, Forge 토큰은 Bonding Curve API에서 자동으로 fetch된다.

Wallet API

엔드포인트 설명
/v1/public/token/balance 토큰 보유 수량 (체인별 호출)
/v1/public/token/stats 토큰 가격·등락률 (전체 조회)
/v1/public/token/info 토큰 카테고리 조회
Environment API Base URL CROSS Chain ID BSC Chain ID
dev / stage https://stg-wallet-server.crosstoken.io/api 612044 97
production https://wallet-server.crosstoken.io/api 612055 56

Forge API (showForgeToken={true} 시 사용)

엔드포인트 설명
/v1/users/{address}/holdings Forge 토큰 보유 리스트
/v1/tokens/{address} Forge 토큰 상세 (이미지 등)
Environment API Base URL
dev / stage https://stg-bonding-curve-api.crosstoken.io/api
production https://bonding-curve-api.crosstoken.io/api

WalletConnectModal

DApp별로 다른 지갑 연결 팝업 UI를 통합하는 Compound Component. Desktop에서는 Dialog, Mobile에서는 Drawer(Bottom Sheet)로 자동 전환된다. 지갑 연결 로직(Wagmi 등)은 외부에서 콜백으로 주입한다.

Dot notation 패턴(WalletConnectModal.Trigger, WalletConnectModal.Content)으로 사용한다.

Quick Start
import { WalletConnectModal } from "@nexus-cross/dapp-ui";

function ConnectButton() {
  return (
    <WalletConnectModal
      wallets={{
        cross_wallet: () => connect({ connectorType: "cross_wallet" }),
        cross_extension: () => connect({ connectorType: "cross_extension" }),
        metamask: () => connect({ connectorType: "metamask" }),
        binance: () => connect({ connectorType: "binance" }),
      }}
      socialProviders={{
        google: () => signInWithProvider("google"),
        apple: () => signInWithProvider("apple"),
      }}
    >
      <WalletConnectModal.Trigger />
      <WalletConnectModal.Content />
    </WalletConnectModal>
  );
}
<WalletConnectModal> (Root)

Context Provider + Dialog/Drawer Root.

Prop Type Default Description
wallets WalletHandlers (필수) 지갑별 연결 콜백 (key가 표시할 지갑 결정)
socialProviders SocialHandlers - Google/Apple 소셜 로그인 콜백
termsUrl string CROSS 공통 약관 Footer Terms 링크 URL (미설정 시 내부 폴백)
privacyUrl string CROSS 공통 정책 Footer Privacy 링크 URL (미설정 시 내부 폴백)
theme 'dark' | 'light' 'dark' 다크/라이트 테마
mobileBreakpoint number 768 이 px 이하에서 Drawer로 전환
drawerDirection 'bottom' | 'left' | 'right' | 'top' 'bottom' 모바일 Drawer 방향
dialogWidth string '480px' Dialog 너비
drawerMaxWidth string - Drawer 최대 너비
drawerMinWidth string - Drawer 최소 너비
style WalletConnectModalStyle - CSS 커스텀 변수 오버라이드 (자동완성 지원)
open boolean - Controlled mode: 외부에서 열림 상태 제어
onOpenChange (open: boolean) => void - 열림 상태 변경 콜백
WalletHandlers 타입
type WalletHandlers = Partial<Record<WalletId, () => void | Promise<void>>>;

// WalletId = 'cross_embedded' | 'cross_wallet' | 'cross_extension' | 'metamask' | 'binance' | 'verse8' | 'tron'

객체에 포함된 key의 지갑만 UI에 표시된다. key는 타입 추론이 된다.

SocialHandlers는 Google/Apple 소셜 로그인 행에만 사용된다.

type SocialHandlers = Partial<Record<'google' | 'apple', () => void | Promise<void>>>;
<WalletConnectModal.Trigger>

Trigger 버튼. children 없이 사용하면 기본 "Connect Wallet" 버튼이 렌더링된다.

Prop Type Default Description
asChild boolean auto true이면 자식 엘리먼트를 trigger로 사용
// 기본 트리거
<WalletConnectModal.Trigger />

// 커스텀 트리거
<WalletConnectModal.Trigger>
  <button className="my-btn">지갑 연결</button>
</WalletConnectModal.Trigger>
<WalletConnectModal.Content>

지갑 선택 콘텐츠. Featured 지갑, 일반 지갑 리스트, 도움말, 이용약관을 렌더링한다.

Prop Type Default Description
className string - 추가 CSS 클래스
스타일 커스터마이징

style prop으로 CSS 커스텀 변수를 오버라이드할 수 있다. WalletConnectModalStyle 타입이 자동완성을 지원한다.

<WalletConnectModal
  wallets={walletHandlers}
  style={{
    '--wcm-dialog-width': '500px',
    '--wcm-drawer-max-width': '500px',
    '--wcm-drawer-min-width': '325px',
    '--wcm-surface-bg': 'var(--color-localGray-0)',
    '--wcm-primary': '#7346f3',
  }}
>

사용 가능한 CSS 변수:

변수 설명
--wcm-primary 주요 색상
--wcm-secondary 보조 색상 (배지 등)
--wcm-surface-bg 모달 배경색
--wcm-surface-default 아이템 배경색
--wcm-surface-subtle Installed 배지 배경
--wcm-border-default 모달 테두리
--wcm-border-subtle 드로어 핸들 색상
--wcm-texticon-primary 제목 텍스트 색상
--wcm-texticon-secondary 본문 텍스트 색상
--wcm-texticon-tertiary 보조 텍스트 색상
--wcm-dialog-width Dialog 너비
--wcm-drawer-max-width Drawer 최대 너비
--wcm-drawer-min-width Drawer 최소 너비
EIP-6963 지갑 감지

패키지 내부에서 EIP-6963 프로토콜(eip6963:announceProvider)과 CROSS 커스텀 이벤트(eip6963:crossAnnounceProvider)를 통해 설치된 지갑을 자동 감지한다.

  • 감지된 지갑: "Installed" 배지 표시, 클릭 시 연결 콜백 실행
  • 미설치 지갑(installUrl 설정 시): "Install" 버튼 표시, 클릭 시 Chrome Web Store로 이동
// 감지 훅을 직접 사용할 수도 있다
import { useWalletDetect } from "@nexus-cross/dapp-ui";

const { wallets, isDetected, isLoading } = useWalletDetect();
if (isDetected("io.metamask")) {
  // MetaMask 설치됨
}
WALLET_REGISTRY

내장된 지갑 메타데이터. 아이콘, 이름, 설명, rdns 등이 포함되어 있다.

WalletId 이름 비고
cross_embedded CROSSx with Social Featured, Google/Apple 로그인
cross_wallet CROSSx 앱 승인 방식
cross_extension CROSSx Extension 브라우저 확장, 데스크톱 전용
metamask MetaMask 브라우저 확장
binance Binance 모바일 앱 / WalletConnect
verse8 Verse8 Wallet For Forge 배지
tron Tron Wallet For SHOP 배지

@nexus-cross/connect-kit-react를 쓰는 경우에는 직접 WalletConnectModal을 조립하지 않아도 된다. <CrossConnectKitProvider>가 이 컴포넌트를 기반으로 한 unified connect modal을 자동 렌더링하며, 지갑 필터링은 walletAllowlist / extraWallets props로 처리한다.


Styling

두 기능 모두 Vanilla CSS + CSS Custom Properties를 사용하며 별도 CSS import가 불필요하다.

Feature 클래스 접두사 Content Font Trigger Font
AppLauncher al- Inter Inter
WalletInfo wi- Sora inherit (dapp 상속)
WalletConnectModal wcm- inherit inherit (dapp 상속)

배경색 등을 Tailwind로 커스텀할 때는 !important가 필요할 수 있다:

<WalletInfo.Trigger className="bg-emerald-900!" />
<WalletInfo.Content className="bg-slate-900!" />

Development

npm install
npm run build       # 빌드
npm run dev         # watch 모드
npm run typecheck   # 타입 체크

Publishing

npm 배포는 GitHub Actions의 Publish to npm 워크플로우(workflow_dispatch)를 통해 수동으로 실행한다. 로컬에서 npm publish를 직접 실행하지 않는다.

브랜치 운영 룰
브랜치 배포 타입
master prod
develop, feature/*, 그 외 beta

prod 배포는 반드시 master 브랜치에서만 실행한다. 그 외 브랜치(develop, feature/* 등)에서는 beta로만 배포한다.

배포 절차
  1. GitHub 저장소 → Actions 탭 → 좌측 Publish to npm 워크플로우 선택
  2. 우측 상단 Run workflow 클릭
  3. 아래 입력 항목을 채우고 Run workflow 실행
항목 설명
Use workflow from 배포할 대상 브랜치 (prod은 master, beta는 develop/feature/* 등)
배포 타입 beta 또는 prod 중 선택 (브랜치 운영 룰에 맞게 선택)
배포할 버전 SemVer 형식의 base 버전 (예: 1.0.0, v 접두사 없이)
배포 타입별 동작
타입 입력 컨벤션 최종 버전 npm dist-tag Git 태그 GitHub Release
prod 현재 npm에 배포된 최신 정식 버전 + 1 (수동) 입력한 버전 그대로 latest v{version} 정식 릴리스
beta 0.0.0 고정 입력 0.0.0-beta.N 자동 beta v0.0.0-beta.N Pre-release
  • beta의 N은 GitHub Releases와 npm registry에 존재하는 최신 beta 번호 중 더 큰 값 + 1로 자동 계산되므로, base 버전은 항상 0.0.0으로 고정 입력해도 충돌하지 않는다.
  • prod는 npm 정책상 동일 버전 재배포가 불가능하므로, npm @nexus-cross/dapp-ui 또는 package.json의 현재 버전을 확인 후 patch/minor/major 단위로 1 증가시켜 입력한다.
  • 실행 흐름: SonarQube 스캔 → 버전 계산 → 빌드 & npm publish → GitHub Release 생성 → Slack 알림.
설치 예시
# 정식 버전 (prod로 배포된 최신)
npm install @nexus-cross/dapp-ui

# 최신 beta
npm install @nexus-cross/dapp-ui@beta

# 특정 beta 버전 고정
npm install @nexus-cross/dapp-ui@0.0.0-beta.12
주의 사항
  • prod 배포는 반드시 master 브랜치에서, beta 배포는 develop/feature/* 등 그 외 브랜치에서 실행한다.
  • 버전은 반드시 SemVer(x.y.z) 형식이어야 하며 v 접두사를 붙이지 않는다.
  • beta 배포는 항상 0.0.0을 입력하면 된다. (실제 publish 버전은 0.0.0-beta.N으로 자동 증가)
  • prod 배포는 동일 버전 재배포가 불가능(npm 정책)하므로, 현재 정식 버전에서 1을 올려 입력한다.
  • 정식 배포 전 beta로 먼저 검증한 뒤 prod로 올리는 흐름을 권장한다.
  • 배포 전 대상 브랜치의 변경 사항과 현재 package.json의 버전이 충돌하지 않는지 확인한다.