誤ったアドレスにトークンを送信すると、資金が永久に失われる可能性があります。アドレス検証により、適切に受信してアクセスできるアドレスにのみトークンを送信できるようになります。
支払いの基本概念については、Solanaでの支払いの仕組みを参照してください。
Solanaアドレスの理解
Solanaアカウントには、オンカーブとオフカーブの2種類のアドレスがあります。
オンカーブアドレス
標準アドレスは、Ed25519鍵ペアの公開鍵です。これらのアドレスは:
- トランザクションに署名できる対応する秘密鍵を持つ
- ウォレットアドレスとして使用される
オフカーブアドレス(PDA)
プログラム派生アドレスは、プログラムIDとシードから決定論的に派生されます。これらのアドレスは:
- 対応する秘密鍵を持たない
- アドレスが派生されたプログラムによってのみ署名できる
支払いにおけるアカウントタイプ
アドレスを使用してネットワークからアカウントを取得し、そのプログラム所有者とアカウントタイプを確認して、アドレスの処理方法を決定します。
アドレスがオンカーブかオフカーブかを知るだけでは、それがどのタイプのアカウントか、どのプログラムが所有しているか、またはそのアドレスにアカウントが存在するかはわかりません。これらの詳細を判断するには、ネットワークからアカウントを取得する必要があります。
System Programアカウント(ウォレット)
System Programが所有するアカウントは標準的なウォレットです。ウォレットにSPLトークンを送信するには、その関連トークンアカウント(ATA)を派生して使用します。
ATAアドレスを派生した後、トークンアカウントがオンチェーンに存在するかどうかを確認します。ATAが存在しない場合、転送と同じトランザクション内に受信者のトークンアカウントを作成する命令を含めることができます。ただし、これには新しいトークンアカウントのrentの支払いが必要です。受信者がATAを所有しているため、rentに支払われたSOLは送信者が回収することはできません。
セーフガードがない場合、ATAの作成を補助することは悪用される可能性があります。悪意のあるユーザーは転送をリクエストし、あなたの費用でATAを作成させ、ATAを閉じてレントSOLを回収し、これを繰り返すことができます。
トークンアカウント
トークンアカウントはToken ProgramまたはToken-2022 Programによって所有され、トークン残高を保持します。受け取ったアドレスがトークンプログラムによって所有されている場合、送信する前にそのアカウントがトークンアカウント(mint accountではない)であり、期待されるトークンmint accountと一致することを確認する必要があります。
Token Programsは、転送における両方のトークンアカウントが同じmintのトークンを保持していることを自動的に検証します。検証が失敗した場合、トランザクションは拒否され、資金は失われません。
Mint account
Mint accountは、特定のトークンのトークン供給量とメタデータを追跡します。Mint accountもToken Programsによって所有されていますが、トークン転送の有効な受取先ではありません。Mintアドレスにトークンを送信しようとすると、トランザクションは失敗しますが、資金は失われません。
その他のアカウント
他のプログラムによって所有されているアカウントには、ポリシー決定が必要です。一部のアカウント(例:マルチシグウォレット)は有効なトークンアカウント所有者である可能性がありますが、他のアカウントは拒否されるべきです。
検証フロー
次の図は、アドレスを検証するための参照決定木を示しています:
アカウントを取得
アドレスを使用して、ネットワークからアカウントの詳細を取得します。
アカウントが存在しない
このアドレスにアカウントが存在しない場合、アドレスがオンカーブかオフカーブかを確認します:
-
オフカーブ(PDA): アクセス不可能なATAへの送信を避けるため、アドレスを保守的に拒否します。既存のアカウントがない場合、アドレスだけからこのPDAを導出したプログラムや、アドレスがATAのものかどうかを判断できません。このアドレスのATAを導出してトークンを送信すると、アクセス不可能なトークンアカウントに資金がロックされる可能性があります。
-
オンカーブ: これはまだ資金が供給されていない有効なウォレットアドレス(公開鍵)です。ATAを導出し、存在するか確認してトークンを送信します。ATAが存在しない場合、その作成に資金を供給するかどうかのポリシー決定を行う必要があります。
アカウントが存在する場合
アカウントが存在する場合、どのプログラムが所有しているか確認します:
-
システムプログラム: これは標準的なウォレットです。ATAを導出し、存在するか確認してトークンを送信します。ATAが存在しない場合、その作成に資金を供給するかどうかのポリシー決定を行う必要があります。
-
トークンプログラム / Token-2022: アカウントがトークンアカウント(ミントアカウントではない)であり、送信しようとしているトークン(ミント)を保持していることを確認します。有効な場合、このアドレスに直接トークンを送信します。ミントアカウントまたは別のミントのトークンアカウントの場合、アドレスを拒否します。
-
その他のプログラム: これはポリシー決定が必要です。マルチシグウォレットなどの一部のプログラムは、トークンアカウントの所有者として許容される場合があります。ポリシーで許可されている場合、ATAを導出して送信します。それ以外の場合、アドレスを拒否します。
デモ
次の例は、アドレス検証ロジックのみを示しています。これは説明目的の参考コードです。
以下のデモでは、3つの可能な結果を使用します。
| 結果 | 意味 | アクション |
|---|---|---|
IS_WALLET | 有効なウォレットアドレス | associated token accountを導出して送信 |
IS_TOKEN_ACCOUNT | 有効なトークンアカウント | このアドレスに直接トークンを送信 |
REJECT | 無効なアドレス | 送信しない |
/*** Validates an input address and classifies it as a wallet, token account, or invalid.** @param inputAddress - The address to validate* @param rpc - Optional RPC client (defaults to mainnet)* @returns Classification result:* - IS_WALLET: Valid wallet address* - IS_TOKEN_ACCOUNT: Valid token account* - REJECT: Invalid address for transfers*/export async function validateAddress(inputAddress: Address,rpc: Rpc<GetAccountInfoApi> = defaultRpc): Promise<ValidationResult> {const account = await fetchJsonParsedAccount(rpc, inputAddress);// Log the account data for democonsole.log("\nAccount:", account);// Account doesn't exist on-chainif (!account.exists) {// Off-curve = PDA that doesn't exist as an account// Reject conservatively to avoid sending to an address that may be inaccessible.if (isOffCurveAddress(inputAddress)) {return { type: "REJECT", reason: "PDA doesn't exist as an account" };}// On-curve = valid keypair address, treat as unfunded walletreturn { type: "IS_WALLET" };}// Account exists, check program ownerconst owner = account.programAddress;// System Program = walletif (owner === SYSTEM_PROGRAM) {return { type: "IS_WALLET" };}// Token Program or Token-2022, check if token accountif (owner === TOKEN_PROGRAM || owner === TOKEN_2022_PROGRAM) {const accountType = (account.data as { parsedAccountMeta?: { type?: string } }).parsedAccountMeta?.type;if (accountType === "account") {return { type: "IS_TOKEN_ACCOUNT" };}// Reject if not a token account (mint account)return {type: "REJECT",reason: "Not a token account"};}// Unknown program ownerreturn { type: "REJECT", reason: "Unknown program owner" };}// =============================================================================// Examples// =============================================================================
Is this page helpful?