概要
URL内にSolanaトランザクションリクエストをエンコードし、支払いやその他のユースケースを実現するための標準プロトコルです。
この仕様に関する大まかな合意に達しており、Phantom、FTX、Slopeに実装が存在します。
この標準は、 BIP 21および EIP 681から着想を得ています。
動機
ネイティブSOL転送、SPLトークン転送、およびSolanaトランザクションをリクエストするための標準URLプロトコルにより、Solanaエコシステム内のアプリとウォレット全体でより良いユーザー体験が実現されます。
これらのURLは、QRコードやNFCタグにエンコードされるか、ユーザーとアプリケーション間で送信され、支払いをリクエストしたり、トランザクションを構成したりすることができます。
アプリケーションは、販売している商品やサービスを提供する前、またはオブジェクトやイベントへのアクセスを許可する前に、トランザクションが確認され、有効であることを確認する必要があります。
モバイルウォレットは、環境内でSolana Pay URLに遭遇した際に、シームレスかつ安全な体験を提供するために、URLスキームを処理するよう登録する必要があります。
これらの問題を解決するためのシンプルなアプローチを標準化することで、アプリケーションとウォレットの基本的な互換性を確保し、開発者がより高度な抽象化に集中できるようにします。
仕様:転送リクエスト
Solana Pay転送リクエストURLは、SOLまたはSPLトークンの転送に対する非対話型リクエストを記述します。
solana:<recipient>?amount=<amount>&spl-token=<spl-token>&reference=<reference>&label=<label>&message=<message>&memo=<memo>
このリクエストが非対話型である理由は、URL内のパラメータがウォレットによってトランザクションを直接構成するために使用されるためです。
受取人
パス名として単一のrecipientフィールドが必須です。値は、ネイティブSOLアカウントのbase58エンコードされた公開鍵である必要があります。associated
token accountを使用してはいけません。
代わりに、SPL
Tokenの転送をリクエストするには、spl-tokenフィールドを使用してSPL Token
mintを指定する必要があり、そこから受取人のassociated token
addressを導出する必要があります。
金額
単一のamountフィールドは、オプションのクエリパラメータとして使用できます。値は、「ユーザー」単位の非負の整数または小数でなければなりません。SOLの場合は、lamportではなくSOLです。トークンの場合は、uiAmountStringを使用し、amountは使用しません。
0は有効な値です。値が1未満の小数の場合、.の前に0を付ける必要があります。指数表記は禁止されています。
値が指定されていない場合、ウォレットはユーザーに金額の入力を求める必要があります。小数点以下の桁数がSOL(9桁)またはSPL Token(mint固有)でサポートされている桁数を超える場合、ウォレットはURLを不正な形式として拒否する必要があります。
SPL Token
単一のspl-tokenフィールドは、オプションのクエリパラメータとして使用できます。値は、SPL
Token mint accountのbase58エンコードされた公開鍵である必要があります。
このフィールドが指定されている場合、Associated Token Accountの規約を使用する必要があり、ウォレットはトランザクションの最後のinstructionとしてTokenProgram.TransferまたはTokenProgram.TransferCheckedのinstructionを含める必要があります。
このフィールドが指定されていない場合、URLはネイティブSOL転送を示しており、ウォレットは代わりにトランザクションの最後のinstructionとしてSystemProgram.Transferのinstructionを含める必要があります。
ウォレットは、recipientフィールドとspl-tokenフィールドからATAアドレスを導出する必要があります。補助token
accountへの転送はサポートされていません。
参照
複数のreferenceフィールドは、オプションのクエリパラメータとして使用できます。値は、base58エンコードされた32バイト配列である必要があります。これらは公開鍵である場合もそうでない場合もあり、曲線上または曲線外である場合もあり、Solana上のアカウントに対応している場合もそうでない場合もあります。
値が提供される場合、ウォレットは提供された順序でそれらを読み取り専用の非署名者キーとして、支払いトランザクション内のSystemProgram.TransferまたはTokenProgram.Transfer/TokenProgram.TransferCheckedインストラクションに含める必要があります。これらの値は支払いリクエストに固有である場合とそうでない場合があり、Solana上のアカウントに対応している場合とそうでない場合があります。
Solanaバリデータはこれらのアカウントキーによってトランザクションをインデックス化するため、referenceの値はクライアントID(最終的な支払いトランザクションを知る前に使用可能なID)として使用できます。getSignaturesForAddressRPCメソッドを使用して、この方法でトランザクションを特定することができます。
ラベル
単一のlabelフィールドは、オプションのクエリパラメータとして許可されています。値は、送金リクエストの送信元を説明するURLエンコードされたUTF-8文字列である必要があります。
例えば、これはリクエストを行っているブランド、店舗、アプリケーション、または個人の名前である可能性があります。ウォレットは値をURLデコードし、デコードされた値をユーザーに表示する必要があります。
メッセージ
単一のmessageフィールドは、オプションのクエリパラメータとして許可されています。値は、送金リクエストの性質を説明するURLエンコードされたUTF-8文字列である必要があります。
例えば、これは購入されるアイテムの名前、注文ID、または感謝のメモである可能性があります。ウォレットは値をURLデコードし、デコードされた値をユーザーに表示する必要があります。
メモ
単一のmemoフィールドは、オプションのクエリパラメータとして許可されています。値は、支払いトランザクション内のSPL Memoインストラクションに含める必要があるURLエンコードされたUTF-8文字列である必要があります。
ウォレットは値をURLデコードし、デコードされた値をユーザーに表示する必要があります。メモはバリデータによって記録されるため、プライベートまたは機密情報を含めるべきではありません。
このフィールドが提供される場合、ウォレットはトランザクション内の他のインストラクションとの曖昧さを避けるため、SOLまたはSPLトークン転送インストラクションの直前、トランザクションの最後から2番目のインストラクションとしてMemoProgramインストラクションを含める必要があります。
例
1 SOLの転送リクエストを記述するURL
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?amount=1&label=Michael&message=Thanks%20for%20all%20the%20fish&memo=OrderId12345
0.01 USDCの転送リクエストを記述するURL
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?amount=0.01&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
SOLの転送リクエストを記述するURL(ユーザーに金額の入力を促す)
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?label=Michael
仕様:トランザクションリクエスト
Solana PayトランザクションリクエストURLは、あらゆるSolanaトランザクションに対するインタラクティブなリクエストを記述します。
solana:<link>
このリクエストがインタラクティブであるのは、URL内のパラメータがウォレットによってHTTPリクエストを作成し、トランザクションを構成するために使用されるためです。
リンク
パス名として単一のlinkフィールドが必要です。この値は、条件付きでURLエンコードされた絶対HTTPSURLでなければなりません。
URLにクエリパラメータが含まれている場合は、URLエンコードする必要があります。プロトコルのクエリパラメータがこの仕様に追加される可能性があります。値をURLエンコードすることで、プロトコルパラメータとの競合を防ぎます。
URLにクエリパラメータが含まれていない場合は、URLエンコードすべきではありません。これにより、より短いURLと密度の低いQRコードが生成されます。
いずれの場合も、ウォレットは値をURLデコードする必要があります。値がURLエンコードされていない場合、これは何も影響しません。デコードされた値が絶対HTTPSURLでない場合、ウォレットはそれを不正な形式として拒否する必要があります。
GETリクエスト
ウォレットは、このURLに対してHTTP GET
JSONリクエストを行う必要があります。リクエストはウォレットやユーザーを識別すべきではありません。
ウォレットはAccept-Encodingヘッダーを含めてリクエストを行い、アプリケーションはHTTP圧縮のためにContent-Encodingヘッダーで応答する必要があります。
ウォレットは、リクエストが行われる際にURLのドメインを表示する必要があります。
GETレスポンス
ウォレットは、HTTP
クライアントエラー、
サーバーエラー、および
リダイレクトレスポンス
を処理する必要があります。アプリケーションは、これらのレスポンス、またはHTTP
OK JSONレスポンスで以下のボディを返す必要があります:
{ "label": "<label>", "icon": "<icon>" }
<label>
の値は、トランザクションリクエストの送信元を説明するUTF-8文字列である必要があります。例えば、これはリクエストを行っているブランド、店舗、アプリケーション、または個人の名前である可能性があります。
<icon> の値は、アイコン画像の絶対HTTPまたはHTTPS
URLである必要があります。ファイルはSVG、PNG、またはWebP画像である必要があり、そうでない場合、ウォレットはこれを不正な形式として拒否する必要があります。
ウォレットは、HTTPキャッシングレスポンスヘッダーで指示されない限り、レスポンスをキャッシュすべきではありません。
ウォレットは、ラベルを表示し、アイコン画像をユーザーに表示する必要があります。
POSTリクエスト
ウォレットは、以下のボディを持つHTTP POST
JSONリクエストをURLに対して行う必要があります:
{ "account": "<account>" }
<account>
の値は、トランザクションに署名する可能性のあるアカウントのbase58エンコードされた公開鍵である必要があります。
ウォレットは、Accept-Encodingヘッダーを付けてリクエストを行うべきであり、アプリケーションはHTTP圧縮のためにContent-Encodingヘッダーを付けてレスポンスを返すべきです。
ウォレットは、リクエストが行われる際にURLのドメインを表示する必要があります。GET
リクエストが行われた場合、ウォレットはレスポンスからラベルを表示し、アイコン画像も表示する必要があります。
POSTレスポンス
ウォレットは、HTTP
クライアントエラー、
サーバーエラー、および
リダイレクトレスポンス
を処理する必要があります。アプリケーションは、これらのレスポンス、またはHTTP
OK JSONレスポンスで以下のボディを返す必要があります:
{ "transaction": "<transaction>" }
<transaction> 値は、base64エンコードされた
シリアライズされたトランザクションである必要があります。ウォレットは、トランザクションをbase64デコードし、
デシリアライズする必要があります。
アプリケーションは、部分的または完全に署名されたトランザクションを返す場合があります。ウォレットは、トランザクションを信頼できないものとして検証する必要があります。
空の署名
トランザクションの
signatures
が空の場合:
- アプリケーションは、
feePayerをリクエスト内のaccount、またはゼロ値(new PublicKey(0)またはnew PublicKey("11111111111111111111111111111111"))に設定する必要があります。 - アプリケーションは、
recentBlockhashを 最新のブロックハッシュ、またはゼロ値(new PublicKey(0).toBase58()または"11111111111111111111111111111111")に設定する必要があります。 - ウォレットは、トランザクション内の
feePayerを無視し、feePayerをリクエスト内のaccountに設定する必要があります。 - ウォレットは、トランザクション内の
recentBlockhashを無視し、recentBlockhashを 最新のブロックハッシュに設定する必要があります。
トランザクションの
signatures
が空でない場合:
- アプリケーションは、
feePayerを 最初の署名の公開鍵に設定する必要があります。 - アプリケーションは、
recentBlockhashを 最新のブロックハッシュに設定する必要があります。 - アプリケーションは、署名する前にトランザクションをシリアライズおよびデシリアライズする必要があります。これにより、アカウントキーの順序が一貫し、 この問題の回避策となります。
- ウォレットは、
feePayerおよびrecentBlockhashを設定してはなりません。 - ウォレットは署名を検証する必要があり、いずれかが無効な場合、ウォレットはトランザクションを不正な形式として拒否する必要があります。
ウォレットは、リクエスト内の account
でのみトランザクションに署名する必要があり、リクエスト内の account
の署名が必要な場合にのみ署名する必要があります。
リクエスト内の account
の署名以外の署名が必要な場合、ウォレットはトランザクションを悪意のあるものとして拒否する必要があります。
アプリケーションは、レスポンスボディにオプションのmessageフィールドを含めることもできます:
{ "message": "<message>", "transaction": "<transaction>" }
<message>の値は、トランザクションレスポンスの性質を説明するUTF-8文字列である必要があります。
例えば、購入されるアイテムの名前、購入に適用される割引、または感謝のメッセージなどが考えられます。ウォレットはこの値をユーザーに表示する必要があります。
ウォレットとアプリケーションは、将来の仕様で追加される可能性のあるリクエストボディとレスポンスボディの追加フィールドを許可する必要があります。
例
トランザクションリクエストを記述するURL
solana:https://example.com/solana-pay
クエリパラメータを含むトランザクションリクエストを記述するURL
solana:https%3A%2F%2Fexample.com%2Fsolana-pay%3Forder%3D12345
GETリクエスト
GET /solana-pay?order=12345 HTTP/1.1Host: example.comConnection: closeAccept: application/jsonAccept-Encoding: br, gzip, deflate
GETレスポンス
HTTP/1.1 200 OKConnection: closeContent-Type: application/jsonContent-Length: 62Content-Encoding: gzip{"label":"Michael Vines","icon":"https://example.com/icon.svg"}
POSTリクエスト
POST /solana-pay?order=12345 HTTP/1.1Host: example.comConnection: closeAccept: application/jsonAccept-Encoding: br, gzip, deflateContent-Type: application/jsonContent-Length: 57{"account":"mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN"}
POSTレスポンス
HTTP/1.1 200 OKConnection: closeContent-Type: application/jsonContent-Length: 298Content-Encoding: gzip{"message":"Thanks for all the fish","transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECC4JMKqNplIXybGb/GhK1ofdVWeuEjXnQor7gi0Y2hMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAMAgAAAAAAAAAAAAAA"}
拡張機能
アプリとウォレットとの互換性を確保しながら、新しいユースケースを可能にするために、追加のフォーマットとフィールドがこの仕様に組み込まれる場合があります。
アプリケーションおよびウォレット開発者からのフィードバックを募るため、仕様への変更を提案するGithub issueを開いてください。
Is this page helpful?