Sanora Club is a miniservice for hosting products, and eventually hooking into spell paths. The goal is to be so lightweight that people can create a product from a single curl. And to have those products be shareable around cyberspace via teleportation.
There are a plethora of web-based solutions for creating product pages, and taking payments. Many of them are just fine if you don't mind handing over 15% or more of your sale to the platform. Of course you're paying for a bunch of features you're probably not going to use, and the marketing you have to do has nothing to do with this platform you're using.
Instead of making and maintaining a bunch of features, Sanora Club gives you the absolute minimum you need to host and sell a product online. Then by leveraging the Planet Nine ecosystem, others can build features that you can add, ideally at a discount from those other platforms. Or not, and you keep more loot.
Sanora Club needs two things from you in order to work: a product, and a way to pay you.
A minimalist approach to product display is a title, description, price and image. Since that's really all we'll be providing, and you may want to provide a landing page for people who either don't have a MAGIC extension, or don't want to purchase right away, there's also an optional redirect url.
And that's it. Five things:
- title
- description
- price
- image
- redirect url (optional)
We're gonna start with Stripe to begin with. You attach your stripe to your Sanora account, and then get paid when people buy your product.
Stripe has all sorts of drop-in uis for this that we'll use, but you can do it via the command line too.
So you create a user with your public key. And then you can start putting products.
PUT
/user/create
Creates a new user if pubKey does not exist, and returns existing uuid if it does.
name required data type description pubKey true string (hex) the publicKey of the user's keypair timestamp true string in a production system timestamps narrow window for replay attacks signature true string (signature) the signature from sessionless for the message
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
curl -X PUT -H "Content-Type: application/json" -d '{"pubKey": "key", "timestamp": "now", "signature": "sig"}' https://<placeholderURL>/user/create
PUT
/user/:uuid/processor/:processor
Creates an account token for a processor
name required data type description name true string the user's name true string the user's email timestamp true string in a production system timestamps narrow window for replay attacks signature true string (signature) the signature from sessionless for the message
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
curl -X PUT -H "Content-Type: application/json" -d '{"name": "name", "email": "[email protected]", "timestamp": "now", "signature": "sig"}' https://<placeholderURL>/user/<uuid>/processor/<processor>
PUT
/user/:uuid/product/:title
Creates or updates the product with the put title
name required data type description title true string the title of the product description true string the description of the product price true string the price of the product redirectURL false string an optional redirect url timestamp true string in a production system timestamps narrow window for replay attacks signature true string (signature) the signature from sessionless for the message
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
curl -X PUT -H "Content-Type: application/json" -d '{"pubKey": "key", "timestamp": "now", "signature": "sig"}' https://<placeholderURL>/user/create
PUT
/user/:uuid/product/:title/artifact
Puts an image for the product with the given title
name required data type description x-pn-artifact-type true epub/pdf/md/etc artifact type x-pn-timestamp true string in a production system timestamps narrow window for replay attacks x-pn-signature true string (signature) the signature from sessionless for the message
name required data type description artifact true artifact type the artifact to upload
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
TODO
PUT
/user/:uuid/product/:title/image/
Puts an image for the product with the given title
name required data type description x-pn-timestamp true string in a production system timestamps narrow window for replay attacks x-pn-signature true string (signature) the signature from sessionless for the message
name required data type description image true jpg/png the image for the product timestamp true string in a production system timestamps narrow window for replay attacks signature true string (signature) the signature from sessionless for the message
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
TODO
PUT
/user/:uuid/orders
Puts an image for the product with the given title
name required data type description timestamp true string in a production system timestamps narrow window for replay attacks order true string the order object to store. Can contain any data you'd like signature true string (signature) the signature from sessionless for the message
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
TODO
GET
/user/:uuid/orders
gets orders. Can take optional params for filtering orders
name required data type description timestamp true string in a production system timestamps narrow window for replay attacks pubKey false string get orders for a specific pubKey product false string get orders of a specific product signature true string (signature) the signature from sessionless for the message
http code content-type response 200
application/json
USER
400
application/json
{"code":"400","message":"Bad Request"}
TODO
GET
/products/base
Gets all products available on this base server
None required - this is a public endpoint that returns all products on the base.
http code content-type response 200
application/json
Array of product objects
404
application/json
{"error":"not found"}
curl -X GET https://dev.sanora.allyabase.com/products/base
Returns an array of all product objects from all users on this base:
[ { "title": "My Product", "description": "Product description", "price": 1000, "uuid": "user-uuid-here", "productId": "product-id-here", "timestamp": "2025-01-01T00:00:00Z" } ]
GET
/teleportable-products
Returns teleportable product feed for cross-base marketplace discovery via BDO teleportation
None required - this endpoint dynamically generates teleportable content with all products from the base.
http code content-type response 200
text/html
HTML page with embedded teleportable product feed 500
text/plain
Error generating product feed
# Get teleportable products https://dev.sanora.allyabase.com/teleportable-products
This endpoint generates a complete teleportable product feed that works with the Planet Nine teleportation system through BDO. The response includes:
- Teleport Tag with Signature: A cryptographically signed
<teleport>
tag containing feed metadata - Teleportal Elements: Individual
<teleportal>
elements for each product with structured data - Visual HTML Display: Inline-styled product cards for direct viewing
- No JavaScript: Pure HTML with inline CSS to ensure compatibility with teleportation
The complete teleportation workflow involves three services working together:
-
Sanora (Content Provider):
- Generates teleportable HTML with signed
<teleport>
tags - Uses sessionless authentication to sign content with its base public key
- Embeds products as
<teleportal>
elements with structured data - Stores the base public key (
basePubKey
) in user objects for client verification
- Generates teleportable HTML with signed
-
BDO (Teleportation Service):
- Validates teleport signatures using the provided public key
- Fetches content from remote URLs (supports
allyabase://
protocol for container networking) - Returns validated content with
valid: true
flag when signatures match - Handles cross-container communication in Docker environments
-
Client Applications (e.g., Ninefy):
- Request teleportation through BDO using the base's public key
- Parse returned HTML to extract
<teleportal>
elements - Convert teleportal data into product objects for display
- Show teleported content in dedicated UI sections
<teleport id="planet-nine-products"
type="feed"
category="marketplace"
signature="[sessionless_signature]"
message="[timestamp]:planet-nine-marketplace-products:teleport"
pubKey="[base_public_key]">
<feed-meta>
<title>Planet Nine Marketplace</title>
<description>Discover unique products from the Planet Nine ecosystem</description>
<last-updated>[ISO_timestamp]</last-updated>
<source-url>[base_url]/teleportable-products</source-url>
</feed-meta>
<teleportal id="[product_id]" category="[category]" price="[cents]" currency="USD">
<title>[Product Title]</title>
<description>[Product Description]</description>
<url>[product_url]</url>
<image>[image_url]</image>
<tags>[comma,separated,tags]</tags>
<in-stock>true</in-stock>
<rating>[0-5]</rating>
</teleportal>
<!-- More teleportal elements... -->
</teleport>
For Docker container environments, the teleportation system supports the allyabase://
protocol to handle container-to-container communication:
- Client sends:
allyabase://sanora/teleportable-products?pubKey=[key]
- BDO translates:
allyabase://sanora
βhttp://127.0.0.1:7243
(internal container port) - Enables: Seamless teleportation across containerized services
- Sessionless Signatures: All teleport tags are signed using secp256k1 cryptographic keys
- Public Key Verification: BDO verifies signatures match the provided public key
- Timestamp Protection: Messages include timestamps to prevent replay attacks
- Base Identity: Each Sanora instance has a unique
basePubKey
for identification
// Client-side teleportation request (from Ninefy)
const teleportUrl = `allyabase://sanora/teleportable-products?pubKey=${basePubKey}`;
const teleportedData = await bdoClient.teleport(uuid, hash, teleportUrl);
if (teleportedData.valid) {
// Parse HTML and extract products
const parser = new DOMParser();
const doc = parser.parseFromString(teleportedData.html, 'text/html');
const teleportals = doc.querySelectorAll('teleportal');
// Convert to product objects
const products = Array.from(teleportals).map(portal => ({
id: portal.getAttribute('id'),
title: portal.querySelector('title')?.textContent,
price: parseInt(portal.getAttribute('price')),
// ... more fields
}));
}
- Dynamic Content Generation: Products fetched from database and embedded in real-time
- JavaScript-free Teleport Tags: All styling uses inline CSS for maximum compatibility
- Cross-Base Discovery: Enables marketplace aggregation across Planet Nine network
- Graceful Degradation: Works even when some products lack complete data
- Visual + Data: Provides both human-readable display and machine-parseable data
Sanora includes a comprehensive SVG-based form widget system located in /public/form-widget.js
that provides dynamic form generation for various field types. The widget is designed for creating product upload forms with validation and file handling capabilities.
text
- Single-line text inputs with validationtextarea
- Multi-line text areas with character limitsimage
- Image upload with preview and validationartifact
- General file upload for digital goods (PDF, EPUB, ZIP, MP3, MP4, EXE, TXT, DOC)catalog
- Specialized file upload for CSV/JSON catalog data (Added January 2025)datetime
- Date and time picker widget
The catalog
field type is specifically designed for hierarchical menu/catalog data upload:
Features:
- File Restrictions: Only accepts
.csv
and.json
files - Size Limit: 10MB maximum (smaller than artifact's 100MB)
- Validation: Strict MIME type checking for catalog data
- Visual Feedback: π½οΈ menu icon and catalog-specific messaging
- Drag & Drop: Full drag-and-drop support with visual feedback
Usage in Form Config:
{
"CSV or JSON File": {
"type": "catalog"
}
}
Data Storage: Catalog files are stored in window.formCatalogData[fieldKey]
with file object, name, size, and type.
Clear Function: Global clearCatalog(fieldKey)
function for removing uploaded catalogs.
The catalog field integrates with Ninefy's menu product type to:
- Upload CSV/JSON files containing hierarchical menu structures
- Parse menu data into tree structures (rider β time span β product)
- Upload individual products to Sanora with price and metadata
- Map Sanora UUIDs back to the menu tree structure
- Store complete menu as public data in BDO for cross-base discovery
Example CSV Format:
,rider,time span,product,price
,adult,two-hour,adult two-hour 250,2.50
,adult,day,adult day 500,5.00
,youth,two-hour,youth two-hour 100,1.00
This creates a hierarchical menu system that enables complex product catalogs while maintaining Sanora's lightweight approach.