Skip to content
Merged
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
45 changes: 45 additions & 0 deletions src/components/templates/TemplateDeployer.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react'
import { buildDeploymentConfig, buildTemplateSource } from '../../lib/templateManager'
import { useStore } from '../../lib/store'
import { downloadScaffold } from '../../lib/contractDevelopment'
import TemplateCustomizer from './TemplateCustomizer'

export default function TemplateDeployer({ template, onClose }) {
Expand Down Expand Up @@ -182,6 +183,50 @@ export default function TemplateDeployer({ template, onClose }) {
</div>
)}
</div>
{template && (
<div style={{
marginTop: '16px',
padding: '12px',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-sm)',
backgroundColor: 'var(--surface)',
}}>
<h4 style={{ fontSize: '12px', color: 'var(--cyan)', margin: '0 0 8px 0' }}>
📥 Quick Downloads
</h4>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
<button
onClick={() => downloadScaffold(template.id)}
style={{
padding: '4px 10px',
fontSize: '10px',
background: 'transparent',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-sm)',
color: 'var(--text-secondary)',
cursor: 'pointer',
}}
>
⬇ Spec + README
</button>
<a
href="https://developers.stellar.org/docs/smart-contracts"
target="_blank"
rel="noopener noreferrer"
style={{
padding: '4px 10px',
fontSize: '10px',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-sm)',
color: 'var(--cyan)',
textDecoration: 'none',
}}
>
📖 Stellar Docs
</a>
</div>
</div>
)}
</div>
)
}
147 changes: 130 additions & 17 deletions src/components/templates/TemplateLibrary.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import React, { useState } from 'react'
import { getAllTemplates } from '../../lib/templateManager'
import { getContractTemplates, downloadScaffold } from '../../lib/contractDevelopment'
import TemplateCard from './TemplateCard'
import TemplateDeployer from './TemplateDeployer'

const CATEGORIES = ['all', 'token', 'escrow', 'governance', 'nft']
const CATEGORIES = ['all', 'token', 'escrow', 'governance', 'nft', 'scaffold']

export default function TemplateLibrary() {
const templates = getAllTemplates()
const contractTemplates = getContractTemplates()
const [selected, setSelected] = useState(null)
const [filter, setFilter] = useState('all')
const [scaffoldDownloaded, setScaffoldDownloaded] = useState(null)

const filtered = filter === 'all' ? templates : templates.filter((t) => t.category === filter)
const filtered = filter === 'all'
? templates
: filter === 'scaffold'
? [] // Scaffold section is separate
: templates.filter((t) => t.category === filter)

const handleScaffoldDownload = (templateId) => {
try {
const bundle = downloadScaffold(templateId)
setScaffoldDownloaded(templateId)
setTimeout(() => setScaffoldDownloaded(null), 2000)
} catch (err) {
console.error('Scaffold download failed:', err)
}
}

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
Expand Down Expand Up @@ -38,20 +55,116 @@ export default function TemplateLibrary() {
</div>

{/* Template grid */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
gap: '12px',
}}>
{filtered.map((template) => (
<TemplateCard
key={template.id}
template={template}
selected={selected?.id === template.id}
onSelect={setSelected}
/>
))}
</div>
{(filter !== 'scaffold') && (
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
gap: '12px',
}}>
{filtered.map((template) => (
<TemplateCard
key={template.id}
template={template}
selected={selected?.id === template.id}
onSelect={setSelected}
/>
))}
</div>
)}

{/* Scaffold Section */}
{(filter === 'all' || filter === 'scaffold') && (
<div style={{
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
padding: '16px',
backgroundColor: 'var(--surface)',
}}>
<h3 style={{
fontSize: '14px',
fontFamily: 'var(--font-mono)',
color: 'var(--cyan)',
margin: '0 0 12px 0',
}}>
📦 Contract Scaffolds
</h3>
<p style={{
fontSize: '11px',
color: 'var(--text-secondary)',
margin: '0 0 16px 0',
}}>
Download starter spec + README for token, escrow, and oracle patterns.
Real contracts require a full Rust build with Soroban SDK.
</p>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: '10px',
}}>
{contractTemplates.map((template) => (
<div
key={template.id}
style={{
border: '1px solid var(--border)',
borderRadius: 'var(--radius-sm)',
padding: '12px',
display: 'flex',
flexDirection: 'column',
gap: '8px',
}}
>
<div style={{ fontWeight: 'bold', fontSize: '13px', color: 'var(--text)' }}>
{template.name}
</div>
<div style={{ fontSize: '10px', color: 'var(--text-secondary)', flex: 1 }}>
{template.description}
</div>
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
{template.tags.map((tag) => (
<span
key={tag}
style={{
fontSize: '9px',
padding: '1px 6px',
borderRadius: 'var(--radius-sm)',
background: 'var(--surface-hover)',
color: 'var(--text-secondary)',
}}
>
{tag}
</span>
))}
</div>
<button
onClick={() => handleScaffoldDownload(template.id)}
style={{
padding: '6px 12px',
background: scaffoldDownloaded === template.id ? 'var(--cyan-glow)' : 'transparent',
border: `1px solid ${scaffoldDownloaded === template.id ? 'var(--cyan)' : 'var(--border)'}`,
borderRadius: 'var(--radius-sm)',
color: scaffoldDownloaded === template.id ? 'var(--cyan)' : 'var(--text-secondary)',
fontSize: '11px',
cursor: 'pointer',
fontFamily: 'var(--font-mono)',
}}
>
{scaffoldDownloaded === template.id ? '✓ Downloaded' : 'Download Scaffold'}
</button>
</div>
))}
</div>
<div style={{ marginTop: '12px', fontSize: '10px', color: 'var(--text-secondary)' }}>
<a
href="https://developers.stellar.org/docs/smart-contracts"
target="_blank"
rel="noopener noreferrer"
style={{ color: 'var(--cyan)', textDecoration: 'none' }}
>
📖 Stellar Smart Contract Docs →
</a>
</div>
</div>
)}

