Skip to content
Blockchain-OraclePublic

About

Decentralized storage on Portaldot V3 for apps, files and AI-agent memory.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

MemDot. Encrypted storage on Portaldot that keeps itself alive.

MemDot

Encrypted storage on Portaldot that keeps itself alive.
Sealed on your device, stored with V3 TransactionStorage, renewed before its 7-day window ends, and shared on your terms.

Open the app  ·  See it live  ·  On-chain registry  ·  API reference

MemDot is a storage layer for Portaldot V3: a dashboard, a CLI, an MCP server, TypeScript and Python SDKs and a REST API that put files and app data on TransactionStorage, encrypted before they leave your device, and record who owns and may read them in a Solidity registry on Revive. A keeper renews everything before the chain's 7-day retention ends, so developers get storage that behaves like storage. Agent memory runs on the same layer: an agent remembers and recalls by meaning, and each memory is an encrypted entry on the chain, read either on your device or by a server you've chosen to trust with it.

Built for the Portaldot Hacker House 2026 · Modular Cloud & Middleware. Jump to how MemDot meets the hackathon.

The problem

Someone building on Portaldot needs to keep data: a user's documents, an app's state, what an agent learned yesterday. Today they have two options.

They can put it in a cloud bucket or their own database. Then the user can't take it elsewhere, can't prove what was stored, and has to trust the operator not to read it. The app is on-chain, but its data isn't.

Or they can use V3's TransactionStorage, which does hold bytes on-chain, and take on everything it leaves out:

  1. It's public. Whatever you store, anyone can fetch by its hash.
  2. It expires. Bytes are kept for 100,800 blocks, which is 7 days. After that they're gone unless someone has called renew.
  3. Renewing is bookkeeping. renew needs the exact block and index where the item lives now, and every renewal moves it, so you need a process that tracks each item and never misses a week.
  4. It knows nothing about ownership. There's no owner, folder, name or permission, and a contract can't read the bytes.
  5. It can't be wrapped. store and renew must be top-level extrinsics, so an EVM wallet or a contract can't call them on a user's behalf.

So nobody ships on it. The chain has a storage primitive, and developers still have nowhere to keep private data that they own and that lasts.

The idea

You sign in with a passkey or a Portaldot wallet, drop in a file, and your job ends there. MemDot encrypts it on your device with a key only you hold, stores the ciphertext on V3, shows you the block it landed in, renews it for you, and lets you share it with one address and take that back.

One journey, with real values. On live V3, the seed script behind this repo's evidence did what a new user does:

  • It created an account. The master key and a 12-word recovery code were made on the device; MemDot's servers received neither.
  • It stored three small files, a note, an agent's JSON and an SVG, packed together as one encrypted segment, so they cost one transaction and renew as one.
  • It stored a 115 KB image as a chunk plus a sealed manifest.
  • It renewed the segment: same CID, new location.
  • It shared the space with a second account, which opened the note on its own device.
  • It verified everything over its own connection to a Portaldot node, fetching each blob by CID and hashing the bytes, without asking MemDot.
  • Each step above was recorded in MemDotRegistry, signed by the owner's key.

Every transaction is linked under Proof.

Who can see what.

Anyone on-chain MemDot's servers Someone you shared with You
File contents and names Ciphertext Ciphertext Readable Readable
Your master key Never Never Never On your device only
Each blob's CID and padded size Public Yes Yes Yes
Who owns a space, and who it's shared with Public, as signed records Yes Yes Yes
A Private namespace's memories Ciphertext Ciphertext and ids Readable Readable
An Assisted namespace's memories Ciphertext Reads the text to extract and search; stores it only sealed, with keywords as keyed hashes Through MemDot Readable, with or without MemDot
An Assisted memory's search vector Never Yes. A vector can approximate the text it came from No Through MemDot
An Assisted memory's user, agent and run ids, metadata and categories, and its namespace's instructions Ciphertext Yes, so they can be filtered and used Through MemDot Yes
A connected agent's own key Its public half Its public half No On the machine it runs on

Revoking a share stops future access: the space moves to a new key epoch for new writes. What someone already decrypted can't be taken back, and MemDot says so in the dialog.

See it live

