Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions examples/nextjs/app/components/lookup-table-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client';

import { useLookupTable } from '@solana/react-hooks';
import { useState } from 'react';

export function LookupTableCard() {
const [address, setAddress] = useState('');
const { data, isLoading, error } = useLookupTable(address || undefined);

return (
<section className="card space-y-4">
<div className="space-y-1">
<p className="small-label">Lookup Table</p>
<h2 className="text-xl font-semibold text-slate-900">Fetch address lookup table</h2>
<p className="text-sm text-slate-600">Uses the fetchLookupTable helper to read LUT data.</p>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-slate-800" htmlFor="lut-address">
Lookup table address
</label>
<input
id="lut-address"
type="text"
value={address}
onChange={(e) => setAddress(e.target.value)}
placeholder="Enter a lookup table address"
className="input"
/>
</div>
{isLoading && <p className="text-sm text-slate-600">Loading...</p>}
{error ? (
<p className="text-sm font-semibold text-red-600">
{error instanceof Error ? error.message : 'Failed to fetch'}
</p>
) : null}
{data && (
<div className="rounded border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-900">
<p>
<span className="font-semibold">Addresses:</span> {data.addresses.length}
</p>
<p>
<span className="font-semibold">Authority:</span> {data.authority ?? 'None (frozen)'}
</p>
</div>
)}
</section>
);
}
48 changes: 48 additions & 0 deletions examples/nextjs/app/components/nonce-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client';

import { useNonceAccount } from '@solana/react-hooks';
import { useState } from 'react';

export function NonceCard() {
const [address, setAddress] = useState('');
const { data, isLoading, error } = useNonceAccount(address || undefined);

return (
<section className="card space-y-4">
<div className="space-y-1">
<p className="small-label">Nonce Account</p>
<h2 className="text-xl font-semibold text-slate-900">Fetch nonce account</h2>
<p className="text-sm text-slate-600">Uses the fetchNonceAccount helper to read durable nonce data.</p>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-slate-800" htmlFor="nonce-address">
Nonce account address
</label>
<input
id="nonce-address"
type="text"
value={address}
onChange={(e) => setAddress(e.target.value)}
placeholder="Enter a nonce account address"
className="input"
/>
</div>
{isLoading && <p className="text-sm text-slate-600">Loading...</p>}
{error ? (
<p className="text-sm font-semibold text-red-600">
{error instanceof Error ? error.message : 'Failed to fetch'}
</p>
) : null}
{data && (
<div className="rounded border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-900">
<p>
<span className="font-semibold">Blockhash:</span> {data.blockhash}
</p>
<p>
<span className="font-semibold">Authority:</span> {data.authority}
</p>
</div>
)}
</section>
);
}
4 changes: 4 additions & 0 deletions examples/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { LookupTableCard } from './components/lookup-table-card';
import { MemoCard } from './components/memo-card';
import { NonceCard } from './components/nonce-card';
import { WalletConnectButton } from './components/wallet-connect-button';

