Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f81426e
feat(plugin): implement plugin manifest manager with install/remove/l…
thewealthyplace Feb 22, 2026
56e568b
feat(plugin): add schema validator for plugin package.json and templa…
thewealthyplace Feb 22, 2026
54eb421
feat(plugin): implement plugin loader with sandbox validation and tem…
thewealthyplace Feb 22, 2026
19189a0
feat(plugin): implement npm installer wrapper for plugin install/unin…
thewealthyplace Feb 22, 2026
61510d8
feat(plugin): implement plugin install/uninstall/list/search commands
thewealthyplace Feb 22, 2026
60974cf
feat(plugin): wire plugin subcommands into CLI (install/uninstall/lis…
thewealthyplace Feb 22, 2026
6106a61
feat(plugin): add official stxforge-plugin-bonding-curve with schema …
thewealthyplace Feb 22, 2026
f0f81b4
feat(plugin): add official stxforge-plugin-subscription with recurrin…
thewealthyplace Feb 22, 2026
01b8b7a
feat(plugin): implement Handlebars template renderer for plugin contr…
thewealthyplace Feb 22, 2026
3ba928d
refactor(plugin): add plugin module barrel export
thewealthyplace Feb 22, 2026
96a1262
test(plugin): add plugin manager unit tests with isolated tmp directory
thewealthyplace Feb 22, 2026
c2fd9f6
test(plugin): add schema validator unit tests for package.json and te…
thewealthyplace Feb 22, 2026
1c6cb03
chore: add package.json with handlebars dependency for plugin templat…
thewealthyplace Feb 22, 2026
c3ac045
docs: add comprehensive plugin authoring guide with schema format and…
thewealthyplace Feb 22, 2026
e9c9775
chore: add initial .stxforge/plugins.json lockfile for plugin versioning
thewealthyplace Feb 22, 2026
e40768b
Merge branch 'main' into fix/issue-3-plugin-system
thewealthyplace Feb 22, 2026
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
3 changes: 3 additions & 0 deletions .stxforge/plugins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": {}
}
125 changes: 125 additions & 0 deletions docs/plugin-authoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Writing a stxforge Plugin

This guide explains how to create, publish, and use a community plugin for stxforge.

## Quick Start

```bash
mkdir stxforge-plugin-my-template
cd stxforge-plugin-my-template
npm init
```

## Plugin Package Structure

```
stxforge-plugin-my-template/
├── package.json # declares stxforge.plugin = true
├── index.js # plugin entry point
├── templates/
│ └── my-template/
│ ├── schema.json # input prompt definition
│ ├── contract.clar.hbs # Handlebars contract template
│ └── test.ts.hbs # optional test template
└── README.md
```

## package.json Requirements

```json
{
"name": "stxforge-plugin-my-template",
"version": "1.0.0",
"stxforge": {
"plugin": true,
"verified": false,
"templates": ["my-template"]
}
}
```

- Name **must** start with `stxforge-plugin-`
- `stxforge.plugin` **must** be `true`
- `stxforge.templates` lists template directory names

## schema.json Format

```json
{
"name": "my-template",
"description": "What this template generates",
"prompts": [
{
"name": "tokenName",
"type": "input",
"message": "Token name:"
},
{
"name": "decimals",
"type": "number",
"message": "Decimals:",
"default": 6
},
{
"name": "mintable",
"type": "confirm",
"message": "Enable minting?",
"default": false
}
]
}
```

## Handlebars Templates

Use `{{variableName}}` to inject prompt answers into your `.clar.hbs` file:

```clarity
;; {{tokenName}} Contract
;; Generated by stxforge-plugin-my-template

(define-fungible-token {{contractName}})

(define-constant DECIMALS u{{decimals}})

{{#if mintable}}
(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) (err u100))
(ft-mint? {{contractName}} amount recipient)))
{{/if}}
```

Available context variables:
- All prompt `name` fields as-is
- `contractName` — auto-generated from `name` (lowercased, hyphenated)

