commit dc0a952140580b8e90152167c2d63d434495fcb8
Author: stephen <stephen@vanderwarker.family>
Date:   Tue Dec 24 17:45:28 2024 -0500

    LFG

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ceaea36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,132 @@
+# ---> Node
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6dbc2d0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# tickstr
+
+A ChatGPT pipe dreamed script for:
+
+https://github.com/nostr-protocol/nips/pull/1658
+
+ 1. <code>git clone https://git.vanderwarker.family/nostr/tickstr</code>
+ 2. <code>cd tickstr</code>
+ 3. <code>npm i</code>
+ 4. <code>edit settings.json</code>
+ 5. <code>nodejs main.js</code>
+ 6. <code>????</code>
+ 7. <code>PROFIT!</code>
+ 8. <code>Remember, that one sat is one sat so it doesn't matter</code>
+ 9. <code>cd ..</code>
+ 10. <code>rm tickstr</code>
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..27feb48
--- /dev/null
+++ b/main.js
@@ -0,0 +1,142 @@
+const WebSocket = require('ws');
+const crypto = require('crypto');
+const elliptic = require('elliptic');
+const Buffer = require('safe-buffer').Buffer
+const schnorr = require('bip-schnorr');
+
+// Use elliptic for ECDSA signing
+const EC = elliptic.ec;
+const ecdsa = new EC('secp256k1');
+
+// Your Nostr private key (replace with your own)
+const privateKeyHex = ''; // Use your actual private key here
+
+// Ensure private key is a valid 32-byte hex string
+if (!/^([0-9a-fA-F]{64})$/.test(privateKeyHex)) {
+  console.error('Invalid private key format. Ensure it is 64 hexadecimal characters.');
+  process.exit(1);
+}
+
+const publicKeyHex = generatePublicKey(privateKeyHex);
+
+// Connect to Binance WebSocket
+const ws = new WebSocket('wss://stream.binance.com:9443/ws/btcusdt@ticker');
+
+// Handle WebSocket connection and data
+ws.on('open', function open() {
+  console.log('Connected to Binance WebSocket');
+});
+
+ws.on('message', function incoming(message) {
+  const data = JSON.parse(message);
+
+  // Extract relevant data
+  const symbol = data.s;
+  const lastPrice = parseFloat(data.c);
+  const weightedAvgPrice = parseFloat(data.w);
+  const highPrice = parseFloat(data.h);
+  const lowPrice = parseFloat(data.l);
+
+  // Create Nostr event
+  const timestamp = Math.floor(Date.now() / 1000);
+  const eventContent = JSON.stringify({
+    symbol,
+    last_price: lastPrice,
+    weighted_avg_price: weightedAvgPrice,
+    high_price: highPrice,
+    low_price: lowPrice
+  });
+
+  const event = {
+    id: '',
+    pubkey: publicKeyHex,  // 32-byte public key
+    created_at: timestamp,
+    kind: 31892,  // Note (you can change this based on your event type)
+    tags: [['d','BTCUSD'],['n', 'BTC'],['n','USD'],['value',lastPrice]],  // Optional tag (you can customize this)
+    content: eventContent,
+    sig: ''  // Initialize sig here, it will be added after content
+  };
+
+  // Compute event ID (sha256 of serialized event)
+  const serializedEvent = serializeEvent(publicKeyHex, timestamp, event.kind, event.tags, eventContent);
+  const eventId = crypto.createHash('sha256').update(serializedEvent).digest('hex');
+  event.id = eventId;
+
+  // Sign the event using elliptic library
+  event.sig = signEvent(privateKeyHex, serializedEvent, event.id);
+
+  // Print the Nostr event with signature under content
+  console.log('Nostr Event:', JSON.stringify(event, null, 2));
+
+  // Optionally, broadcast the event to a Nostr relay (example)
+  sendToRelay(event);
+});
+
+// Function to generate compressed public key from private key using elliptic
+function generatePublicKey(privateKeyHex) {
+  const privateKey = Buffer.from(privateKeyHex, 'hex');
+
+  if (privateKey.length !== 32) {
+    console.error('Private key must be 32 bytes.');
+    process.exit(1);
+  }
+
+  const key = ecdsa.keyFromPrivate(privateKey);
+  const publicKey = key.getPublic(true, 'hex');  // Compressed format
+
+  return publicKey.slice(2); // Remove the '0x' prefix to get 32 bytes
+}
+
+// Function to serialize the event according to NIP-01
+function serializeEvent(pubkey, createdAt, kind, tags, content) {
+  const eventData = [
+    0,  // Prefix for NIP-01 event serialization
+    pubkey,
+    createdAt,
+    kind,
+    tags,
+    content
+  ];
+
+  // Serialize the event to JSON string
+  let eventJson = JSON.stringify(eventData, (key, value) => {
+    // Escape content field (you can also add custom escaping here if necessary)
+    if (key === 'content') {
+      value = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+    }
+    return value;
+  });
+
+  return eventJson;
+}
+
+// Function to sign the event with the private key using elliptic
+function signEvent(privateKeyHex, serializedEvent, eventID) {
+  const message = Buffer.from(eventID, 'hex');
+  const signature = schnorr.sign(privateKeyHex, message);
+  return signature.toString('hex');
+}
+
+// Function to send the event to a Nostr relay
+function sendToRelay(event) {
+  // Example relay URL (replace with the actual Nostr relay URL)
+  const relayUrl = '';  // Replace with your relay URL
+
+  // Create a WebSocket connection to the relay
+  const relayWs = new WebSocket(relayUrl);
+
+  relayWs.on('open', () => {
+    // Once the connection is open, send the event
+    const eventMessage = ['EVENT', event];  // Relay expects an array with 'EVENT' and event data
+    relayWs.send(JSON.stringify(eventMessage));
+    console.log('Event sent to relay:', JSON.stringify(event, null, 2));
+  });
+
+  relayWs.on('error', (err) => {
+    console.error('Error sending event to relay:', err);
+  });
+
+  relayWs.on('close', () => {
+    console.log('Connection to relay closed');
+  });
+}