MemDot's public proof page for the demo segment: this browser fetched the bytes from a Portaldot node, the hash matches the CID, and the block records it.

A stored object's public proof page. Your browser runs the checks, not MemDot.

  1. Open the app and create an account with a passkey or wallet. Setup walks through the recovery code, a first space, an API key and a first file.
  2. Upload something small. The upload sheet shows the fee and who pays before you confirm, and the tray follows it to finalized.
  3. Open the file and press Verify. Your browser fetches the bytes from a Portaldot node and hashes them.
  4. Open the registry. Your space and file appear there as records signed by your key, read from Portaldot's EVM RPC by your browser.

How it works

Your device encrypts and sends ciphertext and signed records to MemDot's API. One worker submits stores, renewals and registry calls to Portaldot V3. Your device verifies directly against the chain.

Edit the Mermaid source

The device encrypts and uploads; MemDot stores on TransactionStorage; the device signs a record that MemDot submits to the registry; the keeper renews before the window ends; the device verifies directly.

Edit the Mermaid source

Choices that came from measuring V3, not from preference:

  • The registry keeps no state. Revive holds about 0.08 tPOT per byte of code and about 4 tPOT per stored word, while a log costs only the call's fee. So MemDotRegistry checks an sr25519 signature and emits one event per record. That took the deploy from 473 to 118 tPOT and makes a record cheap enough to write for every object. Ownership still holds: a space id is hashBlake256(owner, salt), so nobody can record into someone else's space.
  • One signer, one queue. Every relayer extrinsic goes through one pg-boss queue, so nonces never collide, and renewals outrank stores because a missed renewal loses data while a late store only waits.
  • The keyword index can't be read back. Assisted search ranks with BM25 over keyed hashes of each word, so the server finds words without keeping them. Every memory also lives in an encrypted segment on V3, so Restore can wipe the index and derive it again from the chain alone.
  • Small files share a segment. Storing costs about 105 tPOT per MiB plus 0.012 per transaction, and each item needs its own renewal, so files under 64 KiB uploaded together go into one encrypted blob.
  • Reads don't depend on us. Verify and the proof pages talk to a Portaldot node from your browser.

Built on Portaldot V3

Piece What MemDot does with it Where
TransactionStorage.store Holds every encrypted blob, as a top-level extrinsic packages/chain/src/storage.ts
TransactionStorage.renew The keeper moves each item forward before day 7 apps/worker/src/handlers/keeper.ts
bitswap_v1_get Reads bytes by CID, checked against the hash packages/chain/src/bitswap.ts
Revive EVM Runs the Solidity registry; records are real EVM transactions, so any Ethereum tool can read them packages/contracts/src/MemDotRegistry.sol
System precompile: sr25519Verify Checks the owner's signature on every record same contract
System precompile: hashBlake256 Derives space ids from their owner same contract
EVM RPC logs The indexer and the browser read records apps/web/lib/registry.ts
Portaldot Extension, Talisman, SubWallet, polkadot.js Sign-in, self-signed stores, and signVrf unlock packages/sdk/src/browser-wallet.ts

How MemDot meets the hackathon

Track: Modular Cloud & Middleware, "focusing on decentralized middleware, storage/data components, and APIs", to "enrich Portaldot's Web3 Cloud developer toolkit".

Criterion How MemDot meets it Check it
Technical Feasibility (30%): "whether core functions are completed, whether GitHub shows genuine and sustained development activity, and whether the project is deployed on Portaldot testnet with verifiable on-chain interactions" The store, share, renew and verify loop runs live on V3, deployed with its own contract Proof, the live app, commit history
Innovation (30%): "effective use of Portaldot V3.0 features such as WASM/EVM compatibility, modular architecture, and multi-chain capabilities, and whether the project delivers real value beyond existing solutions" Native TransactionStorage plus an EVM contract that verifies Substrate signatures through precompiles, with client-side encryption and automatic renewal on top MemDotRegistry.sol, who sees what
Ecosystem Contribution (20%): "Portaldot infrastructure, developer tools… open-source value" An Apache-2.0 SDK, REST API and dashboard that any Portaldot app can use, plus a public registry anyone can read packages/sdk, API reference
Business Potential (20%): "real market needs… target users and use cases" For app developers who need private user data on-chain and agent builders whose agents forget between sessions. Sponsored quotas let them start without tokens; self-signed spaces let users pay their own way Plans & usage

