-
Notifications
You must be signed in to change notification settings - Fork 33
Social
Melvin Carvalho edited this page Jan 8, 2026
·
1 revision
This document outlines the transformation of Jumble (a Nostr client) into a Bitmark marking client. The Bitmark marking system combines reputation and currency into "marks" that can be awarded to users and content.
- Marks: Divisible units (to 5 decimal places) that function as both reputation and currency
- Marking: The act of awarding marks to users or content as recognition
- Bitmark: The underlying cryptocurrency (operational since 2014, uses 8 PoW algorithms)
- Spendable Karma: Marks can be stored, exchanged, and transferred without value loss
Transform Jumble's visual identity into a Bitmark marking client.
| Asset | Current | New |
|---|---|---|
| App Name | Jumble | Marked (or "GetMarked") |
| Logo | src/assets/Logo.tsx |
New Bitmark-inspired mark logo |
| Icon | src/assets/Icon.tsx |
Stylized "M" or checkmark symbol |
| Favicon | Jumble favicon | Mark icon |
| OGP Images | Jumble branding | Marked branding |
// src/lib/theme.ts (new)
export const markColors = {
primary: '#FF6B35', // Warm orange (marking action)
secondary: '#2E4057', // Deep blue (trust/stability)
accent: '#48A9A6', // Teal (growth/reputation)
gold: '#D4AF37', // Gold (high-value marks)
silver: '#C0C0C0', // Silver (moderate marks)
bronze: '#CD7F32', // Bronze (entry marks)
}src/assets/Logo.tsx → New mark logo SVG
src/assets/Icon.tsx → New icon SVG
public/favicon.ico → New favicon
public/manifest.json → App name, description
index.html → Title, meta tags, OGP
src/i18n/*/translation.json → All "Jumble" references
vite.config.ts → PWA manifest
| Jumble Term | Marked Term |
|---|---|
| Zap | Mark |
| Sats | Marks |
| Lightning | Bitmark |
| Wallet | Mark Wallet |
| Tip | Award |
- New logo component
- New icon component
- Updated manifest.json
- Updated index.html (title, OGP)
- Updated translations
- Color theme implementation
- Loading screens with new branding
Integrate Bitmark wallet functionality so users can hold and manage marks.
┌─────────────────────────────────────────────────────────┐
│ MarkProvider │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ KeyManager │ │ BalanceSync │ │ TransactionLog │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ BitmarkRPC Service │
│ (connects to bitmarkd node) │
└─────────────────────────────────────────────────────────┘
// Derive Bitmark address from Nostr private key
// BIP-32 style derivation: m/44'/19'/0'/0/0
// Where 19 is a hypothetical Bitmark coin type
interface MarkWallet {
address: string // Bitmark address
publicKey: string // Derived from nostr key
balance: number // Current mark balance
pending: number // Unconfirmed marks
}interface ImportedWallet {
type: 'wif' | 'seed' | 'watch-only'
address: string
encryptedPrivKey?: string // Encrypted with nostr key
}// Server-managed marks linked to nostr pubkey
interface CustodialMark {
pubkey: string // Nostr pubkey as identifier
balance: number // Marks held in custody
pendingIn: number // Incoming marks
pendingOut: number // Outgoing marks
}// src/providers/MarkProvider.tsx
type TMarkContext = {
isWalletConnected: boolean
address: string | null
balance: number
pendingBalance: number
defaultMarkAmount: number
updateDefaultAmount: (amount: number) => void
quickMark: boolean
updateQuickMark: (enabled: boolean) => void
sendMark: (recipient: string, amount: number, memo?: string) => Promise<TxResult>
getHistory: () => Promise<MarkTransaction[]>
}src/components/MarkWallet/
├── index.tsx # Main wallet component
├── BalanceDisplay.tsx # Shows mark balance
├── ReceiveMarks.tsx # QR code / address display
├── SendMarks.tsx # Send marks form
├── TransactionHistory.tsx # Mark transaction list
└── WalletSetup.tsx # Initial wallet setup flow
// Local storage keys
const MARK_STORAGE = {
WALLET_ADDRESS: 'mark:wallet:address',
ENCRYPTED_KEY: 'mark:wallet:encKey',
DEFAULT_AMOUNT: 'mark:default:amount', // Default: 1.0 marks
QUICK_MARK: 'mark:quick:enabled',
TX_CACHE: 'mark:tx:cache'
}// src/services/bitmark.service.ts
interface BitmarkService {
// Balance queries
getBalance(address: string): Promise<number>
getUnconfirmed(address: string): Promise<number>
// Transaction operations
createTransaction(params: TxParams): Promise<RawTx>
broadcastTransaction(signedTx: string): Promise<TxId>
getTransaction(txId: string): Promise<Transaction>
// Address utilities
validateAddress(address: string): boolean
deriveAddress(pubkey: string): string
}- MarkProvider context
- Wallet creation/import flow
- Balance display component
- Receive marks (address + QR)
- Basic send functionality
- Transaction history view
- Bitmark RPC service
- Secure key storage
Allow users to award marks to other users as reputation.
┌──────────┐ ┌───────────────┐ ┌──────────────┐
│ Profile │ ──► │ Mark Button │ ──► │ Mark Dialog │
└──────────┘ └───────────────┘ └──────────────┘
│
┌───────────────┐ ▼
│ Confirmation │ ◄── ┌──────────────┐
└───────────────┘ │ Amount Input │
│ └──────────────┘
▼
┌───────────────┐
│ Transaction │
│ Broadcast │
└───────────────┘
// Modify: src/components/Profile/index.tsx
// Replace ProfileZapButton with ProfileMarkButton
<ProfileMarkButton
pubkey={pubkey}
onMark={(amount) => handleMark(amount)}
/>// src/components/ProfileMarkButton/index.tsx
interface MarkButtonProps {
pubkey: string
size?: 'sm' | 'md' | 'lg'
showAmount?: boolean
}
// Displays a mark icon with optional quick-mark
// Long press opens amount selection dialog// src/components/MarkDialog/index.tsx
interface MarkDialogProps {
recipientPubkey: string
recipientProfile?: Profile
onSubmit: (amount: number, memo?: string) => Promise<void>
onCancel: () => void
}
// Features:
// - Preset amounts: 0.1, 1.0, 5.0, 10.0 marks
// - Custom amount input
// - Optional memo/reason
// - Balance check before submit
// - Transaction fee display// src/components/ReputationBadge/index.tsx
interface ReputationBadgeProps {
pubkey: string
size?: 'sm' | 'md'
}
// Shows:
// - Total marks received
// - Mark rank tier (Bronze/Silver/Gold/Platinum)
// - Trend indicator (up/down/stable)// Kind for mark receipts (custom Nostr event)
const MARK_RECEIPT_KIND = 9735 // Similar to zap receipt
interface MarkReceipt {
kind: 9735
pubkey: string // Marker's pubkey
tags: [
['p', recipientPubkey],
['amount', '1.00000'], // Mark amount
['txid', 'bitmark_tx_id'],
['memo', 'Great content!']
]
content: string // Optional message
sig: string
}- ProfileMarkButton component
- MarkDialog component
- ReputationBadge component
- Mark transaction flow
- Replace ZapButton with MarkButton
- Quick-mark functionality
- Mark receipt events (Nostr)
- User reputation aggregation
Enable marking of individual posts/content, not just users.
┌──────────────┐ ┌─────────────┐ ┌───────────────┐
│ Note/Post │ ──► │ Mark Icon │ ──► │ Mark Dialog │
│ (in feed) │ │ (footer) │ │ (for content) │
└──────────────┘ └─────────────┘ └───────────────┘
│
┌─────────────────────┘
▼
┌──────────────┐
│ Mark stored │
│ with eventId │
└──────────────┘
// Modify: src/components/NoteStats/index.tsx
// Add MarkStats alongside likes, reposts, replies
<div className="flex items-center gap-4">
<LikeButton eventId={eventId} />
<RepostButton eventId={eventId} />
<ReplyButton eventId={eventId} />
<MarkButton eventId={eventId} authorPubkey={pubkey} /> // NEW
</div>// src/components/ContentMarkButton/index.tsx
interface ContentMarkButtonProps {
eventId: string // Nostr event ID
authorPubkey: string // Content author
initialMarks?: number // Cached mark count
}
// Shows:
// - Mark icon (filled if user has marked)
// - Total marks received by this content
// - Click to mark, long-press for amount// src/hooks/useContentMarks.ts
interface ContentMarks {
eventId: string
totalMarks: number
markCount: number // Number of unique markers
topMarkers: Marker[] // Top 3 markers
userMarked: boolean // Has current user marked
userMarkAmount?: number // User's mark amount
}
function useContentMarks(eventId: string): ContentMarks// src/components/MarkLeaderboard/index.tsx
interface LeaderboardProps {
type: 'users' | 'content' | 'markers'
timeframe: 'day' | 'week' | 'month' | 'all'
limit?: number
}
// Shows ranked list of:
// - Most marked users
// - Most marked content
// - Most generous markers// Nostr event for content marks
interface ContentMarkReceipt {
kind: 9735
tags: [
['e', eventId], // Marked content
['p', authorPubkey], // Content author (recipient)
['amount', '0.50000'],
['txid', 'bitmark_tx_id']
]
content: 'Excellent article!'
}- ContentMarkButton component
- Mark stats in note footer
- useContentMarks hook
- Mark aggregation service
- Leaderboard component
- Content mark receipts
- "Marked" indicator on posts
- Sort feed by marks option
Persist marked content for discovery, analytics, and proof.
┌─────────────────────────────────────────────────────────────┐
│ Client Layer │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ LocalStorage │ │ IndexedDB │ │ Nostr Relays │ │
│ │ (cache) │ │ (full store) │ │ (mark receipts) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Mark Index Service │
│ (aggregates marks across sources) │
└─────────────────────────────────────────────────────────────┘
// src/services/mark-db.service.ts
interface MarkDB {
marks: {
id: string // txid
type: 'user' | 'content'
fromPubkey: string
toPubkey: string
eventId?: string // For content marks
amount: number
memo?: string
timestamp: number
blockHeight?: number
confirmed: boolean
}
aggregates: {
id: string // pubkey or eventId
type: 'user' | 'content'
totalReceived: number
totalSent: number
markCount: number
lastUpdated: number
}
content: {
eventId: string
content: string // Cached content
authorPubkey: string
totalMarks: number
createdAt: number
cachedAt: number
}
}// src/services/mark-sync.service.ts
interface MarkSyncService {
// Sync mark receipts from Nostr relays
syncMarkReceipts(since: number): Promise<void>
// Sync blockchain confirmations
syncConfirmations(): Promise<void>
// Subscribe to real-time marks
subscribeToMarks(pubkey: string): () => void
// Export data
exportMarks(format: 'json' | 'csv'): Promise<Blob>
}// When content is marked, cache it
interface CachedContent {
eventId: string
rawEvent: NostrEvent // Full Nostr event
renderedContent: string // Processed content
mediaUrls: string[] // Referenced media
markedAt: number
firstMarkTxId: string
}// src/pages/secondary/MarkAnalytics/index.tsx
interface MarkAnalytics {
// User stats
totalReceived: number
totalSent: number
netMarks: number
// Trends
dailyReceived: number[]
dailySent: number[]
// Top content
topMarkedContent: ContentMark[]
// Top connections
topReceivers: UserMark[]
topGivers: UserMark[]
}- IndexedDB service for marks
- Mark sync service
- Content caching on mark
- Analytics dashboard
- Export functionality
- Real-time subscriptions
- Offline mark queue
Decentralize marked data storage using cryptographic anchoring.
┌──────────────────────────────────────────────────────────────────┐
│ Anchor Layer │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Merkle Tree │ ──► │ Tree Root │ ──► │ Bitmark Anchor │ │
│ │ (marks) │ │ (hash) │ │ (OP_RETURN) │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │
├──────────────────────────────────────────────────────────────────┤
│ Distributed Storage │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ IPFS │ │ Nostr │ │ Arweave │ │ Local │ │
│ │ Nodes │ │ Relays │ │ (opt) │ │ Peers │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
// src/lib/merkle-anchor.ts
interface MarkMerkleTree {
// Each leaf is a mark receipt
leaves: MarkLeaf[]
// Tree structure
root: string // SHA-256 root hash
depth: number
// Proof generation
getProof(markId: string): MerkleProof
verifyProof(proof: MerkleProof): boolean
}
interface MarkLeaf {
markId: string // txid
fromPubkey: string
toPubkey: string
eventId?: string
amount: number
timestamp: number
hash: string // leaf hash
}
interface MerkleProof {
leaf: MarkLeaf
siblings: string[] // Sibling hashes
path: ('left' | 'right')[]
root: string
}// src/services/anchor.service.ts
interface AnchorService {
// Create anchor transaction
createAnchor(merkleRoot: string): Promise<AnchorTx>
// Verify anchor
verifyAnchor(txid: string, expectedRoot: string): Promise<boolean>
// Get anchor history
getAnchors(since: number): Promise<Anchor[]>
}
interface Anchor {
txid: string // Bitmark transaction ID
merkleRoot: string // Anchored root
blockHeight: number
timestamp: number
markCount: number // Number of marks in tree
}// src/services/distributed-storage.ts
interface DistributedStorage {
// Store mark tree
store(tree: MarkMerkleTree): Promise<StorageReceipt>
// Retrieve and verify
retrieve(root: string): Promise<MarkMerkleTree>
// Pin important content
pin(cid: string): Promise<void>
// Replicate across nodes
replicate(cid: string, nodes: string[]): Promise<void>
}
interface StorageReceipt {
cid: string // IPFS CID
nostrEventId: string // Backup on Nostr
replicaCount: number
anchorTxId?: string // If anchored
}// src/services/ipfs.service.ts
interface IPFSService {
// Add mark tree
addTree(tree: MarkMerkleTree): Promise<string> // Returns CID
// Get tree by CID
getTree(cid: string): Promise<MarkMerkleTree>
// Pin locally
pin(cid: string): Promise<void>
// Connect to peers
connectPeer(peerId: string): Promise<void>
}// Use Nostr relays as distributed backup
const MARK_TREE_KIND = 30078 // Replaceable event for tree
interface MarkTreeEvent {
kind: 30078
tags: [
['d', merkleRoot], // Unique identifier
['anchor', txid], // Bitmark anchor
['cid', ipfsCid], // IPFS location
['count', '1234'], // Mark count
]
content: JSON.stringify(compressedTree)
pubkey: aggregatorPubkey
}┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ User wants │ ──► │ Fetch proof │ ──► │ Verify against │
│ to verify │ │ from IPFS │ │ blockchain │
└─────────────┘ └─────────────┘ └─────────────────┘
│
┌──────────────────────────┘
▼
┌─────────────────┐
│ Proof valid! │
│ Mark is real │
└─────────────────┘
// Multiple aggregators can create trees
interface Aggregator {
pubkey: string
endpoint: string
reputation: number
lastAnchor: number
// Submit marks to aggregator
submit(marks: Mark[]): Promise<void>
// Get current tree
getCurrentTree(): Promise<MarkMerkleTree>
// Get proof for specific mark
getProof(markId: string): Promise<MerkleProof>
}- Merkle tree library
- Anchor service (Bitmark OP_RETURN)
- IPFS integration
- Nostr backup storage
- Proof generation/verification
- Aggregator protocol
- Verification UI
- Peer discovery
- Cross-aggregator sync
| Phase | Complexity | Dependencies |
|---|---|---|
| 1. Rebrand | Low | None |
| 2. Wallets | High | Phase 1, Bitmark node access |
| 3. Mark Users | Medium | Phase 2 |
| 4. Mark Posts | Medium | Phase 3 |
| 5. Store Content | Medium | Phase 4 |
| 6. Anchored Data | High | Phase 5, IPFS infrastructure |
- Bitmark full node (or light client)
- IPFS node (for Phase 6)
- Nostr relays (existing)
-
@noble/secp256k1- Key derivation -
merkletreejs- Merkle tree operations -
ipfs-http-client- IPFS integration -
idb- IndexedDB wrapper
- Bitmark RPC (JSON-RPC 1.0)
- IPFS HTTP Gateway
- Nostr WebSocket
- Key Management: Never store unencrypted private keys
- Transaction Signing: Sign transactions client-side only
- Proof Verification: Always verify Merkle proofs against blockchain
- Rate Limiting: Prevent mark spam with cooldowns
- Amount Limits: Set sensible min/max mark amounts
- Custodial vs non-custodial for MVP?
- Which Bitmark node to use as default?
- Aggregator incentive model?
- Mark-to-Nostr-Zap bridge?
- Mobile wallet integration (WalletConnect)?
Document Version: 1.0 Created: 2026-01-08