- Kiln is a non-custodial staking provider. As the customer and ultimate owner of your assets, it is your sole responsibility to review, check, and verify all transactions before signing and broadcasting them.
- Kiln may provide certain tooling (such as web applications, APIs, or scripts) to facilitate interaction with supported protocols. These tools are provided for convenience only. They do not constitute advice, nor do they alter the fact that you remain solely responsible for the transactions you generate, sign, and broadcast.
- Kiln does not have access to your private keys and cannot execute, reverse, or validate transactions on your behalf. Kiln disclaims any responsibility or liability for the use of these scripts, including (without limitation) for missed staking rewards, slashing penalties, opportunity costs, or other losses (as defined in your agreement with Kiln).
- This repository and the associated code are provided “AS IS” and “AS AVAILABLE,” without warranties of any kind, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, or non-infringement.
- In no event shall Kiln, its affiliates, or its contributors be liable for any claim, damages, or other liability, whether in contract, tort, or otherwise, arising from, out of, or in connection with the use of this repository or the transactions you execute based on it.
- By using these tools, you acknowledge and agree that you are acting on your own responsibility and at your own risk.
A multi-chain transaction decoder monorepo built with Bun workspaces, Vite, React, Tailwind v4, and shadcn/ui. Decode and inspect raw transactions across different blockchain networks with a unified interface.
├── apps/
│ ├── solana/ # Solana transaction decoder
│ └── ethereum/ # Ethereum transaction decoder
├── packages/
│ └── ui/ # Shared UI components and transaction decoder
└── package.json # Root workspace config
-
Install dependencies:
bun install
-
Run all decoders in development:
bun run dev
-
Run specific chain decoder:
cd apps/solana && bun run dev cd apps/ethereum && bun run dev
- Multi-chain Support: Decode transactions from Ethereum and Solana networks
- Shared UI Package:
@protocols/ui
with reusable components and decoder interface - Transaction Decoder: Core
<TransactionDecoder />
component for reusable ui interface - Chain-specific Parsing: Dedicated parsers for each supported blockchain
- Rich Transaction Data: View decoded instructions, addresses, and transaction details
- Modern Stack: Built with TypeScript, Tailwind v4, and shadcn/ui components
The project uses a standardized protocol adapter pattern that makes adding new chains simple and consistent.
-
Create the app directory:
mkdir apps/your-protocol cd apps/your-protocol
-
Copy base structure from existing protocol:
cp -r ../ethereum/* . # or cp -r ../solana/* .
-
Create your protocol adapter:
// apps/your-protocol/src/your-protocol-adapter.tsx import { type ProtocolAdapter } from '@protocols/shared'; export const yourProtocolAdapter: ProtocolAdapter<YourTransactionType> = { name: 'your-protocol', displayName: 'Your Protocol', placeholder: 'Paste your transaction hex or JSON', parseTransaction: parseYourProtocolTx, computeHash: hashYourProtocolTx, renderSummary: (data) => <YourSummaryComponent transaction={data} />, generateWarnings: (data) => [ // Return array of { message: string } warnings ], };
-
Update your App.tsx:
// apps/your-protocol/src/App.tsx import { ProtocolTransactionDecoder } from '@protocols/ui'; import { yourProtocolAdapter } from './your-protocol-adapter'; function App() { return ( <div className="relative flex flex-col min-h-screen"> <Background /> <Header {...headerProps} /> <ProtocolTransactionDecoder adapter={yourProtocolAdapter} /> </div> ); }
-
Implement your parsing functions:
// apps/your-protocol/src/parser.ts export const parseYourProtocolTx = async (rawTx: string): Promise<YourTransactionType> => { // Your parsing logic }; export const hashYourProtocolTx = (rawTx: string): string => { // Your hashing logic };
The ProtocolAdapter<T, W>
type provides a standardized contract:
type ProtocolAdapter<TDecodedTransaction, TWarning = ProtocolWarning> = {
// Basic info
name: string; // 'ethereum', 'solana', 'near'
displayName: string; // 'Ethereum', 'Solana', 'NEAR'
// Required functions
parseTransaction: (rawTx: string) => Promise<TDecodedTransaction>;
computeHash: (rawTx: string) => string | Promise<string>;
renderSummary: (data: TDecodedTransaction, hash?: string) => ReactNode;
generateWarnings: (data: TDecodedTransaction) => TWarning[];
// Optional UI customization
placeholder?: string;
title?: string;
subtitle?: string;
}
Simple synchronous hash:
computeHash: (rawTx) => keccak256(rawTx)
Async hash (like Solana):
computeHash: async (rawTx) => {
const buffer = await crypto.subtle.digest('SHA-256', hexToUint8Array(rawTx));
return bufferToHex(buffer);
}
Complex warnings:
generateWarnings: (data) => {
const warnings = [];
if (data.value > parseEther('1')) {
warnings.push({ message: `High value transaction: ${formatEther(data.value)} ETH` });
}
if (data.instructions?.some(ix => ix.warning)) {
warnings.push(...data.instructions.filter(ix => ix.warning).map(ix => ({ message: ix.warning })));
}
return warnings;
}
Your protocol should follow this structure:
apps/your-protocol/
├── src/
│ ├── components/ # Protocol-specific UI components
│ │ └── YourSummary.tsx
│ ├── your-protocol-adapter.tsx # Main adapter implementation
│ ├── parser.ts # Transaction parsing logic
│ ├── App.tsx # Main app component
│ └── types.ts # Protocol-specific types
├── package.json
└── tsconfig.json
- Consistent Interface: All protocols use the same UI and interaction patterns
- Type Safety: Full TypeScript support with generics
- Rapid Development: New protocols can be added in minutes,not hours
- Maintainable: Shared UI logic reduces bugs and duplication
- Extensible: Easy to add protocol-specific features while maintaining consistency
Add interactive example transactions for educational purposes. The playbook shows sample operations with step-by-step explanations.
-
Create playbook operations file:
// apps/your-protocol/src/config/playbook-operations.ts import { type PlaybookOperation } from '@protocols/shared'; export const YOUR_PROTOCOL_PLAYBOOK_OPERATIONS: PlaybookOperation[] = [ { label: 'Transfer Tokens', value: 'transfer', description: 'Simple token transfer', rawTransaction: '0x...', // Your sample transaction operationOverview: [ { type: 'text', content: 'This transaction transfers 10 tokens from Alice to Bob...', }, ], stepByStep: [ { title: 'Transfer', program: 'Token Program', description: 'Transfers 10 tokens from Alice to Bob', }, ], }, // Add more operations... ];
-
Add playbook to your App.tsx:
// apps/your-protocol/src/App.tsx import { TransactionPlaybook } from '@protocols/ui'; import { YOUR_PROTOCOL_PLAYBOOK_OPERATIONS } from './config/playbook-operations'; function App() { const [playbook, setPlaybook] = useState(false); const { decodedTransaction, decodeTransaction, hash } = useTransactionDecoder(); const playbookConfig = { protocolName: 'Your Protocol', operations: YOUR_PROTOCOL_PLAYBOOK_OPERATIONS, renderSummary: (data) => <YourSummaryComponent transaction={data} />, decodeTransaction, decodedTransaction, hash, }; return ( <div className="flex"> <div className={cn('w-full', playbook && 'md:mr-[40%]')}> <Header togglePlaybook={() => setPlaybook(!playbook)} /> <ProtocolTransactionDecoder adapter={yourAdapter} /> </div> <TransactionPlaybook config={playbookConfig} isOpen={playbook} onClose={() => setPlaybook(false)} /> </div> ); }
That's it! Your protocol now has an interactive playbook with sample transactions and educational content.