Proof

MemDotRegistry on Portaldot V3 testnet (chain ID 420420777): 0x24ee3a18c64954d979b7475ccdf58ce4750fb5d1. The contract's records are public at /proof/registry.

What happened Transaction Block Check it
Registry deployed 0xa5361222…f890d6 #279,265 0x24ee3a18c64954d979b7475ccdf58ce4750fb5d1
Three files stored in one segment 0x08d10550…0d4ae1 #279,559 bafk2bzacea2…mxzjiw
Larger file stored (chunk + sealed manifest) 0xf2db5b24…658b3a #279,566 bafk2bzacedl…yafdh2
Segment renewed: same CID, moved from block 279,559 0x1955b32e…0da189 #280,474 bafk2bzacea2…mxzjiw
File signed and paid for by the user's own wallet 0x393269a9…36c692 #282,189 bafk2bzacebi…ju34cq
Larger file signed and paid for by the same wallet 0xbf696ed3…782d00 #282,199 bafk2bzacedt…bxrw3s
An agent memory stored as an encrypted segment, written by MemDot's server 0xb079c463…16e440 #290,108 bafk2bzacebd…f3ammk
A Private namespace's search index, sealed on the device 0xd2ad4b7a…90b40f #290,146 bafk2bzaceby…i65jze
Facts a model distilled from a conversation, sealed and stored by the live service 0xb963441b…0d1f99 #292,294 bafk2bzaceac…5b4xty
A space's keys wrapped to a connected agent's own key 0x4e47b19c…b89289 #293,223 bafk2bzaceay…5eqt3c
A Private memory written by that agent, with that key 0x77e333a6…90e2cc #293,226 bafk2bzacecr…pwyx6o
A Private memory written from Python, which the TypeScript SDK then opened 0x4eb6868b…7ad0c6 #294,911 bafk2bzacecm…wyzpea
A coding session's memories, saved by the agent plugin's hooks 0xd0ff8e0d…39d4ff #303,700 bafk2bzacebz…dwekr2
A file encrypted inside an n8n workflow, read back and verified there 0xf1d1dacc…586324 #335,736 bafk2bzacedf…wamcn4
Registry record: Space created 0x43dc7871…a86cf0 #279,570 view
Registry record: Object recorded 0x9cd18679…867ed8 #279,571 view
Registry record: Object recorded 0x7c18bd1c…0387b7 #279,572 view
Registry record: Space shared 0x52e8b74d…87d9c5 #279,580 view

Recorded by docs/evidence/s07-seed-2026-11-01.json, docs/evidence/s07-self-signed-2026-11-01.json, docs/evidence/s08-seed-2026-11-02.json, docs/evidence/s08-live-2026-11-02.json, docs/evidence/s09-agents-2026-11-02.json and docs/evidence/s10-python-2026-11-02.json, docs/evidence/s11-plugin-2026-11-03.json. Registry transaction hashes are the EVM hashes Revive's RPC reports.

Earlier runs are in docs/evidence/: the first store and renew round trip, the service walk and the SDK journey. s13-pro-features-2026-11-05.json records the Playground's chat, the graph, decay, profiles, Dream, exports and Copilot on the live service in both modes, and signed webhooks reaching a receiver on a public address: 56 checks, from pnpm --filter @memdot/sdk journey:pro.