{/* Deployer panel */}
{selected && (
Expand All @@ -62,4 +175,4 @@ export default function TemplateLibrary() {
)}
</div>
)
}
}
130 changes: 130 additions & 0 deletions src/lib/contractDevelopment.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,133 @@ export function generateDeploymentPlan(config = {}) {
],
}
}
/**
* Generate a deterministic WASM hash placeholder for local dev.
* Real builds produce actual hashes; this stub allows UI testing.
*/
export function generateWasmHash(templateId) {
const base = `stellar-dev-${templateId}-wasm-${Date.now()}`
let hash = ''
for (let i = 0; i < 64; i++) {
hash += Math.floor(Math.random() * 16).toString(16)
}
return { hash, placeholder: true, generatedAt: new Date().toISOString() }
}

/**
* Export a starter contract specification (JSON) for a given template.
* Includes ABI-like method signatures and constructor args.
*/
export function exportContractSpec(templateId) {
const template = getContractTemplateById(templateId)
if (!template) throw new Error('Template not found')

const methods = template.source
.match(/pub fn \w+/g)
?.map((m) => m.replace('pub fn ', '')) || []

return {
specVersion: '0.1.0',
contract: {
name: template.name,
id: templateId,
description: template.description,
entrypoint: template.entrypoint,
},
methods: methods.map((name) => ({
name,
kind: 'function',
inputs: [],
outputs: [],
})),
wasmHash: generateWasmHash(templateId),
docs: 'https://developers.stellar.org/docs/smart-contracts',
}
}

/**
* Build a downloadable README scaffold for a contract template.
*/
export function generateContractReadme(templateId) {
const template = getContractTemplateById(templateId)
if (!template) throw new Error('Template not found')

return `# ${template.name}

> Generated by Stellar Dev Dashboard — Scaffold Template

## Description
${template.description}

## Tags
${template.tags.join(', ')}

## Entrypoint
\`${template.entrypoint}\`

## Getting Started

### Prerequisites
- Rust 1.70+
- Soroban CLI: \`cargo install soroban-cli\`
- Target: \`rustup target add wasm32-unknown-unknown\`

### Build
\`\`\`bash
cargo build --target wasm32-unknown-unknown --release
\`\`\`

### Deploy
\`\`\`bash
soroban contract deploy \\
--wasm target/wasm32-unknown-unknown/release/contract.wasm \\
--source-account <SOURCE> \\
--network testnet
\`\`\`

### Test
\`\`\`bash
cargo test
\`\`\`

## Resources
- [Stellar Smart Contracts Documentation](https://developers.stellar.org/docs/smart-contracts)
- [Soroban SDK Reference](https://docs.rs/soroban-sdk)
- [Stellar Testnet Faucet](https://laboratory.stellar.org/#faucet)
`
}

/**
* Download a starter scaffold bundle (spec JSON + README + source) for a template.
* Triggers a browser download of a zip-like JSON bundle.
*/
export function downloadScaffold(templateId) {
const spec = exportContractSpec(templateId)
const readme = generateContractReadme(templateId)
const source = getContractTemplateById(templateId)?.source || ''

const bundle = {
scaffold: templateId,
generatedAt: new Date().toISOString(),
spec,
readme,
source,
links: {
stellarDocs: 'https://developers.stellar.org/docs/smart-contracts',
sorobanCli: 'https://docs.rs/soroban-sdk',
testnetFaucet: 'https://laboratory.stellar.org/#faucet',
},
}

const blob = new Blob([JSON.stringify(bundle, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${templateId}-scaffold.json`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)

return bundle
}