Solana(Anchor) + Nextjs でTODOアプリケーション構築 のスマートコントラクト実装

## 概要

Web３ハッカソンをきっかけに、
最近はバックエンドをブロックチェーン上のスマートコントラクトを使って実装したアプリケーション作りにハマっています。

今回は、ハッカソンでもチャレンジしたSolanaチェーンを用いて、TODOアプリ作りをやってみました。
ハッカソンでは`NativeRust`で構築しようとしてやはり難しかったのですが、
`Anchor`を使うと、慣れは必要ですが、割と簡単にスマートコントラクトを実装できました。
また、`SolanaPlayground`を使うとスマートコントラクトの

- Build
- Deploy
- Test
- フロントエンドへの組み込み

が非常に楽で、いい開発環境が整ってきていることを感じれたので、
その辺りに関しても少し書いてみたいと思います。


前回の記事に引き続き、バックエンドのプログラムの詳細について書き残したいと思います。



## 使用する技術スタック

- フロントエンド (Nextjs)
- バックエンド (Solana Anchor, Rust + QuickNode + Solana Playground)
- ウォレット（Phantom)
- デプロイ (Netlify + Solana Devnet)

となります。


## スマートコントラクトの実装

以下がAnchorを用いたスマートコントラクトのコードです。

- lib.rs
- constant.rs
- error.rs
- states.rs

のファイルを作っています。


### constant.rs

```rs
use anchor_lang::prelude::*;

#[constant]
pub const USER_TAG: &[u8] = b"USER_STATE";

#[constant]
pub const TODO_TAG: &[u8] = b"TODO_STATE";

```

### error.rs

```rs
use anchor_lang::prelude::*;

#[error_code]
pub enum TodoError{
    #[msg("You are not authorized to perform this action.")]
    Unauthorized,
    #[msg("Not allowed")]
    NotAllowed,
    #[msg("Math operation overflow")]
    MathOverflow,
    #[msg("Already marked")]
    AlreadyMarked
}
```

### states.rs

```rs
use anchor_lang::prelude::*;

#[account]
#[derive(Default)]
pub struct UserProfile{
    pub authority: Pubkey,
    pub last_todo: u8,
    pub todo_count: u8
}

#[account]
#[derive(Default)]
pub struct TodoAccount{
    pub authority: Pubkey,
    pub idx: u8,
    pub content: String,
    pub marked: bool
}
```

### lib.rs
```rs
use anchor_lang::prelude::*;

pub mod constant;
pub mod error;
pub mod states;

use crate::{constant::*, error::*, states::*};

declare_id!("xxxxxxxxxxxxxxxxxxxxxxxx");

#[program]
pub mod clever_todo {
    use super::*;

    pub fn initialize_user(ctx: Context<InitializeUser>) -> Result<()> {
        let user_profile = &mut ctx.accounts.user_profile;
        user_profile.todo_count = 0;
        user_profile.last_todo = 0;
        user_profile.authority = ctx.accounts.authority.key();

        Ok(())
    }

    pub fn add_todo(ctx: Context<AddTodo>, _content: String) -> Result<()> {
        let todo_account = &mut ctx.accounts.todo_account;
        let user_profile = &mut ctx.accounts.user_profile;

        todo_account.authority = ctx.accounts.authority.key();
        todo_account.content = _content;
        todo_account.idx = user_profile.last_todo;
        todo_account.marked = false;

        // Increase todo idx for PDA
        user_profile.last_todo = user_profile.last_todo.checked_add(1).unwrap();

        user_profile.todo_count = user_profile.todo_count.checked_add(1).unwrap();

        Ok(())
    }

    pub fn mark_todo(ctx: Context<MarkTodo>, todo_idx: u8) -> Result<()> {
        let todo_account = &mut ctx.accounts.todo_account;
        require!(!todo_account.marked, TodoError::AlreadyMarked);

        todo_account.marked = true;

        Ok(())
    }

    pub fn remove_todo(ctx: Context<RemoveTodo>, todo_idx: u8) -> Result<()> {
        let user_profile = &mut ctx.accounts.user_profile;
        user_profile.todo_count = user_profile.todo_count.checked_sub(1).unwrap();

        Ok(())
    }

}

#[derive(Accounts)]
#[instruction()]
pub struct InitializeUser<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,

    #[account(
        init,
        seeds = [USER_TAG, authority.key().as_ref()],
        bump,
        payer = authority,
        space = 8 + std::mem::size_of::<UserProfile>(),
    )]
    pub user_profile: Box<Account<'info, UserProfile>>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
#[instruction()]
pub struct AddTodo<'info> {
    #[account(
        mut,
        seeds = [USER_TAG, authority.key().as_ref()],
        bump,
        has_one = authority,
    )]
    pub user_profile: Box<Account<'info, UserProfile>>,

    #[account(
        init,
        seeds = [TODO_TAG, authority.key().as_ref(), &[user_profile.last_todo as u8].as_ref()],
        bump,
        payer = authority,
        space = std::mem::size_of::<TodoAccount>() + 8,
        
    )]
    pub todo_account: Box<Account<'info, TodoAccount>>,

    #[account(mut)]
    pub authority: Signer<'info>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
#[instruction(todo_idx: u8)]
pub struct MarkTodo<'info> {
    #[account(
        mut,
        seeds = [USER_TAG, authority.key().as_ref()],
        bump,
        has_one = authority
    )]
    pub user_profile: Box<Account<'info, UserProfile>>,

    #[account(
        mut,
        seeds = [TODO_TAG, authority.key().as_ref(), &[todo_idx as u8].as_ref()],
        bump,
        has_one = authority,
    )]
    pub todo_account: Box<Account<'info, TodoAccount>>,

    #[account(mut)]
    pub authority: Signer<'info>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
#[instruction(todo_idx: u8)]
pub struct RemoveTodo<'info> {
    #[account(
        mut,
        seeds = [USER_TAG, authority.key().as_ref()],
        bump,
        has_one = authority,
    )]
    pub user_profile: Box<Account<'info, UserProfile>>,

    #[account(
        mut,
        close = authority,
        seeds = [TODO_TAG, authority.key().as_ref(), &[todo_idx as u8].as_ref()],
        bump,
        has_one = authority,
    )]
    pub todo_account: Box<Account<'info, TodoAccount>>,

    #[account(mut)]
    pub authority: Signer<'info>,

    pub system_program: Program<'info, System>,
}

```


## まとめ

プログラムを列挙しただけの記事になってしまいましたが、
Anchorを使うとある程度（慣れれば）すっきりとCRUDくらいなら書けたな、という印象です。

これからもいろいろな場面で使えると面白そうです。


今回はこの辺で。


@kenmaro