요약
URL 내에서 솔라나 트랜잭션 요청을 인코딩하여 결제 및 기타 사용 사례를 가능하게 하는 표준 프로토콜입니다.
이 사양에 대한 대략적인 합의가 이루어졌으며, Phantom, FTX 및 Slope에서 구현이 존재합니다.
이 표준은 BIP 21 및 EIP 681에서 영감을 받았습니다.
동기
네이티브 SOL 전송, SPL 토큰 전송 및 솔라나 트랜잭션을 요청하기 위한 표준 URL 프로토콜은 솔라나 생태계 내의 앱과 지갑 전반에서 더 나은 사용자 경험을 제공합니다.
이러한 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 토큰 전송을 요청하려면 spl-token 필드를 사용하여 SPL 토큰 민트를
지정해야 하며, 이로부터 수신자의 연결된 토큰 주소가 파생되어야 합니다.
금액
단일 amount 필드는 선택적 쿼리 매개변수로 허용됩니다. 값은 "사용자" 단위의
음수가 아닌 정수 또는 소수여야 합니다. SOL의 경우 램포트가 아닌 SOL입니다.
토큰의 경우
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 필드에서 ATA 주소를 파생해야 합니다. 보조 토큰
계정으로의 전송은 지원되지 않습니다.
참조
여러 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 필드는 선택적 쿼리 매개변수로 허용됩니다. 이 값은 결제 트랜잭션의
SPL 메모 인스트럭션에 포함되어야 하는
URL 인코딩된
UTF-8 문자열이어야 합니다.
지갑은 이 값을 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과 덜 밀집된 QR 코드가 생성됩니다.
어느 경우든 지갑은 값을 URL 디코딩해야 합니다. 값이 URL 인코딩되지 않은 경우 이는 아무 영향을 미치지 않습니다. 디코딩된 값이 절대 HTTPS URL이 아닌 경우 지갑은 이를 형식 오류로 거부해야 합니다.
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또는 영(zero) 값(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 이슈를 열어주세요.
Is this page helpful?