@solana/pay
Version:
`@solana/pay` is a JavaScript library for facilitating commerce on Solana by using a token transfer URL scheme. The URL scheme ensures that no matter the wallet or service used, the payment request must be created and interpreted in one standard way.
749 lines (731 loc) • 32 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
CreateTransferError: () => CreateTransferError,
FetchTransactionError: () => FetchTransactionError,
FindReferenceError: () => FindReferenceError,
HTTPS_PROTOCOL: () => HTTPS_PROTOCOL,
MEMO_PROGRAM_ADDRESS: () => import_memo.MEMO_PROGRAM_ADDRESS,
ParseURLError: () => ParseURLError,
SOLANA_PROTOCOL: () => SOLANA_PROTOCOL,
SOL_DECIMALS: () => SOL_DECIMALS,
TOKEN_2022_PROGRAM_ADDRESS: () => import_token_2022.TOKEN_2022_PROGRAM_ADDRESS,
ValidateTransferError: () => ValidateTransferError,
createMerchantClient: () => createMerchantClient,
createQR: () => createQR,
createQROptions: () => createQROptions,
createSolanaPayClient: () => createSolanaPayClient,
createTransfer: () => createTransfer,
createWalletClient: () => createWalletClient,
encodeURL: () => encodeURL,
fetchTransaction: () => fetchTransaction,
findReference: () => findReference,
parseURL: () => parseURL,
solanaPayMerchant: () => solanaPayMerchant,
solanaPayWallet: () => solanaPayWallet,
validateTransfer: () => validateTransfer,
watchReference: () => watchReference
});
module.exports = __toCommonJS(index_exports);
// src/constants.ts
var import_memo = require("@solana-program/memo");
var import_token_2022 = require("@solana-program/token-2022");
var SOLANA_PROTOCOL = "solana:";
var HTTPS_PROTOCOL = "https:";
var SOL_DECIMALS = 9;
// src/client.ts
var import_kit6 = require("@solana/kit");
var import_kit_plugin_instruction_plan = require("@solana/kit-plugin-instruction-plan");
var import_kit_plugin_rpc = require("@solana/kit-plugin-rpc");
var import_kit_plugin_signer = require("@solana/kit-plugin-signer");
// src/plugins/merchant.ts
var import_kit2 = require("@solana/kit");
// src/createQR.ts
var import_qr_code_styling = __toESM(require("@solana/qr-code-styling"), 1);
var QRCodeStyling = typeof import_qr_code_styling.default.default === "function" ? import_qr_code_styling.default.default : import_qr_code_styling.default;
function createQR(url, size = 512, background = "white", color = "black") {
return new QRCodeStyling(createQROptions(url, size, background, color));
}
function createQROptions(url, size = 512, background = "white", color = "black") {
return {
type: "svg",
width: size,
height: size,
data: String(url),
margin: 16,
qrOptions: {
typeNumber: 0,
mode: "Byte",
errorCorrectionLevel: "Q"
},
backgroundOptions: { color: background },
dotsOptions: { type: "extra-rounded", color },
cornersSquareOptions: {
type: "extra-rounded",
color
},
cornersDotOptions: { type: "square", color },
imageOptions: { hideBackgroundDots: true, imageSize: 0.15, margin: 8 },
image: `data:image/svg+xml;utf8,<svg fill="${encodeURIComponent(
color
)}" height="16" viewBox="0 0 16 14" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15.9176 11.038-2.6413 2.7713c-.0574.0602-.1269.1082-.2041.141s-.1604.0497-.2446.0497h-12.520966c-.059744 0-.118187-.0171-.168147-.0491-.0499596-.0321-.0892609-.0777-.1130745-.1313-.02381372-.0536-.03110249-.1129-.02097081-.1705.01013171-.0576.03724251-.111.07800141-.1538l2.6432769-2.7713c.05726-.06.12651-.1079.20346-.1407s.15996-.0498.2439-.05h12.52032c.0597 0 .1182.0171.1681.0492.05.032.0893.0776.1131.1313.0238.0536.0311.1128.021.1704-.0102.0576-.0373.1111-.078.1538zm-2.6413-5.58067c-.0574-.0602-.1269-.1082-.2041-.141s-.1604-.04971-.2446-.04966h-12.520966c-.059744 0-.118187.01708-.168147.04913-.0499596.03205-.0892609.07768-.1130745.13129-.02381372.0536-.03110249.11285-.02097081.17045.01013171.05761.03724251.11106.07800141.15379l2.6432769 2.77134c.05726.06004.12651.10794.20346.14073.07695.0328.15996.04979.2439.04993h12.52032c.0597 0 .1182-.01707.1681-.04913.05-.03205.0893-.07768.1131-.13129.0238-.0536.0311-.11285.021-.17045-.0102-.05761-.0373-.11106-.078-.15379zm-12.969666-1.99066h12.520966c.0842.00004.1674-.01687.2446-.04967s.1467-.0808.2041-.141l2.6413-2.771333c.0407-.042736.0678-.096189.078-.153792.0101-.057603.0028-.116847-.021-.170453s-.0631-.0992385-.1131-.1312911c-.0499-.0320526-.1084-.04912893-.1681-.0491309h-12.52032c-.08394.00013975-.16695.0171339-.2439.0499304s-.1462.0806976-.20346.1407366l-2.6425955 2.771333c-.0407196.04269-.0678184.09609-.07797306.15363-.01015467.05754-.00292373.11673.02080606.17031.0237297.05358.0629266.09922.1127835.13132.049857.03211.108207.04928.167893.04941z"/></svg>`
};
}
// src/utils/reference.ts
function normalizeReferences(reference) {
if (!reference) return void 0;
return Array.isArray(reference) ? reference : [reference];
}
// src/encodeURL.ts
function encodeURL(fields) {
return "link" in fields ? encodeTransactionRequestURL(fields) : encodeTransferRequestURL(fields);
}
function encodeTransactionRequestURL({ link, label, message }) {
const pathname = link.search ? encodeURIComponent(String(link).replace(/\/\?/, "?")) : String(link).replace(/\/$/, "");
const url = new URL(SOLANA_PROTOCOL + pathname);
if (label) {
url.searchParams.append("label", label);
}
if (message) {
url.searchParams.append("message", message);
}
return url;
}
function encodeTransferRequestURL({
recipient,
amount,
splToken,
reference,
label,
message,
memo
}) {
const pathname = recipient;
const url = new URL(SOLANA_PROTOCOL + pathname);
if (amount != null) {
url.searchParams.append("amount", amount.toFixed(10).replace(/0+$/, "").replace(/\.$/, ""));
}
if (splToken) {
url.searchParams.append("spl-token", splToken);
}
const refs = normalizeReferences(reference);
if (refs) {
for (const ref of refs) {
url.searchParams.append("reference", ref);
}
}
if (label) {
url.searchParams.append("label", label);
}
if (message) {
url.searchParams.append("message", message);
}
if (memo) {
url.searchParams.append("memo", memo);
}
return url;
}
// src/findReference.ts
var FindReferenceError = class extends Error {
name = "FindReferenceError";
};
async function findReference(rpc, reference, { commitment, ...options } = {}) {
const signatures = await rpc.getSignaturesForAddress(reference, { ...options, ...commitment ? { commitment } : {} }).send();
const length = signatures.length;
if (!length) throw new FindReferenceError("not found");
const oldest = signatures[length - 1];
if (length < (options?.limit || 1e3)) return oldest;
try {
return await findReference(rpc, reference, {
commitment,
...options,
before: oldest.signature
});
} catch (error) {
if (error instanceof FindReferenceError) return oldest;
throw error;
}
}
// src/validateTransfer.ts
var import_kit = require("@solana/kit");
var import_memo2 = require("@solana-program/memo");
var import_system = require("@solana-program/system");
var import_token = require("@solana-program/token");
// src/utils/amount.ts
function amountToBaseUnits(amount, decimals) {
if (!Number.isFinite(amount) || amount < 0) {
throw new Error(`Invalid amount: ${amount}`);
}
if (!Number.isInteger(decimals) || decimals < 0) {
throw new Error(`Invalid decimals: ${decimals}`);
}
const str = amount.toFixed(decimals);
const [whole, frac] = str.split(".");
const significantDigits = whole.replace(/^0+/, "").length + (frac?.replace(/0+$/, "").length ?? 0);
if (significantDigits > 15) {
throw new Error(
`Amount ${amount} with ${decimals} decimals exceeds safe floating-point precision (${significantDigits} significant digits > 15). Use a smaller amount or fewer decimals to avoid silent precision loss.`
);
}
return BigInt(whole + (frac ?? ""));
}
function decimalPlaces(n) {
const s = n.toString();
if (s.includes("e-")) {
const [coeff, exp] = s.split("e-");
const coeffDecimals = (coeff.split(".")[1] || "").length;
return coeffDecimals + Number(exp);
}
const dot = s.indexOf(".");
return dot === -1 ? 0 : s.length - dot - 1;
}
// src/validateTransfer.ts
var ValidateTransferError = class extends Error {
name = "ValidateTransferError";
};
function validateAmount(amount) {
if (!Number.isFinite(amount) || amount < 0) {
throw new ValidateTransferError("amount invalid");
}
}
function parseBase64Transaction(b64TransactionResponse) {
const [base64Transaction] = b64TransactionResponse;
const transactionBytes = (0, import_kit.getBase64Codec)().encode(base64Transaction);
const transaction = (0, import_kit.getTransactionCodec)().decode(transactionBytes);
const compiledMessage = (0, import_kit.getCompiledTransactionMessageCodec)().decode(transaction.messageBytes);
const decompiledMessage = (0, import_kit.decompileTransactionMessage)(compiledMessage);
const { staticAccounts } = compiledMessage;
const instructions = [...decompiledMessage.instructions];
return { instructions, staticAccounts };
}
function getMeta(meta) {
if (!meta) throw new ValidateTransferError("missing meta");
if (meta.err) throw new ValidateTransferError(JSON.stringify(meta.err));
return meta;
}
function validateInstruction(instruction) {
if (!instruction) {
throw new ValidateTransferError("missing instruction");
}
if (!instruction.accounts) {
throw new ValidateTransferError("missing instruction accounts");
}
if (!instruction.data) {
throw new ValidateTransferError("missing instruction data");
}
}
async function validateTransfer(rpc, signature, { recipient, amount, splToken, reference, memo }, options) {
validateAmount(amount);
const refs = normalizeReferences(reference);
const response = await rpc.getTransaction(signature, {
commitment: options?.commitment ?? "confirmed",
maxSupportedTransactionVersion: 0,
encoding: "base64"
}).send();
if (!response) throw new ValidateTransferError("not found");
const meta = getMeta(response.meta);
const { instructions, staticAccounts } = parseBase64Transaction(response.transaction);
const instruction = instructions.pop();
validateInstruction(instruction);
const { pre, post, decimals } = splToken ? await validateSPLTokenTransfer(instruction, staticAccounts, meta, recipient, splToken, refs) : validateSystemTransfer(instruction, staticAccounts, meta, recipient, refs);
const expected = amountToBaseUnits(amount, decimals);
if (post - pre < expected) throw new ValidateTransferError("amount not transferred");
if (memo !== void 0) {
validateMemo(instructions.pop(), memo);
}
return response;
}
function validateMemo(instruction, memo) {
if (!instruction) throw new ValidateTransferError("missing memo instruction");
if (instruction.programAddress !== import_memo.MEMO_PROGRAM_ADDRESS) {
throw new ValidateTransferError("invalid memo program");
}
if (!instruction.data) throw new ValidateTransferError("invalid memo");
const parsed = (0, import_memo2.parseAddMemoInstruction)(instruction);
if (parsed.data.memo !== memo) throw new ValidateTransferError("invalid memo");
}
function validateProgram(instruction, validPrograms) {
if (!validPrograms.includes(instruction.programAddress)) {
throw new ValidateTransferError("invalid transfer");
}
}
function validateReferences(instruction, requiredAccounts, references) {
if (!references) return;
const extraAccounts = instruction.accounts.slice(requiredAccounts);
if (extraAccounts.length !== references.length) throw new ValidateTransferError("invalid references");
for (let i = 0; i < extraAccounts.length; i++) {
if (extraAccounts[i].address !== references[i]) throw new ValidateTransferError(`invalid reference ${i}`);
}
}
function validateSystemTransfer(instruction, staticAccounts, meta, recipient, references) {
validateProgram(instruction, [import_system.SYSTEM_PROGRAM_ADDRESS]);
const instructionType = (0, import_system.identifySystemInstruction)(instruction);
if (instructionType !== import_system.SystemInstruction.TransferSol) {
throw new ValidateTransferError("invalid transfer");
}
const parsed = (0, import_system.parseTransferSolInstruction)(instruction);
if (parsed.accounts.destination.address !== recipient) {
throw new ValidateTransferError("invalid transfer");
}
validateReferences(instruction, Object.keys(parsed.accounts).length, references);
const accountIndex = staticAccounts.indexOf(recipient);
if (accountIndex === -1) throw new ValidateTransferError("recipient not found");
const pre = meta.preBalances[accountIndex];
const post = meta.postBalances[accountIndex];
if (pre === void 0 || post === void 0) throw new ValidateTransferError("missing balance data");
return { pre, post, decimals: SOL_DECIMALS };
}
async function validateSPLTokenTransfer(instruction, staticAccounts, meta, recipient, splToken, references) {
validateProgram(instruction, [import_token.TOKEN_PROGRAM_ADDRESS, import_token_2022.TOKEN_2022_PROGRAM_ADDRESS]);
const instructionType = (0, import_token.identifyTokenInstruction)(instruction);
const [recipientATA] = await (0, import_token.findAssociatedTokenPda)({
owner: recipient,
tokenProgram: instruction.programAddress,
mint: splToken
});
let requiredAccounts;
switch (instructionType) {
case import_token.TokenInstruction.TransferChecked: {
const parsed = (0, import_token.parseTransferCheckedInstruction)(instruction);
if (parsed.accounts.destination.address !== recipientATA) {
throw new ValidateTransferError("invalid transfer");
}
if (parsed.accounts.mint.address !== splToken) {
throw new ValidateTransferError("invalid transfer");
}
requiredAccounts = Object.keys(parsed.accounts).length;
break;
}
case import_token.TokenInstruction.Transfer: {
const parsed = (0, import_token.parseTransferInstruction)(instruction);
if (parsed.accounts.destination.address !== recipientATA) {
throw new ValidateTransferError("invalid transfer");
}
requiredAccounts = Object.keys(parsed.accounts).length;
break;
}
default:
throw new ValidateTransferError("invalid transfer instruction");
}
validateReferences(instruction, requiredAccounts, references);
const accountIndex = staticAccounts.indexOf(recipientATA);
if (accountIndex === -1) throw new ValidateTransferError("recipient not found");
const preBalance = meta.preTokenBalances?.find((x) => x.accountIndex === accountIndex);
const postBalance = meta.postTokenBalances?.find((x) => x.accountIndex === accountIndex);
if (!preBalance || !postBalance) throw new ValidateTransferError("missing balance data");
return {
pre: BigInt(preBalance.uiTokenAmount.amount),
post: BigInt(postBalance.uiTokenAmount.amount),
decimals: preBalance.uiTokenAmount.decimals
};
}
// src/watchReference.ts
async function watchReference(rpcSubscriptions, reference, options) {
const { commitment = "confirmed", abortSignal: externalSignal } = options ?? {};
if (externalSignal?.aborted) {
throw new FindReferenceError("aborted");
}
const abortController = new AbortController();
if (externalSignal) {
externalSignal.addEventListener("abort", () => abortController.abort(externalSignal.reason), { once: true });
}
try {
const subscription = await rpcSubscriptions.logsNotifications({ mentions: [reference] }, { commitment }).subscribe({ abortSignal: abortController.signal });
for await (const notification of subscription) {
return {
signature: notification.value.signature,
err: notification.value.err
};
}
} catch (error) {
if (abortController.signal.aborted) {
throw new FindReferenceError("aborted");
}
throw error;
} finally {
abortController.abort();
}
throw new FindReferenceError("not found");
}
// src/plugins/merchant.ts
function solanaPayMerchant() {
return function installMerchant(client) {
const existingPay = "pay" in client ? client.pay : {};
const pay = {
...existingPay,
encodeURL(fields) {
return encodeURL(fields);
},
createQR(url, size, background, color) {
return createQR(url, size, background, color);
},
createQROptions(url, size, background, color) {
return createQROptions(url, size, background, color);
},
async findReference(reference, options) {
return await findReference(client.rpc, reference, options);
},
async watchReference(reference, options) {
return await watchReference(client.rpcSubscriptions, reference, options);
},
async validateTransfer(signature, fields, options) {
return await validateTransfer(client.rpc, signature, fields, options);
},
buildTransaction(feePayer, instructions) {
const tx = (0, import_kit2.pipe)(
(0, import_kit2.createTransactionMessage)({ version: 0 }),
(m) => (0, import_kit2.setTransactionMessageFeePayer)(feePayer, m),
(m) => (0, import_kit2.appendTransactionMessageInstructions)(instructions, m)
);
return (0, import_kit2.getBase64EncodedWireTransaction)((0, import_kit2.compileTransaction)(tx));
}
};
return Object.freeze({ ...client, pay });
};
}
// src/createTransfer.ts
var import_kit3 = require("@solana/kit");
var import_memo3 = require("@solana-program/memo");
var import_system2 = require("@solana-program/system");
var import_token2 = require("@solana-program/token");
var CreateTransferError = class extends Error {
name = "CreateTransferError";
};
async function createTransfer(rpc, sender, { recipient, amount, splToken, reference, memo }) {
const instructions = [];
if (memo != null) {
instructions.push((0, import_memo3.getAddMemoInstruction)({ memo, signers: [sender] }));
}
const transferInstruction = splToken ? await createSPLTokenInstruction(recipient, amount, splToken, sender, rpc) : await createSystemInstruction(recipient, amount, sender, rpc);
const refs = normalizeReferences(reference);
if (refs) {
const existingAccounts = transferInstruction.accounts ?? [];
const refAccounts = refs.map((ref) => ({
address: ref,
role: import_kit3.AccountRole.READONLY
}));
const updatedInstruction = {
...transferInstruction,
accounts: [...existingAccounts, ...refAccounts]
};
instructions.push(updatedInstruction);
} else {
instructions.push(transferInstruction);
}
return instructions;
}
async function createSystemInstruction(recipient, amount, sender, rpc) {
const {
value: [senderInfo, recipientInfo]
} = await rpc.getMultipleAccounts([sender.address, recipient], { encoding: "base64" }).send();
if (!senderInfo) throw new CreateTransferError("sender not found");
if (senderInfo.owner !== import_system2.SYSTEM_PROGRAM_ADDRESS) throw new CreateTransferError("sender owner invalid");
if (senderInfo.executable) throw new CreateTransferError("sender executable");
if (recipientInfo) {
if (recipientInfo.owner !== import_system2.SYSTEM_PROGRAM_ADDRESS) throw new CreateTransferError("recipient owner invalid");
if (recipientInfo.executable) throw new CreateTransferError("recipient executable");
}
if (decimalPlaces(amount) > SOL_DECIMALS) throw new CreateTransferError("amount decimals invalid");
const lamports = amountToBaseUnits(amount, SOL_DECIMALS);
if (lamports > senderInfo.lamports) throw new CreateTransferError("insufficient funds");
return (0, import_system2.getTransferSolInstruction)({
source: sender,
destination: recipient,
amount: lamports
});
}
async function createSPLTokenInstruction(recipient, amount, splToken, sender, rpc) {
let mint;
try {
mint = await (0, import_token2.fetchMint)(rpc, splToken);
} catch {
throw new CreateTransferError("mint account not found");
}
if (!mint.data.isInitialized) throw new CreateTransferError("mint not initialized");
const tokenProgram = mint.programAddress === import_token_2022.TOKEN_2022_PROGRAM_ADDRESS ? import_token_2022.TOKEN_2022_PROGRAM_ADDRESS : import_token2.TOKEN_PROGRAM_ADDRESS;
if (decimalPlaces(amount) > mint.data.decimals) throw new CreateTransferError("amount decimals invalid");
const tokens = amountToBaseUnits(amount, mint.data.decimals);
const [[senderATA], [recipientATA]] = await Promise.all([
(0, import_token2.findAssociatedTokenPda)({ owner: sender.address, tokenProgram, mint: splToken }),
(0, import_token2.findAssociatedTokenPda)({ owner: recipient, tokenProgram, mint: splToken })
]);
const [senderAccount, recipientAccount] = await Promise.all([
(0, import_token2.fetchToken)(rpc, senderATA),
(0, import_token2.fetchToken)(rpc, recipientATA)
]);
if (senderAccount.data.state === import_token2.AccountState.Uninitialized)
throw new CreateTransferError("sender not initialized");
if (senderAccount.data.state === import_token2.AccountState.Frozen) throw new CreateTransferError("sender frozen");
if (recipientAccount.data.state === import_token2.AccountState.Uninitialized)
throw new CreateTransferError("recipient not initialized");
if (recipientAccount.data.state === import_token2.AccountState.Frozen) throw new CreateTransferError("recipient frozen");
if (tokens > senderAccount.data.amount) throw new CreateTransferError("insufficient funds");
return (0, import_token2.getTransferCheckedInstruction)(
{
source: senderATA,
mint: splToken,
destination: recipientATA,
authority: sender,
amount: tokens,
decimals: mint.data.decimals
},
{ programAddress: tokenProgram }
);
}
// src/fetchTransaction.ts
var import_keys = require("@solana/keys");
var import_kit4 = require("@solana/kit");
var FetchTransactionError = class extends Error {
name = "FetchTransactionError";
};
async function fetchTransaction(rpc, account, link, { commitment } = {}) {
let response;
try {
response = await fetch(String(link), {
method: "POST",
mode: "cors",
credentials: "omit",
headers: {
"Cache-Control": "no-cache",
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({ account })
});
} catch (error) {
throw new FetchTransactionError(`network error: ${error instanceof Error ? error.message : String(error)}`);
}
if (!response.ok) throw new FetchTransactionError(`request failed: ${response.status}`);
let json;
try {
json = await response.json();
} catch {
throw new FetchTransactionError("response is not valid JSON");
}
if (!json?.transaction) throw new FetchTransactionError("missing transaction");
if (typeof json.transaction !== "string") throw new FetchTransactionError("invalid transaction");
let transactionBytes;
try {
transactionBytes = (0, import_kit4.getBase64Encoder)().encode(json.transaction);
} catch {
throw new FetchTransactionError("invalid base64 in transaction");
}
let transaction;
try {
transaction = (0, import_kit4.getTransactionDecoder)().decode(transactionBytes);
} catch {
throw new FetchTransactionError("failed to decode transaction wire format");
}
let compiledMessage;
try {
compiledMessage = (0, import_kit4.getCompiledTransactionMessageDecoder)().decode(transaction.messageBytes);
} catch {
throw new FetchTransactionError("failed to decode compiled transaction message");
}
const signatures = transaction.signatures;
const signerAddresses = Object.keys(signatures).map((addr) => (0, import_kit4.address)(addr));
const hasSignatures = signerAddresses.some((addr) => {
const sig = signatures[addr];
return sig != null && !sig.every((b) => b === 0);
});
if (hasSignatures) {
const feePayer = signerAddresses[0];
if (!feePayer) throw new FetchTransactionError("missing fee payer");
if (compiledMessage.staticAccounts.length === 0 || compiledMessage.staticAccounts[0] !== feePayer) {
throw new FetchTransactionError("invalid fee payer");
}
if (!compiledMessage.lifetimeToken) {
throw new FetchTransactionError("missing recent blockhash");
}
const addressEncoder = (0, import_kit4.getAddressEncoder)();
for (const addr of signerAddresses) {
const sig = signatures[addr];
const isNonZero = sig != null && !sig.every((b) => b === 0);
if (isNonZero) {
const publicKeyBytes = addressEncoder.encode(addr);
const cryptoKey = await crypto.subtle.importKey("raw", publicKeyBytes, { name: "Ed25519" }, false, [
"verify"
]);
(0, import_keys.assertIsSignatureBytes)(sig);
const isValid = await (0, import_keys.verifySignature)(cryptoKey, sig, transaction.messageBytes);
if (!isValid) throw new FetchTransactionError("invalid signature");
} else if (addr === account) {
if (signerAddresses.length === 1) {
const { value } = await rpc.getLatestBlockhash({ commitment }).send();
const msg = (0, import_kit4.decompileTransactionMessage)(compiledMessage);
const updatedMsg = (0, import_kit4.setTransactionMessageLifetimeUsingBlockhash)(value, msg);
return (0, import_kit4.compileTransaction)(updatedMsg);
}
} else {
throw new FetchTransactionError("missing signature");
}
}
return transaction;
} else {
const { value } = await rpc.getLatestBlockhash({ commitment }).send();
const msg = (0, import_kit4.decompileTransactionMessage)(compiledMessage);
const withFeePayer = (0, import_kit4.setTransactionMessageFeePayer)(account, msg);
const withLifetime = (0, import_kit4.setTransactionMessageLifetimeUsingBlockhash)(value, withFeePayer);
return (0, import_kit4.compileTransaction)(withLifetime);
}
}
// src/parseURL.ts
var import_kit5 = require("@solana/kit");
var ParseURLError = class extends Error {
name = "ParseURLError";
};
function parseURL(url) {
if (typeof url === "string") {
if (url.length > 2048) throw new ParseURLError("length invalid");
url = new URL(url);
}
if (url.protocol !== SOLANA_PROTOCOL) throw new ParseURLError("protocol invalid");
if (!url.pathname) throw new ParseURLError("pathname missing");
return /[:%]/.test(url.pathname) ? parseTransactionRequestURL(url) : parseTransferRequestURL(url);
}
function parseTransactionRequestURL({ pathname, searchParams }) {
const link = new URL(decodeURIComponent(pathname));
if (link.protocol !== HTTPS_PROTOCOL) throw new ParseURLError("link invalid");
const label = searchParams.get("label") || void 0;
const message = searchParams.get("message") || void 0;
return {
link,
label,
message
};
}
function parseTransferRequestURL({ pathname, searchParams }) {
let recipient;
try {
recipient = (0, import_kit5.address)(pathname);
} catch {
throw new ParseURLError("recipient invalid");
}
let amount;
const amountParam = searchParams.get("amount");
if (amountParam != null) {
if (!/^\d+(\.\d+)?$/.test(amountParam)) throw new ParseURLError("amount invalid");
amount = Number(amountParam);
if (Number.isNaN(amount)) throw new ParseURLError("amount NaN");
if (amount < 0) throw new ParseURLError("amount negative");
}
let splToken;
const splTokenParam = searchParams.get("spl-token");
if (splTokenParam != null) {
try {
splToken = (0, import_kit5.address)(splTokenParam);
} catch {
throw new ParseURLError("spl-token invalid");
}
}
let reference;
const referenceParams = searchParams.getAll("reference");
if (referenceParams.length) {
try {
reference = referenceParams.map((ref) => (0, import_kit5.address)(ref));
} catch {
throw new ParseURLError("reference invalid");
}
}
const label = searchParams.get("label") || void 0;
const message = searchParams.get("message") || void 0;
const memo = searchParams.get("memo") || void 0;
return {
recipient,
amount,
splToken,
reference,
label,
message,
memo
};
}
// src/plugins/wallet.ts
function solanaPayWallet() {
return function installWallet(client) {
const existingPay = "pay" in client ? client.pay : {};
const pay = {
...existingPay,
parseURL(url) {
return parseURL(url);
},
async createTransfer(fields, sender) {
const signer = sender ?? client.payer;
if (!signer) {
throw new Error("solanaPayWallet.createTransfer requires a sender or client.payer");
}
return await createTransfer(client.rpc, signer, fields);
},
async fetchTransaction(account, link, options) {
return await fetchTransaction(client.rpc, account, link, options);
}
};
return Object.freeze({ ...client, pay });
};
}
// src/client.ts
function createMerchantClient(config) {
return (0, import_kit6.createClient)().use((0, import_kit_plugin_rpc.solanaRpcConnection)({ rpcUrl: config.rpcUrl, rpcSubscriptionsConfig: config.rpcSubscriptionsConfig })).use(solanaPayMerchant());
}
function createWalletClient(config) {
return (0, import_kit6.createClient)().use((0, import_kit_plugin_rpc.solanaRpcConnection)({ rpcUrl: config.rpcUrl, rpcSubscriptionsConfig: config.rpcSubscriptionsConfig })).use((0, import_kit_plugin_signer.payer)(config.payer)).use((0, import_kit_plugin_rpc.rpcTransactionPlanner)()).use((0, import_kit_plugin_rpc.rpcTransactionPlanExecutor)()).use((0, import_kit_plugin_instruction_plan.planAndSendTransactions)()).use(solanaPayWallet());
}
function createSolanaPayClient(config) {
return (0, import_kit6.createClient)().use((0, import_kit_plugin_rpc.solanaRpcConnection)({ rpcUrl: config.rpcUrl, rpcSubscriptionsConfig: config.rpcSubscriptionsConfig })).use((0, import_kit_plugin_signer.payer)(config.payer)).use((0, import_kit_plugin_rpc.rpcTransactionPlanner)()).use((0, import_kit_plugin_rpc.rpcTransactionPlanExecutor)()).use((0, import_kit_plugin_instruction_plan.planAndSendTransactions)()).use(solanaPayMerchant()).use(solanaPayWallet());
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CreateTransferError,
FetchTransactionError,
FindReferenceError,
HTTPS_PROTOCOL,
MEMO_PROGRAM_ADDRESS,
ParseURLError,
SOLANA_PROTOCOL,
SOL_DECIMALS,
TOKEN_2022_PROGRAM_ADDRESS,
ValidateTransferError,
createMerchantClient,
createQR,
createQROptions,
createSolanaPayClient,
createTransfer,
createWalletClient,
encodeURL,
fetchTransaction,
findReference,
parseURL,
solanaPayMerchant,
solanaPayWallet,
validateTransfer,
watchReference
});
//# sourceMappingURL=index.cjs.map