送金先のアドレスが間違っていると、資金が永久に失われる可能性があります。アドレスの検証により、あなたとあなたのユーザーが、資金を適切に受け取りアクセスできるアドレスにのみ送金できるようになります。取引所を運営している場合は、これらのチェックを実施し、送金前にユーザーがアドレスを確認するよう促すことをお勧めします。
検証内容は送信するものによって異なります:
- SPLトークンは部分的に自己保護機能を備えています。Token Programは、アカウントが期待されるミントと一致しない転送を拒否するため、誤ったアドレスへのトークン転送は資金を失うことなく失敗します。このページの大部分はSPLトークンの送信について説明しています。
- ネイティブSOLにはそのような保護機能はありません。System Programの転送はあらゆるアカウントへ成功してしまうため、誤った受取人を指定するとSOLが永久にロックされます。ネイティブSOLの送信を参照してください。
コアとなる支払いの概念については、Solanaにおける支払いの仕組みを参照してください。
Solanaアドレスについて
Solanaアカウントには、オンカーブとオフカーブの2種類のアドレスがあります。
オンカーブアドレス
標準アドレスは、Ed25519 keypairの公開鍵です。これらのアドレスは:
- トランザクションに署名できる対応する秘密鍵を持つ
- ウォレットアドレスとして使用される
オフカーブアドレス(PDA)
Program Derived Addressは、プログラムIDとシードから決定論的に導出されます。これらのアドレスは:
- 対応する秘密鍵を持たない
- アドレスの導出元であるプログラムによってのみ署名できる
スマートウォレットまたはスマートアカウントと一般的に呼ばれるプログラム制御のウォレットは、オフカーブのアドレスを持ちます。本ガイドでは全体を通じて「スマートウォレット」という用語を使用します。
支払いにおけるアカウントの種類
アドレスを使用してネットワークからアカウントを取得し、プログラムオーナーとアカウントタイプを確認して、そのアドレスの処理方法を判断してください。
アドレスがオンカーブかオフカーブかを知るだけでは、アカウントの種類、所有プログラム、またはそのアドレスにアカウントが存在するかどうかはわかりません。これらの詳細を確認するには、ネットワークからアカウントを取得する必要があります。
System Program アカウント(ウォレット)
System Program が所有するアカウントは標準的なウォレットです。SPL トークンをウォレットに送信するには、対応する Associated Token Account (ATA) を導出して使用します。
ATA アドレスを導出したら、その token account がオンチェーンに存在するかどうかを確認します。ATA が存在しない場合は、転送と同じトランザクション内に受信者の token account を作成する instruction を含めることができます。ただし、これには新しい token account の rent の支払いが必要です。受信者が ATA を所有しているため、rent として支払われた SOL は送信者が回収することはできません。
Systemアカウントはオフカーブに存在できます。ユーザーがオフカーブアドレスにトークンを送信しようとしている場合は、スマートウォレットへの送金であることを意図しているかどうか、ユーザーに確認を促すことをお勧めします。
適切な保護措置がない場合、ATA 作成のコスト負担は悪用される可能性があります。悪意のあるユーザーが転送をリクエストし、あなたの費用で ATA を作成させた後、ATA を閉じて rent の SOL を回収し、これを繰り返す可能性があります。
Token Accounts
Token accounts は Token Program または Token-2022 Program によって所有され、トークン残高を保持します。受け取ったアドレスが token program によって所有されている場合、そのアカウントが token account であること(mint account ではないこと)を確認し、送信前に期待するトークンの mint account と一致しているかどうかを検証してください。
Token Programs は、転送における両方の token accounts が同じ mint のトークンを保持していることを自動的に検証します。検証に失敗した場合、トランザクションは拒否され、資金は失われません。
Mint Accounts
Mint accounts は特定トークンのトークン供給量とメタデータを追跡します。Mint accounts も Token Programs によって所有されていますが、トークン転送の有効な受取先ではありません。mint アドレスにトークンを送信しようとするとトランザクションは失敗しますが、資金は失われません。
その他のアカウント
他のプログラムが所有するアカウントは有効ですが、それぞれの所有プログラムが定める権限ルールに従います。スマートウォレットへの送金を意図しているかどうか、ユーザーに確認を促すことをお勧めします。
ネイティブSOLの送金
上記の分類により、SPLトークンの送金先が決まります。ネイティブSOLはより厳格です。安全な送金先はSystem Programウォレット(またはアカウントが未作成のオンカーブアドレスで、後にSystem Programウォレットになるもの)に限られます。
System Programの送金は、ミント、token account、プログラム、PDAを含む任意のアカウントにlamportを追加します。lamportはアカウントの所有プログラムによってのみ移動できるため、誤った送金先にSOLを送ると、資金が永久に失われる可能性があります。
SPLトークンの送金とは異なり、送金先が予期しないアドレスであっても、トランザクションは失敗しません。
ネイティブSOLを送金する場合、IS_WALLET
の結果のみが許容されます。IS_TOKEN_ACCOUNT は許容されません。token
accountはSPLトークンを保持しており、そこに送られたSOLは送信者の管理外となります。
これはSOLが失われる一般的なケースです。ユーザーがトークンのミントアドレス(またはプログラムアドレス)をSOL出金先に貼り付けてしまうことがあります。送金は成功しますが、SOLは回収不能となります。SOL送金に署名する前に、必ず送金先を確認してください。
検証フロー
以下の図は、アドレスを検証するための参考デシジョンツリーを示しています。
アカウントの取得
アドレスを使用してネットワークからアカウントの詳細を取得します。
アカウントが存在しない場合
このアドレスにアカウントが存在しない場合、アドレスがオンカーブかオフカーブかを確認してください:
-
オフカーブ(PDA): associated token accountの作成またはSPLトークンの送信前に、受取人による明示的な確認を求めてください。既存のアカウントがない場合、アドレスだけではどのプログラムがこのPDAを導出したか、または受取人のプログラムがその associated token accountにアクセスできるかどうかを判断することはできません。
-
オンカーブ: これはまだ資金が入金されていない有効なウォレットアドレス(公開鍵)です。ATAを導出し、存在するか確認してから、トークンを送信してください。ATAが存在しない場合、その作成に資金を提供するかどうかのポリシーを決定する必要があります。
アカウントが存在する場合
アカウントが存在する場合、どのプログラムがそれを所有しているか確認してください:
-
System Program: これは標準のウォレットです。ATAを導出し、存在するか確認してから、トークンを送信してください。ATAが存在しない場合、その作成に資金を提供するかどうかのポリシーを決定する必要があります。
-
Token Program / Token-2022: アカウントがtoken account(mint accountではない)であり、送信しようとしているトークン(mint)を保有していることを確認してください。有効であれば、このアドレスに直接トークンを送信してください。mint accountまたは別のmintのtoken accountである場合は、アドレスを拒否してください。
-
その他のプログラム: マルチシグウォレットなどのプログラムは、token accountの有効な所有者です。スマートウォレットへの送金を意図していることをユーザーに確認させてください。
一般的なスマートウォレットの例:
デモ
以下の例はアドレス検証ロジックのみを示しています。これは説明目的の参考コードです。
このデモでは、ATAの導出方法やトークンを送信するトランザクションの構築方法については説明していません。サンプルコードについては、token account および token transfer のドキュメントを参照してください。
以下のデモでは、3つの考えられる結果を使用しています:
| 結果 | 意味 | アクション |
|---|---|---|
IS_WALLET | 有効なウォレットアドレス | associated token accountを導出して送信 |
IS_TOKEN_ACCOUNT | 有効なtoken account | このアドレスに直接トークンを送信 |
USER_VERIFICATION | アドレスには受取人の確認が必要です | SPLトークンの送信またはATAへの資金提供前に、明示的なレビューを求めてください |
REJECT | 無効なアドレス | 送信しない |
次に、各結果をcanReceiveNativeSol(ウォレットのみ)およびcanReceiveSplToken(ウォレットまたはtoken account)を使用して、アセットごとの受け入れ可否にマッピングします。USER_VERIFICATIONの結果は自動的に承認されません。SPLトークンを送信する前に、アプリケーションはユーザーに意図を確認させる必要があります。token accountはIS_TOKEN_ACCOUNTを返すため、SPLトークンを受け取ることはできますが、ネイティブSOLは受け取れません。これがSOLのロックを防ぐための重要な区別です。
/*** Validates an input address and classifies it as a wallet, token account,* address requiring recipient verification, 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* - USER_VERIFICATION: Require explicit review before sending SPL tokens* - 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 onchainif (!account.exists) {// An unfunded PDA may belong to a smart wallet, but the address// alone cannot prove that its program can access a newly-created ATA.if (isOffCurveAddress(inputAddress)) {return {type: "USER_VERIFICATION",reason:"Unfunded PDA: confirm the recipient is using a smart wallet and controls this address"};}// On-curve = valid keypair address, treat as unfunded walletreturn { type: "IS_WALLET" };}// Account exists, check program ownerconst owner = account.programAddress;// A System Program account can be a standard wallet or an off-curve smart// wallet. Require verification before automatically sending to the// latter.if (owner === SYSTEM_PROGRAM) {if (isOffCurveAddress(inputAddress)) {return {type: "USER_VERIFICATION",reason:"Off-curve System Program account: confirm recipient is using a smart wallet"};}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"};}// An account owned by another program can be a smart wallet.return {type: "USER_VERIFICATION",reason:"Program-owned account: confirm the recipient is using a smart wallet"};}/*** Native SOL is only safe to send to a wallet. Any other account locks it.*/function canReceiveNativeSol(result: ValidationResult): boolean {return result.type === "IS_WALLET";}/*** SPL tokens can go to a wallet (via its ATA) or directly to a token account.*/function canReceiveSplToken(result: ValidationResult): boolean {return result.type === "IS_WALLET" || result.type === "IS_TOKEN_ACCOUNT";}// =============================================================================// Examples// =============================================================================
Is this page helpful?