誤ったアドレスへの送金は、資金の永久損失につながる可能性があります。アドレスの検証により、資金を適切に受け取り、アクセスできるアドレスにのみ送金することができます。
検証内容は送信するものによって異なります:
- 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 は送信者が回収することはできません。
適切な保護措置がない場合、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 アドレスにトークンを送信しようとするとトランザクションは失敗しますが、資金は失われません。
その他のアカウント
他のプログラムが所有するアカウントはポリシーの判断が必要です。一部のアカウント(例:マルチシグウォレット)は有効なtoken account所有者である場合がありますが、その他は拒否すべきです。
ネイティブ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): アクセス不能なATAへの送金を避けるため、保守的にアドレスを拒否してください。既存のアカウントがない場合、アドレスだけではこのPDAがどのプログラムから派生したものか、またそのアドレスがATAのものかどうかを判断できません。このアドレスにトークンを送るためにATAを導出すると、アクセスできない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を導出して送信し、そうでない場合はアドレスを拒否してください。
デモ
以下の例はアドレス検証ロジックのみを示しています。これは説明目的の参考コードです。
このデモでは、ATAの導出方法やトークンを送信するトランザクションの構築方法については説明していません。サンプルコードについては、token account および token transfer のドキュメントを参照してください。
以下のデモでは、3つの考えられる結果を使用しています:
| 結果 | 意味 | アクション |
|---|---|---|
IS_WALLET | 有効なウォレットアドレス | associated token accountを導出して送信 |
IS_TOKEN_ACCOUNT | 有効なtoken account | このアドレスに直接トークンを送信 |
REJECT | 無効なアドレス | 送信しない |
次に、各結果をcanReceiveNativeSol
(ウォレットのみ)およびcanReceiveSplToken(ウォレットまたはtoken
account)を使用して、アセットごとの受け入れ可否にマッピングします。token
accountはIS_TOKEN_ACCOUNTを返すため、SPLトークンを受け取ることはできますが、ネイティブSOLは受け取れません。これがSOLがロックされるのを防ぐための重要な区別です。
/*** 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 onchainif (!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" };}/*** 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?