概述
一种在 URL 中编码 Solana 交易请求的标准协议,用于实现支付和其他用例。
动机
用于请求原生 SOL 转账、SPL Token 转账和 Solana 交易的标准 URL 协议,可为 Solana 生态系统中的应用程序和钱包提供更好的用户体验。
这些 URL 可以编码为二维码或 NFC 标签,或在用户和应用程序之间发送以请求支付和组合交易。
应用程序应在释放正在出售的商品或服务,或授予对象或活动的访问权限之前,确保交易已被确认且有效。
移动钱包应注册处理该 URL 协议,以便在环境中遇到 Solana Pay URL 时提供无缝且安全的体验。
通过标准化解决这些问题的简单方法,我们确保了应用程序和钱包的基本兼容性,使开发人员可以专注于更高层次的抽象。
规范:转账请求
Solana Pay 转账请求 URL 描述了对 SOL 或 SPL Token 转账的非交互式请求。
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 铸币地址,并从中派生接收方的 associated token account。
金额
允许将单个 amount
字段作为可选查询参数。该值必须是非负整数或"用户"单位的十进制数。对于 SOL,这是 SOL 而不是 lamport。对于代币,请使用
uiAmountString 而不是 amount。
0 是有效值。如果值是小于 1 的十进制数,则必须在 . 前添加前导
0。禁止使用科学计数法。
如果未提供值,钱包必须提示用户输入金额。如果小数位数超过 SOL(9)或 SPL 代币(特定于铸币账户)支持的位数,钱包必须将该 URL 拒绝为格式错误。
SPL 代币
允许将单个 spl-token
字段作为可选查询参数。该值必须是 SPL 代币铸币账户的 base58 编码公钥。
如果提供了该字段,则必须使用关联代币账户约定,钱包必须在交易的最后一条指令中包含
TokenProgram.Transfer 或 TokenProgram.TransferChecked 指令。
如果未提供该字段,则 URL 描述的是原生 SOL 转账,钱包必须改为在交易的最后一条指令中包含
SystemProgram.Transfer 指令。
钱包必须从 recipient 和 spl-token
字段派生关联代币账户地址。不支持向辅助代币账户转账。
引用
允许将多个 reference
字段作为可选查询参数。这些值必须是 base58 编码的 32 字节数组。它们可能是也可能不是公钥,可能在曲线上也可能不在曲线上,并且可能与 Solana 上的账户对应,也可能不对应。
如果提供了这些值,钱包必须将它们按照提供的顺序作为只读、非签名者密钥包含到支付交易的
SystemProgram.Transfer 或
TokenProgram.Transfer/TokenProgram.TransferChecked
指令中。这些值可能是也可能不是支付请求所特有的,也可能对应或不对应 Solana 上的账户。
由于 Solana 验证节点通过这些账户密钥对交易进行索引,因此 reference
值可以用作客户端 ID(在知道最终支付交易之前可用的 ID)。可以使用
getSignaturesForAddress
RPC 方法以这种方式定位交易。
标签
允许使用单个 label 字段作为可选查询参数。该值必须是经过
URL 编码的 UTF-8 字符串,用于描述转账请求的来源。
例如,这可能是发起请求的品牌、商店、应用程序或个人的名称。钱包应对该值进行 URL 解码,并向用户显示解码后的值。
消息
允许使用单个 message 字段作为可选查询参数。该值必须是经过
URL 编码的 UTF-8 字符串,用于描述转账请求的性质。
例如,这可能是正在购买的商品名称、订单 ID 或感谢信息。钱包应对该值进行 URL 解码,并向用户显示解码后的值。
备注
允许使用单个 memo 字段作为可选查询参数。该值必须是经过
URL 编码的 UTF-8 字符串,且必须包含在支付交易的
SPL Memo 指令中。
钱包必须对该值进行 URL 解码,并应向用户显示解码后的值。该备注将被验证节点记录,因此不应包含私密或敏感信息。
如果提供了该字段,钱包必须在交易的倒数第二条指令位置包含一个 MemoProgram
指令,紧邻 SOL 或 SPL 代币转账指令之前,以避免与交易中的其他指令产生歧义。
示例
描述 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 编码的绝对 HTTPS
URL。
如果 URL 包含查询参数,则必须进行 URL 编码。协议查询参数可能会添加到此规范中。对值进行 URL 编码可防止与协议参数冲突。
如果 URL 不包含查询参数,则不应进行 URL 编码。这样可以生成更短的 URL 和密度更低的二维码。
无论哪种情况,钱包都必须对值进行 URL 解码。如果值未经 URL 编码,这不会产生任何影响。如果解码后的值不是绝对 HTTPS URL,钱包必须将其拒绝为格式错误。
GET 请求
钱包应向该 URL 发起 HTTP GET JSON 请求。该请求不应识别钱包或用户身份。
钱包应在请求中包含 Accept-Encoding 标头,应用程序应使用 Content-Encoding 标头进行响应,以实现 HTTP 压缩。
钱包应在发起请求时显示 URL 的域名。
GET 响应
钱包必须处理 HTTP
客户端错误、
服务器错误
和
重定向响应。应用程序必须返回这些响应,或返回一个 HTTP
OK JSON 响应,响应体为:
{ "label": "<label>", "icon": "<icon>" }
<label>
值必须是一个 UTF-8 字符串,用于描述交易请求的来源。例如,这可能是发起请求的品牌、商店、应用程序或个人的名称。
<icon> 值必须是图标图像的绝对 HTTP 或 HTTPS
URL。文件必须是 SVG、PNG 或 WebP 图像,否则钱包必须将其拒绝为格式错误。
钱包不应缓存响应,除非由 HTTP 缓存响应头指示。
钱包应向用户显示标签并渲染图标图像。
POST 请求
钱包必须向该 URL 发起一个 HTTP POST JSON 请求,请求体为:
{ "account": "<account>" }
<account> 值必须是可能签署交易的账户的 base58 编码公钥。
钱包应使用 Accept-Encoding 头发起请求,应用程序应使用 Content-Encoding 头响应以支持 HTTP 压缩。
钱包应在发起请求时显示 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 提出对规范的修改建议,以征求应用程序和钱包开发者的反馈。
另请参阅
- Solana Pay 规范 v1.1 - 最新改进版本
- 快速入门指南 - 实施指南
- GitHub 代码库 - 参考实现
Is this page helpful?