Post
Share your knowledge.
How to Properly Use the Sui SDK for Frontend Integration?
I'm building a frontend (React/Next.js) for a Sui dApp and need to interact with the blockchain—fetching objects, sending transactions, and listening to events. I’ve tried using the @mysten/sui.js SDK, but I’m running into issues:
Wallet Connection: Sometimes, the wallet doesn’t return the user’s address after connecting.
Transaction Handling: Transactions fail silently or return vague errors.
RPC Limits: I get rate-limited or timeouts when fetching large datasets.
Real-Time Updates: How can I listen for on-chain events (e.g., NFT mints, balance changes)?
What I’ve tried:
✔ Basic SuiClient setup with mainnet and testnet RPCs.
✔ Using useWallet() from @mysten/dapp-kit for wallet integration.
✔ Manual transaction signing with signAndExecuteTransactionBlock.
Questions:
What’s the recommended way to initialize the Sui SDK in a frontend app?
How do I handle errors gracefully (e.g., RPC failures, wallet rejections)?
Are there best practices for optimizing queries (batching, caching, etc.)?
How can I subscribe to real-time updates (e.g., new transactions, object changes)?
- Sui
- SDKs and Developer Tools
Answers
2Expert Answer:
- Initializing the Sui SDK The @mysten/sui.js SDK should be configured with a reliable RPC endpoint. For production apps, consider:
Default RPCs:
ts
import { SuiClient, getFullnodeUrl } from '@mysten/sui.js/client';
const client = new SuiClient({ url: getFullnodeUrl('mainnet') });
Fallback RPCs: Use services like Sui RPC Providers to avoid rate limits.
For wallet integration, use @mysten/dapp-kit:
tsx
import { createNetworkConfig, SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
import { getFullnodeUrl } from '@mysten/sui.js/client';
const { networkConfig } = createNetworkConfig({
mainnet: { url: getFullnodeUrl('mainnet') },
testnet: { url: getFullnodeUrl('testnet') },
});
function App() {
return (
<SuiClientProvider networks={networkConfig} defaultNetwork="mainnet">
<WalletProvider autoConnect>
<YourApp />
</WalletProvider>
</SuiClientProvider>
);
}
- Handling Transactions & Errors Always wrap transactions in error handling:
ts
try {
const tx = await signAndExecuteTransactionBlock({
transactionBlock: txBlock,
options: { showEffects: true },
});
console.log("Tx Digest:", tx.digest);
} catch (err) {
console.error("Tx Failed:", err.message);
// Handle specific errors (e.g., user rejection, insufficient gas)
}
Common errors:
"User rejected the request" → Wallet popup was closed.
"Gas budget exceeded" → Increase gas budget with txBlock.setGasBudget().
"Object not found" → Check if the object ID is correct and still exists.
- Optimizing RPC Calls Batching Requests: Use multiGetObjects for fetching multiple objects in one call.
Caching: Use React Query or SWR to cache RPC responses:
ts
import { useSuiClientQuery } from '@mysten/dapp-kit';
const { data } = useSuiClientQuery('getObject', {
id: objectId,
options: { showContent: true },
});
Pagination: For large datasets, use suix_getDynamicFields with cursors.
- Real-Time Updates Use WebSockets or polling:
WebSocket Subscriptions (advanced):
ts
const unsubscribe = client.subscribeEvent({
filter: { sender: '0x123...' },
onMessage(event) { console.log("New event:", event); },
});
// Cleanup on unmount
return () => unsubscribe();
Polling with Hooks:
ts
useSuiClientSubscription('subscribeEvent', {
filter: { eventType: 'ExampleEvent' },
onData(event) { console.log("Event:", event); },
});
Pro Tips
- Use @mysten/dapp-kit for pre-built wallet hooks.
- Set a custom gas budget for complex transactions.
- Monitor RPC health – switch endpoints if responses are slow.
- Test on Testnet before mainnet deployment.
1. Recommended SDK Initialization
For a production-grade React or Next.js dApp, the recommended stack combines three key packages. This setup separates low-level blockchain interaction from application state management.[1, 2, 3]
-
Core Libraries:
@mysten/sui.js
: The foundational TypeScript SDK for all direct Sui network interactions.[4]@mysten/dapp-kit
: The primary React library with hooks and components for wallet connection and data fetching.[2, 5, 6]@tanstack/react-query
: A required dependency for@mysten/dapp-kit
that handles caching, re-fetching, and managing on-chain data as server state.[1, 7]
-
Provider Setup: Wrap your application's root component with these providers in the correct order to ensure all hooks function properly.[3, 4]
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { SuiClientProvider, WalletProvider, createNetworkConfig } from '@mysten/dapp-kit'; import { getFullnodeUrl } from '@mysten/sui/client'; import '@mysten/dapp-kit/dist/index.css'; const queryClient = new QueryClient(); const { networkConfig } = createNetworkConfig({ mainnet: { url: getFullnodeUrl('mainnet') }, testnet: { url: getFullnodeUrl('testnet') }, }); function AppRoot() { return ( <QueryClientProvider client={queryClient}> <SuiClientProvider networks={networkConfig} defaultNetwork="mainnet"> <WalletProvider autoConnect> {/* Your Application Components */} </WalletProvider> </SuiClientProvider> </QueryClientProvider> ); }
2. Graceful Error Handling
Handling errors effectively is crucial for user experience. Here’s how to manage common issues.
-
Wallet Connection Failures: The issue of a wallet address not being available immediately after connection is typically an asynchronous state problem.
- Solution: Always check if the
account
object returned by theuseCurrentAccount()
hook is null. When fetching data that depends on the address, use theenabled:!!account
option inuseSuiClientQuery
to prevent the query from running until the address is available.[3]
- Solution: Always check if the
-
Transaction Errors: Raw RPC errors are often cryptic. Use the
suiclient-error-decoder
library to translate them into human-readable messages.[8, 9]- Strategy: Coordinate with your smart contract developer to define a map of error codes. In your frontend, use this map with the decoder to parse errors caught in a
try...catch
block .
import { useSignAndExecuteTransactionBlock } from '@mysten/dapp-kit'; import { SuiClientErrorDecoder } from 'suiclient-error-decoder'; // 1. Define your custom error map const myErrorMap = { 1: "Mint limit has been reached." }; const errorDecoder = new SuiClientErrorDecoder({ customErrorCodes: myErrorMap }); // 2. Wrap your transaction call const { mutateAsync: signAndExecute } = useSignAndExecuteTransactionBlock(); async function handleMint(txb) { try { await signAndExecute({ transactionBlock: txb }); console.log("Transaction successful!"); } catch (error) { const decodedError = errorDecoder.parseError(error); // Display decodedError.message to the user console.error(decodedError.message); } }
- Common Errors:
"User rejected the request"
: The user closed the wallet pop-up.[10, 11]"InsufficientGas"
: The gas budget was too low. Let the wallet auto-set the budget; avoid setting it manually unless necessary .
- Strategy: Coordinate with your smart contract developer to define a map of error codes. In your frontend, use this map with the decoder to parse errors caught in a
3. Optimizing Queries
To avoid RPC rate limits and improve performance, use a combination of batching, pagination, and caching.
-
Batching: Instead of fetching objects one by one, use
sui_multiGetObjects
to retrieve data for up to 50 object IDs in a single request. This significantly reduces network traffic . -
Pagination: For large datasets like an object's dynamic fields, use paginated endpoints like
suix_getDynamicFields
. These endpoints use a cursor to fetch data page by page, preventing timeouts . -
Caching: The
useSuiClientQuery
hook from@mysten/dapp-kit
automatically caches responses.[1, 5] To further optimize, set astaleTime
for data that doesn't change frequently (e.g., NFT metadata). This tells React Query to serve cached data for a specified duration before re-fetching, reducing redundant RPC calls.[7, 12, 13]const { data } = useSuiClientQuery( 'getObject', { id: objectId, options: { showContent: true } }, { staleTime: 10 * 60 * 1000 } // Cache is fresh for 10 minutes );
-
RPC Providers: For production applications, use a dedicated RPC provider like QuickNode, Ankr, or Chainstack to ensure reliability and avoid the strict rate limits of public endpoints.[14, 15, 16]
4. Real-Time Updates
There are three primary methods for listening to on-chain events, each with different trade-offs.
-
Polling: The simplest method. Use
useSuiClientQuery
with arefetchInterval
to periodically ask the network for updates. This is suitable for non-critical data where a delay of a few seconds is acceptable.[17] -
WebSocket Subscriptions: This method offers low-latency, real-time updates. However, the official Sui JSON-RPC WebSocket API is deprecated, so you must use a third-party RPC provider that offers WebSocket support.[18, 19] You can use
client.subscribeEvent
to listen for specific events and must manage the subscription lifecycle carefully within a ReactuseEffect
hook to prevent memory leaks.[20, 21, 22]useEffect(() => { const unsubscribe = await client.subscribeEvent({ filter: { MoveEventModule: { package: '0x...', module: 'my_module' } }, onMessage: (event) => { console.log("New event:", event); }, }); // Cleanup on component unmount return () => { unsubscribe(); }; },);
-
Custom Indexer: The most powerful and reliable solution for mission-critical applications. An indexer is a dedicated backend service that processes all on-chain data and stores it in an optimized database. This allows for complex, low-latency queries that are not possible with the standard RPC API. This approach is recommended for production-grade DeFi, gaming, or analytics platforms.[9, 23, 24]
Do you know the answer?
Please log in and share it.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
- Why does BCS require exact field order for deserialization when Move structs have named fields?53
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution42
- Sui Transaction Failing: Objects Reserved for Another Transaction24
- How do ability constraints interact with dynamic fields in heterogeneous collections?04