What's in the product today

  • Dashboard: passkey and wallet sign-in, recovery codes, spaces paid for by MemDot or by your own wallet, files with preview, versions, Verify and Renew now, sharing and revoking, API keys with roles, activity, analytics, webhooks, renewals, usage, settings, and a command palette, on desktop and phone, in light and dark.
  • Agent memory: namespaces in two modes. Private extracts, embeds and searches on your device, with your own model key if you want extraction at all. Assisted does it on the server, for agents that only hold an API key. Both offer remember (distilled by a model or stored as written, with pictures and documents in Assisted), search by meaning and keywords with filters that nest AND, OR and NOT, update with history, forget, users, agents and runs, custom instructions and categories, restore from the chain, and importing another memory product's export. A file in a memory folder teaches its namespace, and deleting the file forgets what it taught.
  • Working with memories: in either mode, and on your device with your own model in Private.
    • Playground: chat with a namespace, beside what it recalled and what it kept.
    • Copilot: ask about your memories and settings. It reads and suggests, shows each change as a before and after, and only your click applies it.
    • Dream: have a model look for what's out of date, said twice or worth summing up, then apply what you agree with. It never combines one person's memories with another's.
    • Graph: who and what your memories mention, and how those connect, with a list view of the same.
    • Profiles: a generated summary of each person, sealed before it's saved, and removed if a memory it rests on is forgotten.
    • Decay: have search lean towards what's in use. It only reorders, so nothing is hidden, and turning it off brings the old order back.
    • Exports: JSON, CSV, or any JSON schema filled in by a model.
  • Agents: memdot login makes a key pair on your machine and opens your browser. You type the code your terminal shows and choose which spaces it can reach, your browser wraps those spaces' keys to its public key, and it collects an API key only it can open. Revoke it under Agents and its next call fails. Hosted MCP clients sign in with OAuth instead, and reach Assisted namespaces only, because they hold no key.
  • CLI: memdot has memory verbs (add, search, list, get, update, delete, history, import, export, restore) and groups for files, spaces, folders, namespaces, entities, events, jobs, agents, keys, usage and logs. --agent answers with one JSON envelope and distinct exit codes, help --json describes every command, and anything that can't be undone asks first or needs --force.
  • Agent plugins: integrations has plugins for Claude Code, Codex, Cursor, Kimi Code, Antigravity, OpenCode, Pi, OpenClaw, DeepSeek Harness and Hermes, plus a portable Agent Plugins package, all generated from one core. They recall what's relevant at the start of work, note what happened with secrets removed and no file contents, save in the background, and bring the MCP tools and skills like /memdot:search. In Claude Code: claude plugin marketplace add Blockchain-Oracle/memdot, then claude plugin install memdot@memdot-plugins.
  • MCP: run it locally with memdot mcp, which decrypts on your machine and reaches Private namespaces and files, or connect any client to the hosted server at https://api.84.46.247.92.sslip.io/mcp.
  • Data: export everything as a decrypted zip and bring it into a new account, or delete the account, which stops every renewal.
  • TypeScript SDK: packages/sdk covers accounts, spaces, folders, files, sharing, jobs, memories in both modes, export and import, and self-signed stores with a wallet.
  • Python SDK: python/memdot is the same client, sync and async, sealing with the same formats, so a file uploaded in the dashboard opens in Python and the other way round. It runs Private memory on your machine with the same embedding model, has the same memdot command and local MCP server, and plugs into LangGraph, CrewAI, Strands and the OpenAI Agents SDK. Tests hold each language to what the other sealed.
  • Framework adapters: @memdot/ai-sdk wraps any AI SDK model with memory in one line, @memdot/langchain adds middleware, tools and a LangGraph store, and @memdot/mastra adds a memory processor and tools. They recall before the model answers, framing memories as notes rather than instructions, save what each exchange taught, and never fail a model call over a memory problem.
  • Automation: n8n-nodes-memdot has memories, encrypted files, and a node for the AI Agent's memory slot. zapier-memdot has a new-memory trigger, and add, update, delete, search and get.
  • Skills: skills teaches your coding agent to build with MemDot, wire it into a project with a failing test first, check an integration with a scorecard, or move off Mem0: npx skills add Blockchain-Oracle/memdot --skill memdot.
  • Examples: examples has a chat with a memory panel, an encrypted file vault and private notes that run entirely in the browser, agents sharing a memory in Python, the same memory in three frameworks, MCP client configs and a CLI cookbook.
  • REST API: OpenAPI 3.1 and a reference, with API keys and per-key rate limits, plus /v1/chat/completions: point any OpenAI client at it, and each call recalls what's relevant and remembers the exchange.
  • Webhooks: tell another service when memories and files change. Deliveries are signed, carry ids and event types only, and retry for about two hours, with a delivery log and verifyWebhook in the SDK.
  • Analytics: requests, failures, latency and memories over 30 days, and top users, agents and categories, from request metadata and counts, never what anything says.
  • Service: relayer and keeper on one signer queue, a chain indexer, a CID gateway with a ciphertext cache, a low-balance breaker that pauses stores and never renewals, and alerts.
  • Public proof pages: one for each CID, transaction and block, plus the registry.

