A decentralized dataset and AI model marketplace on Aptos.
Stash is a wallet-native marketplace for publishing, buying, and accessing datasets or model artifacts without centralized custody. Files and metadata live on Shelby decentralized hot storage, while ownership, payments, revenue, and access control are enforced on Aptos.
AI datasets and model artifacts are valuable assets. Most platforms still depend on centralized hosting, account-level permissions, platform fees, and opaque takedown rules. Stash takes a different route:
- Creators own the listing through Aptos accounts and Move resources.
- Files stay decentralized through Shelby hot storage.
- Access policies are enforced on-chain for public, creator-approved, and paid listings.
- Paid listings settle in APT or ShelbyUSD with a 2.5% protocol fee encoded in Move.
- APT proceeds accrue in escrow for creator claims; ShelbyUSD settles directly after the protocol fee is applied.
- The UI is built for trust: clear wallet states, transaction stages, and real loading/error states.
Stash is live as a Shelbinet review build. It is suitable for test-network evaluation, not mainnet production use.
| Area | Status |
|---|---|
| Frontend shell | Built |
| Wallet connection | Working with Petra |
| Upload flow UI | Built |
| Shelby SDK browser integration | Integrated |
| Move contracts | Deployed on Shelbinet |
| Marketplace pages | Built |
| Dashboard pages | Built |
| Creator upload/listing E2E | Verified on Shelbinet |
| Paid APT purchase/indexer E2E | Verified on Shelbinet |
| Public, gated, and ShelbyUSD flows | Functional E2E verified in the current wallet-bound recovery model |
| Wallet-bound encrypted key recovery | Verified by the creator across four isolated browser contexts for a new protected dataset |
| Key lifecycle hardening | Live on Shelbynet; creator-confirmed rotation/revocation E2E evidence is recorded, while independent audit remains pending |
| Review deployment | Live |
Current review boundary: new protected datasets carry wallet-encrypted recovery material. The creator confirmed recovery and delivery across four isolated browser contexts, using the same creator and buyer wallet accounts in fresh contexts. Datasets created before this change may not contain a recoverable backup and should be republished before relying on cross-browser recovery. A representative application-level transaction set is recorded in the Shelbinet review evidence; internal Shelby blob transactions are intentionally omitted from the concise submission set.
| Limitation | Owner | Intended resolution |
|---|---|---|
| Gated and paid downloads require creator approval or key delivery after the buyer request or purchase. | Stash protocol team | Define and implement an explicit delivery workflow before mainnet. |
| Cross-browser recovery requires the same wallet account and a wallet message signature; changing accounts cannot unlock the backup. | Stash protocol team | Add explicit recovery UX and account-change handling; the initial four-context E2E has been confirmed. |
| Legacy protected datasets created before wallet-backed backups were added may not contain portable recovery material. | Dataset creator | Republish those datasets with the current upload flow before clearing the publishing browser. |
| Key rotation and revocation cannot recall plaintext or files already downloaded before the state change. | Stash protocol team | Keep the lifecycle evidence and explain this boundary during review; complete an independent security audit before mainnet. |
| The contracts and frontend have not undergone an independent security audit. | Stash protocol team | Obtain independent Move and application security review before mainnet. |
For the exact Shelbinet addresses, transaction evidence, verification commands, and current review boundary, see the review evidence.
flowchart LR
creator[Creator Wallet] --> ui[Stash React dApp]
buyer[Buyer Wallet] --> ui
ui --> wallet[Aptos Wallet Adapter]
wallet --> petra[Petra / Aptos Wallets]
ui --> shelby[Shelby SDK]
shelby --> hot[Decentralized Hot Storage]
ui --> aptos[Aptos Fullnode]
aptos --> move[Move Contracts]
move --> marketplace[marketplace.move]
move --> payment[payment.move]
move --> access[access.move]
move --> indexer[Aptos Indexer GraphQL]
indexer --> ui
Shelby stores encrypted dataset payloads and metadata manifests. Stash keeps a storage identifier on-chain, not the raw file.
Move modules define the marketplace behavior:
| Module | Responsibility |
|---|---|
marketplace.move |
Listing lifecycle plus Public, Gated, and Paid policies |
payment.move |
APT escrow/claims, direct ShelbyUSD settlement, and 2.5% protocol fee |
access.move |
Access requests, grants, verification, and buyer-specific key envelopes |
access.move keeps the deployed V2 resource layout intact and stores lifecycle state in a separate AccessV3Store. rotate_buyer_key invalidates the previous envelope and requires a new creator delivery. revoke_access removes the current envelope and blocks future access checks. These controls cannot recall plaintext that a buyer already downloaded.
Aptos Indexer GraphQL is used for marketplace search, dataset pages, dashboard analytics, and event-driven state.
sequenceDiagram
actor Creator
participant UI as Stash dApp
participant Shelby as Shelby Storage
participant Wallet as Petra Wallet
participant Aptos as Aptos Move Contracts
participant Indexer as Aptos Indexer
Creator->>UI: Select files and metadata
UI->>UI: Encrypt gated or paid payloads
UI->>Shelby: Prepare blob commitments
UI->>Wallet: Request Shelby registration signature
Wallet->>Aptos: Submit blob registration
UI->>Shelby: Upload encrypted blob and manifest
UI->>Wallet: Request listing transaction
Wallet->>Aptos: create_listing_v2()
Aptos->>Indexer: Emit listing events
Indexer->>UI: Listing appears in marketplace
sequenceDiagram
actor Buyer
participant UI as Stash dApp
participant Wallet as Petra Wallet
participant Aptos as Payment + Access Contracts
participant Shelby as Shelby Storage
Buyer->>UI: Open dataset detail
UI->>Wallet: Submit request or purchase with buyer public key
UI->>Wallet: Sign recovery message for encrypted buyer-key backup
Wallet->>Aptos: request_access() or purchase_v2()
Aptos->>Aptos: Record request or paid access
Creator->>Aptos: Publish buyer-specific key envelope
UI->>Aptos: Read access and delivery state
UI->>Wallet: Sign recovery message when restoring the buyer key
UI->>Shelby: Download encrypted file
UI->>UI: Decrypt with the recovered buyer key
| Layer | Technology |
|---|---|
| Frontend | Vite, React, TypeScript |
| Styling | Tailwind CSS, custom design tokens |
| Motion | Framer Motion, GSAP |
| Wallet | Aptos Wallet Adapter, Petra |
| Blockchain | Aptos, Move |
| Storage | Shelby SDK |
| Indexing | Aptos Indexer GraphQL |
| Build | Vite production build with Shelby WASM handling |
| Route | Purpose |
|---|---|
/ |
Landing page and protocol overview |
/marketplace |
Dataset discovery, filters, sorting, indexer-backed grid |
/upload |
Creator upload and publish flow |
/dataset/:id |
Dataset detail, purchase, access, download |
/dashboard |
Creator listings, revenue, transactions, analytics |
/purchases |
Buyer library with purchased and approved datasets, delivery status, and return links |
| Mode | Storage | On-chain behavior | Delivery |
|---|---|---|---|
| Public | Plain Shelby blob | Active listing is immediately accessible | Direct download without a wallet |
| Gated | AES-GCM encrypted | Buyer requests access; creator approves | Creator publishes a key envelope encrypted to the buyer; the buyer key backup is wallet-bound |
| Paid / APT | AES-GCM encrypted | One-time APT purchase; creator proceeds accrue in escrow | Creator publishes the buyer key envelope; creator claims APT revenue; the buyer key backup is wallet-bound |
| Paid / ShelbyUSD | AES-GCM encrypted | One-time ShelbyUSD purchase; 97.5% creator and 2.5% treasury settle atomically | Creator publishes the buyer key envelope; no revenue claim is required; the buyer key backup is wallet-bound |
- Node.js 20.19.0 or newer
- npm
- Aptos CLI wrapper (installed in the Move validation section)
- Petra wallet for browser E2E testing
npm cinpm run devnpm run typechecknpm run buildThe repository includes a Playwright smoke suite for the route shell. It verifies that the landing page boots, direct navigation to the main routes does not produce a blank screen, and the upload flow does not overflow a mobile viewport. Wallet-extension transactions and live Shelby data remain manual E2E checks because they require a real wallet and review-network state.
Install the browser once, then run the suite:
npx playwright install chromium
npm run test:e2eInstall the exact Aptos CLI wrapper used by CI before running contract commands:
npm install --no-save @aptos-labs/aptos-cli@3.0.0
npx aptos --install
npx aptos move compile --dev --skip-fetch-latest-git-deps
npx aptos move test --dev --skip-fetch-latest-git-depsThe current Shelbynet review package predates AccessV3Store. After publishing a compatible package upgrade from the real deployer profile, initialize the new resource once:
.\scripts\initialize-shelbynet-v3.ps1 -Address 0xYOUR_PACKAGE_ADDRESS -Profile shelbynetDo not run the initializer against the old package or a development address. The command is intentionally separate from publish-shelbynet.ps1 because an existing package must be upgraded first and the initializer is one-time state setup.
Create .env.local for local dApp configuration.
VITE_APTOS_NETWORK=shelbynet
VITE_APTOS_FULLNODE_URL=https://api.shelbynet.shelby.xyz/v1
VITE_APTOS_INDEXER_URL=https://api.shelbynet.shelby.xyz/v1/graphql
VITE_SHELBY_RPC_URL=https://api.shelbynet.shelby.xyz/shelby
VITE_STASH_MODULE_ADDRESS=0x2d82e8802ab2a3fcce32df2f663a05efcbba9ae00d755b76d242dffb087a4a83
# Optional, depending on network/provider requirements
VITE_APTOS_API_KEY=
VITE_SHELBY_API_KEY=Supported network names in the frontend:
shelbynettestnetdevnetmainnetlocal
When no network is configured, Stash defaults to shelbynet.
The Shelby SDK depends on browser-compatible polyfills and a WASM asset for erasure coding. This repo includes handling for:
Bufferandprocessbrowser compatibility- Shelby
clay.wasmserving through Vite middleware - Vite dependency optimization exclusions for Shelby packages
- Upload cancellation and non-retryable wallet rejection handling
- Bounded retry handling for transient Aptos/Shelby
429and5xxresponses
Stash treats transaction and storage flows as first-class UX:
- Wallet rejection must stop the flow immediately.
- Validation/config errors must not retry silently.
- Aptos and Shelby transient
429/5xxerrors retry with bounded backoff andRetry-Aftersupport. - React Query must not add a second retry layer on top of transport retries.
- Upload state must be cancellable and resettable.
- Public UI should not present mock data as real marketplace state.
sources/
marketplace.move Move listing module
payment.move Move purchase and revenue module
access.move Move access-control module
src/
components/ Shared UI, layout, wallet, tx components
hooks/ dApp hooks for Shelby and marketplace actions
lib/ Aptos, Shelby, network, format, wallet utilities
pages/ Landing, marketplace, upload, detail, dashboard
styles/ Design tokens and global styling
Before public release:
- Deploy Move modules to the target Aptos network.
- Set
VITE_STASH_MODULE_ADDRESSto the deployed package address. - Verify Shelby upload on the target network with a small file.
- Confirm Aptos Indexer events for listing, purchase, access, and claims.
- Run creator E2E: upload, register listing, view listing.
- Run buyer E2E: purchase, verify access, download/decrypt.
- Run production build and route smoke checks.
- Verify no fallback/mock data appears as public marketplace truth.
This repository is proprietary and provided for technical review and evaluation. No license is granted to copy, modify, distribute, sublicense, or use the code without written permission from the project owner. See LICENSE.
Stash is built for data ownership, on-chain access, and decentralized distribution.
The Stash Move package is deployed on Shelbinet.
| Item | Value |
|---|---|
| Network | shelbynet |
| Package address | 0x2d82e8802ab2a3fcce32df2f663a05efcbba9ae00d755b76d242dffb087a4a83 |
| Publish transaction | 0x005794789e05b80ae3dbb91bd0027180bd4f754b633fa3b82e9c8ad5faf7153c |
| Access/payment v2 upgrade | 0x5ac5dd1027bcedebb640aee9fe20193aef777ac5677e2d21cd4ce18e2ee7032a |
| Compatible V3 package upgrade | 0x6e7858299788cd5d2a9771dc78636a4e1216ca9ab70930452380b32270c6b817 |
| Access V3 initializer | 0x3243367ff49ae0af24d9e561be3afeacf170485de6062c9f12a572b04548acfb |
| ShelbyUSD metadata | 0x1b18363a9f1fe5e6ebf247daba5cc1c18052bb232efdc4c50f556053922d98e1 |
| Review app | https://stash-stash2.vercel.app/ |
| Fullnode | https://api.shelbynet.shelby.xyz/v1 |
| Indexer | https://api.shelbynet.shelby.xyz/v1/graphql |
| Shelby RPC | https://api.shelbynet.shelby.xyz/shelby |
The frontend includes this package address as the default Shelbinet module address. VITE_STASH_MODULE_ADDRESS can still override it for future redeployments.
Stash targets Shelbinet by default and includes the current package address. Browser upload, listing creation, paid APT purchase, indexer materialization, and dashboard analytics have been exercised on Shelbinet. The connected wallet still needs the network assets and permissions required by Shelby's active early-access policy.
Use the deploy wallet you want to own the Stash Move package. Do not commit private keys.
npx aptos init --network custom `
--rest-url https://api.shelbynet.shelby.xyz/v1 `
--skip-faucet `
--profile shelbynetIf you already have the deploy key, initialize with --private-key or --private-key-file. The account must have enough gas on Shelbinet before publishing.
Replace <deployer-address> with the account address from the shelbynet profile.
.\scripts\publish-shelbynet.ps1 -Address <deployer-address> -Profile shelbynetThe script compiles the Move package with stash=<deployer-address>, runs Move tests, then publishes against the Shelbinet fullnode.
Create .env.local locally and mirror the same values in the hosting provider.
VITE_APTOS_NETWORK=shelbynet
VITE_APTOS_FULLNODE_URL=https://api.shelbynet.shelby.xyz/v1
VITE_APTOS_INDEXER_URL=https://api.shelbynet.shelby.xyz/v1/graphql
VITE_SHELBY_RPC_URL=https://api.shelbynet.shelby.xyz/shelby
VITE_STASH_MODULE_ADDRESS=0x2d82e8802ab2a3fcce32df2f663a05efcbba9ae00d755b76d242dffb087a4a83
VITE_SHELBY_API_KEY=
VITE_APTOS_API_KEY=VITE_STASH_MODULE_ADDRESS is mandatory for marketplace, dashboard, upload publish, purchase, claim revenue, and access verification. 0xcafe is intentionally rejected by the frontend because it is only the dev-test address from Move.toml.
A real Shelbinet upload was completed with the Shelby CLI using the deployed package wallet.
| Item | Value |
|---|---|
| CLI context | shelbynet |
| Write location | shelbynet-1 |
| Upload transaction | 0x9fcd01ba1721359c22543a7bb00a06b214c45e6b93afb0c3e1d3928ca9fdc8be |
| Explorer | https://explorer.shelby.xyz/shelbynet/account/0x2d82e8802ab2a3fcce32df2f663a05efcbba9ae00d755b76d242dffb087a4a83 |
If browser upload fails before the Petra signature, confirm the connected wallet has ShelbyUSD and a Shelby write location. The CLI validation used shelbynet-1 explicitly.
.\scripts\check-shelbynet.ps1
npm.cmd run buildManual E2E checklist:
- Connect Petra on Shelbinet.
- Upload a small file from
/upload. - Confirm the Shelby blob registration popup appears.
- Confirm the Stash listing registration popup appears.
- Confirm the listing appears in
/marketplacefrom Aptos Indexer events. - Purchase from another wallet.
- Confirm
access::verify_accessreturnstrue. - Download and decrypt the Shelby file.
Current delivery note: buyer-specific key wrapping is implemented on-chain. New protected uploads also store encrypted recovery material: the creator backup is embedded in the Shelby manifest, while the buyer backup is carried in the existing on-chain buyer-key field. Recovery requires a signature from the same wallet account and never stores the wallet signature itself. The creator still approves or delivers each protected request manually. The buyer library at /purchases is derived from indexed purchase, access, and delivery events. Cross-browser recovery for a new protected dataset was creator-confirmed across four isolated browser contexts; legacy datasets without a backup remain device-bound.