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.
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:
- It's public. Whatever you store, anyone can fetch by its hash.
- It expires. Bytes are kept for 100,800 blocks, which is 7 days. After that they're gone unless someone has called
renew. - Renewing is bookkeeping.
renewneeds 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. - It knows nothing about ownership. There's no owner, folder, name or permission, and a contract can't read the bytes.
- It can't be wrapped.
storeandrenewmust 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.
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.
A stored object's public proof page. Your browser runs the checks, not MemDot.
- 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.
- Upload something small. The upload sheet shows the fee and who pays before you confirm, and the tray follows it to finalized.
- Open the file and press Verify. Your browser fetches the bytes from a Portaldot node and hashes them.
- Open the registry. Your space and file appear there as records signed by your key, read from Portaldot's EVM RPC by your browser.
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
MemDotRegistrychecks 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 ishashBlake256(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.
| 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 |
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 |
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.
- 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 loginmakes 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:
memdothas 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.--agentanswers with one JSON envelope and distinct exit codes,help --jsondescribes every command, and anything that can't be undone asks first or needs--force. - Agent plugins:
integrationshas 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, thenclaude 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 athttps://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/sdkcovers accounts, spaces, folders, files, sharing, jobs, memories in both modes, export and import, and self-signed stores with a wallet. - Python SDK:
python/memdotis 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 samememdotcommand 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-sdkwraps any AI SDK model with memory in one line,@memdot/langchainadds middleware, tools and a LangGraph store, and@memdot/mastraadds 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-memdothas memories, encrypted files, and a node for the AI Agent's memory slot.zapier-memdothas a new-memory trigger, and add, update, delete, search and get. - Skills:
skillsteaches 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:
exampleshas 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
verifyWebhookin 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.
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, workerProduction images: apps/api/Dockerfile, apps/worker/Dockerfile and apps/web/Dockerfile, all built from the repo root.
| 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 |
- 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.chatare 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 codexadds 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
storethere, 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.