본문으로 건너뛰기

AppKit 오류 처리

AppKit은 AppError를 던지며, 그 code(AppResultCode)는 버전이 바뀌어도 값이 유지되는 고정 코드이므로 분기 기준으로 사용할 수 있습니다. 그 밖에 저수준 릴레이는 전용 오류(RelayConnectionError, RelayRpcError)를 던지며, 사용자 정의 커넥터·fetch·WebSocket·플랫폼 라이브러리의 원본 오류는 그대로 전파될 수 있습니다.

지갑 쪽에서 발생하는 오류와 두 SDK의 코드 대응은 WalletKit 오류 처리에서 다룹니다.

오류 유형

계층식별 방법대표 사용 위치
AppKiterror instanceof AppError연결, 요청, 서명, 잔액, 모바일 복구
AppKit 릴레이RelayConnectionError, RelayRpcError저수준 WSS와 릴레이 JSON-RPC
프로바이더isProviderRpcError(error)직접 프로바이더 또는 사용자 정의 커넥터
플랫폼그 밖의 unknownfetch, TLS, WebSocket, 사용자 주입 의존성

AppError

AppError 정의

class AppError extends Error {
readonly code: AppResultCode;
readonly reason: unknown;

constructor(
code: AppResultCode,
message?: string,
reason?: unknown,
);
}
필드설명
name항상 AppError
code분기에 사용하는 안정적인 SDK 코드
message사람용 설명. 생략하면 코드 문자열
reason래핑한 원인 또는 백엔드 코드. 없을 수 있으며 모든 경로에서 보존되지는 않음

오류 처리 예제

import {
AppError,
AppResultCode,
type WalletConnection,
} from "@scope-connect/appkit";

export async function connectWithMessage(
connect: () => Promise<WalletConnection>,
): Promise<WalletConnection> {
try {
return await connect();
} catch (error) {
if (!(error instanceof AppError)) throw error;

switch (error.code) {
case AppResultCode.USER_REJECTED:
throw new Error("지갑에서 요청을 승인해 주세요.");
case AppResultCode.WALLET_NOT_FOUND:
throw new Error("지원 지갑을 설치하거나 다른 지갑을 선택해 주세요.");
default:
throw error;
}
}
}

AppResultCode

코드계층·발생 조건사용자 메시지재시도개발자 조치·안전한 로그
INVALID_CONFIG생성자 필수 옵션(clientId) 누락·공백, 초기화 전 사용, 체인 허용 목록 위반, 잘못된 페어링 URI, 모바일 딥링크 전략 없음, 네임스페이스 모호앱 설정을 확인해 주세요설정 수정 후메서드명, 누락 필드명, 체인 ID
WALLET_NOT_FOUND지갑 미설치, EIP-6963 지갑 알림 이벤트를 받지 못함, 지갑 목록에 없는 ID, 사용 가능한 커넥터 없음지갑을 설치하거나 다른 지갑을 선택해 주세요사용자 조치 후지갑 ID, 커넥터 ID
NOT_CONNECTED활성 계정·커넥터·릴레이 세션 없음, 재개할 대기 중 모바일 세션 없음먼저 지갑을 연결해 주세요연결 후메서드명, 연결 상태
USER_REJECTED사용자가 연결·체인 전환·요청·서명을 거부. EIP-1193 4001과 Phantom errorCode "4001"을 정규화요청이 취소되었습니다새 사용자 동작 후메서드명, 체인 ID. 요청 원문은 제외
UNSUPPORTED_METHOD커넥터나 네임스페이스가 작업을 지원하지 않음. 예: Solana 커넥터가 아닌 연결에서 signSolanaTransaction()이 지갑에서는 지원하지 않는 작업입니다같은 조건에서는 아니요메서드명, 커넥터, 네임스페이스
INVALID_RESPONSE65바이트가 아닌 서명, 계정 없이 연결된 세션, Phantom txSignature 누락·손상, 백엔드 비-JSON 응답응답을 확인할 수 없습니다일시 오류 확인 후 제한적으로응답 상태·필드명. 전체 응답 본문은 제외
RPC_ERROR잔액·프로젝트 설정 조회 실패, 선택 의존성(@solana/web3.js) 누락, 모바일 지갑이 보고한 오류네트워크 요청에 실패했습니다원인별전송 종류, 상태 코드, 체인 ID
UNSUPPORTED_CHAIN지갑이 지원하지 않는 네임스페이스의 chainId(예: MetaMask에 solana: 체인), 지원 목록에 없는 체인, switchNetwork()에 비-EVM 체인지원하지 않는 네트워크입니다지원 체인 선택 후CAIP-2 체인 ID
BACKEND_ERROR백엔드 요청 실패·비-200 응답, Phantom 모바일 폴링이 비-4001 errorCode를 보고(reason에 보존)잠시 후 다시 시도해 주세요코드와 멱등성 확인 후상태 코드, 비민감 요청 ID
SESSION_TIMEOUT릴레이 메시지 대기 초과, Phantom 모바일 폴링 제한 시간 초과(기본 120초)세션이 만료되었습니다새 세션으로단계, 경과 시간, 지갑 ID