Run it locally

pnpm install
docker compose -f deploy/compose.dev.yml up -d
cp .env.example .env              # then fill in the secret and a funded testnet mnemonic
pnpm --filter @memdot/chain keygen # prints a fresh relayer account to fund
pnpm dev                          # web on :3100, API on :8787, worker

Production images: apps/api/Dockerfile, apps/worker/Dockerfile and apps/web/Dockerfile, all built from the repo root.

Repository layout

Path What it is
apps/web Next.js dashboard and public proof pages
apps/api Hono REST API, auth and the CID gateway
apps/worker Relayer queue, keeper, indexer and alerts
packages/sdk The TypeScript SDK
python/memdot The Python SDK, CLI, MCP server and framework integrations
packages/cli memdot, the command line
packages/mcp The MCP server, local and hosted
integrations Agent plugins, where agent-plugin-core is the source and the plugin folders are generated from it, plus the n8n node and the Zapier app
packages/ai-sdk, packages/langchain, packages/mastra Framework adapters
skills Installable skills for coding agents
examples Runnable examples, each with its own README
packages/crypto Keys, key bundle, encryption, manifests and segments
packages/chain Portaldot V3 client: store, renew, read and contract calls
packages/contracts MemDotRegistry, its tests and deployment record
packages/memory-engine Extraction, ranking, filters and the on-device index, used by both modes
packages/memory-server Assisted memory's keys, index and how memories reach the chain
packages/db, packages/jobs, packages/config, packages/ui Schema, queue, constants, and the design system

