gitmark 0.0.75 → 0.0.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,8 @@
22
22
  "Bash(node bin/git-mark.js -v)",
23
23
  "Bash(npm test:*)",
24
24
  "Bash(gh api:*)",
25
- "Bash(convert:*)"
25
+ "Bash(convert:*)",
26
+ "Bash(git commit -m ' *)"
26
27
  ],
27
28
  "deny": [],
28
29
  "ask": [],
package/README.md CHANGED
@@ -19,6 +19,9 @@ This gives you `git mark` as a native git subcommand.
19
19
  # Initialize in a git repo (tbtc4 = Bitcoin testnet4)
20
20
  git mark init --chain tbtc4 --voucher txo:tbtc4:txid:vout?amount=X&key=Y
21
21
 
22
+ # Or read vouchers from a file (uses last line, removes on success)
23
+ git mark init --voucher ~/faucet/vouchers.txt
24
+
22
25
  # Make commits, then mark them
23
26
  git commit -m "my change"
24
27
  git mark
package/bin/git-mark.js CHANGED
@@ -18,6 +18,7 @@ import { execSync } from 'child_process';
18
18
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
19
19
  import { join, dirname } from 'path';
20
20
  import { fileURLToPath } from 'url';
21
+ import { homedir } from 'os';
21
22
 
22
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
23
24
  const PKG = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
@@ -197,6 +198,23 @@ function getPrivkey() {
197
198
  function setPrivkey(key) { gitExec(`git config --local nostr.privkey '${key}'`); }
198
199
  function isGitRoot() { return existsSync('.git'); }
199
200
 
201
+ function resolveVoucher(arg) {
202
+ let path = null;
203
+ if (arg.startsWith('file:')) path = arg.slice(5);
204
+ else if (arg.endsWith('.txt')) path = arg;
205
+ if (!path) return { uri: arg, file: null };
206
+ if (path.startsWith('~')) path = path.replace('~', homedir());
207
+ const lines = readFileSync(path, 'utf8').split('\n').map(l => l.trim()).filter(Boolean);
208
+ if (lines.length === 0) { console.error(`No vouchers in ${path}`); process.exit(1); }
209
+ return { uri: lines[lines.length - 1], file: path };
210
+ }
211
+
212
+ function consumeVoucher(path) {
213
+ const lines = readFileSync(path, 'utf8').split('\n').map(l => l.trim()).filter(Boolean);
214
+ lines.pop();
215
+ writeFileSync(path, lines.join('\n') + (lines.length ? '\n' : ''));
216
+ }
217
+
200
218
  // --- Trail file helpers ---
201
219
  function loadTrail() {
202
220
  if (!existsSync(TRAIL_FILE)) return null;
@@ -306,7 +324,8 @@ async function cmdInit(args) {
306
324
  // Voucher funding
307
325
  const voucherIdx = args.indexOf('--voucher');
308
326
  if (voucherIdx !== -1) {
309
- const voucherUri = args[voucherIdx + 1];
327
+ const voucherArg = args[voucherIdx + 1];
328
+ const { uri: voucherUri, file: voucherFile } = resolveVoucher(voucherArg);
310
329
  const txo = parseTxoUri(voucherUri);
311
330
  if (!txo.key) { console.error('Voucher must include &key= parameter'); process.exit(1); }
312
331
  if (!txo.amount) { console.error('Voucher must include &amount= parameter'); process.exit(1); }
@@ -334,6 +353,7 @@ async function cmdInit(args) {
334
353
  );
335
354
  const newTxid = await broadcastTx(rawTx, explorer);
336
355
 
356
+ if (voucherFile) consumeVoucher(voucherFile);
337
357
  savePrivateState({ txid: newTxid, vout: 0, amount: outputAmount }, chain);
338
358
  const xonly = pubkey.slice(2); // 64-char x-only Nostr pubkey
339
359
  // Insert @id first
@@ -455,6 +475,25 @@ async function cmdInfo() {
455
475
  }
456
476
  }
457
477
  } catch (e) {}
478
+
479
+ // Check chain depth safety
480
+ const numMarks = trail.states.length;
481
+ if (numMarks < 25) {
482
+ console.log(`Chain depth: safe (${numMarks} marks, under 25)`);
483
+ } else {
484
+ try {
485
+ const ancestorTxo = parseTxoUri(trail.txo[trail.txo.length - 24]);
486
+ const ancResp = await fetch(`${explorer}/tx/${ancestorTxo.txid}`);
487
+ if (ancResp.ok) {
488
+ const ancData = await ancResp.json();
489
+ if (ancData.status?.confirmed) {
490
+ console.log(`Chain depth: safe (ancestor at -24 confirmed)`);
491
+ } else {
492
+ console.log(`Chain depth: ⚠ ancestor at -24 unconfirmed`);
493
+ }
494
+ }
495
+ } catch (e) {}
496
+ }
458
497
  }
459
498
  }
