rootstockwinks
v2.0.2
Published
A NextJS component that populates meta tags based on API key
Maintainers
Readme
Winks SDK (Rootstock)
A React/Next.js SDK that automatically populates SEO meta tags from a secure API and provides Rootstock-ready wallet integration, token/NFT utilities, signature management, and RPC failover.
Features
- 🚀 Easy Integration: drop-in
Winkswrapper for Next.js - 🔑 API-keyed metadata management (server included)
- 📱 Social meta: Open Graph, Twitter Cards
- 🎯 Full SEO meta coverage
- 💰 Token utilities: ERC-20 transfer/approve/balance/allowance
- 🎨 NFT utilities: ERC-721 owner, ERC-721/1155 transfers, ERC-1155 balance
- 🚰 Testnet faucet: request tRBTC with a single function call
- 🔗 Rootstock ready (Testnet 31) — SDK defaults to Testnet in the example app
- 🌐 Wallet integration via RainbowKit + WalletConnect (styles auto-injected)
- ✍️ Signature management (tx/message/personal/typed data)
- 🔄 Network switching helpers
- 🧭 RPC Management with health checks and latency-based selection
- 🧩 EIP-1193 Provider adapter (MetaMask et al.)
- 🧯 Signature queueing to avoid overlapping prompts
- 🛠️ Dual builds (Rollup + Vite) outputting CJS + ESM
- ✅ Testing setup (Jest unit, Playwright E2E)
Installation
npm install rootstockwinksQuick Start (Next.js)
// pages/_app.js or app/layout.js
import { Winks } from 'rootstockwinks';
export default function App({ Component, pageProps }) {
return (
<Winks apikey="YOUR_API_KEY">
<Component {...pageProps} />
</Winks>
);
}Start the local metadata server and create an API key:
cd server
npm install
npm run build
npm start
# Create API Key
curl -X POST http://localhost:3001/api/keys \
-H "Content-Type: application/json" \
-d '{"name": "My Website"}'
# Set metadata
curl -X POST http://localhost:3001/api/meta/YOUR_API_KEY \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"title": "My Awesome Website",
"description": "The best website ever created",
"ogTitle": "My Awesome Website",
"ogDescription": "The best website ever created",
"ogImage": "https://example.com/og-image.jpg",
"ogUrl": "https://example.com",
"twitterCard": "summary_large_image",
"twitterTitle": "My Awesome Website",
"twitterDescription": "The best website ever created",
"twitterImage": "https://example.com/twitter-image.jpg",
"canonical": "https://example.com",
"robots": "index, follow",
"viewport": "width=device-width, initial-scale=1",
"charset": "utf-8",
"author": "Your Name"
}'WalletConnect project ID (RainbowKit)
RainbowKit requires a WalletConnect project ID. Set it in your app (for the example app, create example/.env.local):
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=YOUR_PROJECT_IDWinks Component
Props:
apikey: stringrequiredchildren: ReactNoderequiredfallback?: MetaData(used if server fetch fails)
MetaData shape:
interface MetaData {
title?: string;
description?: string;
keywords?: string;
ogTitle?: string;
ogDescription?: string;
ogImage?: string;
ogUrl?: string;
twitterCard?: string;
twitterTitle?: string;
twitterDescription?: string;
twitterImage?: string;
canonical?: string;
robots?: string;
viewport?: string;
charset?: string;
author?: string;
}RPC Manager
import { RpcManager } from 'rootstockwinks';
const rpc = new RpcManager();
const provider = rpc.createProvider('mainnet'); // or 'testnet'
const bestUrl = rpc.getBestRpcUrl('mainnet');
// Health snapshot
const health = rpc.getHealth('mainnet');- Periodic health checks (eth_blockNumber) with timeouts
- Chooses lowest-latency healthy endpoint; fails over automatically
EIP-1193 Provider Adapter
import { Eip1193Provider } from 'rootstockwinks';
const eip = new Eip1193Provider(window.ethereum);
await eip.request({ method: 'eth_requestAccounts' });Signature Manager (Queued)
import { SignatureManager, Eip1193Provider } from 'rootstockwinks';
const sm = new SignatureManager(new Eip1193Provider(window.ethereum));
// Requests are queued to avoid multiple wallet prompts
await sm.requestTransactionSignature({ to: '0x...', value: 0n });
await sm.requestMessageSignature('Hello Rootstock');
await sm.requestPersonalSignature('Personal message');
await sm.requestTypedDataSignature({ domain, types, value });Wallet Integration (RainbowKit + wagmi)
Wrap your app:
import { WalletProvider } from 'rootstockwinks';
export default function App({ Component, pageProps }) {
return (
<WalletProvider>
<Component {...pageProps} />
</WalletProvider>
);
}Use the connection UI:
import { WalletConnection } from 'rootstockwinks';
function MyComponent() {
return <WalletConnection showBalance showNetwork />;
}Advanced hook:
import { useWalletIntegration } from 'rootstockwinks';
const {
walletState,
connectWallet,
disconnectWallet,
switchToRootstockMainnet,
switchToRootstockTestnet,
requestTransactionSignature,
requestMessageSignature,
requestPersonalSignature,
requestTypedDataSignature,
sendTransaction,
} = useWalletIntegration();sendTransaction(to, value)now powers the example app’s “Send tRBTC” flow and sends native tRBTC on Rootstock Testnet.
Send native tRBTC:
const result = await sendTransaction('0xRecipient', '0.05');
if (result.success) {
console.log('tx hash', result.txHash);
} else {
console.error(result.error);
}Enhanced Token Transfer Hook
import { useEnhancedTokenTransfer } from 'rootstockwinks';
const {
transferERC20,
transferNFT,
approveToken,
getTokenBalance,
getNFTOwner,
getTokenAllowance,
ensureRootstockNetwork,
address,
isConnected,
} = useEnhancedTokenTransfer();- Use this hook for ERC-20/ERC-721/ERC-1155 contracts. For native tRBTC, prefer
useWalletIntegration().sendTransaction.
Simple Token/NFT Functions
import {
transferERC20,
approveToken,
getTokenBalance,
getTokenAllowance,
getNFTOwner,
transferNFT,
} from 'rootstockwinks';
import { ethers } from 'ethers';
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
await transferERC20('0xToken', '0xRecipient', '1.0', signer);
await approveToken('0xToken', '0xSpender', '100.0', signer);
const bal = await getTokenBalance('0xToken', '0xAccount', provider);
const allowance = await getTokenAllowance('0xToken', '0xOwner', '0xSpender', provider);
const owner = await getNFTOwner('0xNft', '123', provider);
await transferNFT('0xNft', '0xFrom', '0xTo', '123', signer);Faucet
Request testnet tRBTC for any Rootstock Testnet address. This calls the hosted faucet API — no wallet or signer required.
Signature
import { faucet } from 'rootstockwinks';
import type { FaucetResult } from 'rootstockwinks';
faucet(address: string): Promise<FaucetResult>| Parameter | Type | Description |
|-----------|----------|--------------------------------------------------|
| address | string | The Rootstock Testnet wallet address to fund. |
Returns a FaucetResult object:
interface FaucetResult {
txHash?: string; // Transaction hash of the faucet transfer
message?: string; // Human-readable status message from the API
[key: string]: unknown; // Any additional fields returned by the API
}Basic usage
import { faucet } from 'rootstockwinks';
const result = await faucet('0xYourRootstockAddress');
console.log('Funded! tx hash:', result.txHash);With error handling
import { faucet } from 'rootstockwinks';
try {
const result = await faucet('0xYourRootstockAddress');
console.log('Success:', result.message);
console.log('Transaction:', result.txHash);
} catch (err) {
console.error(err.message); // e.g. "Faucet error: address must not be empty"
}In a React component
import { useState } from 'react';
import { faucet } from 'rootstockwinks';
export function FaucetButton({ address }: { address: string }) {
const [txHash, setTxHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleFaucet = async () => {
setLoading(true);
setError(null);
try {
const result = await faucet(address);
setTxHash(result.txHash ?? null);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
};
return (
<div>
<button onClick={handleFaucet} disabled={loading}>
{loading ? 'Requesting…' : 'Get tRBTC'}
</button>
{txHash && <p>Transaction: {txHash}</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}Notes
- Works on Rootstock Testnet (Chain ID 31) only — do not use mainnet addresses.
- The address is automatically trimmed; passing an empty string throws immediately.
- Errors from the API (e.g. rate limiting, invalid address) are surfaced as
Errorwith the message"Faucet error: <reason>".
Staking
import { stakeTransaction, getWalletSigner } from 'rootstockwinks';
import { StakeTransactionParams, StakeTransactionResult } from 'rootstockwinks';
// Get a signer from the injected browser wallet
getWalletSigner(): Promise<ethers.JsonRpcSigner>
// Execute the staking transaction
stakeTransaction(params: StakeTransactionParams): Promise<StakeTransactionResult>Basic Usage
import { stakeTransaction, getWalletSigner } from 'rootstockwinks';
try {
const signer = await getWalletSigner();
const result = await stakeTransaction({
txHash: "0xYourTransactionHash",
signer,
});
if (result.status === "success") {
console.log("Transaction successful! New tx hash:", result.hash);
}
} catch (error) {
console.error("Transaction failed:", error);
}Advanced Options (Value Overrides & Dry Run)
const result = await stakeTransaction({
txHash: "0x...",
signer,
options: {
valueEth: "0.1", // Send 0.1 native token instead of original amount
useOriginalNonce: false, // Use current nonce (default)
dryRun: true, // Only sign, do not broadcast
},
});Social Sharing (Post-Transaction)
The SDK provides utilities for generating Twitter (X) intents so users can tweet about their successful transactions implicitly highlighting Rootstock.
import { generatePostTransactionTweet, openTweet } from 'rootstockwinks';
// After a successful transaction...
const tweetUrl = generatePostTransactionTweet({
action: "staked",
amount: "100",
token: "tRBTC",
txHash: "0x...", // The transaction hash
network: "testnet"
});
// To automatically open it in a new tab:
openTweet(tweetUrl);Network Configuration
- Rootstock Mainnet: Chain ID 30, RPC
https://public-node.rsk.co, Explorerhttps://explorer.rootstock.io, Currency RBTC - Rootstock Testnet: Chain ID 31, RPC
https://public-node.testnet.rsk.co, Explorerhttps://explorer.testnet.rootstock.io, Currency tRBTC
Server API (local metadata server)
GET /healthPOST /api/keys→{ id, key, name, createdAt, isActive }GET /api/keys(auth)DELETE /api/keys/:key(auth)GET /api/meta/:apiKey→ returnsMetaDataPOST /api/meta/:apiKey(auth){ metadata: MetaData }PUT /api/meta/:apiKey(auth){ metadata: MetaData }DELETE /api/meta/:apiKey(auth)
Auth header: X-API-Key: {apiKey}
Development
Build
npm run build # TypeScript + Rollup (CJS + ESM)
npm run build:vite # Vite library build (optional)Testing
npm test # Jest unit tests
npx playwright install chromium
npm run test:e2e # Playwright (ensure example app is running or use a prod build)License
MIT
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add some amazing feature' - Push:
git push origin feature/amazing-feature - Open a Pull Request
