본문으로 건너뛰기

설치

WalletKit은 지갑 앱이 임베드하는 Kotlin Multiplatform 라이브러리입니다. 이 문서는 라이브러리를 Android·iOS·JVM 대상에 연결하고 WalletKit 인스턴스를 만드는 데 필요한 최소 설정을 다룹니다.

파트너 프리뷰입니다

현재 버전은 0.0.1입니다. iOS 패키지는 공개 Swift Package로 설치할 수 있습니다. Android·JVM 패키지는 아직 Maven Central에 게시하지 않았으며, 파트너 프리뷰 기간에는 SCOPE Connect 담당자를 통해 아티팩트를 전달합니다. 접근 권한과 릴레이·백엔드 엔드포인트, 지원 범위도 담당자와 협의합니다.

사전 요구 사항

대상최소 요구 사항비고
AndroidAPI 28, compile SDK 35ChaCha20-Poly1305 JCA는 API 28 이상에서 동작합니다
iOSiOS 16, arm64·simulator arm64공개 Swift Package가 정적 XCFramework를 제공합니다
JVMJDK 21secp256k1-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.1Xcode 또는 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 라이브러리 제품을 앱 대상에 연결합니다.

Package.swift
dependencies: [
.package(
url: "https://github.com/Lambda256/scope-connect-walletkit-swift.git",
exact: "0.0.1"
),
]
KMP 예외를 Swift에서 반드시 잡으세요

세션을 시작하는 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")),
)
iOS에서는 공유 Kotlin 모듈에서 인스턴스를 만듭니다

Kotlin 기본 인자는 Swift로 브릿징되지 않아 Swift에서 WalletKitConfig를 만들려면 모든 생성자 인자를 명시해야 하고, 기본 릴레이 전송을 만드는 팩토리는 Swift에서 재구성할 수 없습니다. 파트너 프리뷰에서는 위 구성 코드를 앱의 공유 Kotlin 모듈에 두고, Swift는 구성된 인스턴스를 호출합니다. Swift 호출 방법은 세션 연결승인 단계의 Swift 탭에서 다룹니다.

WalletKitConfig 옵션

필드기본값설명
metadata필수dApp의 연결 승인 화면에 표시할 지갑 정보. 현재는 name 하나입니다
handshakeTimeoutMs20_000세션 연결 각 단계의 대기 시간(ms)
signRequestTimeoutMs300_000서명 요청 대기 시간(ms). dApp과 마찬가지로 사용자가 내용을 확인하고 승인할 수 있도록 5분간 기다립니다
userHashnull콘솔 분석용 설치 단위 식별값(선택). null이면 userHash를 전송하지 않습니다 — 대체 id를 만들지 않고, 방문 비컨을 생략하며 릴레이 이벤트에도 사용자 식별값을 기록하지 않습니다
transportFactoryKtorRelayTransport테스트용 전송 구현을 주입하기 위한 팩토리

릴레이 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의 수집·보존 정책을 먼저 정하세요

플랫폼 값은 릴레이 발행 요청에 항상 포함됩니다. userHash를 지정하면 사용자 식별값도 암호화되지 않은 릴레이 메타데이터로 전송되고 방문 이벤트가 활성화됩니다. 기본값 null에서는 userHash와 방문 이벤트만 생략됩니다. 이전 버전이 만들던 프로세스 단위 UUIDv4 대체 ID는 제거되었습니다. 값을 지정할 때는 운영 적용 전 수집 목적, 동의, 보존 기간과 비활성화 정책을 보안 검토로 확정하세요. 자세한 전송 동작은 릴레이 전송에서 확인합니다.

정확한 생성자 정의와 기본값은 WalletKit API에서 확인합니다.

페어링 URI 수신 설정

지갑은 dApp이 만든 페어링 URI를 QR 스캔이나 scope: 딥링크로 받습니다. 라이브러리는 URI 문자열을 받아 처리할 뿐이므로 URI가 지갑 앱까지 도달하는 경로는 플랫폼별로 등록합니다. 아래 설정 없이는 딥링크가 앱에 전달되지 않고 릴레이 연결도 실패합니다.

Android

AndroidManifest.xml
<!-- 릴레이 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을 앱에 등록합니다.

Info.plist
<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는 별도 네트워크 권한이 필요하지 않습니다.

다음 단계