新着:HeliusがLight Protocolを買収
5分以内でcNFTミンターのモバイルアプリを構築する
ブログ/開発

5分以内でcNFTミンターのモバイルアプリを構築する

Developer ExperienceエンジニアXのAnam AnsariLinkedInのAnam Ansari
読了時間:5分

このチュートリアルで学べること

このチュートリアルでは、NFT(非代替性トークン)をミントするAndroidアプリケーションの構築方法を解説します。ユーザーが画像を簡単に撮影し、Solana上で圧縮NFTをミントできるようにすることが目的です。そのために、Helius Mint APIを使用します。このAPIを使用すると、圧縮NFTをより迅速にミントでき、定型コードも最小限に抑えられるため、作業と設計にかかる時間を大幅に短縮できます。Androidアプリケーションは、KotlinとJetpack Composeを使用して記述します。

圧縮NFTとは?

圧縮NFT(cNFT)は、状態圧縮と並行Merkleツリーを使用してストレージコストを削減します。圧縮NFTは、非圧縮NFTと同じ構造を維持しながら、コストを2,400〜24,000分の1に抑えられます。

前提条件

このチュートリアルを始める前に、Kotlinを使用したAndroid開発の基礎を理解し、Solanaに関する知識を身につけておく必要があります。

ツール

  • Android Studio:Giraffe | バージョン2022.3.1
  • エミュレーターまたはモバイルデバイス:アプリケーションのテストに使用します。MWA対応ウォレットアプリケーションがインストールされていることを確認してください(Solfareを推奨します)。

APIキー

Helius Mint APIを使用するには、APIキーが必要です。取得手順は次のとおりです。

  1. Helius Developer Portalにアクセスします。
  2. Solanaウォレット、Google、またはGitHubアカウントを使用してアカウントを作成します。
  3. ログイン後、画面の案内に従ってAPIキーを生成します。
  4. このチュートリアル全体でAPI呼び出しに使用するため、キーを安全に保管してください。

アプリの構築

ステップ1:プロジェクトのセットアップ

このプロジェクト用に、スターターパックのコードベースを用意しました。 

次の手順で開始します。

  1. GitHub上のスターターパックにアクセスします。
  2. リポジトリをフォークし、ローカル環境にクローンします。

コードをローカルに用意したら、次の手順に進みます。

  1. Android Studioでプロジェクトを開きます。
  2. Gradleのビルドプロセスが完了するまで待ちます。
  3. コードを実行します。

スターターアプリを操作して、機能を確認してください。アプリにウォレット接続ボタンが組み込まれていることが分かります。このボタンを使用すると、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コードとロジックを含むコンポーザブルがある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を使用すれば、わずか1回の呼び出しで、これまで以上に簡単にcNFTをミントできます。

次のステップ

このアプリには、さらに多くの機能を追加して改善できます。その一例が、DAS APIの統合です。Digital Asset Standard(DAS)APIは、トークンやNFTを含むデジタル資産を操作するための統一インターフェースを提供する、オープンソースの仕様およびシステムです。代替可能トークン、標準NFT、圧縮NFTなど、さまざまな資産タイプに対応しています。 このAPIを使用すると、ミントされたすべての圧縮NFTを取得してアプリケーション内に表示できるため、ユーザーはデジタル資産の全体像を確認できます。

関連リソース

Jetpack Composeを使用したAndroid開発、Solanaブロックチェーンの統合、APIとの連携について理解を深めるには、以下のリソースをご覧ください。

ヘルプやサポートが必要な場合は、Discordからお気軽にお問い合わせください。完全なコードベースは、参考資料としてGitHubで公開されています。

‍

Heliusを購読

Solana開発の最新情報や新しい記事の公開通知を受け取れます

拡大画像