WalletKit 오류 처리
WalletKit은 공통 오류 코드 enum을 제공하지 않고 표준 예외로 실패를 전달합니다. 지갑 앱은 예외 경로와 사용자 거부(approved = false)를 서로 다른 분기로 다룹니다.
dApp 쪽에서 던지는 AppError·AppResultCode와 릴레이·프로바이더 오류는 AppKit 오류 처리에서 다룹니다.
오류 유형
| 계층 | 식별 방법 | 대표 사용 위치 |
|---|---|---|
| WalletKit | RelayRpcException, TimeoutCancellationException 등 표준 예외 | 지갑 앱의 페어링과 서명 응답 |
| 플랫폼 | 그 밖의 Throwable | Ktor, TLS, WebSocket, 주입한 의존성 |
Swift에서는 전파된 KMP 예외가 NSError로 감싸여 오므로 항상 catch 경로를 둡니다 — 감싸지 않으면 앱이 종료됩니다.
네트워크 작업은 Kotlin 비동기 작업의 취소를 삼키지 말고 호출자에게 전달하며, 자동 재연결 중에도 사용자 취소와 명시적 close()를 구분합니다.
WalletKit이 전달하는 예외
WalletKit(Kotlin Multiplatform)은 공통 오류 코드 enum 없이 표준 예외로 실패를 전달합니다. connect()는 @Throws(Throwable::class)로 선언되어 Swift에서 try/catch로 잡을 수 있습니다. 선언되지 않은 KMP 예외는 iOS 앱을 종료시키므로 지갑 앱은 항상 catch 경로를 둡니다.
RelayRpcException
AppKit RelayRpcError와 동일한 릴레이 JSON-RPC 코드 체계를 사용합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
code | Int | 릴레이 JSON-RPC 코드 |
rpcMessage | String | 릴레이가 반환한 메시지 |
isForbiddenTopic | Boolean | 코드 2403 여부 |
상황별 예외
| 상황 | 지갑 앱이 받는 오류 | 기본 동작 |
|---|---|---|
| 잘못된 페어링 URI | PairingUri.parse 예외 | — |
| 아직 생성되지 않은 토픽 구독 | RelayRpcException 2403 | SDK가 250ms 간격 최대 60회 재시도 후 실패 처리 |
| 세션 연결 제한 시간 초과 | TimeoutCancellationException | 기본 20초 |
| 서명 요청 제한 시간 초과 | TimeoutCancellationException | 기본 5분(signRequestTimeoutMs) |
Signer 구현 실패 | 구현이 던진 예외 | 호출자에게 그대로 전달 |
사용자가 서명 요청을 거부하면 WalletKit은 dApp에 EIP-1193 4001 오류 응답을 보내고 WalletSessionResult(approved = false)를 반환합니다. 거부는 예외가 아니므로 approved 필드로 분기합니다.
지갑 앱에서 오류 처리
- Kotlin (Android · JVM)
- Swift (iOS)
try {
val result = wallet.connect(uri, signer)
if (!result.approved) { /* 사용자 거부 */ }
} catch (e: RelayRpcException) {
if (e.isForbiddenTopic) { /* 2403 — 재시도는 SDK 내부에서 처리 */ }
else WalletKitLog.error("relay rpc ${e.code}", e)
} catch (e: Throwable) {
WalletKitLog.error("connect failed", e)
}
do {
let result = try await walletKit.connect(pairingUri: uri, signer: signer)
if !result.approved { /* 사용자 거부 */ }
} catch {
// Kotlin 예외는 NSError 로 감싸여 오므로
// userInfo["KotlinException"] 에서 원본 타입을 복원해 분기합니다.
if let rpc = (error as NSError).userInfo["KotlinException"] as? RelayRpcException {
if rpc.isForbiddenTopic { /* 2403 — 재시도는 SDK 내부에서 처리 */ }
else { WalletKitLog.shared.error(message: "relay rpc \(rpc.code)", cause: nil) }
} else {
WalletKitLog.shared.error(message: "connect failed", cause: nil)
}
}
WalletKit 응답과 AppKit 오류 대응
지갑 측 응답과 dApp 측 오류는 다음과 같이 대응됩니다.
| 사용자 행동 | 지갑(WalletKit) 응답 | dApp(AppKit)에서 받는 결과 |
|---|---|---|
| 서명 거부 | 태그 1109 {error:{code:4001}} | AppError(USER_REJECTED) |
| 연결 거부 | 응답 미전송, approved = false | 세션 Promise 대기 초과 시 SESSION_TIMEOUT |
| 아직 생성되지 않은 토픽 구독 | RelayRpcException 2403 후 재시도 | 없음. 토픽은 dApp이 생성 |
WalletKit은 서명 거부에만 릴레이 error 응답을 보내며, AppKit은 이를 성공 값이 아닌 USER_REJECTED 오류로 전달합니다. 연결 거부는 별도 응답을 보내지 않습니다.
제한 시간
| 경로 | 현재 기본값 | 공개 재정의 |
|---|---|---|
| 세션 연결 각 단계 | 20초 | WalletKitConfig.handshakeTimeoutMs |
| settle 이후 서명 요청 대기 | 5분 | WalletKitConfig.signRequestTimeoutMs |
| 토픽 구독 재시도 | 250ms 간격 최대 60회 | 재정의 불가 — handshakeTimeoutMs와 별개로 동작 |
두 대기 모두 초과 시 TimeoutCancellationException을 던집니다. 이 예외는 CancellationException의 하위 타입이므로, 취소를 그대로 상위로 전달하는 코드가 타임아웃까지 사용자 취소로 오분류할 수 있습니다 — 타임아웃 실패 화면이 필요하면 타입을 먼저 확인합니다.
dApp 측 제한 시간(릴레이 제안·settle 각 300초)은 AppKit 오류 처리에서 확인합니다.
재시도 원칙
| 상황 | 자동 재시도 | 이유 |
|---|---|---|
| 사용자 거부 | 아니요 | 새 사용자 의도가 필요 |
| 잘못된 페어링 URI | 아니요 | 동일 입력은 같은 결과 |
| 토픽 미생성(2403) | SDK 내부에서 처리 | dApp이 토픽을 만드는 짧은 경쟁 상태 |
| 세션 연결·서명 요청 제한 시간 | 새 세션만 | 기존 세션 상태를 보장할 수 없음 |
| 소켓 단절 | 전송이 자동 재연결 | 대기 중 도착한 요청은 재연결 후 재생됨 |
오류 로그에는 페어링 URI, 채널 키, 서명 요청 원문, 서명과 개인키를 남기지 않습니다.