Honest limits

  • Testnet only. Everything here runs on Portaldot V3 testnet, and the app is on a temporary address until its own domain is live. Accounts made on this address will need their recovery code after the move, because passkeys are tied to a domain.
  • Sponsored storage is small. The relayer pays about 105 tPOT per MiB from a testnet budget of a few hundred tPOT, so a sponsored space holds 128 KiB, up to 5 spaces per account. Stores pause if the relayer runs low; renewals keep going. A space can move to your own wallet at any point: it then has no quota, and the wallet pays for each store.
  • Real extensions still being walked. Wallet sign-in, MetaMask sign-in and wallet-paid stores ran in the dashboard on a local build against live V3, using test wallets that sign the way an extension does. Touch ID passkey unlock and Talisman or SubWallet unlock need a real authenticator and extension, and haven't been walked on the live site yet. The recovery code works everywhere.
  • Not built yet: the docs site. It's next in the build plan.
  • Python has the core, not the newest tools. Graph, profiles, Dream, Copilot, webhooks, analytics and memories.chat are in TypeScript and the dashboard. From Python, the REST API reaches them for Assisted namespaces, and Private needs TypeScript or the dashboard.
  • Private leans on your model and your device. Its profiles, Dream, Copilot and Playground need a model of your own, since MemDot can't read that text. Decay counts from a memory's last change there, because what you read stays on your device, and analytics can count Private memories but not name their users or categories.
  • Not on npm or PyPI yet. The CLI and the n8n node install from the site, Python installs from git, and the TypeScript packages and examples run from a clone of this repository.
  • The Zapier app hasn't met Zapier. Every action runs against the live service through Zapier's own tool, but it hasn't been pushed to a Zapier account or run inside a real Zap.
  • n8n and Zapier reach Assisted namespaces only. n8n has nowhere to run the local search model, and a Zap runs on Zapier's servers. n8n also does files, encrypted inside your own n8n with an agent identity you give it. Zapier doesn't, because Zapier's servers would have to hold that key. The n8n node needs self-hosted n8n, since n8n Cloud only installs verified nodes.
  • Not every plugin has met its host in a full session. On Codex, Kimi Code, OpenCode, Pi and OpenClaw, a real session answered from a fact seeded in an earlier one. On Claude Code the plugin installs from this repository's marketplace and its real hooks ran, but no signed-in session has answered from them yet; the same hooks were driven through whole sessions by a script instead, in both memory modes. Hermes loads the plugin and runs real turns through it as its memory provider, though its one-shot mode scopes itself to its own install folder, so recall in the right repository was only shown by calling the provider directly. DeepSeek Harness mounts the plugin and reaches prompt assembly, and needs a DeepSeek key to go further, so its session handling hasn't been through a full turn, and it doesn't note tool calls yet. Cursor and Antigravity are checked against their documented formats only: Cursor's CLI needs a sign-in before it runs anything, and Antigravity wasn't installed.
  • Codex takes one extra step. Its CLI runs hooks from its own config, not from plugins, so setup codex adds them there, and you trust them once in /hooks.
  • Plugins recall on the first prompt of a session, not every prompt. Each save costs a model call and a chain write, so they save every 10 turns and at the end of a session, not constantly, and a very short session saves nothing.
  • Secret redaction is pattern matching. It catches known key formats, named secrets, command-line flags, addresses with passwords, seed phrases and 32-byte hex values before the plugin writes or sends anything, which also removes transaction hashes from notes. A secret in an unusual shape can still get through, and what a model saves by calling a memory tool directly is stored as sent, so don't paste a secret into a session you wouldn't want remembered.
  • A session's notes sit on your machine for a while. Until they're saved, and for a day after a session goes quiet, its prompts, answers and what it recalled are in ~/.memdot/plugin, readable only by you, with secrets removed. A save that keeps failing stays there for up to two weeks.
  • Python can't write to a self-signed space. A wallet signs each store there, with Portaldot's runtime types, which the Python package doesn't carry. It reads those spaces; writing them takes the dashboard or the TypeScript SDK. Passkeys and browser wallets stay in the browser too, so Python signs in with the recovery code or a key.
  • An agent's reach is per space, not per namespace. A space's key opens everything in it, so an agent you connect can read that space's files and namespaces. Keep what it shouldn't see in another space.
  • A hosted agent reaches Assisted namespaces only, and its names for them are the ones your browser gave it when you connected it.
  • Revoking an agent stops its keys, not what it read. It can't call MemDot any more and it never gets the space's next key, but anything it already decrypted may have been kept. Other agents in that space get the new key the next time you open MemDot, and can't read what's written after the change until then.
  • A connected agent trusts MemDot for its first keys. The owner's browser seals a space's keys to the agent, but the agent can't yet prove who sealed what it receives, so a compromised server could hand it a keyring of its own and read what that agent writes next. Your own devices don't have this problem: they derive their keys, and they only wrap keys for a share they made themselves. Sharing with someone also takes MemDot's word for what their sharing key is.
  • What an agent stores isn't in the public registry right away. Records take the owner's signature. Memory segments it wrote are recorded the next time you unlock; files it stored aren't recorded yet, though they're on chain and verifiable.
  • The live service's shared model is on a free tier. Each account gets 10 model calls and 100 embedded searches a day on it, and every account shares the key's daily quota. Past an account's allowance, a write that asks for a model is refused with MODEL_LIMIT, writes that don't still work, and search falls back to keywords. If the shared key itself runs out, new memories are stored as written. An account's own key, set under Configuration, has no limit.
  • Assisted means trusting the server with the text. It reads what you send, and it can open what it stored: its keys derive from one secret in the service's environment. The database alone opens nothing, and you can read every Assisted memory without MemDot, but use Private for anything MemDot shouldn't see.
  • Forgetting is immediate in search, slower on chain. A forgotten memory leaves search and lists at once. Its encrypted entry stays in its segment until the segment is rewritten or expires, and anything already read can't be recalled.
  • A memory can be poisoned. Extraction ignores instructions planted in a conversation, and recalled text is marked as data for whatever model reads it next, but a model can still be fooled. Treat memories as notes, not commands.
  • Rebuilding from the chain is partial. Stored items and renewals rebuild from chain events. Rebuilding spaces and objects from registry records is not written yet, though the records are on-chain.
  • A record follows its upload. Registry records are submitted after the store they describe and never block it, so one can lag or, if the relayer fails, be missing. Stored bytes are unaffected.
  • Content checks happen in the client. A contract can't read TransactionStorage, so the registry vouches for who signed a record, not for the bytes. Verify checks the bytes.
  • Not yet audited, and the keeper runs on one server.

Licence

Apache-2.0.

About

Decentralized storage on Portaldot V3 for apps, files and AI-agent memory.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages