Résumé
Un protocole standard pour encoder les demandes de transaction Solana dans des URL afin de permettre les paiements et d'autres cas d'usage.
Cette norme s'inspire de BIP 21 et EIP 681.
Motivation
Un protocole d'URL standard pour demander des transferts SOL natifs, des transferts de jetons SPL et des transactions Solana permet une meilleure expérience utilisateur dans l'ensemble des applications et portefeuilles de l'écosystème Solana.
Ces URL peuvent être encodées dans des codes QR ou des étiquettes NFC, ou envoyées entre les utilisateurs et les applications pour demander un paiement et composer des transactions.
Les applications doivent s'assurer qu'une transaction a été confirmée et est valide avant de libérer les biens ou services vendus, ou d'accorder l'accès à des objets ou des événements.
Les portefeuilles mobiles doivent s'enregistrer pour gérer le schéma d'URL afin de fournir une expérience fluide et sécurisée lorsque des URL Solana Pay sont rencontrées dans l'environnement.
En standardisant une approche simple pour résoudre ces problèmes, nous garantissons une compatibilité de base entre les applications et les portefeuilles afin que les développeurs puissent se concentrer sur des abstractions de plus haut niveau.
Spécification : Demande de transfert
Une URL de demande de transfert Solana Pay décrit une demande non interactive pour un transfert de SOL ou de jeton SPL.
solana:<recipient>?amount=<amount>&spl-token=<spl-token>&reference=<reference>&label=<label>&message=<message>&memo=<memo>
La demande est non interactive car les paramètres de l'URL sont utilisés par un portefeuille pour composer directement une transaction.
Destinataire
Un seul champ recipient est requis comme chemin d'accès. La valeur doit être
la clé publique encodée en base58 d'un compte SOL natif. Les associated token
accounts ne doivent pas être utilisés.
Au lieu de cela, pour demander un transfert de jeton SPL, le champ spl-token
doit être utilisé pour spécifier un mint de jeton SPL, à partir duquel l'adresse
de token account associée du destinataire doit être dérivée.
Montant
Un seul champ amount est autorisé en tant que paramètre de requête optionnel.
La valeur doit être un nombre entier non négatif ou un nombre décimal en unités
« utilisateur ». Pour SOL, cela signifie SOL et non lamport. Pour les tokens,
utilisez
uiAmountString et non amount.
0 est une valeur valide. Si la valeur est un nombre décimal inférieur à 1,
il doit avoir un 0 avant le .. La notation scientifique est interdite.
Si aucune valeur n'est fournie, le portefeuille doit inviter l'utilisateur à saisir le montant. Si le nombre de décimales dépasse ce qui est pris en charge pour SOL (9) ou le jeton SPL (spécifique au mint), le portefeuille doit rejeter l'URL comme mal formée.
Jeton SPL
Un seul champ spl-token est autorisé en tant que paramètre de requête
optionnel. La valeur doit être la clé publique encodée en base58 d'un mint
account de jeton SPL.
Si le champ est fourni, la convention
Associated Token Program doit
être utilisée, et le portefeuille doit inclure une instruction
TokenProgram.Transfer ou TokenProgram.TransferChecked comme dernière
instruction de la transaction.
Si le champ n'est pas fourni, l'URL décrit un transfert SOL natif, et le
portefeuille doit inclure une instruction SystemProgram.Transfer comme
dernière instruction de la transaction à la place.
Le portefeuille doit dériver l'adresse ATA à partir des champs recipient et
spl-token. Les transferts vers des token account auxiliaires ne sont pas pris
en charge.
Référence
Plusieurs champs reference sont autorisés en tant que paramètres de requête
optionnels. Les valeurs doivent être des tableaux de 32 octets encodés en
base58. Ceux-ci peuvent ou non être des clés publiques, sur ou hors de la
courbe, et peuvent ou non correspondre à des comptes sur Solana.
Si les valeurs sont fournies, le portefeuille doit les inclure dans l'ordre
fourni en tant que clés en lecture seule, non-signataires de l'instruction
SystemProgram.Transfer ou
TokenProgram.Transfer/TokenProgram.TransferChecked dans la transaction de
paiement. Les valeurs peuvent être ou non uniques à la demande de paiement, et
peuvent correspondre ou non à un compte sur Solana.
Étant donné que les validateurs Solana indexent les transactions par ces clés de
compte, les valeurs reference peuvent être utilisées comme identifiants
clients (identifiants utilisables avant de connaître la transaction de paiement
finale). La méthode RPC
getSignaturesForAddress
peut être utilisée pour localiser les transactions de cette manière.
Libellé
Un seul champ label est autorisé en tant que paramètre de requête optionnel.
La valeur doit être une chaîne UTF-8
encodée en URL
qui décrit la source de la demande de transfert.
Par exemple, il peut s'agir du nom d'une marque, d'un magasin, d'une application ou d'une personne effectuant la demande. Le portefeuille doit décoder l'URL la valeur et afficher la valeur décodée à l'utilisateur.
Message
Un seul champ message est autorisé en tant que paramètre de requête optionnel.
La valeur doit être une chaîne UTF-8
encodée en URL
qui décrit la nature de la demande de transfert.
Par exemple, il peut s'agir du nom d'un article acheté, d'un numéro de commande ou d'une note de remerciement. Le portefeuille doit décoder l'URL la valeur et afficher la valeur décodée à l'utilisateur.
Mémo
Un seul champ memo est autorisé en tant que paramètre de requête optionnel. La
valeur doit être une chaîne UTF-8
encodée en URL
qui doit être incluse dans une instruction
SPL Memo dans la transaction de paiement.
Le portefeuille doit décoder l'URL la valeur et devrait afficher la valeur décodée à l'utilisateur. Le mémo sera enregistré par les validateurs et ne doit pas inclure d'informations privées ou sensibles.
Si le champ est fourni, le portefeuille doit inclure une instruction
MemoProgram en tant qu'avant-dernière instruction de la transaction,
immédiatement avant l'instruction de transfert SOL ou de jeton SPL, afin
d'éviter toute ambiguïté avec d'autres instructions dans la transaction.
Exemples
URL décrivant une demande de transfert de 1 SOL
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?amount=1&label=Michael&message=Thanks%20for%20all%20the%20fish&memo=OrderId12345
URL décrivant une demande de transfert de 0,01 USDC
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?amount=0.01&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
URL décrivant une demande de transfert de SOL (montant demandé à l'utilisateur)
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?label=Michael
Spécification : Demande de transaction
Une URL de demande de transaction Solana Pay décrit une demande interactive pour n'importe quelle transaction Solana.
solana:<link>
La demande est interactive car les paramètres de l'URL sont utilisés par un portefeuille pour effectuer une requête HTTP afin de composer une transaction.
Lien
Un seul champ link est requis en tant que chemin d'accès. La valeur doit être
une URL HTTPS absolue conditionnellement
encodée en URL.
Si l'URL contient des paramètres de requête, elle doit être encodée en URL. Des paramètres de requête de protocole peuvent être ajoutés à cette spécification. L'encodage en URL de la valeur évite les conflits avec les paramètres de protocole.
Si l'URL ne contient pas de paramètres de requête, elle ne doit pas être encodée en URL. Cela produit une URL plus courte et un code QR moins dense.
Dans les deux cas, le portefeuille doit décoder l'URL. Cela n'a aucun effet si la valeur n'est pas encodée en URL. Si la valeur décodée n'est pas une URL HTTPS absolue, le portefeuille doit la rejeter comme étant mal formée.
Requête GET
Le portefeuille doit effectuer une requête JSON HTTP GET à l'URL. La requête
ne doit pas identifier le portefeuille ni l'utilisateur.
Le portefeuille doit effectuer la requête avec un en-tête Accept-Encoding, et l'application doit répondre avec un en-tête Content-Encoding pour la compression HTTP.
Le portefeuille doit afficher le domaine de l'URL pendant que la requête est effectuée.
Réponse GET
Le portefeuille doit gérer les
erreurs client
HTTP, les
erreurs serveur,
et les
réponses de redirection.
L'application doit répondre avec celles-ci, ou avec une réponse JSON HTTP OK
ayant un corps de :
{ "label": "<label>", "icon": "<icon>" }
La valeur <label> doit être une chaîne UTF-8 qui décrit la source de la
demande de transaction. Par exemple, il peut s'agir du nom d'une marque, d'un
magasin, d'une application ou d'une personne effectuant la demande.
La valeur <icon> doit être une URL HTTP ou HTTPS absolue d'une image d'icône.
Le fichier doit être une image SVG, PNG ou WebP, sinon le portefeuille doit la
rejeter comme mal formée.
Le portefeuille ne doit pas mettre en cache la réponse, sauf si les en-têtes de mise en cache HTTP le spécifient.
Le portefeuille doit afficher l'étiquette et afficher l'image de l'icône à l'utilisateur.
Requête POST
Le portefeuille doit effectuer une requête JSON HTTP POST vers l'URL avec un
corps de :
{ "account": "<account>" }
La valeur <account> doit être la clé publique encodée en base58 d'un compte
pouvant signer la transaction.
Le portefeuille doit effectuer la requête avec un en-tête Accept-Encoding, et l'application doit répondre avec un en-tête Content-Encoding pour la compression HTTP.
Le portefeuille doit afficher le domaine de l'URL pendant que la requête est
effectuée. Si une requête GET a été effectuée, le portefeuille doit également
afficher l'étiquette et afficher l'image de l'icône provenant de la réponse.
Réponse POST
Le portefeuille doit gérer les
erreurs client
HTTP, les
erreurs serveur,
et les
réponses de redirection.
L'application doit répondre avec celles-ci, ou avec une réponse JSON HTTP OK
ayant un corps de :
{ "transaction": "<transaction>" }
La valeur <transaction> doit être une
transaction sérialisée
encodée en base64. Le portefeuille doit décoder la transaction en base64 et la
désérialiser.
L'application peut répondre avec une transaction partiellement ou entièrement signée. Le portefeuille doit valider la transaction comme non fiable.
Signatures vides
Si les
signatures
de la transaction sont vides :
- L'application doit définir le
feePayersur leaccountdans la requête, ou la valeur zéro (new PublicKey(0)ounew PublicKey("11111111111111111111111111111111")). - L'application doit définir le
recentBlockhashsur le blockhash le plus récent, ou la valeur zéro (new PublicKey(0).toBase58()ou"11111111111111111111111111111111"). - Le portefeuille doit ignorer le
feePayerdans la transaction et définir lefeePayersur leaccountdans la requête. - Le portefeuille doit ignorer le
recentBlockhashdans la transaction et définir lerecentBlockhashsur le blockhash le plus récent.
Signatures non vides
Si les
signatures
de la transaction ne sont pas vides :
- L'application doit définir le
feePayersur la clé publique de la première signature. - L'application doit définir le
recentBlockhashsur le blockhash le plus récent. - L'application doit sérialiser et désérialiser la transaction avant de la signer. Cela garantit un ordre cohérent des clés de compte, comme solution de contournement pour ce problème.
- Le portefeuille ne doit pas définir le
feePayeret lerecentBlockhash. - Le portefeuille doit vérifier les signatures, et si l'une d'elles est invalide, le portefeuille doit rejeter la transaction comme mal formée.
Le portefeuille ne doit signer la transaction qu'avec le account dans la
requête, et doit le faire uniquement si une signature pour le account dans la
requête est attendue.
Si une signature autre qu'une signature pour le account dans la requête est
attendue, le portefeuille doit rejeter la transaction comme malveillante.
Champ de message optionnel
L'application peut également inclure un champ message optionnel dans le corps
de la réponse :
{ "message": "<message>", "transaction": "<transaction>" }
La valeur <message> doit être une chaîne UTF-8 qui décrit la nature de la
réponse à la transaction.
Par exemple, il peut s'agir du nom d'un article acheté, d'une réduction appliquée à l'achat ou d'un message de remerciement. Le portefeuille doit afficher cette valeur à l'utilisateur.
Le portefeuille et l'application doivent accepter des champs supplémentaires dans le corps de la requête et le corps de la réponse, qui pourront être ajoutés par les spécifications futures.
Exemples
URL décrivant une demande de transaction
solana:https://example.com/solana-pay
URL décrivant une demande de transaction avec paramètres de requête
solana:https%3A%2F%2Fexample.com%2Fsolana-pay%3Forder%3D12345
Exemple de requête GET
GET /solana-pay?order=12345 HTTP/1.1Host: example.comConnection: closeAccept: application/jsonAccept-Encoding: br, gzip, deflate
Exemple de réponse GET
HTTP/1.1 200 OKConnection: closeContent-Type: application/jsonContent-Length: 62Content-Encoding: gzip{"label":"Michael Vines","icon":"https://example.com/icon.svg"}
Exemple de requête 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"}
Exemple de réponse POST
HTTP/1.1 200 OKConnection: closeContent-Type: application/jsonContent-Length: 298Content-Encoding: gzip{"message":"Thanks for all the fish","transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECC4JMKqNplIXybGb/GhK1ofdVWeuEjXnQor7gi0Y2hMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAMAgAAAAAAAAAAAAAA"}
Extensions
Des formats et champs supplémentaires peuvent être intégrés à cette spécification pour permettre de nouveaux cas d'usage tout en assurant la compatibilité avec les applications et les portefeuilles.
Veuillez ouvrir une issue GitHub pour proposer des modifications à la spécification afin de solliciter les retours des développeurs d'applications et de portefeuilles.
Un exemple concret d'une telle proposition.
Voir aussi
- Spécification Solana Pay v1.1 - Dernière version avec améliorations
- Guide de démarrage rapide - Guides d'implémentation
- Dépôt GitHub - Implémentations de référence
Is this page helpful?