Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,112 @@ The one constant is `deployment-info.json`.
## License

[Open BSV License](./LICENSE.txt)

# Locksmith

### "For those of us who think purposely freezing our money for a cause is cool."

### Locksmith is a Bitcoin SV (BSV) application that lets users lock a small amount of satoshis in a smart contract until a specified future block height. This time-lock is accompanied by a custom message and cannot be redeemed until the blockchain reaches the unlock height.

## Features

- Lock 10–1000 satoshis for 1–10 blocks.
- Attach a personal message to each lock.
- View current active locks and the number of blocks remaining.
- Automatically redeem funds once the lock expires.
- Lookup service and topic manager to manage the overlay.

## Tech Stack

| Layer | Technology |
|----------------|--------------------------------------------|
| Frontend | React + TypeScript + MUI |
| Smart Contract | sCrypt (Locksmith contract) |
| Blockchain | Bitcoin SV (via `@bsv/sdk`, `@bsv/overlay`) |
| Backend | Node.js + Express + MongoDB |
| Storage | MongoDB (lock metadata) |
| Overlay | LookupService + TopicManager via `@bsv/overlay` |

## Screenshots

The frontend allows you to lock satoshis with a reason and see other users' active locks when it is deployed using CARS (see above).
![App page](https://github.com/user-attachments/assets/9363c9a5-0535-4805-a376-a0322aeef074)

## Getting Started

### Prerequisites

- Node.js ≥ v18
- MongoDB running locally or remotely
- [BRC-100](https://github.com/bitcoin-sv/BRCs/blob/master/wallet/0100.md) Wallet integration (get your [Metanet Desktop](https://github.com/bitcoin-sv/metanet-desktop/releases) wallet)
- Docker (optional, but integral if using LARS (see above))

## Frontend Setup
```bash
cd frontend
npm i
npm run start
Visit: http://localhost:8090
```
## Backend Setup
```bash
cd backend
npm i
npm run build
npm run start
Server runs on: http://localhost:8080
```

## Contracts
### Locksmith
Built using: ([sCrypt.io](https://scrypt.io/))
```ts
class Locksmith extends SmartContract {
@prop() address: Addr
@prop() lockUntilHeight: bigint
@prop() message: ByteString

@method(SigHash.ANYONECANPAY_NONE)
unlock(sig: Sig, pubKey: PubKey) {
assert(lock conditions...)
}
}
```
## The contract enforces:
- Lock until a future block height
- Signature verification
- Message attachment for context

## Lock Flow
### Lock funds
- User inputs satoshis, block duration, and message.
- Transaction is created using `walletClient.createAction()`.
- Funds are locked in a smart contract and broadcast via `SHIPBroadcaster()`.

### Monitor locks
- The frontend polls the overlay every 10 seconds.
- Active locks are displayed with their remaining blocks.

### Unlock funds
- When the lock expires, the app automatically constructs and broadcasts a redeeming transaction using `walletClient.createAction()` and the original contract and signature.

## Overlay
[documentation](https://docs.projectbabbage.com/docs/concepts/overlays)
- Two overlay components handle indexing and filtering:
- LookupService: Tracks active locks in the MongoDB.
- TopicManager: Identifies outputs created using the Locksmith contract.

## Directory Structure
```css
locksmith/
├── frontend/
│ └── App.tsx
├── backend/
│ ├── src/
│ │ ├── contracts/Locksmith.ts
│ │ ├── lookup-services/
│ │ └── topic-managers/
├── artifacts/
├── types/
├── mod.ts
```
12 changes: 7 additions & 5 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
LinearProgress,
Container
} from '@mui/material'
import { lock, startBackgroundUnlockWatchman } from './utils/utils'
import { lock, startBackgroundUnlockWatchman, truncate } from './utils/utils'
import { NoMncModal } from 'metanet-react-prompt'
import { LookupResolver, Transaction, Utils, WalletClient } from '@bsv/sdk'
import { HodlockerToken, Token } from './types/types'
Expand Down Expand Up @@ -242,14 +242,16 @@ export const App: React.FC = () => {
<br />
<TextField
disabled={loading}
label="Your message:"
label="Your message 1 -> 256 chars:"
value={message}
fullWidth
rows={8}
multiline
onChange={(e: {
target: { value: React.SetStateAction<string> }
}) => setMessage(e.target.value)}
inputProps={{ maxLength: 256 }}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const truncated = truncate(e.target.value, 256)
setMessage(truncated)
}}
/>
<br />
<br />
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ export const lock = async (
setHodlocker: React.Dispatch<React.SetStateAction<HodlockerToken[]>>,
hodlocker: HodlockerToken[]
): Promise<string | undefined> => {
if (lockBlockCount < 0) {
throw new Error('Lock block count must be zero or positive')
if (lockBlockCount < 1 || lockBlockCount > 10) {
throw new Error('Lock block count must be positive and less than 10')
}
if (satoshis < 10 || satoshis > 1000) {
throw new Error(
Expand All @@ -76,6 +76,9 @@ export const lock = async (
if (message.length < 1) {
throw new Error('Message is required for the lock')
}
if (message.length > 260) {
throw new Error('Message is too long')
}

const currentBlockHeightObj = await constants.walletClient.getHeight()
const lockBlockHeight = currentBlockHeightObj.height + lockBlockCount
Expand Down
Loading