## Trust Model

| Badge | Meaning |
|-------|---------|
| `[verified]` | Published by the stxforge core team or audited partners |
| `[community]` | Published by the community — review before using |

To request verified status, open a PR in the stxforge GitHub org.

## Publishing

```bash
npm publish --access public
```

Users install via:

```bash
stxforge plugin install my-template
# or
stxforge plugin install stxforge-plugin-my-template
```

## Security Guidelines

- Never access the filesystem outside the project directory
- Do not make network requests from templates
- Do not `require` arbitrary user-supplied modules
- All prompts should have sensible defaults
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
"dependencies": {
"chalk": "^5.3.0",
"commander": "^11.1.0",
"inquirer": "^9.2.12",
"fs-extra": "^11.2.0",
"handlebars": "^4.7.8",
"ora": "^7.0.1"
},
"devDependencies": {
"vitest": "^1.2.0"
},
"keywords": ["stacks", "clarity", "blockchain", "cli", "scaffolding"],
"keywords": ["stacks", "clarity", "blockchain", "cli", "scaffolding", "plugins"],
"license": "MIT"
}
5 changes: 5 additions & 0 deletions plugins/stxforge-plugin-bonding-curve/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
name: 'stxforge-plugin-bonding-curve',
version: require('./package.json').version,
templates: ['bonding-curve'],
};
13 changes: 13 additions & 0 deletions plugins/stxforge-plugin-bonding-curve/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "stxforge-plugin-bonding-curve",
"version": "1.0.0",
"description": "Bonding curve token contract template for stxforge",
"main": "index.js",
"stxforge": {
"plugin": true,
"verified": true,
"templates": ["bonding-curve"]
},
"keywords": ["stxforge-plugin", "stacks", "clarity", "bonding-curve", "defi"],
"license": "MIT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
;; {{name}} — Bonding Curve Token
;; Generated by stxforge-plugin-bonding-curve
;; Price function: price = slope * supply^2

(define-fungible-token {{contractName}})

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-INSUFFICIENT-STX (err u101))
(define-constant ERR-ZERO-AMOUNT (err u102))
(define-constant SLOPE u{{slope}})

(define-data-var total-reserve uint u0)

;; Price to buy `amount` tokens given current supply
(define-read-only (get-buy-price (amount uint))
(let (
(supply (ft-get-supply {{contractName}}))
(new-supply (+ supply amount))
)
;; Integral of slope*x from supply to new-supply = slope*(new^2 - old^2)/2
(ok (/ (* SLOPE (- (* new-supply new-supply) (* supply supply))) u2))
)
)

;; Price received for selling `amount` tokens
(define-read-only (get-sell-price (amount uint))
(let (
(supply (ft-get-supply {{contractName}}))
(new-supply (- supply amount))
)
(ok (/ (* SLOPE (- (* supply supply) (* new-supply new-supply))) u2))
)
)

;; Buy tokens by sending STX
(define-public (buy (amount uint))
(let ((price (unwrap! (get-buy-price amount) (err u500))))
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(try! (stx-transfer? price tx-sender (as-contract tx-sender)))
(var-set total-reserve (+ (var-get total-reserve) price))
(ft-mint? {{contractName}} amount tx-sender)
)
)

;; Sell tokens and receive STX
(define-public (sell (amount uint))
(let ((proceeds (unwrap! (get-sell-price amount) (err u500))))
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(try! (ft-burn? {{contractName}} amount tx-sender))
(var-set total-reserve (- (var-get total-reserve) proceeds))
(as-contract (stx-transfer? proceeds tx-sender tx-sender))
)
)

(define-read-only (get-reserve) (ok (var-get total-reserve)))
(define-read-only (get-supply) (ok (ft-get-supply {{contractName}})))
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "bonding-curve",
"description": "A token with an automated market maker using a bonding curve price function",
"prompts": [
{
"name": "name",
"type": "input",
"message": "Token name:",
"validate": "length >= 2"
},
{
"name": "symbol",
"type": "input",
"message": "Token symbol (uppercase):"
},
{
"name": "reserveToken",
"type": "input",
"message": "Reserve token contract (e.g. .wrapped-stx):",
"default": ".wrapped-stx"
},
{
"name": "slope",
"type": "number",
"message": "Curve slope (integer, higher = steeper price growth):",
"default": 1
}
]
}
5 changes: 5 additions & 0 deletions plugins/stxforge-plugin-subscription/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
name: 'stxforge-plugin-subscription',
version: require('./package.json').version,
templates: ['subscription'],
};
13 changes: 13 additions & 0 deletions plugins/stxforge-plugin-subscription/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "stxforge-plugin-subscription",
"version": "1.0.0",
"description": "Recurring subscription payment contract template for stxforge",
"main": "index.js",
"stxforge": {
"plugin": true,
"verified": true,
"templates": ["subscription"]
},
"keywords": ["stxforge-plugin", "stacks", "clarity", "subscription", "payments"],
"license": "MIT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
;; {{name}} Subscription — On-chain Recurring Payments
;; Generated by stxforge-plugin-subscription

(define-constant CONTRACT-OWNER tx-sender)
(define-constant PRICE u{{pricePerPeriod}})
(define-constant PERIOD u{{periodBlocks}})
(define-constant GRACE-PERIOD u{{gracePeriod}})
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-NOT-SUB (err u101))
(define-constant ERR-ALREADY-SUB (err u102))
(define-constant ERR-EXPIRED (err u103))

;; subscriber -> expiry block height
(define-map subscriptions principal uint)

;; Subscribe for one billing period
(define-public (subscribe)
(let (
(expiry (+ block-height PERIOD))
(existing (map-get? subscriptions tx-sender))
)
(asserts! (is-none existing) ERR-ALREADY-SUB)
(try! (stx-transfer? PRICE tx-sender CONTRACT-OWNER))
(map-set subscriptions tx-sender expiry)
(ok expiry)
)
)

;; Renew an existing subscription for one more period
(define-public (renew)
(let (
(current-expiry (unwrap! (map-get? subscriptions tx-sender) ERR-NOT-SUB))
(new-expiry (+ (max current-expiry block-height) PERIOD))
)
(try! (stx-transfer? PRICE tx-sender CONTRACT-OWNER))
(map-set subscriptions tx-sender new-expiry)
(ok new-expiry)
)
)

;; Cancel and remove subscription record
(define-public (cancel)
(begin
(asserts! (is-some (map-get? subscriptions tx-sender)) ERR-NOT-SUB)
(map-delete subscriptions tx-sender)
(ok true)
)
)

;; Check if a principal has an active subscription (including grace period)
(define-read-only (is-active (subscriber principal))
(match (map-get? subscriptions subscriber)
expiry (ok (<= block-height (+ expiry GRACE-PERIOD)))
(ok false)
)
)

;; Get subscription expiry block for a principal
(define-read-only (get-expiry (subscriber principal))
(ok (map-get? subscriptions subscriber))
)

;; Owner withdraws accumulated STX
(define-public (withdraw (amount uint))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
(as-contract (stx-transfer? amount tx-sender CONTRACT-OWNER))
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "subscription",
"description": "On-chain recurring subscription payments in STX with configurable billing period",
"prompts": [
{
"name": "name",
"type": "input",
"message": "Service name:",
"validate": "length >= 2"
},
{
"name": "pricePerPeriod",
"type": "number",
"message": "Price per billing period (in uSTX):",
"default": 1000000
},
{
"name": "periodBlocks",
"type": "number",
"message": "Billing period in blocks (e.g. 4320 = ~30 days):",
"default": 4320
},
{
"name": "gracePeriod",
"type": "number",
"message": "Grace period in blocks before access is revoked:",
"default": 144
}
]
}
Loading