From aa28323232fdca5e6491cc9f8de5cf6ca48d7574 Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Mon, 11 Aug 2025 07:48:04 +0800 Subject: [PATCH 1/4] Refactor transaction sending and package structure --- CLAUDE.md | 9 + .../src/routes/examples/wrapped-sol.tsx | 4 +- devenv.nix | 1 - package.json | 2 +- packages/grill/src/contexts/grill-context.ts | 8 +- packages/grill/src/hooks/use-send-tx.ts | 274 +----------------- packages/grill/src/index.ts | 1 + .../src/providers/grill-headless-provider.tsx | 24 +- .../grill/src/providers/grill-provider.tsx | 2 +- packages/grill/src/types.ts | 35 ++- .../src/utils/get-confirmed-transaction.ts | 27 ++ packages/grill/src/utils/index.ts | 3 + .../src/utils/internal/create-send-tx.ts | 184 ++++++++++++ .../src/utils/poll-confirm-transaction.ts | 82 ++++++ ...{reloadAccounts.ts => refetch-accounts.ts} | 4 +- 15 files changed, 376 insertions(+), 284 deletions(-) create mode 100644 packages/grill/src/utils/get-confirmed-transaction.ts create mode 100644 packages/grill/src/utils/index.ts create mode 100644 packages/grill/src/utils/internal/create-send-tx.ts create mode 100644 packages/grill/src/utils/poll-confirm-transaction.ts rename packages/grill/src/utils/{reloadAccounts.ts => refetch-accounts.ts} (90%) diff --git a/CLAUDE.md b/CLAUDE.md index 5b9941e..2aaaacd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,6 +44,10 @@ bun run ci:publish # Publish packages to npm cd apps/example-dapp bun run dev # Start Vite dev server bun run build # Build the app + +# IMPORTANT: After making code changes +bun run build # Build to check for TypeScript errors +bun run lint:fix # Fix linting and formatting issues ``` ## Architecture @@ -106,6 +110,11 @@ Provides two contexts: - **Use double quotes for strings** (not single quotes) - Follow default Prettier settings +### After Making Code Changes +**Always run these commands to ensure code quality:** +1. `bun run build` - Check for TypeScript errors +2. `bun run lint:fix` - Fix linting and formatting issues + ### React Components - Small, focused components - Use function components with hooks diff --git a/apps/example-dapp/src/routes/examples/wrapped-sol.tsx b/apps/example-dapp/src/routes/examples/wrapped-sol.tsx index 3da6598..f2853b8 100644 --- a/apps/example-dapp/src/routes/examples/wrapped-sol.tsx +++ b/apps/example-dapp/src/routes/examples/wrapped-sol.tsx @@ -107,7 +107,7 @@ const WrappedSOLPage: FC = () => { setWrapAmount(""); } catch (error) { console.error("Error wrapping SOL:", error); - // Error already handled by sendTX toast + // Error already handled by mutation } finally { setIsWrapping(false); } @@ -135,7 +135,7 @@ const WrappedSOLPage: FC = () => { console.log("Transaction:", explorerLink); } catch (error) { console.error("Error closing wSOL account:", error); - // Error already handled by sendTX toast + // Error already handled by mutation } finally { setIsClosing(false); } diff --git a/devenv.nix b/devenv.nix index 493883e..5fa4e3b 100644 --- a/devenv.nix +++ b/devenv.nix @@ -5,7 +5,6 @@ packages = with pkgs; [ git nixfmt-rfc-style - biome ]; languages.javascript = { diff --git a/package.json b/package.json index 018e3f6..0628530 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "changeset:version": "changeset version", "changeset:publish": "changeset publish", "ci:version": "changeset version && bun update", - "ci:publish": "changeset publish", + "ci:publish": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag", "prepare": "husky", "codegen": "turbo run codegen" }, diff --git a/packages/grill/src/contexts/grill-context.ts b/packages/grill/src/contexts/grill-context.ts index 7597e7d..dfa0614 100644 --- a/packages/grill/src/contexts/grill-context.ts +++ b/packages/grill/src/contexts/grill-context.ts @@ -1,7 +1,7 @@ import type { DataLoader } from "@macalinao/dataloader-es"; import type { Address, EncodedAccount } from "@solana/kit"; import { createContext, useContext } from "react"; -import type { TransactionStatusEventCallback } from "../types.js"; +import type { SendTXFunction } from "../utils/internal/create-send-tx.js"; /** * Context value interface for SolanaAccountProvider. @@ -10,12 +10,12 @@ import type { TransactionStatusEventCallback } from "../types.js"; export interface GrillContextValue { /** DataLoader instance for batching and caching Solana account requests */ accountLoader: DataLoader; - reloadAccounts: (addresses: Address[]) => Promise; + refetchAccounts: (addresses: Address[]) => Promise; /** - * Internal callback for sending transaction status events. + * Function to send transactions with batching and confirmation */ - internal_onTransactionStatusEvent: TransactionStatusEventCallback; + sendTX: SendTXFunction; } /** diff --git a/packages/grill/src/hooks/use-send-tx.ts b/packages/grill/src/hooks/use-send-tx.ts index 10d8686..d51a5a7 100644 --- a/packages/grill/src/hooks/use-send-tx.ts +++ b/packages/grill/src/hooks/use-send-tx.ts @@ -1,275 +1,15 @@ -import type { - AddressesByLookupTableAddress, - Instruction, - Signature, - TransactionSigner, -} from "@solana/kit"; -import { - compressTransactionMessageUsingAddressLookupTables, - getBase58Decoder, - signAndSendTransactionMessageWithSigners, -} from "@solana/kit"; -import { createTransaction, getExplorerLink } from "gill"; - -import { useSolanaClient } from "gill-react"; -import { useCallback } from "react"; import { useGrillContext } from "../contexts/grill-context.js"; -import { useKitWallet } from "./use-kit-wallet.js"; - -export type TransactionId = string; - -export type TransactionStatusEvent = { - title: string; - id: TransactionId; -} & ( - | { - type: "error-wallet-not-connected"; - } - | { - type: "preparing"; - } - | { - type: "awaiting-wallet-signature"; - } - | { - type: "waiting-for-confirmation"; - sig: Signature; - explorerLink: string; - } - | { - type: "confirmed"; - sig: Signature; - explorerLink: string; - } - | { - type: "error-transaction-failed"; - errorMessage: string; - sig: Signature; - explorerLink: string; - } -); - -export interface SendTXOptions { - luts?: AddressesByLookupTableAddress; - signers?: TransactionSigner[]; -} -export type SendTXFunction = ( - name: string, - ixs: readonly Instruction[], - options?: SendTXOptions, -) => Promise; +export type { + SendTXFunction, + SendTXOptions, +} from "../utils/internal/create-send-tx.js"; /** * Hook that provides a function to send transactions using the modern @solana/kit API * while maintaining compatibility with the wallet adapter. */ -export const useSendTX = (): SendTXFunction => { - const { reloadAccounts, internal_onTransactionStatusEvent } = - useGrillContext(); - const { signer } = useKitWallet(); - const { rpc } = useSolanaClient(); - return useCallback( - async ( - name: string, - ixs: readonly Instruction[], - options: SendTXOptions = {}, - ): Promise => { - const txId = Math.random().toString(36).substring(2, 15); - const baseEvent = { - id: txId, - title: name, - }; - if (!signer) { - internal_onTransactionStatusEvent({ - ...baseEvent, - type: "error-wallet-not-connected", - }); - throw new Error("Wallet not connected"); - } - - internal_onTransactionStatusEvent({ - ...baseEvent, - type: "preparing", - }); - - const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); - const transactionMessage = createTransaction({ - version: 0, - feePayer: signer, - instructions: [...ixs], - latestBlockhash, - // the compute budget values are HIGHLY recommend to be set in order to maximize your transaction landing rate - // TODO(igm): make this configurable and/or dynamic based on the instructions - computeUnitLimit: 1_400_000, - computeUnitPrice: 100_000n, - }); - - // Apply address lookup tables if provided to compress the transaction - const addressLookupTables = options.luts ?? {}; - const finalTransactionMessage = - Object.keys(addressLookupTables).length > 0 - ? compressTransactionMessageUsingAddressLookupTables( - transactionMessage, - addressLookupTables, - ) - : transactionMessage; - - internal_onTransactionStatusEvent({ - ...baseEvent, - type: "awaiting-wallet-signature", - }); - - // Send transaction using wallet adapter - const sigBytes = await signAndSendTransactionMessageWithSigners( - finalTransactionMessage, - ); - const decoder = getBase58Decoder(); - const sig = decoder.decode(sigBytes) as Signature; - const sentTxEvent = { - ...baseEvent, - sig, - explorerLink: getExplorerLink({ transaction: sig }), - }; - - internal_onTransactionStatusEvent({ - ...sentTxEvent, - type: "waiting-for-confirmation", - }); - - try { - // Wait for confirmation using modern RPC - const confirmationStrategy = { - signature: sig, - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - }; - - // Poll for transaction confirmation - let confirmed = false; - let confirmationError: Error | null = null; - const maxRetries = 30; - let retries = 0; - - while (retries < maxRetries) { - try { - const signatureStatus = await rpc - .getSignatureStatuses([sig]) - .send(); - - if (signatureStatus.value[0]) { - const status = signatureStatus.value[0]; - if ( - status.confirmationStatus === "confirmed" || - status.confirmationStatus === "finalized" - ) { - confirmed = true; - if (status.err) { - confirmationError = new Error("Transaction failed on-chain"); - } - break; - } - } - - // Check if blockhash is still valid - const blockHeight = await rpc.getBlockHeight().send(); - if (blockHeight > confirmationStrategy.lastValidBlockHeight) { - throw new Error( - "Transaction expired - blockhash no longer valid", - ); - } - - // Wait before next attempt - await new Promise((resolve) => setTimeout(resolve, 1000)); - retries++; - } catch (error) { - console.error("Error checking transaction status:", error); - throw error; - } - } - - if (!confirmed) { - throw new Error("Transaction confirmation timeout"); - } - - if (confirmationError) { - throw confirmationError; - } - - // Get transaction details for logging using modern RPC - const result = await rpc - .getTransaction(sig, { - commitment: "confirmed", - maxSupportedTransactionVersion: 0, - encoding: "jsonParsed", - }) - .send(); - - if (result) { - // Reload the accounts that were written to - const writableAccounts = result.transaction.message.accountKeys - .filter((key) => key.writable) - .map((k) => k.pubkey); - await reloadAccounts(writableAccounts); - } - - internal_onTransactionStatusEvent({ - ...sentTxEvent, - type: "confirmed", - }); - - if (result?.meta?.logMessages) { - console.log(name, result.meta.logMessages.join("\n")); - } - - // Return the signature as a base58 string - return sig; - } catch (error: unknown) { - // Log error details for debugging - console.error(`${name} transaction failed:`, error); - - // Extract error logs - const extractErrorLogs = (err: unknown): string[] => { - if ( - err && - typeof err === "object" && - "logs" in err && - Array.isArray((err as { logs: unknown }).logs) - ) { - return (err as { logs: string[] }).logs; - } - if ( - err && - typeof err === "object" && - "context" in err && - typeof (err as { context: unknown }).context === "object" && - (err as { context: { logs?: unknown } }).context.logs && - Array.isArray((err as { context: { logs: unknown } }).context.logs) - ) { - return (err as { context: { logs: string[] } }).context.logs; - } - return []; - }; - - const errorLogs = extractErrorLogs(error); - if (errorLogs.length > 0) { - console.log("Transaction logs:"); - for (const log of errorLogs) { - console.log(" ", log); - } - } - - const errorMessage = - error instanceof Error ? error.message : "Transaction failed."; - - internal_onTransactionStatusEvent({ - ...sentTxEvent, - type: "error-transaction-failed", - errorMessage, - }); - throw error; - } - }, - [internal_onTransactionStatusEvent, reloadAccounts, rpc, signer], - ); +export const useSendTX = () => { + const { sendTX } = useGrillContext(); + return sendTX; }; diff --git a/packages/grill/src/index.ts b/packages/grill/src/index.ts index acc3729..6c04d01 100644 --- a/packages/grill/src/index.ts +++ b/packages/grill/src/index.ts @@ -2,3 +2,4 @@ export * from "./contexts/index.js"; export * from "./hooks/index.js"; export * from "./providers/index.js"; export * from "./types.js"; +export * from "./utils/index.js"; diff --git a/packages/grill/src/providers/grill-headless-provider.tsx b/packages/grill/src/providers/grill-headless-provider.tsx index 726f0e2..44e46f6 100644 --- a/packages/grill/src/providers/grill-headless-provider.tsx +++ b/packages/grill/src/providers/grill-headless-provider.tsx @@ -5,8 +5,10 @@ import { useSolanaClient } from "gill-react"; import type { FC, ReactNode } from "react"; import { useCallback, useMemo } from "react"; import { GrillContext } from "../contexts/grill-context.js"; +import { useKitWallet } from "../hooks/use-kit-wallet.js"; import type { TransactionStatusEventCallback } from "../types.js"; -import { reloadAccounts as doReloadAccounts } from "../utils/reloadAccounts.js"; +import { createSendTX } from "../utils/internal/create-send-tx.js"; +import { refetchAccounts as doRefetchAccounts } from "../utils/refetch-accounts.js"; export interface GrillHeadlessProviderProps { children: ReactNode; @@ -34,6 +36,7 @@ export const GrillHeadlessProvider: FC = ({ }) => { const { rpc } = useSolanaClient(); const queryClient = useQueryClient(); + const { signer } = useKitWallet(); const accountLoader = useMemo( () => @@ -45,9 +48,9 @@ export const GrillHeadlessProvider: FC = ({ [rpc, maxBatchSize, batchDurationMs], ); - const reloadAccounts = useCallback( + const refetchAccounts = useCallback( async (addresses: Address[]) => { - await doReloadAccounts({ + await doRefetchAccounts({ queryClient, accountLoader, addresses, @@ -56,12 +59,23 @@ export const GrillHeadlessProvider: FC = ({ [queryClient, accountLoader], ); + const sendTX = useMemo( + () => + createSendTX({ + signer, + rpc, + refetchAccounts, + onTransactionStatusEvent, + }), + [signer, rpc, refetchAccounts, onTransactionStatusEvent], + ); + return ( {children} diff --git a/packages/grill/src/providers/grill-provider.tsx b/packages/grill/src/providers/grill-provider.tsx index 7e86b0d..c1c2d45 100644 --- a/packages/grill/src/providers/grill-provider.tsx +++ b/packages/grill/src/providers/grill-provider.tsx @@ -1,7 +1,7 @@ import type { FC } from "react"; import { useCallback, useRef } from "react"; import { toast } from "sonner"; -import type { TransactionStatusEvent } from "../hooks/use-send-tx.js"; +import type { TransactionStatusEvent } from "../types.js"; import type { GrillHeadlessProviderProps } from "./grill-headless-provider.js"; import { GrillHeadlessProvider } from "./grill-headless-provider.js"; diff --git a/packages/grill/src/types.ts b/packages/grill/src/types.ts index 906e3db..925ccd1 100644 --- a/packages/grill/src/types.ts +++ b/packages/grill/src/types.ts @@ -1,4 +1,37 @@ -import type { TransactionStatusEvent } from "./hooks/use-send-tx.js"; +import type { Signature } from "@solana/kit"; + +export type TransactionId = string; + +export type TransactionStatusEvent = { + title: string; + id: TransactionId; +} & ( + | { + type: "error-wallet-not-connected"; + } + | { + type: "preparing"; + } + | { + type: "awaiting-wallet-signature"; + } + | { + type: "waiting-for-confirmation"; + sig: Signature; + explorerLink: string; + } + | { + type: "confirmed"; + sig: Signature; + explorerLink: string; + } + | { + type: "error-transaction-failed"; + errorMessage: string; + sig: Signature; + explorerLink: string; + } +); export type TransactionStatusEventCallback = ( e: TransactionStatusEvent, diff --git a/packages/grill/src/utils/get-confirmed-transaction.ts b/packages/grill/src/utils/get-confirmed-transaction.ts new file mode 100644 index 0000000..812f61e --- /dev/null +++ b/packages/grill/src/utils/get-confirmed-transaction.ts @@ -0,0 +1,27 @@ +import type { GetTransactionApi, Rpc, Signature } from "@solana/kit"; + +/** + * Gets a confirmed transaction from the blockchain with parsed JSON encoding + * @param rpc - The RPC client with GetTransactionApi + * @param signature - The transaction signature to fetch + * @returns The transaction details or null if not found + */ +export const getConfirmedTransaction = async ( + rpc: Rpc, + signature: Signature, +) => { + return await rpc + .getTransaction(signature, { + commitment: "confirmed", + maxSupportedTransactionVersion: 0, + encoding: "jsonParsed", + }) + .send(); +}; + +/** + * Type for a confirmed transaction fetched from the blockchain + */ +export type ConfirmedTransaction = NonNullable< + Awaited> +>; diff --git a/packages/grill/src/utils/index.ts b/packages/grill/src/utils/index.ts new file mode 100644 index 0000000..c8bf101 --- /dev/null +++ b/packages/grill/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./get-confirmed-transaction.js"; +export * from "./poll-confirm-transaction.js"; +export * from "./refetch-accounts.js"; diff --git a/packages/grill/src/utils/internal/create-send-tx.ts b/packages/grill/src/utils/internal/create-send-tx.ts new file mode 100644 index 0000000..ad903f6 --- /dev/null +++ b/packages/grill/src/utils/internal/create-send-tx.ts @@ -0,0 +1,184 @@ +import type { + Address, + AddressesByLookupTableAddress, + Instruction, + Signature, + TransactionSendingSigner, + TransactionSigner, +} from "@solana/kit"; +import { + compressTransactionMessageUsingAddressLookupTables, + getBase58Decoder, + signAndSendTransactionMessageWithSigners, +} from "@solana/kit"; +import type { SolanaClient } from "gill"; +import { createTransaction, getExplorerLink } from "gill"; +import type { TransactionStatusEvent } from "../../types.js"; +import { pollConfirmTransaction } from "../poll-confirm-transaction.js"; + +export interface SendTXOptions { + luts?: AddressesByLookupTableAddress; + signers?: TransactionSigner[]; +} + +export type SendTXFunction = ( + name: string, + ixs: readonly Instruction[], + options?: SendTXOptions, +) => Promise; + +export interface CreateSendTXParams { + signer: TransactionSendingSigner | null; + rpc: SolanaClient["rpc"]; + refetchAccounts: (addresses: Address[]) => Promise; + onTransactionStatusEvent: (event: TransactionStatusEvent) => void; +} + +/** + * Creates a function to send transactions using the modern @solana/kit API + * while maintaining compatibility with the wallet adapter. + */ +export const createSendTX = ({ + signer, + rpc, + refetchAccounts, + onTransactionStatusEvent, +}: CreateSendTXParams): SendTXFunction => { + return async ( + name: string, + ixs: readonly Instruction[], + options: SendTXOptions = {}, + ): Promise => { + const txId = Math.random().toString(36).substring(2, 15); + const baseEvent = { + id: txId, + title: name, + }; + if (!signer) { + onTransactionStatusEvent({ + ...baseEvent, + type: "error-wallet-not-connected", + }); + throw new Error("Wallet not connected"); + } + + onTransactionStatusEvent({ + ...baseEvent, + type: "preparing", + }); + + const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); + const transactionMessage = createTransaction({ + version: 0, + feePayer: signer, + instructions: [...ixs], + latestBlockhash, + // the compute budget values are HIGHLY recommend to be set in order to maximize your transaction landing rate + // TODO(igm): make this configurable and/or dynamic based on the instructions + computeUnitLimit: 1_400_000, + computeUnitPrice: 100_000n, + }); + + // Apply address lookup tables if provided to compress the transaction + const addressLookupTables = options.luts ?? {}; + const finalTransactionMessage = + Object.keys(addressLookupTables).length > 0 + ? compressTransactionMessageUsingAddressLookupTables( + transactionMessage, + addressLookupTables, + ) + : transactionMessage; + + onTransactionStatusEvent({ + ...baseEvent, + type: "awaiting-wallet-signature", + }); + + // Send transaction using wallet adapter + const sigBytes = await signAndSendTransactionMessageWithSigners( + finalTransactionMessage, + ); + const decoder = getBase58Decoder(); + const sig = decoder.decode(sigBytes) as Signature; + const sentTxEvent = { + ...baseEvent, + sig, + explorerLink: getExplorerLink({ transaction: sig }), + }; + + onTransactionStatusEvent({ + ...sentTxEvent, + type: "waiting-for-confirmation", + }); + + try { + const result = await pollConfirmTransaction({ + signature: sig, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + rpc, + }); + + // Reload the accounts that were written to + const writableAccounts = result.transaction.message.accountKeys + .filter((key) => key.writable) + .map((k) => k.pubkey); + await refetchAccounts(writableAccounts); + + onTransactionStatusEvent({ + ...sentTxEvent, + type: "confirmed", + }); + + if (result.meta?.logMessages) { + console.log(name, result.meta.logMessages.join("\n")); + } + + // Return the signature as a base58 string + return sig; + } catch (error: unknown) { + // Log error details for debugging + console.error(`${name} transaction failed:`, error); + + // Extract error logs + const extractErrorLogs = (err: unknown): string[] => { + if ( + err && + typeof err === "object" && + "logs" in err && + Array.isArray((err as { logs: unknown }).logs) + ) { + return (err as { logs: string[] }).logs; + } + if ( + err && + typeof err === "object" && + "context" in err && + typeof (err as { context: unknown }).context === "object" && + (err as { context: { logs?: unknown } }).context.logs && + Array.isArray((err as { context: { logs: unknown } }).context.logs) + ) { + return (err as { context: { logs: string[] } }).context.logs; + } + return []; + }; + + const errorLogs = extractErrorLogs(error); + if (errorLogs.length > 0) { + console.log("Transaction logs:"); + for (const log of errorLogs) { + console.log(" ", log); + } + } + + const errorMessage = + error instanceof Error ? error.message : "Transaction failed."; + + onTransactionStatusEvent({ + ...sentTxEvent, + type: "error-transaction-failed", + errorMessage, + }); + throw error; + } + }; +}; diff --git a/packages/grill/src/utils/poll-confirm-transaction.ts b/packages/grill/src/utils/poll-confirm-transaction.ts new file mode 100644 index 0000000..a518d57 --- /dev/null +++ b/packages/grill/src/utils/poll-confirm-transaction.ts @@ -0,0 +1,82 @@ +import type { Signature } from "@solana/kit"; +import type { SolanaClient } from "gill"; +import { getConfirmedTransaction } from "./get-confirmed-transaction.js"; + +export interface PollConfirmTransactionOptions { + signature: Signature; + lastValidBlockHeight: bigint; + rpc: SolanaClient["rpc"]; + maxRetries?: number; + retryInterval?: number; +} + +/** + * Polls for transaction confirmation status. + * + * @param options - Options for polling transaction confirmation + * @returns Promise that resolves when transaction is confirmed or times out + * @throws Error if transaction fails on-chain or expires + */ +export async function pollConfirmTransaction({ + signature, + lastValidBlockHeight, + rpc, + maxRetries = 30, + retryInterval = 1000, +}: PollConfirmTransactionOptions) { + let confirmed = false; + let confirmationError: Error | null = null; + let retries = 0; + + while (retries < maxRetries) { + try { + const signatureStatus = await rpc + .getSignatureStatuses([signature]) + .send(); + + if (signatureStatus.value[0]) { + const status = signatureStatus.value[0]; + if ( + status.confirmationStatus === "confirmed" || + status.confirmationStatus === "finalized" + ) { + confirmed = true; + if (status.err) { + confirmationError = new Error("Transaction failed on-chain"); + } + break; + } + } + + // Check if blockhash is still valid + const blockHeight = await rpc.getBlockHeight().send(); + if (blockHeight > lastValidBlockHeight) { + throw new Error("Transaction expired - blockhash no longer valid"); + } + + // Wait before next attempt + await new Promise((resolve) => setTimeout(resolve, retryInterval)); + retries++; + } catch (error) { + console.error("Error checking transaction status:", error); + throw error; + } + } + + if (!confirmed) { + throw new Error("Transaction confirmation timeout"); + } + + if (confirmationError) { + throw confirmationError; + } + + // Get transaction details + const transaction = await getConfirmedTransaction(rpc, signature); + + if (!transaction) { + throw new Error("Transaction not found after confirmation"); + } + + return transaction; +} diff --git a/packages/grill/src/utils/reloadAccounts.ts b/packages/grill/src/utils/refetch-accounts.ts similarity index 90% rename from packages/grill/src/utils/reloadAccounts.ts rename to packages/grill/src/utils/refetch-accounts.ts index ac00793..e793f4b 100644 --- a/packages/grill/src/utils/reloadAccounts.ts +++ b/packages/grill/src/utils/refetch-accounts.ts @@ -4,11 +4,11 @@ import type { Address } from "gill"; import { createAccountQueryKey } from "../hooks/use-account.js"; /** - * Reloads the accounts for the given addresses + * Refetches the accounts for the given addresses * @param queryClient - The query client to invalidate the accounts for * @param addresses - The addresses to invalidate the accounts for */ -export const reloadAccounts = async ({ +export const refetchAccounts = async ({ queryClient, accountLoader, addresses, From 81bda83fcc07eb39384a1a7b5f2561eb3720f204 Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Mon, 11 Aug 2025 07:49:06 +0800 Subject: [PATCH 2/4] changeset --- .changeset/config.json | 4 ++-- .changeset/purple-kings-call.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/purple-kings-call.md diff --git a/.changeset/config.json b/.changeset/config.json index fce1c26..2ae416b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,11 @@ { "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", - "commit": false, + "commit": true, "fixed": [], "linked": [], "access": "public", - "baseBranch": "main", + "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [] } diff --git a/.changeset/purple-kings-call.md b/.changeset/purple-kings-call.md new file mode 100644 index 0000000..c891b6e --- /dev/null +++ b/.changeset/purple-kings-call.md @@ -0,0 +1,5 @@ +--- +"@macalinao/grill": minor +--- + +Refactor transaction sending From 180cbe9cfb7c4d25c5280e07a50a174cce782e76 Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Mon, 11 Aug 2025 07:49:54 +0800 Subject: [PATCH 3/4] docs(changeset): Force republish --- .changeset/funny-olives-happen.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/funny-olives-happen.md diff --git a/.changeset/funny-olives-happen.md b/.changeset/funny-olives-happen.md new file mode 100644 index 0000000..7d976ca --- /dev/null +++ b/.changeset/funny-olives-happen.md @@ -0,0 +1,8 @@ +--- +"@macalinao/grill": minor +"@macalinao/dataloader-es": minor +"@macalinao/solana-batch-accounts-loader": minor +"@macalinao/wallet-adapter-compat": minor +--- + +Force republish From 81a7f979d1a0dd8e77ba4387a7182b9dd413c959 Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Mon, 11 Aug 2025 09:20:11 +0800 Subject: [PATCH 4/4] docs --- README.md | 4 +++- apps/example-dapp/package.json | 8 ++++++++ .../src/components/layout/main/index.tsx | 11 +++++++++++ package.json | 5 +++++ packages/dataloader-es/README.md | 2 ++ packages/dataloader-es/package.json | 13 +++++++++++-- packages/grill/README.md | 2 ++ packages/grill/package.json | 14 ++++++++++++++ packages/solana-batch-accounts-loader/README.md | 2 ++ packages/solana-batch-accounts-loader/package.json | 11 ++++++++++- packages/wallet-adapter-compat/README.md | 2 ++ packages/wallet-adapter-compat/package.json | 13 +++++++++++-- 12 files changed, 81 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 330eb64..2713b6c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Grill - Modern Solana Development Kit +[![npm version](https://img.shields.io/npm/v/@macalinao/grill.svg)](https://www.npmjs.com/package/@macalinao/grill) + A comprehensive toolkit for building Solana applications with React, featuring automatic account batching and caching. ## Packages @@ -128,4 +130,4 @@ Apache-2.0 ## Author -Ian Macalinao \ No newline at end of file +Ian Macalinao \ No newline at end of file diff --git a/apps/example-dapp/package.json b/apps/example-dapp/package.json index 8c715c3..6203e67 100644 --- a/apps/example-dapp/package.json +++ b/apps/example-dapp/package.json @@ -3,6 +3,14 @@ "private": true, "version": "0.0.0", "type": "module", + "description": "Example Solana dApp demonstrating Grill features", + "author": "Ian Macalinao ", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/macalinao/grill.git", + "directory": "apps/example-dapp" + }, "scripts": { "dev": "vite", "build": "tsc -b && vite build", diff --git a/apps/example-dapp/src/components/layout/main/index.tsx b/apps/example-dapp/src/components/layout/main/index.tsx index 73425e9..2de39f7 100644 --- a/apps/example-dapp/src/components/layout/main/index.tsx +++ b/apps/example-dapp/src/components/layout/main/index.tsx @@ -38,6 +38,17 @@ export const MainLayout: FC = ({ className }) => { + + + + GitHub + + + diff --git a/package.json b/package.json index 0628530..dfec196 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,11 @@ "private": true, "type": "module", "license": "Apache-2.0", + "author": "Ian Macalinao ", + "repository": { + "type": "git", + "url": "git+https://github.com/macalinao/grill.git" + }, "workspaces": { "packages": [ "packages/*", diff --git a/packages/dataloader-es/README.md b/packages/dataloader-es/README.md index 3a75c93..350398f 100644 --- a/packages/dataloader-es/README.md +++ b/packages/dataloader-es/README.md @@ -1,5 +1,7 @@ # @macalinao/dataloader-es +[![npm version](https://img.shields.io/npm/v/@macalinao/dataloader-es.svg)](https://www.npmjs.com/package/@macalinao/dataloader-es) + A modern ESM-native TypeScript implementation of Facebook's [DataLoader](https://github.com/graphql/dataloader) pattern for efficient batching and caching of data loading operations. ## Description diff --git a/packages/dataloader-es/package.json b/packages/dataloader-es/package.json index ee3300e..7a0e6c5 100644 --- a/packages/dataloader-es/package.json +++ b/packages/dataloader-es/package.json @@ -1,11 +1,20 @@ { "name": "@macalinao/dataloader-es", "version": "0.1.0", - "description": "ESM-native DataLoader implementation for efficient batching and caching", + "description": "Modern ESM-native TypeScript implementation of Facebook's DataLoader pattern for efficient batching and caching", "type": "module", "author": "Ian Macalinao ", - "homepage": "https://github.com/macalinao/grill", + "homepage": "https://grill.ianm.com", "license": "MIT", + "keywords": [ + "dataloader", + "batching", + "caching", + "graphql", + "esm", + "typescript", + "performance" + ], "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/packages/grill/README.md b/packages/grill/README.md index 4c72619..10562a2 100644 --- a/packages/grill/README.md +++ b/packages/grill/README.md @@ -1,5 +1,7 @@ # @macalinao/grill +[![npm version](https://img.shields.io/npm/v/@macalinao/grill.svg)](https://www.npmjs.com/package/@macalinao/grill) + Modern Solana development kit for React applications with automatic account batching, caching, and transaction notifications. ## Features diff --git a/packages/grill/package.json b/packages/grill/package.json index b48d189..5150b91 100644 --- a/packages/grill/package.json +++ b/packages/grill/package.json @@ -1,6 +1,20 @@ { "name": "@macalinao/grill", "version": "0.1.0", + "description": "Modern Solana development kit for React applications with automatic account batching, caching, and transaction notifications", + "license": "Apache-2.0", + "author": "Ian Macalinao ", + "homepage": "https://grill.ianm.com", + "keywords": [ + "solana", + "react", + "blockchain", + "web3", + "dataloader", + "batching", + "caching", + "gill" + ], "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", diff --git a/packages/solana-batch-accounts-loader/README.md b/packages/solana-batch-accounts-loader/README.md index ed1a7a8..23f0479 100644 --- a/packages/solana-batch-accounts-loader/README.md +++ b/packages/solana-batch-accounts-loader/README.md @@ -1,5 +1,7 @@ # @macalinao/solana-batch-accounts-loader +[![npm version](https://img.shields.io/npm/v/@macalinao/solana-batch-accounts-loader.svg)](https://www.npmjs.com/package/@macalinao/solana-batch-accounts-loader) + Efficient batch account loading for Solana using DataLoader and [@solana/kit](https://github.com/solana-developers/solana-web3.js-v2). ## Installation diff --git a/packages/solana-batch-accounts-loader/package.json b/packages/solana-batch-accounts-loader/package.json index 369c76e..a91cc04 100644 --- a/packages/solana-batch-accounts-loader/package.json +++ b/packages/solana-batch-accounts-loader/package.json @@ -4,8 +4,17 @@ "description": "Efficient batch account loading for Solana using DataLoader and @solana/kit", "type": "module", "author": "Ian Macalinao ", - "homepage": "https://github.com/macalinao/grill", + "homepage": "https://grill.ianm.com", "license": "Apache-2.0", + "keywords": [ + "solana", + "dataloader", + "batching", + "rpc", + "accounts", + "blockchain", + "web3" + ], "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/packages/wallet-adapter-compat/README.md b/packages/wallet-adapter-compat/README.md index a075639..d7edde1 100644 --- a/packages/wallet-adapter-compat/README.md +++ b/packages/wallet-adapter-compat/README.md @@ -1,5 +1,7 @@ # @macalinao/wallet-adapter-compat +[![npm version](https://img.shields.io/npm/v/@macalinao/wallet-adapter-compat.svg)](https://www.npmjs.com/package/@macalinao/wallet-adapter-compat) + Compatibility layer that bridges @solana/wallet-adapter with @solana/kit and grill. ## Purpose diff --git a/packages/wallet-adapter-compat/package.json b/packages/wallet-adapter-compat/package.json index 868eb81..5df3a6c 100644 --- a/packages/wallet-adapter-compat/package.json +++ b/packages/wallet-adapter-compat/package.json @@ -1,11 +1,20 @@ { "name": "@macalinao/wallet-adapter-compat", "version": "0.1.0", - "description": "Solana wallet adapter compatibility layer for @solana/kit", + "description": "Compatibility layer that bridges @solana/wallet-adapter with @solana/kit and grill", "type": "module", "author": "Ian Macalinao ", - "homepage": "https://github.com/macalinao/grill", + "homepage": "https://grill.ianm.com", "license": "Apache-2.0", + "keywords": [ + "solana", + "wallet", + "adapter", + "kit", + "blockchain", + "web3", + "compatibility" + ], "main": "dist/index.js", "types": "dist/index.d.ts", "exports": {