Frontend Uploading

Encrypt STL files and cover images, upload to UHRP, and broadcast PushDrop transactions.

Read as Markdown

This page shows how to scaffold and implement the Upload Files flow in your React frontend. We'll:

  1. Clone and set up the starter repository
  2. Explore the starter's directory structure
  3. Explain the UploadFile.tsx component and its inputs
  4. Walk through publishCommitment.ts in detail:
    • Validate file inputs and prepare encryption
    • Upload to UHRP
    • Create and broadcast the PushDrop token
    • Submit metadata to the Key Server

1. Clone & set up the starter

We provide a starter file for the frontend for this quickstart that provides the general structure of the project and all of the UI elements which will not be covered in this guide. This starter will fully replace the current frontend folder, so begin by removing and replacing it from the root directory.

1rm -rf frontend
2git clone https://github.com/p2ppsr/marketplace-frontend-starter.git frontend
3cd frontend
4npm install
Copied!

This starter provides pre-built pages, routes, and UI components so you can focus on implementing the upload and store logic rather than boilerplate setup.


2. Starter directory structure

After npm install, your directory should look like:

1backend/
2frontend/                        ← React + Vite project (replacing the template)
3├── index.html
4├── package.json
5├── public/                     
6├── src
7│   ├── App.tsx
8│   ├── constants.ts             ← Key server constants
9│   ├── index.tsx
10│   ├── Layout.tsx
11│   ├── pages
12│   │   ├── Account.tsx
13│   │   ├── Details.tsx
14│   │   ├── Home.tsx             ← placeholder main page
15│   │   ├── Store.tsx
16│   │   └── UploadFile.tsx       ← file upload form
17│   ├── theme.ts
18│   └── utils
19│       ├── decodeOutputs.ts     ← decoding overlay outputs (to implement)
20│       └── publishCommitment.ts ← core upload logic (to implement)
21├── tsconfig.json
22└── vite.config.mjs
23key-server
Copied!
  • App.tsx configures the pages and routes that are accessible.
  • constants.ts exports keyServer endpoint based on localhost vs. production.
  • pages/UploadFile.tsx is where the upload UI elements are.
  • utils/publishCommitment.ts handles PushDrop, UHRP, and Key Server calls.

3. What happens in UploadFile.tsx

Open frontend/src/pages/UploadFile.tsx. Its main job is to collect these values from the uploader:

  • file: File — the STL file to encrypt and sell
  • name: string — title for this listing
  • description: string — text shown on the detail pages
  • satoshis: number — price the buyer pays
  • publicKey: string — uploader's BSV identity key
  • expiration: number — days until the file listing expires
  • coverImage: File — unencrypted image for listing
  • setStatusText: (text: string) => void — a callback to show progress/status text

On submit, it calls:

1publishCommitment({
2  file,
3  name,
4  description,
5  satoshis,
6  publicKey,
7  expiration,
8  coverImage,
9  setStatusText
10})
Copied!

All of these values drive the core upload logic in publishCommitment.ts.


4. Implementing publishCommitment.ts

Located at frontend/src/utils/publishCommitment.ts, this function will:

  1. Validate inputs & prepare encryption
  2. Upload encrypted files to UHRP
  3. Create & broadcast a PushDrop token on-chain
  4. Submit file metadata to the Key Server

Below is the full outline with commentary for each major step.

1// frontend/src/utils/publishCommitment.ts
2import {
3  SymmetricKey,
4  StorageUploader,
5  WalletClient,
6  Utils,
7  PushDrop,
8  TopicBroadcaster,
9  Transaction,
10  AuthFetch,
11  StorageUtils,
12  LookupResolver
13} from '@bsv/sdk'
14import constants from '../constants'
15
16export async function publishCommitment({
17  file,
18  name,
19  description,
20  satoshis,
21  publicKey,
22  expiration,
23  coverImage,
24  setStatusText
25}: {
26  file: File
27  name: string
28  description: string
29  satoshis: number
30  publicKey: string
31  expiration: number
32  coverImage: File
33  setStatusText: (text: string) => void
34}): Promise<string> {
35  try {
36    // …implement steps below…
37  } catch (error) {
38    setStatusText('')
39    console.error('Error publishing commitment:', error)
40    throw `Error publishing commitment: ${error}`
41  }
42}
Copied!

4.1 Validate & prepare encryption

1// 1. Decide UHRP gateway
2const storageURL = 'https://nanostore.babbage.systems'
3
4// 2. Generate a one-time symmetric key to encrypt the STL file
5const symmetricKey = SymmetricKey.fromRandom()
6
7// 3. Read file into array and encrypt
8const fileArray = Array.from(new Uint8Array(await file.arrayBuffer()))
9const encryptedFile = symmetricKey.encrypt(fileArray) as number[]
10const uploadableFile = { data: encryptedFile, type: file.type }
11
12// 4. Error if encryption/prep fails
13if (!uploadableFile) {
14  setStatusText('Error preparing STL file.')
15  throw new Error('Failed to prepare STL file for upload.')
16}
Copied!
  • SymmetricKey.fromRandom() creates a new AES key that can be used by the purchaser to decrypt.
  • Convert File into an encrypted file using the symmetric key.
  • Wrap encrypted data and the MIME type in uploadableFile.

4.2 Upload to UHRP

