설치
WalletKit은 지갑 앱이 임베드하는 Kotlin Multiplatform 라이브러리입니다. 이 문서는 라이브러리를 Android·iOS·JVM 대상에 연결하고 WalletKit 인스턴스를 만드는 데 필요한 최소 설정을 다룹니다.
현재 버전은 0.0.1입니다. iOS 패키지는 공개 Swift Package로 설치할 수 있습니다. Android·JVM 패키지는 아직 Maven Central에 게시하지 않았으며, 파트너 프리뷰 기간에는 SCOPE Connect 담당자를 통해 아티팩트를 전달합니다. 접근 권한과 릴레이·백엔드 엔드포인트, 지원 범위도 담당자와 협의합니다.
사전 요구 사항
| 대상 | 최소 요구 사항 | 비고 |
|---|---|---|
| Android | API 28, compile SDK 35 | ChaCha20-Poly1305 JCA는 API 28 이상에서 동작합니다 |
| iOS | iOS 16, arm64·simulator arm64 | 공개 Swift Package가 정적 XCFramework를 제공합니다 |
| JVM | JDK 21 | secp256k1-kmp JNI 아티팩트가 JVM 21을 요구합니다 |
현재 버전은 0.0.1입니다. Android·JVM에는 io.lambda256.scopeconnect:walletkit 좌표를 사용하지만 원격 저장소에는 아직 공개되지 않았습니다. 라이브러리 이름과 주요 API는 WalletKit API에서 확인합니다.
WalletKit 패키지 추가
플랫폼에 따라 패키지 준비 방식이 다릅니다.
| 대상 | 패키지 | 설치 경로 |
|---|---|---|
| Android · JVM | 파트너 전달 Maven 아티팩트 | 로컬 Maven 저장소(~/.m2) |
| iOS | 공개 Swift Package 0.0.1 | Xcode 또는 Package.swift |
Android·JVM의 Maven Central 발행 작업은 계정과 io.lambda256 네임스페이스 온보딩 뒤 진행됩니다. 그전까지 사용할 아티팩트와 설치 방법은 SCOPE Connect 담당자가 전달합니다.
Android · JVM (Gradle)
전달받은 아티팩트를 로컬 Maven 저장소(~/.m2)에 설치한 뒤, 로컬 Maven을 저장소로 추가하고 KMP 라이브러리 의존성을 선언합니다.
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
google()
}
}
// build.gradle.kts (Android 앱 또는 KMP 모듈)
dependencies {
implementation("io.lambda256.scopeconnect:walletkit:0.0.1")
}
Android는 minSdk를 28 이상으로 설정합니다. WalletKit은 JDK 암호 제공자가 X25519·Ed25519를 제공하지 않는 Android ART를 위해 BouncyCastle 제공자를 포함합니다. 채널 암호 동작은 일반 JVM 단위 테스트가 아니라 실기기·에뮬레이터 계측 테스트로 검증합니다.
iOS (Swift Package)
Xcode의 File › Add Package Dependencies에서 아래 공개 저장소 URL을 입력하고 버전 0.0.1을 선택합니다.
https://github.com/Lambda256/scope-connect-walletkit-swift
Swift 패키지에서 직접 선언할 때는 공개 저장소와 정확한 버전을 지정합니다. 패키지가 제공하는 WalletKit 라이브러리 제품을 앱 대상에 연결합니다.
dependencies: [
.package(
url: "https://github.com/Lambda256/scope-connect-walletkit-swift.git",
exact: "0.0.1"
),
]
세션을 시작하는 connect()는 @Throws(Throwable::class)로 선언되어 있습니다. Swift에서 반드시 try/catch로 감싸 호출하세요. 감싸지 않으면 전파된 KMP 예외가 iOS 앱을 종료시킵니다.
WalletKit 인스턴스 만들기
라이브러리를 연결한 뒤 WalletKit을 만드는 데 필요한 최소 설정은 표시 메타데이터 하나입니다.
import io.lambda256.scopeconnect.walletkit.WalletKit
import io.lambda256.scopeconnect.walletkit.WalletKitConfig
import io.lambda256.scopeconnect.walletkit.WalletMetadata
val wallet = WalletKit(
WalletKitConfig(metadata = WalletMetadata(name = "My Wallet")),
)
WalletKitConfig 옵션
| 필드 | 기본값 | 설명 |
|---|---|---|
metadata | 필수 | dApp의 연결 승인 화면에 표시할 지갑 정보. 현재는 name 하나입니다 |
handshakeTimeoutMs | 20_000 | 세션 연결 각 단계의 대기 시간(ms) |
signRequestTimeoutMs | 300_000 | 서명 요청 대기 시간(ms). dApp과 마찬가지로 사용자가 내용을 확인하고 승인할 수 있도록 5분간 기다립니다 |
userHash | null | 콘솔 분석용 설치 단위 식별값(선택). null이면 userHash를 전송하지 않습니다 — 대체 id를 만들지 않고, 방문 비컨을 생략하며 릴레이 이벤트에도 사용자 식별값을 기록하지 않습니다 |
transportFactory | KtorRelayTransport | 테스트용 전송 구현을 주입하기 위한 팩토리 |
릴레이 URL은 런타임 설정이 아닙니다. 연결 대상은 WalletKit 빌드 시점에 고정되며(RelayDefaults.DEFAULT_RELAY_URL), 스캔한 페어링 URI의 relay-url보다 우선합니다. 다른 릴레이를 쓰려면 그 값으로 다시 빌드합니다:
./gradlew :walletkit:build -PscopeRelayUrl=wss://relay.example:1443/relay
# 또는
SCOPE_RELAY_URL=wss://relay.example:1443/relay ./gradlew :walletkit:build
테스트에서 전송 구현을 교체할 때는 URL이 아니라 transportFactory를 주입합니다.
플랫폼 값은 릴레이 발행 요청에 항상 포함됩니다. userHash를 지정하면 사용자 식별값도 암호화되지 않은 릴레이 메타데이터로 전송되고 방문 이벤트가 활성화됩니다. 기본값 null에서는 userHash와 방문 이벤트만 생략됩니다. 이전 버전이 만들던 프로세스 단위 UUIDv4 대체 ID는 제거되었습니다. 값을 지정할 때는 운영 적용 전 수집 목적, 동의, 보존 기간과 비활성화 정책을 보안 검토로 확정하세요. 자세한 전송 동작은 릴레이 전송에서 확인합니다.
정확한 생성자 정의와 기본값은 WalletKit API에서 확인합니다.
페어링 URI 수신 설정
지갑은 dApp이 만든 페어링 URI를 QR 스캔이나 scope: 딥링크로 받습니다. 라이브러리는 URI 문자열을 받아 처리할 뿐이므로 URI가 지갑 앱까지 도달하는 경로는 플랫폼별로 등록합니다. 아래 설정 없이는 딥링크가 앱에 전달되지 않고 릴레이 연결도 실패합니다.
Android
<!-- 릴레이 WSS 연결에 필요 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- QR 스캔 경로를 지원할 때만 -->
<uses-permission android:name="android.permission.CAMERA" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="scope" />
</intent-filter>
</activity>
launchMode="singleTask"면 딥링크가 액티비티를 재생성하지 않고 onNewIntent()로 전달됩니다. 두 진입 지점 모두에서 URI를 읽습니다.
// 콜드 스타트: onCreate의 intent / 실행 중: onNewIntent
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
scopeUriFrom(intent)?.let { /* connect(uri, signer) 호출 */ }
}
private fun scopeUriFrom(intent: Intent?): String? {
val data = intent?.data ?: return null
return if (data.scheme == "scope") data.toString() else null
}
iOS
scope URL scheme을 앱에 등록합니다.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>CFBundleURLName</key><string>{앱 번들 식별자}</string>
<key>CFBundleURLSchemes</key>
<array><string>scope</string></array>
</dict>
</array>
<!-- QR 스캔 경로를 지원할 때만 -->
<key>NSCameraUsageDescription</key>
<string>Scan a dApp's QR pairing code to connect your wallet.</string>
SwiftUI에서는 onOpenURL로 받고 scheme을 확인한 뒤 넘깁니다.
WindowGroup {
ContentView()
.onOpenURL { url in
guard url.scheme == "scope" else { return }
// url.absoluteString 을 connect(pairingUri:signer:) 로 전달
}
}
Android는 INTERNET 권한이 없으면 릴레이 WSS 연결 단계에서 실패합니다. iOS는 별도 네트워크 권한이 필요하지 않습니다.