/SKILL.md
# ChunkTech Skill Store files and browser extensions on-chain using chunked calldata transactions. ## When to Use - User wants to store a file permanently on-chain - User wants to distribute a Chrome/Firefox extension without app stores - User needs censorship-resistant file hosting - User wants cross-chain storage (cheap L2 data, durable L1 pointer) ## Key Concepts **Chunking**: Files are split into 33.3KB pieces, each sent as a self-transfer transaction with the data in calldata. **Inscr
npx skillsauth add jefdiesel/chunktech chunktechInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Store files and browser extensions on-chain using chunked calldata transactions.
Chunking: Files are split into 33.3KB pieces, each sent as a self-transfer transaction with the data in calldata.
Inscription: A single transaction containing a self-loading HTML page that can fetch and reassemble data from other transactions.
Cross-chain: Store bulk data on Base (cheap), store the "unchunker" inscription on Ethereum (durable).
import { ChunkTech } from 'chunktech';
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);
const walletClient = createWalletClient({ account, chain: base, transport: http() });
const ct = new ChunkTech({ walletClient });
const result = await ct.upload(fileBuffer);
console.log('TX hashes:', result.txHashes);
import { ExtensionUploader } from 'chunktech';
import { createWalletClient, http } from 'viem';
import { base, mainnet } from 'viem/chains';
import { readFileSync } from 'fs';
const uploader = new ExtensionUploader({
keyChain: 'ethereum',
dataChain: 'base',
keyWalletClient: createWalletClient({ account, chain: mainnet, transport: http() }),
dataWalletClient: createWalletClient({ account, chain: base, transport: http() }),
});
const result = await uploader.upload({
name: 'My Extension',
version: '1.0.0',
developer: 'dev.eth',
chrome: readFileSync('dist/chrome.zip'),
firefox: readFileSync('dist/firefox.xpi'),
});
console.log('Inscription:', result.inscriptionTxHash);
const result = await ct.download(txHashes);
// result.data = Uint8Array of reassembled file
Extension Distribution:
┌────────────────┐ ┌─────────────────┐
│ Ethereum │ │ Base │
│ │ │ │
│ 1 inscription │────▶│ N data chunks │
│ (HTML loader) │ │ (extension zip) │
└────────────────┘ └─────────────────┘
│
▼
User views inscription
│
▼
HTML fetches chunks from Base
│
▼
Reassembles + verifies SHA256
│
▼
Download button appears
// Main classes
ChunkTech // Single-chain upload/download
ExtensionUploader // Browser extension distribution
CrossChainUploader // Generic cross-chain with HTML loader
// Utilities
chunkData // Split file into chunks
encodeChunk // Encode chunk for calldata
decodeChunk // Decode chunk from calldata
sendChunks // Send chunks as transactions
assembleFromHashes // Reassemble from tx hashes
generateExtensionLoader // Generate extension download page HTML
// Encryption (optional)
generateEncryptionKeys
encryptForRecipients
decryptForRecipient
| Chain | Cost per KB | 500KB file | |-------|-------------|------------| | Base | ~$0.001 | ~$0.50 | | Arbitrum | ~$0.001 | ~$0.50 | | Ethereum | ~$0.50 | ~$250 |
Recommended: Data on Base, inscription pointer on Ethereum.
Upload both, inscription shows download buttons for each with browser detection.
Use X3DH encryption with recipient public keys. Only specified recipients can decrypt.
Each version is a new inscription. Use ENS or a registry contract to point to latest.
Create self-contained HTML pages that fetch and display any on-chain content. Good for:
data:application/zip;base64,<content>// inscribe.js - Inscribe a zip to Base
import { readFileSync } from 'fs';
import { createPublicClient, createWalletClient, http, toHex } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { createHash } from 'crypto';
const zipData = readFileSync('package.zip');
const base64 = zipData.toString('base64');
const sha256 = createHash('sha256').update(zipData).digest('hex');
const dataUri = `data:application/zip;base64,${base64}`;
const calldata = toHex(new TextEncoder().encode(dataUri));
const hash = await walletClient.sendTransaction({
to: account.address, // self-transfer
data: calldata,
});
// Save hash and sha256 for viewer
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
</head>
<body>
<button id="download-btn" disabled>Download</button>
<div id="file-tabs"></div>
<pre id="code-content"></pre>
<script>
const TX_HASH = '0x...';
const EXPECTED_SHA256 = '...';
const RPC_URL = 'https://mainnet.base.org';
async function fetchFromChain() {
// 1. Fetch tx via RPC
const response = await fetch(RPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_getTransactionByHash',
params: [TX_HASH],
id: 1
})
});
const { result: tx } = await response.json();
// 2. Decode calldata -> data URI -> base64 -> bytes
const hex = tx.input.slice(2);
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
}
const dataUri = new TextDecoder().decode(bytes);
const base64 = dataUri.match(/base64,(.+)$/)[1];
const zipBytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
// 3. Verify SHA256
const hashBuffer = await crypto.subtle.digest('SHA-256', zipBytes);
const sha256 = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0')).join('');
if (sha256 !== EXPECTED_SHA256) throw new Error('SHA256 mismatch');
// 4. Extract and display files
const zip = await JSZip.loadAsync(zipBytes);
// ... build tabs, show content
}
</script>
</body>
</html>
/* Dark + Accent (e.g., black + #c3ff00) */
body { background: #000; color: #e0e0e0; }
.highlight { color: #c3ff00; }
.btn { background: #c3ff00; color: #000; }
/* Dark + Red (e.g., #0a0a0f + #e94560) */
body { background: #0a0a0f; color: #e0e0e0; }
.highlight { color: #e94560; }
.btn { background: linear-gradient(135deg, #e94560, #ff6b35); }
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.