React Native 모바일 지갑 연동: Solana MWA와 Reown AppKit 아키텍처 비교 및 세

삼코딩

@samcoding

React Native 모바일 지갑 연동: Solana MWA와 Reown AppKit 아키텍처 비교 및 세션 관리 패턴

React Native 모바일 지갑 연동: Solana MWA와 Reown AppKit 아키텍처 비교 및 세션 관리 패턴

리액트 네이티브 환경에서 모바일 디앱을 개발할 때 가장 까다로운 장벽은 지갑 연동과 세션 유지입니다. 데스크톱 브라우저와 달리 모바일 환경은 운영체제 수준의 백그라운드 제약이 엄격하고 확장 프로그램을 통한 프로바이더 주입이 원천 차단되기 때문입니다. 이 글에서는 솔라나 모바일 지갑 어댑터(MWA)와 EVM 생태계의 대표 주자인 리온 앱킷(Reown AppKit)의 핵심 아키텍처 차이를 비교하고, 사용자가 불편함을 느끼지 않도록 끊김 없는 세션을 구현하는 실무 패턴을 살펴봅니다.

솔라나 MWA 아키텍처: 로컬 웹소켓과 보안 토큰 재인증

솔라나 모바일 지갑 어댑터는 안드로이드 환경에서 외부 중계 서버 없이 디앱과 지갑이 직접 통신할 수 있도록 로컬 웹소켓 서버를 구동합니다. 디앱이 인텐트 스키마를 호출하면 지갑 앱이 백그라운드에서 임의의 포트로 보안 웹소켓 서버를 열고, 디앱은 클라이언트로 여기에 직접 연결해 서명을 요청하는 방식입니다. 외부 레이턴시가 없어 통신 속도가 매우 빠르고 높은 수준의 로컬 보안을 보장합니다.

반면 iOS 환경에서는 이러한 로컬 웹소켓 서버 모델을 유지하기 어렵습니다. 애플의 정책상 앱이 백그라운드로 전환되면 약 30초 이내에 모든 백그라운드 프로세스와 네트워크 소켓 연결을 강제로 차단하기 때문입니다. 이 때문에 모바일 어댑터의 영구적인 로컬 연결은 사실상 안드로이드 환경에 특화되어 작동합니다.

이러한 플랫폼 제약 속에서 매끄러운 사용자 경험을 제공하기 위해 모바일 지갑 어댑터는 일회성 연동 후 발급받은 인증 토큰을 캐싱하여 재사용하는 세션 재인증 패턴을 사용합니다. 첫 연결에서 권한을 얻은 뒤 토큰을 보안 저장소에 저장하고, 이후 트랜잭션이 필요할 때마다 재인증 API를 호출해 사용자 개입 없이 백그라운드에서 조용히 세션을 복원합니다.

리액트 네이티브 환경에서 이를 구현하는 핵심 흐름은 다음과 같습니다.

typescript
import { transact } from '@solana-mobile/mobile-wallet-adapter-protocol';
import * as SecureStore from 'expo-secure-store';

async function silentReauthorize() {
  const storedToken = await SecureStore.getItemAsync('mwa_auth_token');
  if (!storedToken) return null;

  return await transact(async (wallet) => {
    const result = await wallet.reauthorize({
      auth_token: storedToken,
      identity_uri: 'https://mydapp.com',
      identity_label: 'My Web3 App',
    });
    return result;
  });
}

이 방식을 활용하면 모바일 화면을 전환하거나 매번 번거로운 지갑 승인 팝업을 거치지 않고도 백그라운드에서 서명 세션을 효율적으로 유지할 수 있습니다.

리온 앱킷 아키텍처: 클라우드 릴레이와 직결 링크 모드

모바일 환경의 웹 브라우저는 보안상의 이유로 외부 확장 프로그램 방식의 프로바이더 주입을 차단합니다. 이 때문에 리온 앱킷은 디앱과 지갑이 직접 통신하는 대신, 중간에서 암호화된 메시지를 중계하는 클라우드 기반의 웹소켓 릴레이 서버를 기본 전송 계층으로 채택하여 이 제약을 우회합니다.

