導入
PDA は「プログラムだけが動かせるアカウント」であると同時に、実際にデータ(残高やカウンタなど)を保存する場所にもなります。ここで肝心なのは、どんなシードを選ぶかという設計です。シードの選び方次第で「誰の・何のためのアカウントか」が決まります。
説明
シードの組み合わせ方によって、PDA の役割が変わります。
graph TB
subgraph perUser["ユーザーごとに1つ"]
U1["user1のPubkey"] --> D1["seeds: [b'vault', user1]"] --> P1["PDA A"]
U2["user2のPubkey"] --> D2["seeds: [b'vault', user2]"] --> P2["PDA B"]
end
subgraph global["プログラム全体で1つだけ"]
D3["seeds: [b'config']"] --> P3["PDA C(設定用)"]
end
[b"vault", user.key.as_ref()]… ユーザーごとに1つの保管庫。ユーザーが変われば PDA も変わります。[b"config"]… 固定シードのみなので、プログラム全体でただ1つのアカウント(例: 管理者アドレスや手数料率などの設定を置く場所)になります。
実務では、計算した bump もアカウントのデータの中に保存しておくのが定石です。毎回 find_program_address を計算するのは処理コストがかかるため、一度計算した bump を保存しておき、以後は保存済みの bump を使って検証する(create_program_address で照合する)ことで無駄な再計算を省けます。
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct VaultAccount {
pub owner: [u8; 32], // このvaultの持ち主(ユーザー)のPubkey
pub balance: u64,
pub bump: u8, // 導出時に使ったbumpを保存しておく
}
決定的(毎回同じ結果になる)ということは、**「あとからこの PDA を誰でも確実に見つけ出せる」**ということでもあります。クライアント側は秘密鍵もアカウントの作成履歴も知らなくても、["vault", ユーザーのアドレス] というシードのルールさえ知っていれば、いつでも同じ計算でそのユーザーの保管庫アドレスにたどり着けます。これは、通常のアカウント(ランダムな鍵ペアで作る)にはない、PDA ならではの利点です。
演習
あるトークン交換サービスで「ユーザーごとの注文履歴」を PDA で管理したいとします。どんなシードの組み合わせにすれば、ユーザーごとに一意な PDA が求まるか考えてみましょう。
ヒント1を見る
レッスン22の「vault」の例と同じ考え方で、固定の目印文字列とユーザーのPubkeyを組み合わせます。
ヒント2を見る
1人のユーザーが複数の注文履歴アカウントを持てるようにしたい場合、シードに注文番号のような追加の材料を足す設計もあります。