# Frontend Uploading

Canonical: https://docs.projectbabbage.com/docs/quickstarts/metamarket/frontend-uploading

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

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.

```bash
rm -rf frontend
git clone https://github.com/p2ppsr/marketplace-frontend-starter.git frontend
cd frontend
npm install
```

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:

```
backend/
frontend/                        ← React + Vite project (replacing the template)
├── index.html
├── package.json
├── public/                     
├── src
│   ├── App.tsx
│   ├── constants.ts             ← Key server constants
│   ├── index.tsx
│   ├── Layout.tsx
│   ├── pages
│   │   ├── Account.tsx
│   │   ├── Details.tsx
│   │   ├── Home.tsx             ← placeholder main page
│   │   ├── Store.tsx
│   │   └── UploadFile.tsx       ← file upload form
│   ├── theme.ts
│   └── utils
│       ├── decodeOutputs.ts     ← decoding overlay outputs (to implement)
│       └── publishCommitment.ts ← core upload logic (to implement)
├── tsconfig.json
└── vite.config.mjs
key-server
```

- `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:

```ts
publishCommitment({
  file,
  name,
  description,
  satoshis,
  publicKey,
  expiration,
  coverImage,
  setStatusText
})
```

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.

```ts
// frontend/src/utils/publishCommitment.ts
import {
  SymmetricKey,
  StorageUploader,
  WalletClient,
  Utils,
  PushDrop,
  TopicBroadcaster,
  Transaction,
  AuthFetch,
  StorageUtils,
  LookupResolver
} from '@bsv/sdk'
import constants from '../constants'

export async function publishCommitment({
  file,
  name,
  description,
  satoshis,
  publicKey,
  expiration,
  coverImage,
  setStatusText
}: {
  file: File
  name: string
  description: string
  satoshis: number
  publicKey: string
  expiration: number
  coverImage: File
  setStatusText: (text: string) => void
}): Promise<string> {
  try {
    // …implement steps below…
  } catch (error) {
    setStatusText('')
    console.error('Error publishing commitment:', error)
    throw `Error publishing commitment: ${error}`
  }
}
```

### 4.1 Validate & prepare encryption

```ts
// 1. Decide UHRP gateway
const storageURL = 'https://nanostore.babbage.systems'

// 2. Generate a one-time symmetric key to encrypt the STL file
const symmetricKey = SymmetricKey.fromRandom()

// 3. Read file into array and encrypt
const fileArray = Array.from(new Uint8Array(await file.arrayBuffer()))
const encryptedFile = symmetricKey.encrypt(fileArray) as number[]
const uploadableFile = { data: encryptedFile, type: file.type }

// 4. Error if encryption/prep fails
if (!uploadableFile) {
  setStatusText('Error preparing STL file.')
  throw new Error('Failed to prepare STL file for upload.')
}
```

-   `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

```ts
// 5. Convert expiration days → minutes for UHRP retention
const retentionPeriod = expiration * 24 * 60 

// 6. Initialize a WalletClient (local by default)
const wallet = new WalletClient('auto')
const storageUploader = new StorageUploader({ storageURL, wallet })

// 7. Update status and push encrypted STL
setStatusText('Uploading files to UHRP...')
const stl = await storageUploader.publishFile({
  file: uploadableFile,
  retentionPeriod
})
console.log(`STL UHRP URL: ${stl.uhrpURL}`)

// 8. Similarly encrypt & upload the cover image
const coverArray = Array.from(new Uint8Array(await coverImage.arrayBuffer()))
const uploadableCover = { data: coverArray, type: coverImage.type }
const cover = await storageUploader.publishFile({
  file: uploadableCover,
  retentionPeriod
})
```

- `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

```ts
// 9. Compute exact expiry timestamp (ms since Jan 1, 1970)
const expiryTime = Date.now() + expiration * 24 * 60 * 60 * 1000

// 10. Build PushDrop “fields” array in the correct order
const fields = [
  Utils.toArray(stl.uhrpURL, 'utf8'),
  Utils.toArray(name, 'utf8'),
  Utils.toArray(description, 'utf8'),
  Utils.toArray('' + satoshis, 'utf8'),
  Utils.toArray(publicKey, 'utf8'),
  Utils.toArray('' + file.size, 'utf8'),
  Utils.toArray('' + expiryTime, 'utf8'),
  Utils.toArray(cover.uhrpURL, 'utf8'),
]

// 11. Lock fields into a PushDrop output script
const pushdrop = new PushDrop(wallet)
const lockingScript = await pushdrop.lock(
  fields,
  [2, 'marketplace'], // pushdrop “tag” 
  '1',                // fee rate
  'anyone',           // who can spend later
  true                // include signature key
)

// 12. Create a transaction on-chain (1 satoshi outputs)
setStatusText('Creating on-chain transaction...')
const { txid, tx } = await wallet.createAction({
  outputs: [{
    lockingScript: lockingScript.toHex(),
    satoshis: 1,
    outputDescription: 'Marketplace upload token'
  }],
  description: 'Publish to Marketplace',
  options: {
    acceptDelayedBroadcast: false,
    randomizeOutputs: false
  }
})
if (!tx) {
  setStatusText('')
  throw new Error('Failed to create transaction action')
}

// 13. Broadcast via TopicBroadcaster so Overlay will admit it
setStatusText('Broadcasting transaction...')
await new TopicBroadcaster(['tm_demo_market'], {
  networkPreset:
    window.location.hostname === 'localhost' ? 'local' : 'mainnet'
}).broadcast(Transaction.fromAtomicBEEF(tx))
```

- 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

```ts
// 14. Notify user and prepare authenticated fetch
setStatusText('Registering with Key Server...')
const authFetch = new AuthFetch(wallet)

// 15. Build request body
const body = {
  fileUrl: stl.uhrpURL,
  encryptionKey: symmetricKey.toHex(),
  satoshis,
  publicKey
}

// 16. Send POST to /submit endpoint
const response = await authFetch.fetch(
  `${constants.keyServer}/submit`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body
  }
)
if (!response.ok) {
  throw new Error(`Key Server error: ${response.statusText}`)
}

// 17. Success
return 'Upload Successful!'
```

-   `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](https://docs.projectbabbage.com/assets/images/metamarket-upload-success.webp)

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!