앱킷은 모바일 앱의 잦은 백그라운드 전환과 킬 이벤트 속에서도 안정적인 세션을 유지하기 위해 비동기 저장소를 활용합니다. 개발자가 @react-native-async-storage/async-storage 패키지를 프로젝트에 링크하고 앱킷의 스토리지 인스턴스로 넘겨주면, 앱킷은 활성화된 페어링 정보와 연결 파라미터를 영구 저장합니다. 이후 앱이 다시 켜질 때 이 데이터를 읽어와 이전 릴레이 세션의 웹소켓 핸드셰이크를 자동으로 재구성합니다.

typescript
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createAppKit, defaultWagmiConfig } from '@reown/appkit-wagmi-react-native';

const metadata = {
  name: 'My React Native dApp',
  url: 'https://mydapp.com',
  redirect: {
    native: 'mydapp://',
    universal: 'https://mydapp.com',
    linkMode: true // 링크 모드 활성화
  }
};

const wagmiConfig = defaultWagmiConfig({
  metadata,
  projectId: 'YOUR_PROJECT_ID',
  chains,
  storage: createStorage({
    storage: AsyncStorage
  })
});

클라우드 릴레이 방식은 안정적이지만, 매번 외부 중계 서버를 거쳐야 하므로 서명 요청 시 물리적인 네트워크 지연이 발생합니다. 이 병목을 해결하기 위해 도입된 기능이 바로 링크 모드입니다. 링크 모드는 웹소켓 릴레이 서버를 완전히 우회하여, iOS의 유니버설 링크와 안드로이드의 앱 링크를 통해 지갑 앱으로 서명 페이로드를 다이렉트로 넘겨주는 초저지연 직결 전송 프로토콜입니다. 릴레이 노드를 거치지 않기 때문에 터치 즉시 지갑 승인 팝업이 뜨는 데스크톱 수준의 빠른 반응 속도를 확보할 수 있습니다.

앱 정체성 검증: 도메인 자산 링크 파일 설정 규칙

모바일 dApp과 지갑이 안전하게 통신하려면 운영체제 수준에서 서로의 신원을 검증해야 합니다. 이 단계를 누락하면 지갑 앱에서 '신뢰할 수 없는 앱' 경고를 띄우거나, OS 단에서 딥링크가 차단되어 연동이 불완전해집니다. 이를 방지하려면 dApp의 웹 도메인과 모바일 앱의 서명 정보를 연동하는 도메인 자산 링크 파일 설정이 필수적입니다.

안드로이드에서 솔라나 MWA를 사용할 때는 dApp 웹 도메인의 .well-known/assetlinks.json 경로에 디지털 자산 링크 파일을 배포해야 합니다. 이 파일에는 dApp의 안드로이드 패키지 명과 SHA256 서명 인증서 지문을 명시해야 합니다. 지갑은 이 정보를 확인해 신원이 증명된 앱으로 인식하고 서명 팝업을 즉시 활성화합니다.

클라우드 릴레이 서버를 거치지 않고 유니버설 링크와 앱 링크를 직접 라우팅하는 리온 앱킷의 링크 모드를 도입할 때도 마찬가지입니다. dApp의 웹 서버에 apple-app-site-association 파일과 assetlinks.json을 올바르게 호스팅해 두고, dApp 메타데이터에 관련 속성을 설정해야 운영체제 수준의 직접 연결이 활성화됩니다.

typescript
const metadata = {
  name: 'My dApp',
  description: 'Mobile dApp',
  url: 'https://mydapp.com',
  icons: ['https://mydapp.com/icon.png'],
  redirect: {
    native: 'mydapp://',
    universal: 'https://mydapp.com'
  },
  linkMode: true // 링크 모드 활성화
};