주소, 페어링 URI, 채널 키, 서명 원문, 개인키, Client Secret은 오류 로그에 남기지 않습니다.

메서드별 오류

각 공개 메서드가 던지는 코드는 JSDoc @throws 선언을 기준으로 합니다.

메서드발생하는 AppResultCode그 밖에 전달될 수 있는 오류
connect(options?)INVALID_CONFIG(초기화 전, 허용 목록 밖 체인), WALLET_NOT_FOUND사용자 정의 커넥터 오류
selectWallet(walletId, options?)INVALID_CONFIG, WALLET_NOT_FOUND, UNSUPPORTED_CHAIN, USER_REJECTED, BACKEND_ERROR, RPC_ERROR, SESSION_TIMEOUT, INVALID_RESPONSE모바일 세션·선택 의존성 오류
pair(options)INVALID_CONFIG(초기화 전), BACKEND_ERROR, INVALID_RESPONSE호출 단계의 RelayConnectionError, 세션 PromiseRelayRpcError·SESSION_TIMEOUT
switchNetwork(chainId)NOT_CONNECTED, UNSUPPORTED_CHAIN, INVALID_CONFIG(허용 목록 밖), USER_REJECTED4902 발생 후 체인 추가까지 실패하면 프로바이더 오류
addNetwork(input, options?)NOT_CONNECTED(지갑 미선택), UNSUPPORTED_METHOD(릴레이·Klip A2A·비-EVM 연결), INVALID_CONFIG(EIP-3085 파라미터 위반, switch 시 허용 목록 밖), UNSUPPORTED_CHAIN, USER_REJECTED지갑의 -32602 등 프로바이더 오류
request(args)NOT_CONNECTED(릴레이 세션 미수립 포함)프로바이더·커넥터 자체 오류
signTransferAuthorization(typedData, options?)NOT_CONNECTED, INVALID_CONFIG, INVALID_RESPONSE, USER_REJECTED릴레이 오류
signSolanaTransaction(transaction, options?)NOT_CONNECTED, UNSUPPORTED_METHOD, INVALID_CONFIG, USER_REJECTED, RPC_ERROR, INVALID_RESPONSE, BACKEND_ERROR, SESSION_TIMEOUT선택 의존성·모바일 세션 오류
getBalance(query)NOT_CONNECTED(계정 없음), UNSUPPORTED_CHAIN, RPC_ERROR주입한 fetch 원인
resumeSolanaConnect(params)NOT_CONNECTED, USER_REJECTED, BACKEND_ERROR, WALLET_NOT_FOUND, SESSION_TIMEOUT, INVALID_RESPONSE세션 어댑터 오류
resumeSolanaSignTransaction(params)NOT_CONNECTED, USER_REJECTED, BACKEND_ERROR, SESSION_TIMEOUT, INVALID_RESPONSE세션 어댑터 오류
resumeSolanaMobileFromCallback(query)USER_REJECTED, BACKEND_ERROR, SESSION_TIMEOUT, WALLET_NOT_FOUND, INVALID_RESPONSE세션 어댑터 오류
resumeUniversalLink()INVALID_CONFIG(초기화 전), USER_REJECTED, RPC_ERROR, SESSION_TIMEOUT, BACKEND_ERROR
config (getter)INVALID_CONFIG(초기화 전 접근)
disconnect()고정 코드 없음사용자 정의 커넥터 거부, 플랫폼 오류

request()init() 여부를 먼저 검사하지 않습니다. 연결이 없으면 INVALID_CONFIG이 아니라 NOT_CONNECTED입니다. 반대로 getBalance()는 계정과 백엔드 설정이 있으면 연결이나 초기화 없이도 실행할 수 있습니다.

AppKit 릴레이 오류

RelayConnectionError

WebSocket 연결 또는 종료를 나타냅니다.

