
Security News
When Autonomous Agents Escape: Why Socket Signed the Cyber Defense Open Letter
Socket joins more than 100 technology, cybersecurity, and financial organizations calling for a global surge in cyber defense.
@txtcel/protocol
Advanced tools
TypeScript SDK for the Txtcel Solana program: instruction builders, account codecs and PDA derivation.
TypeScript SDK for the Txtcel Solana program — a thin, framework-agnostic wrapper around the on-chain protocol. It gives you:
build*Instruction(...) for every program instruction.buildSendMessageTransactions, createRootAlloc, …derive*Pda(...) helpers that mirror the program's seeds.load* helpers that fetch + decode in one call.No React, no wallet adapter — just @solana/web3.js primitives, so it works in
the browser, in Node scripts and in tests.
Note: This README is LLM-generated from the source and reviewed by hand. The code is the source of truth. A condensed, agent-oriented overview lives in
llms.txt.
npm install @txtcel/protocol @solana/web3.js
@solana/web3.js (v1) is a peer dependency — install it in your app.
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import {
createRootAlloc,
buildSendMessageTransactions,
loadThreadNode,
} from '@txtcel/protocol'
const connection = new Connection('https://api.mainnet-beta.solana.com')
const programId = '<YOUR_PROGRAM_ID>'
const payer = Keypair.generate()
// 1. Create a channel (thread). `seed` is the thread's 32-byte identity.
const { seed, threadPda } = await createRootAlloc(connection, programId, payer)
// 2. Post a message (auto-chunks if it exceeds one transaction).
const txs = await buildSendMessageTransactions(
connection, new PublicKey(programId), payer.publicKey, seed, 'gm world',
)
// 3. Read the thread back.
const thread = await loadThreadNode(connection, new PublicKey(programId), new PublicKey(threadPda))
All builders take the programId explicitly — the SDK has no hard-coded address,
so the same package works across clusters and deployments.
seed— most functions take aseed: Uint8Array. This is the thread's 32-byte identity (the thread account's pubkey bytes).createRootAllocreturns it; for an existing thread it'snew PublicKey(threadPda).toBytes(). Child PDAs (alloc/content/access/likes/author-fee) all derive from it.
Everything is re-exported from the package root (src/index.ts).
createRootAlloc(connection, programId, payer, messageFee?, title?, onSent?)Creates a new thread (channel) + root alloc and confirms it with a local
Keypair payer. Generates a fresh thread keypair internally (it co-signs).
programId: string, payer: KeypairmessageFee: bigint = 0n — per-message author fee in lamports.title: string = '' — channel title (≤ MAX_TITLE_LEN bytes).onSent?: () => void — called right after the tx is submitted.{ signature, seed, threadPda, allocPda }.createRootAllocWithWallet(connection, programId, wallet, messageFee?, title?, onSent?)Same as above but for a wallet-adapter style signer
(WalletSigner = { publicKey, signTransaction }). The wallet is fee payer; the
generated thread keypair is an extra signer.
buildSendMessageTransactions(connection, programId, payerKey, seed, text, replyTo?)The recommended way to post a message. Returns an array of POST-ONLY
Transactions: the first is a FillSlot, followed by AppendContent txs when
text is larger than one transaction. It never co-bundles prepare_alloc
(growing the chain is the separate buildExtendAllocTransaction). It:
N = thread.lastAllocSeq),N-1 (when it exists) plus the tail page N — in one
getMultipleAccountsInfo,DESIRED_CANDIDATES = 3 to reduce write
contention,maxFee slippage cap (≈ 2× base fee + message fee),Liveness fallback: if the whole 2-page window is full (rare burst), a separate
prepare_alloc(N) transaction (carrying the occupancy witness set for
non-authors) is prepended before the fill_slot targeting page N+1, so a
post is always possible — sign the returned array as one batch. replyTo?: { allocSeq: number; slot: number } | null threads the message as a reply.
Throws 'Text must not be empty' / 'Body is too long'.
buildExtendAllocTransaction(connection, programId, payerKey, seed)Returns a best-effort prepare_alloc(N) transaction that grows the alloc
chain when the tail page N = thread.lastAllocSeq has filled >= EXTEND_THRESHOLD slots, else null. Extension is occupancy-gated on-chain:
for non-authors the transaction carries EXTEND_THRESHOLD witness accounts
(randomly sampled occupied tail slots) proving the tail is full enough; the
thread author extends by signature alone. Decoupled from posting: fire it
alongside a post and ignore its failure (e.g. InvalidAllocSeq when
another sender already extended, or WitnessSlotEmpty when a witnessed
message was deleted mid-flight).
sendMessageWithWallet(connection, programId, wallet, seed, text, replyTo?, onSent?)Posts a message with a wallet using a single approval: builds the post txs
plus the best-effort extend tx, signs them all in one signAllTransactions
call (falling back to per-tx signTransaction), then sends them as independent
transactions — fill_slot then append_content chunks (confirmed), and the
extend tx best-effort (its failure is swallowed). Returns
{ signatures, extendSignature }.
buildSendMessageTransaction(...) — deprecatedReturns only the first transaction. Use buildSendMessageTransactions for
chunked sending.
type WalletSigner{ publicKey: PublicKey; signTransaction: (tx) => Promise<Transaction>; signAllTransactions?: (txs) => Promise<Transaction[]> }
Each returns a TransactionInstruction you add to your own Transaction. They
mirror the on-chain instructions 1:1 (see the program README for account
semantics, fees and errors).
| Builder | On-chain instruction | Key args |
|---|---|---|
buildCreateRootAllocInstruction(programId, payer, seed, messageFee?, title?) | CreateRootAlloc | thread keypair pubkey as seed, picks a random treasury shard. |
buildFillSlotInstruction(opts) | FillSlot | see options below. |
buildPrepareAllocInstruction(programId, payer, seed, allocSeq, witnessSlots?) | PrepareAlloc | pre-creates allocSeq + 1; non-authors pass EXTEND_THRESHOLD occupied tail slot indexes as the witness proof. |
buildAppendContentInstruction(programId, payer, contentAccount, threadAccount, settingsAccount, treasuryShard, authorFeeShard, chunk, treasuryShardIdx, authorFeeShardIdx) | AppendContent | append bytes to your message. |
buildLikeContentInstruction(programId, payer, seed, allocSeq, slot, maxFee) | LikeContent | random shards chosen internally. |
buildCloseAccountInstruction(programId, payer, targetAccount, likesAccount?) | CloseAccount | optional likes account resets the slot counter. |
buildSweepTreasuryInstruction(programId, treasuryWallet, shardIndices) | SweepTreasury | [settings, treasury, ...shards]. |
buildSweepAuthorFeesInstruction(programId, seed, threadAccount, authorWallet, shardIndices) | SweepAuthorFees | author signs. |
buildInitSettingsInstruction(programId, authority, treasury) | InitSettings | derives ProgramData PDA; upgrade authority signs. |
buildSetTreasuryInstruction(programId, authority, treasury) | SetTreasury | admin only. |
buildSetAdminInstruction(programId, authority, newAdmin) | SetAdmin | admin only. |
buildSetFeeInstruction(programId, authority, kind, feeBps) | SetBaseFee/SetAuthorFeeCut/SetEntryCut/SetLikeCut | kind: FeeKind. |
buildSetBaseFeeInstruction(programId, authority, feeBps) | SetBaseFee | admin only. |
buildSetAuthorFeeCutInstruction(programId, authority, feeBps) | SetAuthorFeeCut | admin only. |
buildSetEntryCutInstruction(programId, authority, feeBps) | SetEntryCut | admin only. |
buildSetLikeCutInstruction(programId, authority, feeBps) | SetLikeCut | admin only. |
buildSetMessageFeeInstruction(programId, authority, threadAccount, fee) | SetMessageFee | thread author. |
buildSetLikeFeeInstruction(programId, authority, threadAccount, fee) | SetLikeFee | thread author. |
buildSetEntryFeeInstruction(programId, authority, accessAccount, fee) | SetEntryFee | thread admin. |
buildInitThreadAccessInstruction(programId, authority, seed, enabled) | InitThreadAccess | thread author. |
buildSetThreadAccessInstruction(programId, authority, accessAccount, enabled) | SetThreadAccess | thread admin. |
buildAddToWhitelistInstruction(programId, authority, seed, wallet) | AddToWhitelist | thread admin. |
buildRemoveFromWhitelistInstruction(programId, authority, seed, wallet) | RemoveFromWhitelist | thread admin. |
buildAddToBlacklistInstruction(programId, authority, seed, wallet) | AddToBlacklist | thread admin. |
buildRemoveFromBlacklistInstruction(programId, authority, seed, wallet) | RemoveFromBlacklist | thread admin. |
buildAddToFeeWhitelistInstruction(programId, authority, seed, wallet) | AddToFeeWhitelist | thread admin. |
buildRemoveFromFeeWhitelistInstruction(programId, authority, seed, wallet) | RemoveFromFeeWhitelist | thread admin. |
buildRequestAccessInstruction(programId, payer, seed) | RequestAccess | pay entry fee to join. |
buildSubscribeInstruction({ programId, user, seed }) | Subscribe | follow a channel; no fee, user pays registry/shard rent. |
buildUnsubscribeInstruction({ programId, user, seed }) | Unsubscribe | unfollow a channel; refunds freed registry rent. |
buildFillSlotInstruction(opts) options{
programId: PublicKey
payer: PublicKey
seed: Uint8Array
candidates: Array<{ allocSeq: number; slot: number; pda: PublicKey }>
treasuryShardIdx: number
authorFeeShardIdx: number
bodyBytes: Uint8Array // opaque payload; UTF-8 for the default text kind
kind?: number // message-type discriminator, defaults to KIND_TEXT
maxFee: bigint // slippage cap on base + author fee
replyAllocSeq?: number | null
replySlot?: number | null
}
The builder appends the mandatory access + per-wallet entry PDAs (derived
for payer) at fixed positions. fill_slot is element-only and never grows the
alloc chain — linking a new page is the separate prepare_alloc instruction.
type FeeKind'base' | 'authorCut' | 'entryCut' | 'likeCut' — selects which platform fee
buildSetFeeInstruction updates.
All mirror the program's seeds exactly (see the program README PDA table).
| Function | Returns |
|---|---|
deriveSettingsPda(programId) | global settings PDA. |
deriveThreadPda(programId, seed) | the thread account address (= new PublicKey(seed); thread is a full-address account, not a PDA). |
deriveAllocPda(programId, seed, allocSeq) | alloc node PDA. |
deriveContentPda(programId, seed, allocSeq, slot) | content (message) PDA. |
deriveAccessPda(programId, seed) | ThreadAccess PDA. |
deriveAccessEntryPda(programId, seed, wallet) | per-wallet AccessEntry PDA. |
deriveLikesPda(programId, seed, allocSeq) | AllocLikes PDA. |
deriveTreasuryShardPda(programId, shard) | treasury vault shard PDA. |
deriveAuthorFeePda(programId, seed, shard) | author-fee vault shard PDA. |
deriveProgramDataPda(programId) | BPF loader ProgramData account (holds upgrade authority). |
deriveFollowRegistryPda(programId, owner) | per-wallet FollowRegistry PDA. |
deriveFollowerShardPda(programId, seed, shard) | per-channel FollowerShard counter PDA. |
followerShardIndex(owner) | shard index for a wallet (owner.toBytes()[0] % N_FOLLOWER_SHARDS). |
randomTreasuryShard() | random index in [0, N_TREASURY_SHARDS). |
randomAuthorFeeShard() | random index in [0, N_AUTHOR_FEE_SHARDS). |
Helper seed encoders u16Seed(n) / u32Seed(n) are also exported.
Fetch (getAccountInfo, 'confirmed') + validate owner/tag + decode in one
call.
| Loader | Returns | Missing account |
|---|---|---|
loadThreadNode(connection, programId, pubkey) | ThreadNodeData | throws. |
loadAllocNode(connection, programId, pubkey) | AllocNodeData | throws. |
loadContentNode(connection, programId, pubkey) | ContentNodeData | throws. |
loadProgramSettings(connection, programId) | ProgramSettingsData | null | returns null. |
loadThreadAccess(connection, programId, accessKey) | ThreadAccessData | throws. |
loadAllocLikes(connection, programId, pubkey) | AllocLikesData | null | returns null. |
loadAccessEntries(connection, programId, seed) | { whitelist, blacklist, feeExempt } of base58 strings | scans program accounts by tag + seed. |
loadFollowRegistry(connection, programId, owner) | FollowRegistryData | null | returns null. |
loadFollowerCount(connection, programId, seed) | bigint (sum of all follower shards) | returns 0n. |
Pure decoders (Uint8Array -> typed data), used by the loaders but exported for
when you already have raw account data (e.g. from getProgramAccounts or a
websocket subscription).
decodeContent(pubkey, data) → ContentNodeDatadecodeAlloc(pubkey, data) → AllocNodeDatadecodeThread(pubkey, data) → ThreadNodeDatadecodeSettings(pubkey, data) → ProgramSettingsDatadecodeThreadAccess(pubkey, data) → ThreadAccessDatadecodeAccessEntry(pubkey, data) → AccessEntryDatadecodeAllocLikes(pubkey, data) → AllocLikesDatadecodeFollowRegistry(pubkey, data) → FollowRegistryDatadecodeFollowerShard(pubkey, data) → FollowerShardDataThe raw zorsh schemas (ContentNodeSchema, FillSlotInstr, …) live in
codec/schemas.ts if you need to (de)serialize manually.
type ContentNodeData = {
pubkey: string
allocSeq: number
slot: number
seed: Uint8Array // owning thread's identity bytes
author: string
createdAt: string // ISO-8601
replyAllocSeq: number | null
replySlot: number | null
contentKind: number // KIND_* discriminator
body: Uint8Array // opaque, raw bytes
text: string // decoded UTF-8 when contentKind === KIND_TEXT
}
type AllocNodeData = {
pubkey: string; seed: Uint8Array; allocSeq: number
}
type ThreadNodeData = {
pubkey: string; seed: Uint8Array; allocCount: number; lastAllocSeq: number
author: string; messageFee: bigint; likeFee: bigint; title: string
}
type ProgramSettingsData = {
pubkey: string; admin: string; treasury: string
baseFeeBps: number; authorFeeCutBps: number; entryCutBps: number; likeCutBps: number
}
type ThreadAccessData = {
pubkey: string; seed: Uint8Array; enabled: boolean; admin: string
entryFee: bigint; whitelistCount: number
}
type AccessEntryData = { pubkey: string; seed: Uint8Array; wallet: string; status: number }
type AllocLikesData = { pubkey: string; allocSeq: number; counts: number[] }
| Group | Constants |
|---|---|
| Account tags | TAG_CONTENT, TAG_ALLOC, TAG_THREAD, TAG_SETTINGS, TAG_ACCESS, TAG_LIKES, TAG_ACCESS_ENTRY, TAG_FOLLOW_REGISTRY, TAG_FOLLOWER_SHARD |
| Content kinds | KIND_TEXT |
| Access status | ACCESS_ALLOWED, ACCESS_DENIED, ACCESS_FEE_EXEMPT |
| Layout sizes | CONTENT_SLOTS, EXTEND_THRESHOLD, EXTEND_STALE_SLOTS, MAX_BODY_LEN, MAX_TITLE_LEN, INDEX_NONE, PUBKEY_SIZE |
| Shards / follows | N_TREASURY_SHARDS, N_AUTHOR_FEE_SHARDS, N_FOLLOWER_SHARDS, MAX_FOLLOWS |
| Fees | MAX_FEE_CUT_BPS (5000; on-chain max for fee cuts / base fee) |
| Seeds | SEED_SETTINGS, SEED_ALLOC, SEED_CONTENT, SEED_ACCESS, SEED_LIKES, SEED_TREASURY_SHARD, SEED_AUTHOR_FEE, SEED_ACL, SEED_FOLLOWS, SEED_FOLLOWER_COUNT |
| Misc | Instruction (variant index map), BPF_LOADER_UPGRADEABLE_ID, DESIRED_CANDIDATES, BPS_DIVISOR, TX_POLL_TIMEOUT_MS, TX_POLL_INTERVAL_MS |
The Instruction object maps instruction names to their on-chain variant index
(e.g. Instruction.FillSlot === 1), matching ProgramInstruction in the
program's instruction.rs.
import { Connection, PublicKey } from '@solana/web3.js'
import { buildSendMessageTransactions } from '@txtcel/protocol'
const txs = await buildSendMessageTransactions(
connection, programId, wallet.publicKey, seed, longText,
)
for (const tx of txs) {
tx.feePayer = wallet.publicKey
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
const signed = await wallet.signTransaction(tx)
await connection.sendRawTransaction(signed.serialize())
}
import { Transaction } from '@solana/web3.js'
import {
buildInitThreadAccessInstruction,
buildAddToWhitelistInstruction,
} from '@txtcel/protocol'
const tx = new Transaction()
.add(buildInitThreadAccessInstruction(programId, author, seed, true))
.add(buildAddToWhitelistInstruction(programId, author, seed, memberWallet))
import { buildSweepTreasuryInstruction } from '@txtcel/protocol'
const ix = buildSweepTreasuryInstruction(programId, treasuryWallet, [0, 1, 2])
npm install
npm run build # tsup -> dist (ESM + CJS + .d.ts)
npm run typecheck
npm run build
npm publish # publishConfig.access is already "public"
FAQs
TypeScript SDK for the Txtcel Solana program: instruction builders, account codecs and PDA derivation.
The npm package @txtcel/protocol receives a total of 13 weekly downloads. As such, @txtcel/protocol popularity was classified as not popular.
We found that @txtcel/protocol demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Socket joins more than 100 technology, cybersecurity, and financial organizations calling for a global surge in cyber defense.

Product
Enterprise security teams can now detect malware, credential theft, suspicious network activity, and risky updates across Microsoft Edge extensions.

Research
/Security News
Socket researchers found 18 Chrome extensions and one Edge extension delivering a wallet drainer, credential theft, and other malicious payloads.