Skip to content

Commit f956a50

Browse files
0.0.3: Add Bitcoin anchor support (--anchor flag)
1 parent 866c43a commit f956a50

2 files changed

Lines changed: 99 additions & 16 deletions

File tree

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "nostr-git-sync",
3-
"version": "0.0.2",
4-
"description": "Decentralized git sync daemon using Nostr (NIP-34)",
3+
"version": "0.0.3",
4+
"description": "Decentralized git sync daemon using Nostr (NIP-34) with optional Bitcoin anchoring",
55
"type": "module",
66
"main": "src/daemon.js",
77
"bin": {
@@ -15,6 +15,9 @@
1515
"nostr-tools": "^2.19.4",
1616
"ws": "^8.18.0"
1717
},
18+
"optionalDependencies": {
19+
"blocktrails": "^0.0.10"
20+
},
1821
"engines": {
1922
"node": ">=18.0.0"
2023
},
@@ -23,7 +26,10 @@
2326
"git",
2427
"sync",
2528
"decentralized",
26-
"nip-34"
29+
"nip-34",
30+
"bitcoin",
31+
"anchor",
32+
"blocktrails"
2733
],
2834
"license": "MIT"
2935
}

src/publish.js

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,45 @@ const DEFAULT_RELAYS = [
88
'wss://relay.nostr.band'
99
]
1010

11+
/**
12+
* Create Bitcoin anchor for commit using blocktrails
13+
* Returns txo URI or null if anchoring fails/skipped
14+
*/
15+
async function createAnchor(commit, repoPath, options = {}) {
16+
const { network = 'tbtc4', dryRun = false } = options
17+
18+
try {
19+
const args = ['mark', commit]
20+
if (dryRun) args.push('--dry')
21+
22+
const output = execSync(`blocktrails ${args.join(' ')}`, {
23+
cwd: repoPath,
24+
encoding: 'utf-8',
25+
stdio: ['pipe', 'pipe', 'pipe']
26+
})
27+
28+
// Parse TXID from output: "TXID: <txid>"
29+
const txidMatch = output.match(/TXID:\s*([a-f0-9]{64})/i)
30+
if (txidMatch) {
31+
const txid = txidMatch[1]
32+
// Output is always vout 0 for mark command
33+
return `txo:${network}:${txid}:0`
34+
}
35+
36+
console.log('[anchor] Could not parse TXID from blocktrails output')
37+
return null
38+
} catch (err) {
39+
console.log(`[anchor] Blocktrails not available or failed: ${err.message}`)
40+
return null
41+
}
42+
}
43+
1144
/**
1245
* Publish a 30618 repo state event
1346
*/
14-
export async function publishState(privateKeyHex, relays, repoId, repoPath) {
47+
export async function publishState(privateKeyHex, relays, repoId, repoPath, options = {}) {
48+
const { anchor = false, network = 'tbtc4', dryRun = false } = options
49+
1550
// Get current branch and commit
1651
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
1752
cwd: repoPath,
@@ -23,14 +58,27 @@ export async function publishState(privateKeyHex, relays, repoId, repoPath) {
2358
encoding: 'utf-8'
2459
}).trim()
2560

61+
// Build tags
62+
const tags = [
63+
['d', repoId],
64+
[`refs/heads/${branch}`, commit]
65+
]
66+
67+
// Create Bitcoin anchor if requested
68+
if (anchor) {
69+
console.log(`[anchor] Creating Bitcoin anchor for ${commit.slice(0, 7)}...`)
70+
const txoUri = await createAnchor(commit, repoPath, { network, dryRun })
71+
if (txoUri) {
72+
tags.push(['c', txoUri])
73+
console.log(`[anchor] ✓ Anchored: ${txoUri}`)
74+
}
75+
}
76+
2677
// Create and sign event
2778
const event = finalizeEvent({
2879
kind: 30618,
2980
created_at: Math.floor(Date.now() / 1000),
30-
tags: [
31-
['d', repoId],
32-
[`refs/heads/${branch}`, commit]
33-
],
81+
tags,
3482
content: ''
3583
}, hexToBytes(privateKeyHex))
3684

@@ -102,14 +150,43 @@ function hexToBytes(hex) {
102150

103151
// CLI usage
104152
if (process.argv[1].endsWith('publish.js')) {
153+
const args = process.argv.slice(2)
154+
const flags = args.filter(a => a.startsWith('-'))
155+
const positional = args.filter(a => !a.startsWith('-'))
156+
157+
const anchor = flags.includes('--anchor') || flags.includes('-a')
158+
const help = flags.includes('--help') || flags.includes('-h')
159+
160+
if (help) {
161+
console.log(`
162+
nostr-git-sync publish - Publish 30618 repo state event
163+
164+
Usage:
165+
node src/publish.js [options] [privkey] [repo-id] [repo-path]
166+
167+
Options:
168+
-a, --anchor Create Bitcoin anchor (requires blocktrails CLI)
169+
-h, --help Show this help
170+
171+
If no arguments provided, reads from git config:
172+
git config nostr.privkey <hex>
173+
git config nostr.repoid <id> (optional, defaults to directory name)
174+
175+
Examples:
176+
node src/publish.js # Use git config
177+
node src/publish.js --anchor # With Bitcoin anchor
178+
node src/publish.js <key> my-repo . # Explicit args
179+
`)
180+
process.exit(0)
181+
}
182+
105183
let privkey, repoId, repoPath = '.'
106184

107-
// Check args
108-
if (process.argv[2] && !process.argv[2].startsWith('-')) {
185+
if (positional.length > 0) {
109186
// Args provided: privkey repoId [repoPath]
110-
privkey = process.argv[2]
111-
repoId = process.argv[3]
112-
repoPath = process.argv[4] || '.'
187+
privkey = positional[0]
188+
repoId = positional[1]
189+
repoPath = positional[2] || '.'
113190
} else {
114191
// Read from git config
115192
try {
@@ -124,12 +201,12 @@ if (process.argv[1].endsWith('publish.js')) {
124201
}
125202

126203
if (!privkey) {
127-
console.log('Usage: node src/publish.js <privkey-hex> <repo-id> [repo-path]')
128-
console.log(' or: git config nostr.privkey <hex> && node src/publish.js')
204+
console.log('Usage: node src/publish.js [--anchor] <privkey-hex> <repo-id> [repo-path]')
205+
console.log(' or: git config nostr.privkey <hex> && node src/publish.js [--anchor]')
129206
process.exit(1)
130207
}
131208

132-
publishState(privkey, DEFAULT_RELAYS, repoId, repoPath)
209+
publishState(privkey, DEFAULT_RELAYS, repoId, repoPath, { anchor })
133210
.then(() => process.exit(0))
134211
.catch(err => {
135212
console.error('Error:', err.message)

0 commit comments

Comments
 (0)