1// 5. Convert expiration days → minutes for UHRP retention
2const retentionPeriod = expiration * 24 * 60 
3
4// 6. Initialize a WalletClient (local by default)
5const wallet = new WalletClient('auto')
6const storageUploader = new StorageUploader({ storageURL, wallet })
7
8// 7. Update status and push encrypted STL
9setStatusText('Uploading files to UHRP...')
10const stl = await storageUploader.publishFile({
11  file: uploadableFile,
12  retentionPeriod
13})
14console.log(`STL UHRP URL: ${stl.uhrpURL}`)
15
16// 8. Similarly encrypt & upload the cover image
17const coverArray = Array.from(new Uint8Array(await coverImage.arrayBuffer()))
18const uploadableCover = { data: coverArray, type: coverImage.type }
19const cover = await storageUploader.publishFile({
20  file: uploadableCover,
21  retentionPeriod
22})
Copied!
  • StorageUploader.publishFile returns an object with uhrpURL once the file is stored on UHRP.
  • We do this twice: once for the encrypted STL, once for the unencrypted cover image.

4.3 Create and broadcast the PushDrop token

1// 9. Compute exact expiry timestamp (ms since Jan 1, 1970)
2const expiryTime = Date.now() + expiration * 24 * 60 * 60 * 1000
3
4// 10. Build PushDrop “fields” array in the correct order
5const fields = [
6  Utils.toArray(stl.uhrpURL, 'utf8'),
7  Utils.toArray(name, 'utf8'),
8  Utils.toArray(description, 'utf8'),
9  Utils.toArray('' + satoshis, 'utf8'),
10  Utils.toArray(publicKey, 'utf8'),
11  Utils.toArray('' + file.size, 'utf8'),
12  Utils.toArray('' + expiryTime, 'utf8'),
13  Utils.toArray(cover.uhrpURL, 'utf8'),
14]
15
16// 11. Lock fields into a PushDrop output script
17const pushdrop = new PushDrop(wallet)
18const lockingScript = await pushdrop.lock(
19  fields,
20  [2, 'marketplace'], // pushdrop “tag” 
21  '1',                // fee rate
22  'anyone',           // who can spend later
23  true                // include signature key
24)
25
26// 12. Create a transaction on-chain (1 satoshi outputs)
27setStatusText('Creating on-chain transaction...')
28const { txid, tx } = await wallet.createAction({
29  outputs: [{
30    lockingScript: lockingScript.toHex(),
31    satoshis: 1,
32    outputDescription: 'Marketplace upload token'
33  }],
34  description: 'Publish to Marketplace',
35  options: {
36    acceptDelayedBroadcast: false,
37    randomizeOutputs: false
38  }
39})
40if (!tx) {
41  setStatusText('')
42  throw new Error('Failed to create transaction action')
43}
44
45// 13. Broadcast via TopicBroadcaster so Overlay will admit it
46setStatusText('Broadcasting transaction...')
47await new TopicBroadcaster(['tm_demo_market'], {
48  networkPreset:
49    window.location.hostname === 'localhost' ? 'local' : 'mainnet'
50}).broadcast(Transaction.fromAtomicBEEF(tx))
Copied!
  • the fields array maps exactly to:

    1. fileUrl (encrypted STL)
    2. name
    3. description
    4. satoshis
    5. publicKey
    6. file.size
    7. expiryTime
    8. coverUrl
  • PushDrop.lock(...) returns a Script locking those fields.

  • WalletClient.createAction builds an on-chain TX (1 sat output to pushdrop script).

  • TopicBroadcaster.broadcast(...) pushes the raw BEEF to your Overlay backend to be admitted and stored by your Topic Manager and Lookup Service.


4.4 Submit to the Key Server

1// 14. Notify user and prepare authenticated fetch
2setStatusText('Registering with Key Server...')
3const authFetch = new AuthFetch(wallet)
4
5// 15. Build request body
6const body = {
7  fileUrl: stl.uhrpURL,
8  encryptionKey: symmetricKey.toHex(),
9  satoshis,
10  publicKey
11}
12
13// 16. Send POST to /submit endpoint
14const response = await authFetch.fetch(
15  `${constants.keyServer}/submit`,
16  {
17    method: 'POST',
18    headers: { 'Content-Type': 'application/json' },
19    body
20  }
21)
22if (!response.ok) {
23  throw new Error(`Key Server error: ${response.statusText}`)
24}
25
26// 17. Success
27return 'Upload Successful!'
Copied!
  • AuthFetch signs the request with your identity key to access the Key Server, which is protected by the AuthMiddleware.
  • The Key Server's /submit checks on-chain via Overlay's Lookup Service, then stores the { fileUrl, encryptionKey, satoshis, publicKey } in MongoDB.

5. Testing the Upload Flow

  1. Run everything locally
    • From root: npx lars
    • In a separate terminal: cd key-server && npm run start
  2. Navigate to http://localhost:5173/upload
  3. Fill in all fields
    • Pick an STL file and a cover image
    • Enter name, description, price, and expiration days
    • Click Submit
  4. Watch status updates
    • “Uploading files to UHRP...”
    • “Creating on-chain transaction...”
    • “Broadcasting transaction...”
    • “Registering with Key Server...”
    • “Upload Successful!”
  5. Verify results
    • UHRP returns two URLs (STL & cover).
    • On-chain token appears in your backend Overlay logs.
    • MongoDB collection files now contains a record with fileUrl, encryptionKey, satoshis, and publicKey.
Upload Files Page Success

You've successfully wired up the Upload Files page with: file encryption, UHRP upload, PushDrop transaction, Overlay admission, and Key Server storage. The next steps are to build the Store and Details pages to browse and purchase these listings!

Was this page helpful?