460
499
  if (priv) {
@@ -516,7 +555,8 @@ function cmdUpdate() {
516
555
  export {
517
556
  taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
518
557
  pubkeyToAddress, parseTxoUri, p2trScript, buildTransaction,
519
- TRAIL_FILE, PRIVATE_FILE, CHAINS, isDirty, loadTrailFromNotes, loadFullTrail
558
+ TRAIL_FILE, PRIVATE_FILE, CHAINS, isDirty, loadTrailFromNotes, loadFullTrail,
559
+ resolveVoucher, consumeVoucher
520
560
  };
521
561
 
522
562
  // --- CLI ---
package/index.html CHANGED
@@ -68,6 +68,9 @@
68
68
  <pre># Initialize in a git repo
69
69
  git mark init --chain tbtc4 --voucher txo:tbtc4:txid:vout?amount=X&amp;key=Y
70
70
 
71
+ # Or read vouchers from a file (last line used, removed on success)
72
+ git mark init --voucher ~/faucet/vouchers.txt
73
+
71
74
  # Make commits, then mark them
72
75
  git commit -m "my change"
73
76
  git mark
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmark",
3
- "version": "0.0.75",
3
+ "version": "0.0.77",
4
4
  "type": "module",
5
5
  "description": "Anchor git commits to Bitcoin via blocktrails",
6
6
  "main": "bin/git-mark.js",
@@ -5,8 +5,12 @@ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
5
5
 
6
6
  import {
7
7
  taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
8
- pubkeyToAddress, parseTxoUri, p2trScript, CHAINS, isDirty, loadFullTrail, loadTrailFromNotes
8
+ pubkeyToAddress, parseTxoUri, p2trScript, CHAINS, isDirty, loadFullTrail, loadTrailFromNotes,
9
+ resolveVoucher, consumeVoucher
9
10
  } from '../bin/git-mark.js';
11
+ import { writeFileSync, readFileSync, mkdtempSync, rmSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { tmpdir } from 'os';
10
14
 
11
15
  describe('Key chaining', () => {
12
16
  const privkey = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
@@ -254,3 +258,105 @@ describe('TXO URI in git config format', () => {
254
258
  assert.strictEqual(parsed.amount, state.amount);
255
259
  });
256
260
  });
261
+
262
+ describe('Voucher resolution', () => {
263
+ const uri1 = 'txo:tbtc4:aaa111:0?amount=15568&key=c9cb5a57062b61afc58e5e76d8e587117ba06d2c4e9891ee658896a828e758a6';
264
+ const uri2 = 'txo:tbtc4:bbb222:1?amount=15568&key=c9cb5a57062b61afc58e5e76d8e587117ba06d2c4e9891ee658896a828e758a6';
265
+ const uri3 = 'txo:tbtc4:ccc333:2?amount=15568&key=c9cb5a57062b61afc58e5e76d8e587117ba06d2c4e9891ee658896a828e758a6';
266
+
267
+ it('returns URI as-is when not a file', () => {
268
+ const r = resolveVoucher(uri1);
269
+ assert.strictEqual(r.uri, uri1);
270
+ assert.strictEqual(r.file, null);
271
+ });
272
+
273
+ it('reads last line from .txt file', () => {
274
+ const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
275
+ try {
276
+ const path = join(dir, 'vouchers.txt');
277
+ writeFileSync(path, `${uri1}\n${uri2}\n${uri3}\n`);
278
+ const r = resolveVoucher(path);
279
+ assert.strictEqual(r.uri, uri3);
280
+ assert.strictEqual(r.file, path);
281
+ } finally {
282
+ rmSync(dir, { recursive: true, force: true });
283
+ }
284
+ });
285
+
286
+ it('reads last line when using file: prefix', () => {
287
+ const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
288
+ try {
289
+ const path = join(dir, 'vouchers'); // no .txt suffix
290
+ writeFileSync(path, `${uri1}\n${uri2}\n`);
291
+ const r = resolveVoucher(`file:${path}`);
292
+ assert.strictEqual(r.uri, uri2);
293
+ assert.strictEqual(r.file, path);
294
+ } finally {
295
+ rmSync(dir, { recursive: true, force: true });
296
+ }
297
+ });
298
+
299
+ it('expands ~ to home directory', () => {
300
+ const fakeHome = mkdtempSync(join(tmpdir(), 'gitmark-home-'));
301
+ const originalHome = process.env.HOME;
302
+ process.env.HOME = fakeHome;
303
+ try {
304
+ const path = join(fakeHome, 'vouchers.txt');
305
+ writeFileSync(path, `${uri1}\n`);
306
+ const r = resolveVoucher('~/vouchers.txt');
307
+ assert.strictEqual(r.uri, uri1);
308
+ assert.strictEqual(r.file, path);
309
+ } finally {
310
+ process.env.HOME = originalHome;
311
+ rmSync(fakeHome, { recursive: true, force: true });
312
+ }
313
+ });
314
+
315
+ it('exits with error on empty file', () => {
316
+ const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
317
+ const originalExit = process.exit;
318
+ const originalError = console.error;
319
+ let exitCode = null;
320
+ let errorMsg = null;
321
+ process.exit = (code) => { exitCode = code; throw new Error('exit called'); };
322
+ console.error = (msg) => { errorMsg = msg; };
323
+ try {
324
+ const path = join(dir, 'empty.txt');
325
+ writeFileSync(path, '');
326
+ assert.throws(() => resolveVoucher(path), /exit called/);
327
+ assert.strictEqual(exitCode, 1);
328
+ assert.match(errorMsg, /No vouchers/);
329
+ } finally {
330
+ process.exit = originalExit;
331
+ console.error = originalError;
332
+ rmSync(dir, { recursive: true, force: true });
333
+ }
334
+ });
335
+
336
+ it('consumeVoucher removes last line', () => {
337
+ const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
338
+ try {
339
+ const path = join(dir, 'vouchers.txt');
340
+ writeFileSync(path, `${uri1}\n${uri2}\n${uri3}\n`);
341
+ consumeVoucher(path);
342
+ const remaining = readFileSync(path, 'utf8').trim().split('\n');
343
+ assert.strictEqual(remaining.length, 2);
344
+ assert.strictEqual(remaining[0], uri1);
345
+ assert.strictEqual(remaining[1], uri2);
346
+ } finally {
347
+ rmSync(dir, { recursive: true, force: true });
348
+ }
349
+ });
350
+
351
+ it('consumeVoucher handles last remaining line', () => {
352
+ const dir = mkdtempSync(join(tmpdir(), 'gitmark-test-'));
353
+ try {
354
+ const path = join(dir, 'vouchers.txt');
355
+ writeFileSync(path, `${uri1}\n`);
356
+ consumeVoucher(path);
357
+ assert.strictEqual(readFileSync(path, 'utf8'), '');
358
+ } finally {
359
+ rmSync(dir, { recursive: true, force: true });
360
+ }
361
+ });
362
+ });