
5분 안에 cNFT 민팅 모바일 앱 만들기
이 튜토리얼에서 다루는 내용
이 튜토리얼에서는 NFT(대체 불가능 토큰) 민팅 Android 애플리케이션을 만드는 방법을 다룹니다. 사용자가 이미지를 간편하게 촬영하고 Solana에서 압축 NFT를 민팅할 수 있도록 구현합니다. 이를 위해 Helius Mint API를 사용합니다. 이 API는 압축 NFT를 더 빠르게 민팅하고 상용구 코드를 최소화하여 개발 및 설계 시간을 크게 줄여 줍니다. Android 애플리케이션은 Kotlin과 Jetpack Compose로 작성합니다.
압축 NFT란 무엇인가요?
압축 NFT(cNFT)는 상태 압축과 동시성 Merkle 트리를 사용해 스토리지 비용을 절감합니다. 압축 NFT는 동일한 구조를 유지하면서도 비압축 NFT보다 비용이 2,400~24,000배 저렴합니다.
사전 준비 사항
이 튜토리얼을 시작하기 전에 Kotlin을 사용한 Android 개발의 기본 개념을 이해하고 Solana에 익숙해야 합니다.
도구
API 키
Helius Mint API를 사용하려면 API 키가 필요합니다. 다음 단계에 따라 발급받으세요.
- Helius 개발자 포털에 접속합니다.
- Solana 지갑, Google 또는 GitHub 계정으로 계정을 만듭니다.
- 로그인한 후 안내에 따라 API 키를 생성합니다.
- 이 튜토리얼 전반에서 API 호출에 사용되므로 키를 안전하게 보관하세요.
앱 만들기
1단계: 프로젝트 설정
이 프로젝트를 위한 스타터 팩 코드베이스를 준비했습니다.
다음 단계에 따라 시작하세요.
- GitHub에서 스타터 팩에 접속합니다.
- 저장소를 포크하고 로컬 환경에 클론합니다.
코드를 로컬에 준비했다면 다음 단계를 진행하세요.
- Android Studio에서 프로젝트를 엽니다.
- Gradle 빌드가 완료될 때까지 기다립니다.
- 코드를 실행합니다.
스타터 앱의 기능을 살펴보며 익숙해지세요. 앱에 통합된 지갑 연결 버튼을 확인할 수 있습니다. 이 버튼을 사용하면 Mobile Wallet Adapter(MWA) 호환 지갑 앱에 연결할 수 있습니다. Mobile Wallet Adapter(MWA)는 모바일 dApp과 모바일 지갑 앱을 연결하는 프로토콜 사양으로, Solana 트랜잭션과 메시지 서명을 위한 통신을 지원합니다. Mobile Wallet Adapter는 이러한 지갑을 지원합니다. 또한 현재 사진을 촬영할 수 있는 "사진 촬영" 버튼도 확인할 수 있습니다.
Helius API 키를 코드베이스에 안전하게 저장하려면 local.properties 파일로 이동하여 API 키를 추가하세요. 이 파일은 민감한 정보를 보관하도록 설계되었으며 Git에서 무시되므로 API 키를 안전하게 보호할 수 있습니다.
api_key=<your-api-key>
이제 촬영한 이미지를 압축 NFT(cNFT)로 민팅하는 로직을 구현하겠습니다.
2단계: 민팅 로직
목표는 "사진 촬영" 버튼을 카메라 기능과 통합하는 것입니다. 이 버튼을 통합하면 사용자가 이미지를 촬영하고 NFT로 민팅할 수 있습니다. 카메라를 열고 이미지를 촬영하는 스타터 코드는 이미 제공되어 있습니다. 이제 민팅 프로세스를 관리할 함수를 만들겠습니다.
Helius Mint API
이 메서드는 압축 NFT를 민팅합니다. 실행하면 트랜잭션이 확정될 때까지 대기하며, 네트워크 사용량이 많은 시간에는 최대 60초가 걸릴 수 있습니다. 자세한 내용은 문서를 확인하세요.
먼저 java/com/example/hmint/MainViewModel.kt로 이동하여 MainViewModel 클래스 안에 mintCNft 함수를 만드세요.
@HiltViewModel
class MainViewModel @Inject constructor(
private val walletAdapter: MobileWalletAdapter,
private val walletConnectionUseCase: WalletConnectionUseCase,
) : ViewModel() {
// ...
fun mintCNft(imageUri: Uri, nftName: String, user: String) =
viewModelScope.launch {
_state.update {
_state.value.copy(
isLoading = true,
)
}
}
}이 함수 안에서 민팅 로직을 처리할 코루틴을 시작합니다. 먼저 API 키를 가져오고 필요한 변수와 requestBody을 정의합니다. 이후 요구 사항에 따라 requestBody에 속성과 데이터를 추가할 수 있습니다.
withContext(viewModelScope.coroutineContext + Dispatchers.IO) {
val apiKey = System.getProperty("api_key") ?: ""
val url = URL("https://mainnet.helius-rpc.com/?api-key=$apiKey")
val mediaType = "application/json".toMediaTypeOrNull()
val requestBody = """
{
"jsonrpc": "2.0",
"id": "helius-test",
"method": "mintCompressedNft",
"params": {
"name": "$nftName",
"symbol": "HM",
"owner": "$user",
"description": "This is a test cNFT minted by hMint app",
"attributes": [
{
"trait_type": "hMint",
"value": "1"
}
],
"imageUrl": "$imageUri",
"sellerFeeBasisPoints": 6900
}
}
""${'"'}.trimIndent()
}그다음 요청을 구성하고 OkHttpClient을 초기화합니다.
val body = requestBody.toRequestBody(mediaType)
val request = Request.Builder()
.url(url)
.post(body)
.addHeader("accept", "application/json")
.addHeader("content-type", "application/json")
.build()
val client = OkHttpClient()클라이언트를 호출하기 전에 데이터 클래스에서 응답을 정의하겠습니다. data이라는 새 패키지를 만들고, 그 안에 NFTResponse.kt 파일을 새로 만들어 API 호출에서 받은 응답의 구조를 정의하세요.
package com.example.hmint.data
data class NftMintResponse(
val jsonrpc: String,
val id: String,
val result: MintResult
)
data class MintResult(
val signature: String,
val minted: Boolean,
val assetId: String
)그런 다음 MainViewModel 클래스 안에서 WalletViewState 데이터 클래스에 mintResponse 값을 추가합니다. 응답을 받으면 이 값을 업데이트합니다.
data class WalletViewState(
// ...
val mintResponse: String = ""
)mintCNft 함수로 돌아가 클라이언트로 요청을 실행하고 실패 또는 응답을 적절히 처리합니다. 응답을 받으면 Gson을 사용해 데이터를 앞에서 만든 NFTMintResponse 데이터 클래스로 파싱한 다음, mintResponse 값을 그에 맞게 업데이트합니다.
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle failure
e.printStackTrace()
Log.d(TAG, "NFT Mint Failed")
_state.update {
_state.value.copy(
isLoading = false,
mintResponse = "Mint Failed"
)
}
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val responseBody = response.body?.string() ?: ""
val gson = Gson()
val nft = gson.fromJson(responseBody, NftMintResponse::class.java)
Log.d(TAG, "NFT Mint Successful: $nft")
_state.update {
_state.value.copy(
isLoading = false,
mintResponse = "Minted Successfully"
)
}
} else {
// Handle non-successful response
Log.d(TAG, "NFT Mint Failed: ${response.code}")
_state.update {
_state.value.copy(
isLoading = false,
mintResponse = "Minted Failed"
)
}
}
}
})UI에서는 민팅 함수의 실행 결과를 사용자에게 알려 주어야 합니다. 이미지 촬영을 위한 UI 코드와 로직이 포함된 composable이 있는 java/com/example/hmint/composables/MintButton.kt로 이동하세요. cameraLauncher을 선언한 MintButton 안에서 mintCNft 함수를 호출합니다. 또한 mintResponse이 업데이트되면 Toast를 표시하는 코드도 추가하세요.
@Composable
fun MintButton(
mainViewModel: MainViewModel = hiltViewModel()
) {
val cameraLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) {
// ...
mainViewModel.mintCNft(uri, "hMint", viewState.userAddress)
}
viewState.mintResponse.let { message ->
if(message.isNotEmpty()){
Toast.makeText(LocalContext.current, message, Toast.LENGTH_SHORT).show()
}
}
// ...
}3단계: 앱 실행
코딩을 모두 마쳤으니 애플리케이션을 실행해 보겠습니다. 먼저 지갑을 연결한 다음 이미지를 촬영하세요. 앱이 원활하게 NFT를 민팅하는 모습을 확인할 수 있습니다.
마무리
잘하셨습니다! 사용자가 압축 NFT를 민팅할 수 있는 Android 애플리케이션을 성공적으로 개발했습니다. Helius Mint API를 사용하면 단 한 번의 호출만으로 그 어느 때보다 간단하게 cNFT를 민팅할 수 있습니다.
다음 단계
이 앱에는 더 살펴보고 개선할 부분이 많습니다. 그중 하나는 DAS API 통합입니다. Digital Asset Standard(DAS) API는 토큰과 NFT를 비롯한 디지털 자산과 상호작용할 수 있는 통합 인터페이스를 제공하는 오픈 소스 사양이자 시스템입니다. 대체 가능 토큰, 표준 NFT, 압축 NFT 등 다양한 자산 유형을 지원합니다. 이 API를 사용하면 민팅된 모든 압축 NFT를 가져와 애플리케이션에 표시할 수 있으며, 사용자는 자신의 디지털 자산을 한눈에 확인할 수 있습니다.
관련 자료
Jetpack Compose를 활용한 Android 개발 역량을 높이고 Solana 블록체인 통합과 API 연동을 자세히 알아보려면 다음 자료를 살펴보세요.
- Jetpack Compose
- Kotlin의 Solana Mobile Adapter
- Hilt를 사용한 의존성 주입
- Android의 OkHttpClient
- Solana 압축
- Helius MINT API
도움이나 지원이 필요하면 언제든 Discord로 문의하세요. 전체 코드베이스는 GitHub에서 확인할 수 있습니다.
관련 아티클
Helius 구독하기
최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요


