Solana Pay 사양 v1

요약

결제 및 기타 사용 사례를 가능하게 하기 위해 URL 내에서 Solana 트랜잭션 요청을 인코딩하는 표준 프로토콜입니다.

이 표준은 BIP 21EIP 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 토큰 전송을 요청하려면 spl-token 필드를 사용하여 SPL 토큰 민트를 지정해야 하며, 이로부터 수신자의 associated token address가 파생되어야 합니다.

금액

단일 amount 필드는 선택적 쿼리 매개변수로 허용됩니다. 값은 "사용자" 단위의 음수가 아닌 정수 또는 십진수여야 합니다. SOL의 경우 lamport가 아닌 SOL을 사용합니다. 토큰의 경우 amount가 아닌 uiAmountString를 사용합니다.

0는 유효한 값입니다. 값이 1보다 작은 십진수인 경우 . 앞에 0가 있어야 합니다. 과학적 표기법은 허용되지 않습니다.

값이 제공되지 않으면 지갑은 사용자에게 금액을 입력하도록 요청해야 합니다. 소수점 이하 자릿수가 SOL(9) 또는 SPL 토큰(민트별)에서 지원하는 자릿수를 초과하는 경우 지갑은 해당 URL을 형식 오류로 거부해야 합니다.

SPL 토큰

단일 spl-token 필드는 선택적 쿼리 매개변수로 허용됩니다. 값은 SPL 토큰 민트 계정의 base58 인코딩된 공개 키여야 합니다.

이 필드가 제공되는 경우 Associated Token Account 규칙을 사용해야 하며, 지갑은 트랜잭션의 마지막 인스트럭션으로 TokenProgram.Transfer 또는 TokenProgram.TransferChecked 인스트럭션을 포함해야 합니다.

이 필드가 제공되지 않으면 URL은 네이티브 SOL 전송을 설명하며, 지갑은 대신 트랜잭션의 마지막 인스트럭션으로 SystemProgram.Transfer 인스트럭션을 포함해야 합니다.

지갑은 recipientspl-token 필드에서 ATA 주소를 파생해야 합니다. 보조 토큰 계정으로의 전송은 지원되지 않습니다.

참조

여러 reference 필드는 선택적 쿼리 매개변수로 허용됩니다. 값은 base58 인코딩된 32바이트 배열이어야 합니다. 이들은 공개 키일 수도 있고 아닐 수도 있으며, 곡선 위 또는 곡선 밖에 있을 수 있고, Solana의 계정과 일치할 수도 있고 아닐 수도 있습니다.

값이 제공되면 지갑은 제공된 순서대로 해당 값을 읽기 전용, 비서명자 키로 결제 트랜잭션의 SystemProgram.Transfer 또는 TokenProgram.Transfer/TokenProgram.TransferChecked 인스트럭션에 포함해야 합니다. 이 값들은 결제 요청에 고유할 수도 있고 그렇지 않을 수도 있으며, 솔라나의 계정에 해당할 수도 있고 그렇지 않을 수도 있습니다.

솔라나 validator들은 이러한 계정 키로 트랜잭션을 인덱싱하기 때문에, reference 값은 클라이언트 ID(최종 결제 트랜잭션을 알기 전에 사용할 수 있는 ID)로 사용될 수 있습니다. 이 방식으로 트랜잭션을 찾기 위해 getSignaturesForAddress RPC 메서드를 사용할 수 있습니다.

레이블

선택적 쿼리 매개변수로 단일 label 필드가 허용됩니다. 값은 전송 요청의 출처를 설명하는 URL 인코딩된 UTF-8 문자열이어야 합니다.

예를 들어, 요청을 하는 브랜드, 상점, 애플리케이션 또는 사람의 이름일 수 있습니다. 지갑은 값을 URL 디코딩하고 디코딩된 값을 사용자에게 표시해야 합니다.

메시지

선택적 쿼리 매개변수로 단일 message 필드가 허용됩니다. 값은 전송 요청의 성격을 설명하는 URL 인코딩된 UTF-8 문자열이어야 합니다.

예를 들어, 구매 중인 항목의 이름, 주문 ID 또는 감사 메모일 수 있습니다. 지갑은 값을 URL 디코딩하고 디코딩된 값을 사용자에게 표시해야 합니다.

메모

선택적 쿼리 매개변수로 단일 memo 필드가 허용됩니다. 값은 결제 트랜잭션의 SPL Memo 인스트럭션에 포함되어야 하는 URL 인코딩된 UTF-8 문자열이어야 합니다.

지갑은 값을 URL 디코딩해야 하며 디코딩된 값을 사용자에게 표시하는 것이 좋습니다. 메모는 validator들에 의해 기록되므로 개인정보나 민감한 정보를 포함해서는 안 됩니다.

필드가 제공되는 경우, 지갑은 트랜잭션의 마지막에서 두 번째 명령어로 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 요청

지갑은 다음과 같은 본문으로 URL에 HTTP POST JSON 요청을 해야 합니다:

{ "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최신 블록해시로 설정해야 합니다.
  • 애플리케이션은 트랜잭션에 서명하기 전에 직렬화 및 역직렬화해야 합니다. 이는 계정 키의 일관된 순서를 보장하며, 이 이슈에 대한 해결 방법입니다.
  • 지갑은 feePayerrecentBlockhash를 설정해서는 안 됩니다.
  • 지갑은 서명을 검증해야 하며, 유효하지 않은 서명이 있는 경우 트랜잭션을 형식 오류로 거부해야 합니다.

지갑은 요청의 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.1
Host: example.com
Connection: close
Accept: application/json
Accept-Encoding: br, gzip, deflate

GET 응답 예시

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Content-Length: 62
Content-Encoding: gzip
{"label":"Michael Vines","icon":"https://example.com/icon.svg"}

POST 요청 예시

POST /solana-pay?order=12345 HTTP/1.1
Host: example.com
Connection: close
Accept: application/json
Accept-Encoding: br, gzip, deflate
Content-Type: application/json
Content-Length: 57
{"account":"mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN"}

POST 응답 예시

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Content-Length: 298
Content-Encoding: gzip
{"message":"Thanks for all the fish","transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECC4JMKqNplIXybGb/GhK1ofdVWeuEjXnQor7gi0Y2hMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAMAgAAAAAAAAAAAAAA"}

확장

앱 및 지갑과의 호환성을 보장하면서 새로운 사용 사례를 가능하게 하기 위해 추가 형식과 필드가 본 사양에 통합될 수 있습니다.

애플리케이션 및 지갑 개발자의 피드백을 받기 위해 사양 변경을 제안하려면 GitHub 이슈를 열어주시기 바랍니다.

이러한 제안의 실제 사례입니다.

참고 자료

Is this page helpful?

관리자

© 2026 솔라나 재단.
모든 권리 보유.
연결하기