Skip to content

Commit

Permalink
solana-pay
Browse files Browse the repository at this point in the history
  • Loading branch information
tkorkmazeth committed Oct 2, 2024
1 parent 7a64689 commit bae43a7
Show file tree
Hide file tree
Showing 15 changed files with 656 additions and 38 deletions.
58 changes: 58 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@metaplex-foundation/umi-uploader-irys": "^0.10.0-beta.0",
"@metaplex-foundation/umi-web3js-adapters": "^0.9.2",
"@noble/ed25519": "^1.7.1",
"@solana/pay": "^0.2.5",
"@solana/wallet-adapter-base": "^0.9.22",
"@solana/wallet-adapter-react": "^0.15.32",
"@solana/wallet-adapter-react-ui": "^0.9.31",
Expand Down
1 change: 1 addition & 0 deletions src/components/CreateNft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const MintNFT: FC = () => {
.add(
mintFromCandyMachineV2(umi, {
candyMachine: loadedCandyMachine.publicKey,
//@ts-ignore
mintAuthority: loadedCandyMachine.mintAuthority,
nftOwner,
nftMint,
Expand Down
114 changes: 114 additions & 0 deletions src/components/SendTransactionRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { PublicKey, Transaction, TransactionSignature } from "@solana/web3.js";
import axios from "axios";
import { PostError, PostResponse } from "pages/api/transaction";
import { FC, useCallback } from "react";
import { notify } from "../utils/notifications";
import { useNetworkConfiguration } from "../contexts/NetworkConfigurationProvider";

type SendTransactionRequestProps = {
reference: PublicKey;
};

export const SendTransactionRequest: FC<SendTransactionRequestProps> = ({
reference,
}) => {
const { connection } = useConnection();
const { publicKey, sendTransaction } = useWallet();
const { networkConfiguration } = useNetworkConfiguration();

const onClick = useCallback(async () => {
if (!publicKey) {
notify({ type: "error", message: `Wallet not connected!` });
console.error("Send Transaction: Wallet not connected!");
return;
}

let signature: TransactionSignature = "";
try {
// Request the transaction from transaction request API
const { data } = await axios.post(
`/api/transaction?network=${networkConfiguration}&reference=${reference.toBase58()}`,
{
account: publicKey,
},
{
// Don't throw for 4xx responses, we handle them
validateStatus: (s) => s < 500,
}
);

const response = data as PostResponse | PostError;

if ("error" in response) {
console.error(`Failed to fetch transaction! ${response.error}`);
notify({
type: "error",
message: "Failed to fetch transaction!",
description: response.error,
});
return;
}

const message = response.message;
notify({
type: "info",
message: "Fetched transaction!",
description: `message: ${message}`,
});

// De-serialize the returned transaction
const transaction = Transaction.from(
Buffer.from(response.transaction, "base64")
);

// Debug: log current and expected signers of the transaction
// The API can return a partially signed transaction
console.log("Fetched transaction", transaction);
const currentSigners = [
...new Set(
transaction.signatures
.filter((k) => k.signature !== null)
.map((k) => k.publicKey.toBase58())
),
];
const expectedSigners = [
...new Set(
transaction.instructions.flatMap((i) =>
i.keys.filter((k) => k.isSigner).map((k) => k.pubkey.toBase58())
)
),
];
console.log({
currentSigners,
expectedSigners,
transaction: response.transaction,
});

// Send the transaction
await sendTransaction(transaction, connection);
} catch (error: any) {
notify({
type: "error",
message: `Transaction failed!`,
description: error?.message,
txid: signature,
});
console.error(`Transaction failed! ${error?.message}`, signature);
return;
}
}, [publicKey, networkConfiguration, reference, sendTransaction, connection]);

return (
<div>
<button
className="group w-60 m-2 btn animate-pulse disabled:animate-none bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500 ... "
onClick={onClick}
disabled={!publicKey}
>
<div className="hidden group-disabled:block ">Wallet not connected</div>
<span className="block group-disabled:hidden">Send with wallet</span>
</button>
</div>
);
};
93 changes: 93 additions & 0 deletions src/components/SendTransferRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import {
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
Transaction,
TransactionSignature,
} from "@solana/web3.js";
import { FC, useCallback } from "react";
import { notify } from "../utils/notifications";

type SendTransferRequestProps = {
reference: PublicKey;
};

export const SendTransferRequest: FC<SendTransferRequestProps> = ({
reference,
}) => {
const { connection } = useConnection();
const { publicKey, sendTransaction } = useWallet();

const onClick = useCallback(async () => {
if (!publicKey) {
notify({ type: "error", message: `Wallet not connected!` });
console.error("Send Transaction: Wallet not connected!");
return;
}

let signature: TransactionSignature = "";
try {
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();

// Transfer transaction
const transaction = new Transaction({
feePayer: publicKey,
blockhash,
lastValidBlockHeight,
});

const transferInstruction = SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: Keypair.generate().publicKey,
lamports: LAMPORTS_PER_SOL / 1000,
});

// Add reference as a key to the instruction
transferInstruction.keys.push({
pubkey: reference,
isSigner: false,
isWritable: false,
});

transaction.add(transferInstruction);

// Debug: log current and expected signers of the transaction
console.log("Created transaction", transaction);
const currentSigners = transaction.signatures
.filter((k) => k.signature !== null)
.map((k) => k.publicKey.toBase58());
const expectedSigners = transaction.instructions.flatMap((i) =>
i.keys.filter((k) => k.isSigner).map((k) => k.pubkey.toBase58())
);
console.log({ currentSigners, expectedSigners });

// Send the transaction
await sendTransaction(transaction, connection);
} catch (error: any) {
notify({
type: "error",
message: `Transaction failed!`,
description: error?.message,
txid: signature,
});
console.error(`Transaction failed! ${error?.message}`, signature);
return;
}
}, [publicKey, connection, reference, sendTransaction]);

return (
<div>
<button
className="group w-60 m-2 btn animate-pulse disabled:animate-none bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500 ... "
onClick={onClick}
disabled={!publicKey}
>
<div className="hidden group-disabled:block ">Wallet not connected</div>
<span className="block group-disabled:hidden">Send with wallet</span>
</button>
</div>
);
};
40 changes: 40 additions & 0 deletions src/components/TransactionRequestQR.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createQR, encodeURL, TransactionRequestURLFields } from "@solana/pay";
import { PublicKey } from "@solana/web3.js";
import { useNetworkConfiguration } from "contexts/NetworkConfigurationProvider";
import { FC, useEffect, useRef } from "react";

type TransactionRequestQRProps = {
reference: PublicKey;
};

export const TransactionRequestQR: FC<TransactionRequestQRProps> = ({
reference,
}) => {
const qrRef = useRef<HTMLDivElement>(null);
const { networkConfiguration } = useNetworkConfiguration();

useEffect(() => {
// window.location is only available in the browser, so create the URL in here
const { location } = window;
const apiUrl = `${location.protocol}//${
location.host
}/api/transaction?network=${networkConfiguration}&reference=${reference.toBase58()}`;
const urlParams: TransactionRequestURLFields = {
link: new URL(apiUrl),
label: "My Store",
};
const solanaUrl = encodeURL(urlParams);
const qr = createQR(solanaUrl, 512, "transparent");
qr.update({ backgroundOptions: { round: 1000 } });
if (qrRef.current) {
qrRef.current.innerHTML = "";
qr.append(qrRef.current);
}
}, [networkConfiguration, reference]);

return (
<div className="rounded-2xl">
<div ref={qrRef} />
</div>
);
};
38 changes: 38 additions & 0 deletions src/components/TransferRequestQR.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createQR, encodeURL, TransferRequestURLFields } from "@solana/pay";
import { Keypair, PublicKey } from "@solana/web3.js";
import BigNumber from "bignumber.js";
import { FC, useEffect, useRef } from "react";

type TransferRequestQRProps = {
reference: PublicKey;
};

export const TransferRequestQR: FC<TransferRequestQRProps> = ({
reference,
}) => {
const qrRef = useRef<HTMLDivElement>(null);

useEffect(() => {
// Create a transfer request QR code
const urlParams: TransferRequestURLFields = {
recipient: Keypair.generate().publicKey,
amount: new BigNumber(1 / 1000), // amount in SOL
reference,
label: "My Store",
message: "Thankyou for your purchase!",
};
const solanaUrl = encodeURL(urlParams);
const qr = createQR(solanaUrl, 512, "transparent");
qr.update({ backgroundOptions: { round: 1000 } });
if (qrRef.current) {
qrRef.current.innerHTML = "";
qr.append(qrRef.current);
}
}, [reference]);

return (
<div className="rounded-2xl">
<div ref={qrRef} />
</div>
);
};
Loading

0 comments on commit bae43a7

Please sign in to comment.