실무 연동의 한계: 연속 서명 루프 버그와 극복 방법

모바일 디앱 개발에서 가장 흔히 발생하는 고질적인 장애는 연속적인 트랜잭션 서명 요청 시 지갑과 앱이 무한히 번갈아 열리는 루핑 오토 스위치 버그입니다. 리온 앱킷이나 일반적인 딥링크 방식을 사용하는 EVM 환경에서는 토큰 승인 후 즉시 계약 실행을 트리거하는 것처럼 연속된 서명이 필요할 때 이 문제가 발생합니다. 첫 번째 딥링크 요청이 완전히 완료되기 전에 두 번째 딥링크가 전송되면서 모바일 지갑이 서명 팝업을 띄우지 못하고 먹통이 되거나, 두 앱 사이에서 무한히 화면이 전환되는 상태에 갇히게 됩니다. 이 현상은 개발자 커뮤니티에서 APKT-3503이라는 식별자로 활발히 해결책이 논의되어 온 대표적인 제약입니다.

반면 솔라나 MWA 아키텍처는 로컬 웹소켓 연결 덕분에 연속 서명을 매끄럽게 처리합니다. 안드로이드 환경에서는 최초 1회 인텐트를 통해 로컬 웹소켓 세션이 수립되면, 이후의 모든 서명 요청이 운영체제 수준의 딥링크 전환 없이 단일 웹소켓 파이프라인을 타고 처리되기 때문에 무한 루프 버그에서 자유롭습니다.

EVM 환경에서 리온 앱킷을 사용하며 이 버그를 해결하려면 리액트 네이티브 코드 레벨에서 명시적인 연속 서명 지연이나 단계별 인터랙션 설계를 도입해야 합니다. 가장 효과적인 방법은 첫 번째 트랜잭션의 영수증이 확인된 후, 지갑 앱과 모바일 운영체제가 정상적으로 이전 세션을 정리하고 복귀할 수 있도록 약 2~3초의 대기 시간을 인위적으로 제공하는 것입니다.

typescript
// APKT-3503 루프 버그를 방지하기 위한 연속 서명 지연 처리 패턴
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

async function handleSequentialTransactions() {
  try {
    // 1단계: 토큰 승인 트랜잭션 전송 및 대기
    const approveHash = await writeContractAsync(approveConfig);
    await waitForTransactionReceipt({ hash: approveHash });
    
    // 지갑 앱이 정상적으로 복귀하고 앱킷 세션이 정돈될 시간 확보
    await delay(3000);
    
    // 2단계: 본 트랜잭션 실행
    const executeHash = await writeContractAsync(executeConfig);
    await waitForTransactionReceipt({ hash: executeHash });
  } catch (error) {
    console.error("순차 서명 중 오류 발생:", error);
  } 
}

기술적인 지연 처리와 더불어, UX 설계 관점에서도 하나의 버튼으로 두 서명을 연속 호출하기보다 '1단계 승인 완료'를 UI에 명확히 인지시킨 후 사용자가 직접 다음 단계를 실행하도록 유도하는 디자인 패턴을 결합하는 것이 프로덕션 안정성을 확보하는 지름길입니다.

플랫폼별 최적의 모바일 지갑 연동 전략

모바일 Web3 애플리케이션을 구축할 때는 타깃 생태계의 특성과 플랫폼별 제약 조건을 명확히 파악하고 아키텍처를 선택해야 합니다. 솔라나 중심의 서비스라면 보안 저장소 기반의 토큰 재인증 패턴을 활용해 안드로이드 사용자에게 지연 없는 최상의 사용자 경험을 제공하는 설계가 효과적입니다. 반면 EVM과 멀티체인 생태계를 아우르는 프로덕트라면 리온 앱킷의 비동기 저장소 세션 복원과 링크 모드를 선제적으로 적용해 릴레이 서버 병목과 연속 서명 루프 버그를 예방해야 합니다.


참고 링크

No comments yet.