class RelayConnectionError extends Error {
readonly closeCode: number;
}
closeCode의미재시도
4401릴레이 인증 실패 또는 만료 토큰토큰 재발급 후 한 번
4403클라이언트 거부권한·환경 수정 후
1013과부하 또는 속도 제한지수 대기 후
-1열기 전 전송 오류 또는 로컬 종료원인 확인 후
그 밖의 값WebSocket 종료 코드코드 의미에 따라

릴레이 메시지를 기다리는 동안 소켓이 닫히면 제한 시간까지 기다리지 않고 즉시 RelayConnectionError로 거부됩니다. 제한 시간 초과는 AppErrorSESSION_TIMEOUT으로 전달됩니다.

RelayRpcError

릴레이 JSON-RPC 오류입니다.

class RelayRpcError extends Error {
readonly code: number;
readonly rpcMessage: string;
readonly isForbiddenTopic: boolean;
}
코드의미조치
2403인증된 dApp이 아직 만들지 않은 토픽에 지갑이 참여dApp 구독을 확인하고 짧게 제한 재시도
-32602잘못된 파라미터입력을 수정하고 같은 값으로 재시도하지 않음
그 밖의 값릴레이가 반환한 JSON-RPC 오류rpcMessage와 서버 정책 확인
import {
RelayConnectionError,
RelayRpcError,
} from "@scope-connect/appkit";

function relayRetryable(error: unknown): boolean {
if (error instanceof RelayConnectionError) {
return error.closeCode === 1013 || error.closeCode === -1;
}
return error instanceof RelayRpcError && error.isForbiddenTopic;
}

릴레이 토큰 만료

AppKit은 (재)연결 전에 만료가 임박한(5초 여유) 릴레이 토큰을 재발급해, 오래된 ?auth= 토큰이 WSS 핸드셰이크에서 4401로 거부되는 것을 예방합니다. 그럼에도 4401이 발생하면 시계 오차나 서버 측 무효화를 확인합니다.

프로바이더 오류

직접 EIP-1193 프로바이더를 호출하거나 사용자 정의 커넥터가 자체 오류를 전달하면 숫자 코드를 볼 수 있습니다.

interface ProviderRpcError {
code: number;
message?: string;
}

isProviderRpcError(value: unknown): value is ProviderRpcError
const USER_REJECTED_CODE = 4001

기본 AppKit EVM 커넥터는 MetaMask의 data.originalError.code처럼 중첩된 코드도 확인하고, 코드 4001을 AppError(USER_REJECTED)로 정규화합니다. AppKit 메서드를 호출한 코드는 AppError를 먼저 처리하고, 지갑이나 프로바이더가 직접 전달한 오류는 보조 분기로 둡니다. 공개 API로는 위의 isProviderRpcErrorUSER_REJECTED_CODE를 사용합니다.

코드 4902는 EVM 지갑이 대상 체인을 아직 모를 때 발생하며, 모바일 MetaMask의 테스트넷 전환에서 흔합니다. switchNetwork()는 이를 감지해 네트워크 기본값으로 wallet_addEthereumChain을 호출하고 다시 전환합니다.

import {
AppError,
AppResultCode,
isProviderRpcError,
USER_REJECTED_CODE,
} from "@scope-connect/appkit";

function wasRejected(error: unknown): boolean {
if (error instanceof AppError) {
return error.code === AppResultCode.USER_REJECTED;
}
return isProviderRpcError(error) && error.code === USER_REJECTED_CODE;
}

제한 시간

경로현재 기본값공개 재정의
AppKit 릴레이 제안 응답300초PairOptions에서 제공하지 않음
AppKit 릴레이 세션 settle300초PairOptions에서 제공하지 않음
Solana 모바일 폴링기본 120초기본 AppKit API에서 직접 재정의하지 않음

지갑 측 제한 시간(연결 20초, 서명 요청 5분)은 WalletKit 오류 처리에서 확인합니다.

제한 시간 뒤 기존 세션을 무조건 다시 사용하지 않습니다. 연결·페어링은 새 사용자 동작으로 시작하고, 트랜잭션 관련 작업은 기존 제출 여부를 먼저 확인해 중복 실행을 방지합니다.

재시도 원칙

상황자동 재시도이유
사용자 거부아니요새 사용자 의도가 필요
잘못된 설정·파라미터·지원하지 않는 체인아니요동일 입력은 같은 결과
속도 제한·일시 네트워크 오류제한적으로지수 대기와 상한 필요
릴레이 토픽 준비 지연제한적으로dApp이 토픽을 만드는 짧은 경쟁 상태일 수 있음
세션 제한 시간새 세션만기존 상태가 유효한지 보장할 수 없음
트랜잭션 제출 결과 불명자동 재제출 금지중복 전송 위험