Skip to content

Commit

Permalink
Merge pull request #77 from solana-labs/link-request
Browse files Browse the repository at this point in the history
Specify and implement Solana Pay Transaction Requests
  • Loading branch information
jordaaash authored Apr 29, 2022
2 parents fb40bfc + b5a0e88 commit b8da65e
Show file tree
Hide file tree
Showing 150 changed files with 3,720 additions and 4,470 deletions.
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ The Solana blockchain confirms transactions in less than a second and costs on a

## Supporting Wallets

* Phantom ([iOS](https://apps.apple.com/us/app/phantom-solana-wallet/id1598432977))
* Crypto Please ([iOS](https://apps.apple.com/us/app/crypto-please/id1559625715), [Android](https://play.google.com/store/apps/details?id=com.pleasecrypto.flutter))
* FTX ([iOS](https://apps.apple.com/us/app/ftx-trade-btc-eth-shib/id1095564685), [Android](https://play.google.com/store/apps/details?id=com.blockfolio.blockfolio))
* Glow ([iOS](https://apps.apple.com/app/id1599584512))

### Coming Soon

* Slope
* Solflare
* Reactor Wallet
- Phantom ([iOS](https://apps.apple.com/us/app/phantom-solana-wallet/id1598432977), [Android](https://play.google.com/store/apps/details?id=app.phantom&hl=en_US&gl=US))
- Solflare ([iOS](https://apps.apple.com/us/app/solflare/id1580902717), [Android](https://play.google.com/store/apps/details?id=com.solflare.mobile))
- Glow ([iOS](https://apps.apple.com/app/id1599584512))
- Slope ([iOS](https://apps.apple.com/us/app/slope-wallet/id1574624530), [Android](https://play.google.com/store/apps/details?id=com.wd.wallet&hl=en_US&gl=US))
- Crypto Please ([iOS](https://apps.apple.com/us/app/crypto-please/id1559625715), [Android](https://play.google.com/store/apps/details?id=com.pleasecrypto.flutter))
- FTX ([iOS](https://apps.apple.com/us/app/ftx-trade-btc-eth-shib/id1095564685), [Android](https://play.google.com/store/apps/details?id=com.blockfolio.blockfolio))

## How to use Solana Pay

Expand Down
220 changes: 192 additions & 28 deletions SPEC.md

Large diffs are not rendered by default.

38 changes: 17 additions & 21 deletions core/example/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createAssociatedTokenAccount } from '@solana/spl-token';
import { clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js';
import BigNumber from 'bignumber.js';
import { createTransaction, encodeURL, findTransactionSignature, parseURL, validateTransactionSignature } from '../src';
import { clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL, sendAndConfirmRawTransaction } from '@solana/web3.js';
import { createTransfer, encodeURL, findReference, parseURL, TransferRequestURL, validateTransfer } from '../src';

(async function () {
const cluster = 'devnet';
Expand Down Expand Up @@ -34,7 +33,9 @@ import { createTransaction, encodeURL, findTransactionSignature, parseURL, valid
console.log(originalURL);

// Wallet gets URL from deep link / QR code
const { recipient, amount, splToken, reference, label, message, memo } = parseURL(originalURL);
const { recipient, amount, splToken, reference, label, message, memo } = parseURL(
originalURL
) as TransferRequestURL;

// Apps can encode the URL from the required and optional parameters
const encodedURL = encodeURL({ recipient, amount, splToken, reference, label, message, memo });
Expand All @@ -53,28 +54,24 @@ import { createTransaction, encodeURL, findTransactionSignature, parseURL, valid
}

// Create a transaction to transfer native SOL or SPL tokens
const transaction = await createTransaction(connection, wallet.publicKey, recipient, amount!, {
const transaction = await createTransfer(connection, wallet.publicKey, {
recipient,
amount,
splToken,
reference,
memo,
});

// Sign and send the transaction
transaction.feePayer = wallet.publicKey;
transaction.recentBlockhash = (await connection.getRecentBlockhash()).blockhash;
// Sign, send, and confirm the transaction
transaction.sign(wallet);

const rawTransaction = transaction.serialize();
const signature = await sendAndConfirmRawTransaction(connection, rawTransaction);

const signature = await connection.sendRawTransaction(rawTransaction);

// Confirm the transaction
const result = await connection.confirmTransaction(signature, 'confirmed');

console.log(result);
console.log(signature);

// Merchant app locates the transaction signature from the unique reference address it provided in the transfer link
const found = await findTransactionSignature(connection, originalReference);
const found = await findReference(connection, originalReference);

// Matches the signature of the transaction
console.log(found.signature);
Expand All @@ -83,12 +80,11 @@ import { createTransaction, encodeURL, findTransactionSignature, parseURL, valid
console.log(found.memo);

// Merchant app should always validate that the transaction transferred the expected amount to the recipient
const response = await validateTransactionSignature(
connection,
found.signature,
const response = await validateTransfer(connection, found.signature, {
recipient,
amount!,
amount,
splToken,
reference
);
reference,
memo,
});
})();
20 changes: 4 additions & 16 deletions core/example/payment-flow-merchant/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
import BigNumber from 'bignumber.js';
import {
encodeURL,
findTransactionSignature,
FindTransactionSignatureError,
validateTransactionSignature,
} from '../../src';
import { encodeURL, findReference, FindReferenceError, validateTransfer } from '../../src';
import { MERCHANT_WALLET } from './constants';
import { establishConnection } from './establishConnection';
import { simulateCheckout } from './simulateCheckout';
Expand Down Expand Up @@ -76,12 +71,12 @@ async function main() {
const interval = setInterval(async () => {
console.count('Checking for transaction...');
try {
signatureInfo = await findTransactionSignature(connection, reference, undefined, 'confirmed');
signatureInfo = await findReference(connection, reference, { finality: 'confirmed' });
console.log('\n 🖌 Signature found: ', signatureInfo.signature);
clearInterval(interval);
resolve(signatureInfo);
} catch (error: any) {
if (!(error instanceof FindTransactionSignatureError)) {
if (!(error instanceof FindReferenceError)) {
console.error(error);
clearInterval(interval);
reject(error);
Expand All @@ -105,14 +100,7 @@ async function main() {
console.log('\n6. 🔗 Validate transaction \n');

try {
await validateTransactionSignature(
connection,
signature,
MERCHANT_WALLET,
amount,
undefined,
reference
);
await validateTransfer(connection, signature, { recipient: MERCHANT_WALLET, amount });

// Update payment status
paymentStatus = 'validated';
Expand Down
12 changes: 5 additions & 7 deletions core/example/payment-flow-merchant/simulateWalletInteraction.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Connection, LAMPORTS_PER_SOL, sendAndConfirmTransaction } from '@solana/web3.js';
import BigNumber from 'bignumber.js';
import { createTransaction, parseURL } from '../../src';
import { TransferRequestURL } from '../../lib/types';
import { createTransfer, parseURL } from '../../src';
import { CUSTOMER_WALLET } from './constants';

export async function simulateWalletInteraction(connection: Connection, url: string) {
export async function simulateWalletInteraction(connection: Connection, url: URL) {
/**
* For example only
*
* The URL that triggers the wallet interaction; follows the Solana Pay URL scheme
* The parameters needed to create the correct transaction is encoded within the URL
*/
const { recipient, message, memo, amount, reference, label } = parseURL(url);
const { recipient, amount, reference, label, message, memo } = parseURL(url) as TransferRequestURL;
console.log('label: ', label);
console.log('message: ', message);

Expand All @@ -24,10 +25,7 @@ export async function simulateWalletInteraction(connection: Connection, url: str
/**
* Create the transaction with the parameters decoded from the URL
*/
const tx = await createTransaction(connection, CUSTOMER_WALLET.publicKey, recipient, amount as BigNumber, {
reference,
memo,
});
const tx = await createTransfer(connection, CUSTOMER_WALLET.publicKey, { recipient, amount, reference, memo });

/**
* Send the transaction to the network
Expand Down
20 changes: 12 additions & 8 deletions core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solana/pay",
"version": "0.1.3",
"version": "0.2.0",
"author": "Solana Maintainers <[email protected]>",
"repository": "https://github.com/solana-labs/solana-pay",
"license": "Apache-2.0",
Expand Down Expand Up @@ -37,18 +37,21 @@
},
"dependencies": {
"@solana/qr-code-styling": "^1.6.0-beta.0",
"@solana/spl-token": "^0.2.0-alpha.1",
"@solana/web3.js": "^1.31.0",
"bignumber.js": "^9.0.2"
"@solana/spl-token": "^0.2.0",
"@solana/web3.js": "^1.36.0",
"bignumber.js": "^9.0.2",
"cross-fetch": "^3.1.5",
"js-base64": "^3.7.2",
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"@types/eslint": "^8.2.1",
"@types/eslint-plugin-prettier": "^3.1.0",
"@types/jest": "^27.4.0",
"@types/node": "^16.11.14",
"@types/prettier": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
Expand All @@ -57,9 +60,10 @@
"prettier": "^2.5.1",
"shx": "^0.3.3",
"ts-jest": "^27.1.2",
"ts-node": "^10.4.0",
"ts-node": "^10.7.0",
"tsc-esm": "^1.0.4",
"tslib": "^2.3.1",
"typedoc": "^0.22.10",
"typedoc": "^0.22.13",
"typescript": "^4.5.4",
"typescript-esm": "^2.0.0"
}
Expand Down
5 changes: 4 additions & 1 deletion core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { PublicKey } from '@solana/web3.js';
import BigNumber from 'bignumber.js';

/** @internal */
export const URL_PROTOCOL = 'solana:';
export const SOLANA_PROTOCOL = 'solana:';

/** @internal */
export const HTTPS_PROTOCOL = 'https:';

/** @internal */
export const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
Expand Down
14 changes: 7 additions & 7 deletions core/src/createQR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ import QRCodeStyling, {
/**
* Create a QR code from a Solana Pay URL.
*
* @param url - The URL to encode in the QR code.
* @param size - Size of canvas in `px`.
* @param background - Background color for QR code.
* @param color - Color for QR code pattern.
* @param url - The URL to encode.
* @param size - Width and height in pixels.
* @param background - Background color, which should be light for device compatibility.
* @param color - Foreground color, which should be dark for device compatibility.
*/
export function createQR(url: string, size = 512, background = 'white', color = 'black'): QRCodeStyling {
export function createQR(url: string | URL, size = 512, background = 'white', color = 'black'): QRCodeStyling {
return new QRCodeStyling(createQROptions(url, size, background, color));
}

/** @ignore */
export function createQROptions(url: string, size = 512, background = 'white', color = 'black'): Options {
export function createQROptions(url: string | URL, size = 512, background = 'white', color = 'black'): Options {
return {
type: 'svg' as DrawType,
width: size,
height: size,
data: url,
data: String(url),
margin: 16,
qrOptions: {
typeNumber: 0 as TypeNumber,
Expand Down
Loading

0 comments on commit b8da65e

Please sign in to comment.