export default function HomePage() {
Expand All @@ -23,6 +25,8 @@ export default function HomePage() {
</div>
</section>
<MemoCard />
<NonceCard />
<LookupTableCard />
</main>
);
}
27 changes: 26 additions & 1 deletion packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,31 @@ const signature = await usdc.sendTransfer({
console.log(signature.toString());
```

### Fetch address lookup tables

```ts
import { toAddress } from "@solana/client";

// Single lookup table
const lut = await client.actions.fetchLookupTable(
toAddress("AddressLookupTab1e1111111111111111111111111"),
);
console.log(`Addresses in LUT: ${lut.addresses.length}`);

// Multiple lookup tables
const luts = await client.actions.fetchLookupTables([lutAddress1, lutAddress2]);
```

### Fetch nonce accounts

```ts
const nonce = await client.actions.fetchNonceAccount(
toAddress("NonceAccountAddress111111111111111111111111"),
);
console.log(`Nonce: ${nonce.blockhash}`);
console.log(`Authority: ${nonce.authority}`);
```

### Build and send arbitrary transactions

```ts
Expand Down Expand Up @@ -156,7 +181,7 @@ watcher.abort();

- Wallet connectors: `autoDiscover()` picks up Wallet Standard injectables; compose `phantom()`, `solflare()`, `backpack()`, or `injected()` when you need explicit control.
- Store: built on Zustand; pass `createStore` to `createClient` for custom persistence or server-side stores. `serializeSolanaState` / `deserializeSolanaState` help save and restore cluster + wallet metadata.
- Actions: `fetchAccount`, `fetchBalance`, `setCluster`, `requestAirdrop`, `sendTransaction`, and wallet connect/disconnect keep the store in sync.
- Actions: `fetchAccount`, `fetchBalance`, `fetchLookupTable`, `fetchLookupTables`, `fetchNonceAccount`, `setCluster`, `requestAirdrop`, `sendTransaction`, and wallet connect/disconnect keep the store in sync.
- Watchers: `watchAccount`, `watchBalance`, and `watchSignature` stream updates into the store and return an `abort()` handle for cleanup.
- Helpers: `solTransfer`, `splToken`, and `transaction` cover common transfers plus low-level `prepare`/`sign`/`toWire` flows. Transaction versions default to `0` when any instruction references address lookup tables, otherwise `legacy`; override with `version` when needed.

Expand Down
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@solana-program/system": "catalog:solana",
"@solana-program/compute-budget": "catalog:solana",
"@solana-program/token": "catalog:solana",
"@solana-program/address-lookup-table": "catalog:solana",
"@wallet-standard/app": "catalog:solana",
"@wallet-standard/base": "catalog:solana",
"@wallet-standard/errors": "catalog:solana",
Expand Down
42 changes: 42 additions & 0 deletions packages/client/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import type {
FetchAccountReturnType,
FetchBalanceParameters,
FetchBalanceReturnType,
FetchLookupTableParameters,
FetchLookupTableReturnType,
FetchLookupTablesParameters,
FetchLookupTablesReturnType,
FetchNonceAccountParameters,
FetchNonceAccountReturnType,
RequestAirdropParameters,
RequestAirdropReturnType,
SendTransactionParameters,
Expand Down Expand Up @@ -59,6 +65,42 @@ export function fetchBalance(client: SolanaClient, params: FetchBalanceParameter
return client.actions.fetchBalance(params.address, params.commitment);
}

/**
* Fetch an address lookup table.
*
* @param client - Solana client instance.
* @param params - Lookup table address and optional commitment override.
*/
export function fetchLookupTable(client: SolanaClient, params: FetchLookupTableParameters): FetchLookupTableReturnType {
return client.actions.fetchLookupTable(params.address, params.commitment);
}

/**
* Fetch multiple address lookup tables.
*
* @param client - Solana client instance.
* @param params - Lookup table addresses and optional commitment override.
*/
export function fetchLookupTables(
client: SolanaClient,
params: FetchLookupTablesParameters,
): FetchLookupTablesReturnType {
return client.actions.fetchLookupTables(params.addresses, params.commitment);
}

/**
* Fetch a nonce account.
*
* @param client - Solana client instance.
* @param params - Nonce account address and optional commitment override.
*/
export function fetchNonceAccount(
client: SolanaClient,
params: FetchNonceAccountParameters,
): FetchNonceAccountReturnType {
return client.actions.fetchNonceAccount(params.address, params.commitment);
}

/**
* Request an airdrop to an address.
*
Expand Down
76 changes: 74 additions & 2 deletions packages/client/src/client/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@ import type {
Signature,
Transaction,
} from '@solana/kit';
import { airdropFactory, getBase64EncodedWireTransaction } from '@solana/kit';
import { airdropFactory, getBase64EncodedWireTransaction, isSome } from '@solana/kit';
import type { TransactionWithLastValidBlockHeight } from '@solana/transaction-confirmation';
import {
createBlockHeightExceedencePromiseFactory,
createRecentSignatureConfirmationPromiseFactory,
waitForRecentTransactionConfirmation,
} from '@solana/transaction-confirmation';
import { fetchAddressLookupTable, fetchAllAddressLookupTable } from '@solana-program/address-lookup-table';
import { fetchNonce } from '@solana-program/system';

import { createLogger, formatError } from '../logging/logger';
import { createSolanaRpcClient } from '../rpc/createSolanaRpcClient';
import type { ClientActions, ClientState, ClientStore, SolanaClientRuntime, WalletRegistry } from '../types';
import type {
AddressLookupTableData,
ClientActions,
ClientState,
ClientStore,
NonceAccountData,
SolanaClientRuntime,
WalletRegistry,
} from '../types';
import { now } from '../utils';

type MutableRuntime = SolanaClientRuntime;
Expand Down Expand Up @@ -372,6 +382,65 @@ export function createActions({ connectors, logger: inputLogger, runtime, store
}
}

/**
* Fetches an address lookup table.
*
* @param addr - Lookup table address.
* @param commitment - Optional commitment override.
* @returns Parsed lookup table data.
*/
async function fetchLookupTable(addr: Address, commitment?: Commitment): Promise<AddressLookupTableData> {
const account = await fetchAddressLookupTable(runtime.rpc, addr, {
commitment: getCommitment(commitment),
});
const { addresses, authority, deactivationSlot, lastExtendedSlot, lastExtendedSlotStartIndex } = account.data;
return {
addresses,
authority: isSome(authority) ? authority.value : undefined,
deactivationSlot,
lastExtendedSlot,
lastExtendedSlotStartIndex,
};
}

/**
* Fetches multiple address lookup tables.
*
* @param addresses - Lookup table addresses.
* @param commitment - Optional commitment override.
* @returns Array of parsed lookup table data.
*/
async function fetchLookupTables(
addresses: readonly Address[],
commitment?: Commitment,
): Promise<readonly AddressLookupTableData[]> {
if (addresses.length === 0) return [];
const accounts = await fetchAllAddressLookupTable(runtime.rpc, addresses as Address[], {
commitment: getCommitment(commitment),
});
return accounts.map(({ data }) => ({
addresses: data.addresses,
authority: isSome(data.authority) ? data.authority.value : undefined,
deactivationSlot: data.deactivationSlot,
lastExtendedSlot: data.lastExtendedSlot,
lastExtendedSlotStartIndex: data.lastExtendedSlotStartIndex,
}));
}

/**
* Fetches a nonce account.
*
* @param addr - Nonce account address.
* @param commitment - Optional commitment override.
* @returns Parsed nonce data.
*/
async function fetchNonceAccount(addr: Address, commitment?: Commitment): Promise<NonceAccountData> {
const account = await fetchNonce(runtime.rpc, addr, {
commitment: getCommitment(commitment),
});
return { authority: account.data.authority, blockhash: account.data.blockhash };
}

/**
* Sends a transaction and waits for confirmation using the runtime helpers.
*
Expand Down Expand Up @@ -495,6 +564,9 @@ export function createActions({ connectors, logger: inputLogger, runtime, store
disconnectWallet,
fetchAccount,
fetchBalance,
fetchLookupTable,
fetchLookupTables,
fetchNonceAccount,
requestAirdrop,
sendTransaction,
setCluster,
Expand Down
11 changes: 11 additions & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export {
disconnectWallet,
fetchAccount,
fetchBalance,
fetchLookupTable,
fetchLookupTables,
fetchNonceAccount,
requestAirdrop,
sendTransaction,
setCluster,
Expand Down Expand Up @@ -123,6 +126,7 @@ export type {
AccountCache,
AccountCacheEntry,
AccountWatcherConfig,
AddressLookupTableData,
BalanceWatcherConfig,
ClientActions,
ClientHelpers,
Expand All @@ -139,6 +143,13 @@ export type {
FetchAccountReturnType,
FetchBalanceParameters,
FetchBalanceReturnType,
FetchLookupTableParameters,
FetchLookupTableReturnType,
FetchLookupTablesParameters,
FetchLookupTablesReturnType,
FetchNonceAccountParameters,
FetchNonceAccountReturnType,
NonceAccountData,
RequestAirdropParameters,
RequestAirdropReturnType,
SendTransactionParameters,
Expand Down
Loading