diff --git a/solana/web3.js-2.0/basics/transferSol.ts b/solana/web3.js-2.0/basics/transferSol.ts index 8985175..0f50b9f 100644 --- a/solana/web3.js-2.0/basics/transferSol.ts +++ b/solana/web3.js-2.0/basics/transferSol.ts @@ -1,4 +1,4 @@ -// Guide walkthrough: +// Guide walkthrough: How to Send Transactions with Solana Web3.js 2.0 // https://www.quicknode.com/guides/solana-development/solana-web3.js-2.0/transfer-sol import { diff --git a/solana/web3.js-2.0/fungibles/fungibles.ts b/solana/web3.js-2.0/fungibles/fungibles.ts index faaba66..275f6ef 100644 --- a/solana/web3.js-2.0/fungibles/fungibles.ts +++ b/solana/web3.js-2.0/fungibles/fungibles.ts @@ -1,3 +1,6 @@ +// Source code for Guide: Creating a Fungible Token with Solana Web3.js 2.0 +// https://www.quicknode.com/guides/solana-development/tooling/web3-2/fungibles + import { getCreateAccountInstruction } from "@solana-program/system"; import { findAssociatedTokenPda, diff --git a/solana/web3.js-2.0/quicknode-addons/guide1.ts b/solana/web3.js-2.0/quicknode-addons/guide1.ts new file mode 100644 index 0000000..cd10564 --- /dev/null +++ b/solana/web3.js-2.0/quicknode-addons/guide1.ts @@ -0,0 +1,67 @@ +// Source code for Guide: How to Use QuickNode Add-ons using Solana Web3.js 2.0 (Part 1) +// https://www.quicknode.com/guides/solana-development/tooling/web3-2/qn-add-ons + +import { + Rpc, + createDefaultRpcTransport, + createRpc, + RpcTransport, + createRpcApi, + createSolanaRpcApi, +} from "@solana/web3.js"; +import { + EstimatePriorityFeesResponse, + EstimatePriorityFeesParams +} from "./types"; + +type PriorityFeeApi = { + qn_estimatePriorityFees(params: EstimatePriorityFeesParams): EstimatePriorityFeesResponse; +} + +interface createQuickNodeTransportParams { + endpoint: string; +} + +function createQuickNodeTransport({ endpoint }: createQuickNodeTransportParams): RpcTransport { + const jsonRpcTransport = createDefaultRpcTransport({ url: endpoint }); + + return async (...args: Parameters): Promise => { + return await jsonRpcTransport(...args); + }; +} + +/** + * + * @param endpoint - Solana HTTP Endpoint + * Establish connection to Solana cluster + * Change as needed to the network you're using. + * Get a free Devnet or Mainnet endpoint from: + * https://www.quicknode.com/signup?utm_source=internal&utm_campaign=sample-apps&utm_content=solana-web3.js-2.0-add-ons + * @returns RPC instance with priority fee API + */ +export function createPriorityFeeApi(endpoint: string): Rpc { + const api0 = createSolanaRpcApi(); + const api = createRpcApi({ + parametersTransformer: (params: any[]) => params[0], + responseTransformer: (response: any) => response.result, + }); + const transport = createQuickNodeTransport({ + endpoint, + }); + return createRpc({ api, transport }); +} + + + +async function main() { + const quickNodeRpc = createPriorityFeeApi('https://example.solana-mainnet.quiknode.pro/123456/'); + + const priorityFees = await quickNodeRpc.qn_estimatePriorityFees({ + account: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4', + last_n_blocks: 100 + }).send(); + + console.log(priorityFees); +} + +main(); \ No newline at end of file diff --git a/solana/web3.js-2.0/quicknode-addons/guide2.ts b/solana/web3.js-2.0/quicknode-addons/guide2.ts new file mode 100644 index 0000000..65b628e --- /dev/null +++ b/solana/web3.js-2.0/quicknode-addons/guide2.ts @@ -0,0 +1,185 @@ +// Source code for Guide: How to Use QuickNode Add-ons using Solana Web3.js 2.0 (Part 2) +// https://www.quicknode.com/guides/solana-development/tooling/web3-2/qn-add-ons-2 + +import { + Rpc, + createDefaultRpcTransport, + createRpc, + RpcTransport, + createRpcApi, +} from "@solana/web3.js"; +import { + EstimatePriorityFeesResponse, + EstimatePriorityFeesParams, + IpfsUploadRequest, + IpfsUploadResponse, + QuoteGetRequest, QuoteResponse +} from "./types"; +import * as fs from 'fs'; + +type PriorityFeeApi = { + qn_estimatePriorityFees(params: EstimatePriorityFeesParams): Promise; +} + +type MetisApi = { + metis_quote(params: QuoteGetRequest): Promise; + // Add other Metis methods here +} + +type IpfsApi = { + ipfs_upload(params: IpfsUploadRequest): Promise; +} + +type QuickNodeAddons = PriorityFeeApi & MetisApi & IpfsApi; + +function createQuickNodeTransport({ endpoint, metisEndpoint, ipfsApiKey }: CreateAddonsApiParams): RpcTransport { + const jsonRpcTransport = createDefaultRpcTransport({ url: endpoint }); + + return async (...args: Parameters): Promise => { + const { method, params } = args[0].payload as { method: string; params: unknown }; + switch (true) { + case method.startsWith('metis_'): + return handleMetisRequest(method, params, metisEndpoint); + + case method === 'ipfs_upload': + return handleIpfsUpload(params as IpfsUploadRequest, ipfsApiKey); + + default: + return jsonRpcTransport(...args); + } + }; +} + +async function handleMetisRequest(method: string, params: TParams, metisEndpoint?: string): Promise { + const DEFAULT_METIS_ENDPOINT = 'https://public.jupiterapi.com'; + const jupiterMethod = method.replace('metis_', ''); + const url = new URL(`${metisEndpoint || DEFAULT_METIS_ENDPOINT}/${jupiterMethod}`); + const paramsToUse = Array.isArray(params) ? params[0] : params; + + if (typeof paramsToUse === 'object' && paramsToUse !== null) { + Object.entries(paramsToUse as Record).forEach(([key, value]) => { + url.searchParams.append(key, String(value)); + }); + } + + const response = await fetch(url.toString(), { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + if (!response.ok) { + throw new Error(`Error making fetch request to ${url}: ${response.statusText}`); + } + + const data = await response.json(); + return { result: data } as TResponse; +} + + +async function handleIpfsUpload(params: IpfsUploadRequest, ipfsApiKey?: string): Promise { + if (!ipfsApiKey) { + throw new Error('No IPFS API key provided'); + } + + const { filePath, fileName, fileType } = params; + const fileContent = fs.readFileSync(filePath); + const file = new File([fileContent], fileName, { type: fileType }); + const formData = new FormData(); + formData.append("Body", file); + formData.append("Key", file.name); + formData.append("ContentType", file.type); + + const url = new URL('https://api.quicknode.com/ipfs/rest/v1/s3/put-object'); + const response = await fetch(url, { + method: 'POST', + headers: { + 'x-api-key': ipfsApiKey, + }, + body: formData, + redirect: "follow", + }); + + if (!response.ok) { + throw new Error(`Error making fetch request to ${url}: ${response.statusText}`); + } + const data = await response.json(); + + + return { result: data } as T; +} + +interface CreateAddonsApiParams { + endpoint: string; + metisEndpoint?: string; + ipfsApiKey?: string; +} + +/** + * Creates an RPC instance with QuickNode Addons API + * + * @param {CreateAddonsApiParams} params - Configuration parameters for creating the API + * @param {string} params.endpoint - Solana HTTP Endpoint. Establish connection to Solana cluster. + * Change as needed to the network you're using. + * Get a free Devnet or Mainnet endpoint from: + * https://www.quicknode.com/signup?utm_source=internal&utm_campaign=sample-apps&utm_content=solana-web3.js-2.0-add-ons + * @param {string} [params.metisEndpoint] - Optional. Endpoint for Metis services if required. (defaults to 'https://public.jupiterapi.com') + * More information at: + * https://marketplace.quicknode.com/add-on/metis-jupiter-v6-swap-api?utm_source=internal&utm_campaign=sample-apps&utm_content=solana-web3.js-2.0-add-ons + * @param {string} [params.ipfsApiKey] - Optional. API key for IPFS services if required. + * More information at: + * hthttps://quicknode.com/ipfs?utm_source=internal&utm_campaign=sample-apps&utm_content=solana-web3.js-2.0-add-ons + * @returns {Rpc} RPC instance with QuickNode Addons API + */ + + +export function createAddonsApi(params: CreateAddonsApiParams): Rpc { + const api = createRpcApi({ + parametersTransformer: (params: unknown[]) => params[0], + responseTransformer: (response: any) => response.result, + }); + + const transport = createQuickNodeTransport(params); + + return createRpc({ api, transport }); +} + +async function main() { + const quickNodeRpc = createAddonsApi({ + endpoint: 'https://replace-me.solana-mainnet.quiknode.pro/123456/', + ipfsApiKey: 'QN_REPLACE_WITH_YOUR_IPFS_API_KEY', + }); + + try { + const priorityFees = await quickNodeRpc.qn_estimatePriorityFees({ + account: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4', + last_n_blocks: 100 + }).send(); + console.log(`Priority Fees (Med Per CU): ${priorityFees.per_compute_unit.medium}`); + } catch (error) { + console.error('Error estimating priority fees:', error); + } + + try { + const metisQuote = await quickNodeRpc.metis_quote({ + inputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + outputMint: "So11111111111111111111111111111111111111112", + amount: 10.03 * 1e6, + }).send(); + console.log(`Metis Quote (lamports): ${metisQuote.outAmount}`); + } catch (error) { + console.error('Error getting Metis quote:', error); + } + + try { + const result = await quickNodeRpc.ipfs_upload({ + filePath: 'test.png', + fileName: 'test5.png', + fileType: 'image/png', + }).send(); + console.log('File uploaded successfully! CID:', result.pin.cid); + } catch (error) { + console.error('Error uploading file:', error); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/solana/web3.js-2.0/quicknode-addons/types.ts b/solana/web3.js-2.0/quicknode-addons/types.ts new file mode 100644 index 0000000..b9d1ccd --- /dev/null +++ b/solana/web3.js-2.0/quicknode-addons/types.ts @@ -0,0 +1,127 @@ +/* -- PRIORITY FEES API -- */ + +interface FeeEstimates { + extreme: number; + high: number; + low: number; + medium: number; + percentiles: { + [key: string]: number; + }; +} + +interface EstimatePriorityFeesResponse { + context: { + slot: number; + }; + per_compute_unit: FeeEstimates; + per_transaction: FeeEstimates; +}; + +interface EstimatePriorityFeesParams { + last_n_blocks?: number; + account?: string; +} + +/* -- METIS API -- */ + +declare const QuoteGetSwapModeEnum: { + readonly ExactIn: "ExactIn"; + readonly ExactOut: "ExactOut"; +}; +type QuoteGetSwapModeEnum = typeof QuoteGetSwapModeEnum[keyof typeof QuoteGetSwapModeEnum]; + +declare const SwapMode: { + readonly ExactIn: "ExactIn"; + readonly ExactOut: "ExactOut"; +}; +type SwapMode = typeof SwapMode[keyof typeof SwapMode]; + +interface QuoteGetRequest { + inputMint: string; + outputMint: string; + amount: number; + slippageBps?: number; + swapMode?: QuoteGetSwapModeEnum; + dexes?: Array; + excludeDexes?: Array; + restrictIntermediateTokens?: boolean; + onlyDirectRoutes?: boolean; + asLegacyTransaction?: boolean; + platformFeeBps?: number; + maxAccounts?: number; +} + +interface PlatformFee { + amount?: string; + feeBps?: number; +} + +interface RoutePlanStep { + swapInfo: SwapInfo; + percent: number; +} + +interface SwapInfo { + ammKey: string; + label?: string; + inputMint: string; + outputMint: string; + inAmount: string; + outAmount: string; + feeAmount: string; + feeMint: string; +} + +interface QuoteResponse { + inputMint: string; + inAmount: string; + outputMint: string; + outAmount: string; + otherAmountThreshold: string; + swapMode: SwapMode; + slippageBps: number; + platformFee?: PlatformFee; + priceImpactPct: string; + routePlan: Array; + contextSlot?: number; + timeTaken?: number; +} + +/* -- IPFS API -- */ + +interface IpfsUploadRequest { + filePath: string; + fileName: string; + fileType: string; +} + +interface Pin { + cid: string; + name: string; + origins: string[]; + meta: Record; +} + +interface Info { + size: string; +} + +interface IpfsUploadResponse { + requestid: string; + status: string; + created: string; + pin: Pin; + info: Info; + delegates: string[]; +} + +export type { + FeeEstimates, + EstimatePriorityFeesResponse, + EstimatePriorityFeesParams, + QuoteGetRequest, + QuoteResponse, + IpfsUploadRequest, + IpfsUploadResponse +}; \ No newline at end of file