From c705809278952b995b15c4ad5cb303859d7e392b Mon Sep 17 00:00:00 2001
From: thebullishbitcoiner
<139607837+thebullishbitcoiner@users.noreply.github.com>
Date: Tue, 3 Dec 2024 23:09:04 -0600
Subject: [PATCH] (1) Added NDK 2.10.7 (2) Implemented Nut Splits (3) Removed
setDataOutput and corresponding GUI elements
---
package-lock.json | 242 +++++++++++++++++++++++++++-
package.json | 1 +
src/components/NutSplits.js | 171 ++++++++++++++++++++
src/components/NutSplits.module.css | 37 +++++
src/pages/index.js | 126 ++++++++-------
src/styles/globals.css | 70 ++++++--
6 files changed, 569 insertions(+), 78 deletions(-)
create mode 100644 src/components/NutSplits.js
create mode 100644 src/components/NutSplits.module.css
diff --git a/package-lock.json b/package-lock.json
index c7fffe3..ee80987 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@noble/secp256k1": "^2.1.0",
+ "@nostr-dev-kit/ndk": "^2.10.7",
"bech32-buffer": "^0.2.1",
"bitcoin-qr": "^1.4.1",
"js-confetti": "^0.12.0",
@@ -2251,6 +2252,27 @@
"node": ">= 8"
}
},
+ "node_modules/@nostr-dev-kit/ndk": {
+ "version": "2.10.7",
+ "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.10.7.tgz",
+ "integrity": "sha512-cylva8jsaAGMijxAI32CnJWlzvwD4sWyl86/+RMS6xpZn4MIgeVUfBFc/pYkcfZzDP3v1Z9mIPsuiICRyvu9yQ==",
+ "dependencies": {
+ "@noble/curves": "^1.6.0",
+ "@noble/hashes": "^1.5.0",
+ "@noble/secp256k1": "^2.1.0",
+ "@scure/base": "^1.1.9",
+ "debug": "^4.3.6",
+ "light-bolt11-decoder": "^3.2.0",
+ "nostr-tools": "^2.7.1",
+ "tseep": "^1.2.2",
+ "typescript-lru-cache": "^2.0.0",
+ "utf8-buffer": "^1.0.0",
+ "websocket-polyfill": "^0.0.3"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -3385,6 +3407,18 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "node_modules/bufferutil": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
+ "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
@@ -3684,6 +3718,18 @@
"node": ">=4"
}
},
+ "node_modules/d": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
+ "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
+ "dependencies": {
+ "es5-ext": "^0.10.64",
+ "type": "^2.7.2"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -3739,11 +3785,11 @@
}
},
"node_modules/debug": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
- "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
- "ms": "2.1.2"
+ "ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -4157,6 +4203,43 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es5-ext": {
+ "version": "0.10.64",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
+ "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "es6-iterator": "^2.0.3",
+ "es6-symbol": "^3.1.3",
+ "esniff": "^2.0.1",
+ "next-tick": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+ "dependencies": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "node_modules/es6-symbol": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
+ "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
+ "dependencies": {
+ "d": "^1.0.2",
+ "ext": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@@ -4530,6 +4613,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esniff": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
+ "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
+ "dependencies": {
+ "d": "^1.0.1",
+ "es5-ext": "^0.10.62",
+ "event-emitter": "^0.3.5",
+ "type": "^2.7.2"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -4591,6 +4688,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/event-emitter": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+ "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
+ "dependencies": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ },
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -4600,6 +4706,14 @@
"node": ">=0.8.x"
}
},
+ "node_modules/ext": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
+ "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
+ "dependencies": {
+ "type": "^2.7.2"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5587,6 +5701,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
+ },
"node_modules/is-weakmap": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
@@ -6105,9 +6224,9 @@
}
},
"node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/mz": {
"version": "2.7.0",
@@ -6498,6 +6617,11 @@
"webpack": "^4.4.0 || ^5.9.0"
}
},
+ "node_modules/next-tick": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -6525,6 +6649,16 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-gyp-build": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@@ -8376,11 +8510,26 @@
"strip-bom": "^3.0.0"
}
},
+ "node_modules/tseep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.3.1.tgz",
+ "integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ=="
+ },
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
},
+ "node_modules/tstl": {
+ "version": "2.5.16",
+ "resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.16.tgz",
+ "integrity": "sha512-+O2ybLVLKcBwKm4HymCEwZIT0PpwS3gCYnxfSDEjJEKADvIFruaQjd3m7CAKNU1c7N3X3WjVz87re7TA2A5FUw=="
+ },
+ "node_modules/type": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
+ "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -8474,6 +8623,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/typedarray-to-buffer": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "dependencies": {
+ "is-typedarray": "^1.0.0"
+ }
+ },
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
@@ -8488,6 +8645,11 @@
"node": ">=14.17"
}
},
+ "node_modules/typescript-lru-cache": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz",
+ "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA=="
+ },
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -8608,6 +8770,26 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/utf-8-validate": {
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
+ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
+ "node_modules/utf8-buffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/utf8-buffer/-/utf8-buffer-1.0.0.tgz",
+ "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -8728,6 +8910,44 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/websocket": {
+ "version": "1.0.35",
+ "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz",
+ "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==",
+ "dependencies": {
+ "bufferutil": "^4.0.1",
+ "debug": "^2.2.0",
+ "es5-ext": "^0.10.63",
+ "typedarray-to-buffer": "^3.1.5",
+ "utf-8-validate": "^5.0.2",
+ "yaeti": "^0.0.6"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/websocket-polyfill": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
+ "integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
+ "dependencies": {
+ "tstl": "^2.0.7",
+ "websocket": "^1.0.28"
+ }
+ },
+ "node_modules/websocket/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/websocket/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
"node_modules/whatwg-url": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
@@ -9291,6 +9511,14 @@
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
+ "node_modules/yaeti": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
+ "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
+ "engines": {
+ "node": ">=0.10.32"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index d6a07f2..4418440 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@noble/secp256k1": "^2.1.0",
+ "@nostr-dev-kit/ndk": "^2.10.7",
"bech32-buffer": "^0.2.1",
"bitcoin-qr": "^1.4.1",
"js-confetti": "^0.12.0",
diff --git a/src/components/NutSplits.js b/src/components/NutSplits.js
new file mode 100644
index 0000000..48d45a0
--- /dev/null
+++ b/src/components/NutSplits.js
@@ -0,0 +1,171 @@
+import React, { useState, useEffect } from 'react';
+import NDK from "@nostr-dev-kit/ndk";
+import { decode } from 'bech32-buffer';
+import { bytesToHex } from '@noble/hashes/utils';
+import { SearchIcon } from "@bitcoin-design/bitcoin-icons-react/filled";
+import styles from './NutSplits.module.css';
+
+const NutSplits = ({ onSendNuts, onClose }) => {
+ const [noteId, setNoteId] = useState('');
+ const [commenters, setCommenters] = useState([]);
+ const [selectedCommenters, setSelectedCommenters] = useState({});
+ const [ndk, setNdk] = useState(null);
+ const [loading, setLoading] = useState(false); // New loading state
+
+ useEffect(() => {
+ const ndkInstance = new NDK({ explicitRelayUrls: ["wss://relay.nostr.band", "wss://relay.damus.io", "wss://relay.primal.net"] });
+
+ const connectNdk = async () => {
+ try {
+ await ndkInstance.connect();
+ setNdk(ndkInstance);
+ } catch (error) {
+ console.error("Failed to connect to NDK:", error);
+ }
+ };
+
+ connectNdk();
+ }, []);
+
+ const handleSearch = async () => {
+ try {
+ if (!ndk) {
+ console.error("NDK is not initialized");
+ return;
+ }
+
+ let decodedData = decode(noteId);
+ let hexID = bytesToHex(decodedData.data);
+
+ setLoading(true); // Set loading to true before fetching
+ try {
+ const fetchedCommenters = await fetchCommenters(hexID);
+ setCommenters(fetchedCommenters);
+ } catch (error) {
+ console.error("Failed to fetch commenters:", error);
+ } finally {
+ setLoading(false); // Set loading to false after fetching
+ }
+ } catch (error) {
+ console.error("Search failed:", error);
+ }
+ };
+
+ const fetchCommenters = async (noteId) => {
+ const filter2 = { kinds: [1], ['#e']: [noteId] };
+
+ let events;
+ try {
+ events = await ndk.fetchEvents(filter2);
+ } catch (error) {
+ console.error("Failed to fetch events:", error);
+ return [];
+ }
+
+ const commenters = [];
+ const existingNpubs = new Set();
+
+ for (const event of events) {
+ let currentPubkey = event.pubkey;
+ let currentNpub = event.author.npub;
+
+ if (!existingNpubs.has(currentNpub)) {
+ let currentUser = ndk.getUser({ npub: currentNpub });
+ await currentUser.fetchProfile();
+
+ const commenter = {
+ name: currentUser.profile.name,
+ npub: currentNpub,
+ pubkey: currentPubkey,
+ };
+
+ commenters.push(commenter);
+ existingNpubs.add(currentNpub);
+ }
+ }
+
+ return commenters;
+ };
+
+ const handleCheckboxChange = (id) => {
+ setSelectedCommenters((prev) => ({
+ ...prev,
+ [id]: !prev[id],
+ }));
+ };
+
+ const handleSendNuts = () => {
+ const selected = Object.keys(selectedCommenters).filter(id => selectedCommenters[id]);
+ onSendNuts(selected);
+ };
+
+ const handleSelectAll = () => {
+ const allSelected = {};
+ commenters.forEach(commenter => {
+ allSelected[commenter.npub] = true;
+ });
+ setSelectedCommenters(allSelected);
+ };
+
+ const handleDeselectAll = () => {
+ setSelectedCommenters({});
+ };
+
+ const handleClose = () => {
+ setNoteId('');
+ setCommenters([]);
+ setSelectedCommenters({});
+ onClose();
+ };
+
+ return (
+
+
+
×
+
Nut Splits
+
+ setNoteId(e.target.value)}
+ placeholder="Enter Nostr Note ID"
+ />
+
+
+
+ {loading ? (
+
+ Loading
+ .
+ .
+ .
+
+ ) : (
+ commenters.map(commenter => (
+
+ handleCheckboxChange(commenter.npub)}
+ />
+ {commenter.name ? commenter.name : commenter.npub}
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default NutSplits;
+
diff --git a/src/components/NutSplits.module.css b/src/components/NutSplits.module.css
new file mode 100644
index 0000000..73334bd
--- /dev/null
+++ b/src/components/NutSplits.module.css
@@ -0,0 +1,37 @@
+.loading-animation {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%; /* Adjust as needed */
+ font-size: 20px; /* Adjust as needed */
+ color: #ff9900; /* Loading text color */
+}
+
+.dot {
+ animation: blink 1s infinite;
+ font-size: 24px; /* Adjust size of dots */
+}
+
+.dot:nth-child(1) {
+ animation-delay: 0s;
+}
+
+.dot:nth-child(2) {
+ animation-delay: 0.2s; /* Delay for the second dot */
+}
+
+.dot:nth-child(3) {
+ animation-delay: 0.4s; /* Delay for the third dot */
+}
+
+@keyframes blink {
+ 0%, 20% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
diff --git a/src/pages/index.js b/src/pages/index.js
index 6c69e58..ecf5767 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -9,6 +9,7 @@ import Mints from "@/components/Mints";
import EcashOrLightning from "@/components/EcashOrLightning";
import Transactions from "@/components/Transactions";
import QRCodeScanner from '@/components/QRCodeScanner';
+import NutSplits from '@/components/NutSplits';
import QRCode from 'qrcode';
import JSConfetti from 'js-confetti';
@@ -43,6 +44,9 @@ const Wallet = () => {
const [isScanQRModalOpen, setIsScanQRModalOpen] = useState(false);
const [scannedData, setScannedData] = useState('');
+ // For NutSplits component
+ const [isNutSplitsModalOpen, setIsNutSplitsModalOpen] = useState(false);
+
const [typewriterMessages, setTypewriterMessages] = useState([]);
const [isTypewriterModalOpen, setIsTypewriterModalOpen] = useState(false);
@@ -66,13 +70,6 @@ const Wallet = () => {
}
};
- const [formData, setFormData] = useState({
- mintUrl: "",
- meltInvoice: "",
- swapAmount: "",
- });
- const [dataOutput, setDataOutput] = useState(null);
-
/**
* @type {[CashuWallet|null, React.Dispatch>]}
*/
@@ -102,8 +99,6 @@ const Wallet = () => {
if (savedState !== null) {
setIsBalanceHidden(savedState === 'true');
}
-
- setFormData((prevData) => ({ ...prevData, mintUrl: url }));
}
else {
const introMessages = [
@@ -117,19 +112,10 @@ const Wallet = () => {
}
}, []);
- const handleChange = (e) => {
- const { name, value } = e.target;
- setFormData((prevData) => ({
- ...prevData,
- [name]: value,
- }));
- };
-
const handleMintChange = async (newMint) => {
try {
const mint = new CashuMint(newMint);
const info = await mint.getInfo();
- setDataOutput(info);
const { keysets } = await mint.getKeys();
const satKeyset = keysets.find((k) => k.unit === "sat");
@@ -154,7 +140,6 @@ const Wallet = () => {
const handleQRScan = (data) => {
setScannedData(data);
setIsScanQRModalOpen(false); // Close the modal after scanning
- setDataOutput(data);
// Handle scanned data
if (data.startsWith('cashu')) { // Cashu token
@@ -190,7 +175,6 @@ const Wallet = () => {
//const quote = await wallet.getMintQuote(amount);
const quote = await wallet.createMintQuote(amount);
- setDataOutput(quote);
storeJSON(quote);
//Close the receive Lightning modal just before showing the invoice modal
@@ -202,7 +186,6 @@ const Wallet = () => {
const { proofs } = await wallet.mintTokens(amount, quote.quote, {
keysetId: wallet.keys.id,
});
- setDataOutput({ "minted proofs": proofs });
//Add new proofs to local storage
var proofsArray = { "proofs": proofs };
@@ -218,7 +201,6 @@ const Wallet = () => {
clearInterval(intervalId);
} catch (error) {
console.error("Quote probably not paid: ", quote.request, error);
- setDataOutput({ timestamp: new Date().toLocaleTimeString(), error: "Failed to mint", details: error });
}
}, 5000);
}
@@ -255,7 +237,6 @@ const Wallet = () => {
// Use the info from localStorage
const mintInfo = storedMintInfo[mintURL];
- setDataOutput(mintInfo);
storeJSON(mintInfo);
const { keysets } = await mint.getKeys();
@@ -273,7 +254,7 @@ const Wallet = () => {
return;
} catch (error) {
console.error(error);
- setDataOutput({ error: "Failed to receive ecash.", details: error });
+ storeJSON({ error: "Failed to receive ecash.", details: error });
}
}
@@ -284,7 +265,6 @@ const Wallet = () => {
addTransaction_Ecash("Receive", mintURL, totalAmount, token);
showToast(`Received ${totalAmount} ${totalAmount === 1 ? 'sat' : 'sats'}!`);
- setDataOutput(proofs);
storeJSON(proofs);
} catch (error) {
console.error(error);
@@ -326,11 +306,9 @@ const Wallet = () => {
removeProofs(proofs, wallet.mint.mintUrl);
addProofs(returnChange, wallet.mint.mintUrl);
addTransaction_Ecash("Send", wallet.mint.mintUrl, amount, encodedToken);
-
- setDataOutput(encodedToken);
} catch (error) {
console.error(error);
- setDataOutput({ error: "Failed to send ecash", details: error });
+ storeJSON({ error: "Failed to send ecash", details: error });
}
}
@@ -447,7 +425,7 @@ const Wallet = () => {
}
catch (error) {
console.error(error);
- setDataOutput({ error: "Failed to melt tokens", details: error });
+ storeJSON({ error: "Failed to melt tokens", details: error });
}
}
@@ -507,7 +485,6 @@ const Wallet = () => {
//Wait to get melt quote from mint
const quote = await wallet.createMeltQuote(invoice);
document.getElementById('waiting_message').textContent = "Getting melt quote...";
- setDataOutput([{ "got melt quote": quote }]);
storeJSON(quote);
const amount = quote.amount + quote.fee_reserve;
@@ -600,7 +577,6 @@ const Wallet = () => {
//Wait to get melt quote from mint
const quote = await wallet.createMeltQuote(invoice);
document.getElementById('waiting_message').textContent = "Getting melt quote...";
- setDataOutput([{ "got melt quote": quote }]);
storeJSON(quote);
const amount = quote.amount + quote.fee_reserve;
@@ -636,17 +612,14 @@ const Wallet = () => {
} catch (error) {
console.error(error);
showToast(error);
- setDataOutput({ error: "Failed to zap deez nuts", details: error });
+ storeJSON({ error: "Failed to zap deez nuts", details: error });
}
} // End zapDeezNuts
- async function sendNuts(npub = 'npub1cashuq3y9av98ljm2y75z8cek39d8ux6jk3g6vafkl5j0uj4m5ks378fhq') {
+ async function sendNuts(npub, amount, message) {
let decodedData = decode(npub);
let hexPubKey = bytesToHex(decodedData.data);
- let { amount, message } = await showSendNutsModal(npub);
- closeSendNutsModal();
-
const storedMintData = JSON.parse(localStorage.getItem("activeMint"));
const { url, keyset } = storedMintData;
@@ -690,24 +663,16 @@ const Wallet = () => {
const encodedToken = getEncodedToken(tokenData)
// Send the message and token separately
- if (message){
+ if (message) {
sendEncryptedMessage(hexPubKey, message);
}
sendEncryptedMessage(hexPubKey, encodedToken);
removeProofs(proofs, url);
addProofs(returnChange, wallet.mint.mintUrl);
-
- // Confetti is reserved for donations
- if (npub === 'npub1cashuq3y9av98ljm2y75z8cek39d8ux6jk3g6vafkl5j0uj4m5ks378fhq'){
- showToast("Succesfully sent nuts! Thank you!");
- showConfetti(amount);
- } else {
- showToast(`${amount} sats sent to ${npub} via Nostr DM`)
- }
} catch (error) {
console.error(error);
- setDataOutput({ error: true, details: error });
+ storeJSON({ error: true, details: error });
}
}
@@ -763,7 +728,7 @@ const Wallet = () => {
await Promise.any(pool.publish(relays, signedEvent));
} catch (error) {
console.error(error);
- setDataOutput({ error: true, details: error });
+ storeJSON({ error: true, details: error });
}
}
@@ -1145,7 +1110,6 @@ const Wallet = () => {
modal.style.display = 'block';
}
-
function closeReceiveEcashModal() {
const modal = document.getElementById('receive_ecash_modal');
document.getElementById('cashu_token').value = '';
@@ -1253,10 +1217,17 @@ const Wallet = () => {
localStorage.setItem('json', JSON.stringify(existingData));
}
- //Gets the npub for the selected contact, appends "@npub.cash", and copies it to the clipboard
- const handleContactSelect = (contact) => {
- let npub = contact.npub;
- sendNuts(npub);
+ const handleContactSelect = async (contact) => {
+ let npub = contact.npub; // Move this line up to define npub before using it
+
+ let { amount, message } = await showSendNutsModal(npub);
+ closeSendNutsModal();
+
+ sendNuts(npub, amount, message);
+
+ showToast(`${amount} sats sent to ${npub} via Nostr DM`)
+
+ // Uncomment the following lines if you want to copy the contact address to clipboard
// const contactAddress = `${contact.npub}@npub.cash`;
// navigator.clipboard.writeText(contactAddress).then(() => {
// showToast(`Copied to clipboard: ${contactAddress}`);
@@ -1265,6 +1236,18 @@ const Wallet = () => {
// });
};
+ const handleSendNuts = async () => {
+ let npub = 'npub1cashuq3y9av98ljm2y75z8cek39d8ux6jk3g6vafkl5j0uj4m5ks378fhq';
+
+ let { amount, message } = await showSendNutsModal(npub);
+ closeSendNutsModal();
+
+ sendNuts(npub, amount, message);
+
+ showToast("Succesfully sent nuts! Thank you!");
+ showConfetti(amount);
+ };
+
const exportJSON = () => {
const existingData = JSON.parse(localStorage.getItem('json')) || {};
const dataStr = JSON.stringify(existingData);
@@ -1358,13 +1341,36 @@ const Wallet = () => {
localStorage.setItem('isBalanceHidden', newState);
};
+ const closeNutSplitsModal = () => {
+ setIsNutSplitsModalOpen(false);
+ }
+
+ const handleNutSplit = (selectedCommenters) => {
+ setIsNutSplitsModalOpen(false); // Close the modal
+
+ let amount = 1;
+ let message = "This is a test nut!";
+
+ console.log('Selected Commenters:', selectedCommenters);
+
+ // Loop through each commenter
+ selectedCommenters.forEach((npub) => {
+ sendNuts(npub, amount, message);
+ // Handle each commenter as needed
+ console.log('Nut(s) sent to:', npub);
+ // You can perform actions with each commenter here
+ });
+
+ showToast(`Sent ${amount} sat(s) each to ${selectedCommenters.length} recipients`);
+ };
+
return (
-
v0.2.62
+
v0.2.63
@@ -1540,21 +1546,27 @@ const Wallet = () => {
-
+
Advanced
-
Data Output
-
{JSON.stringify(dataOutput, null, 2)}
-
+
+
+ {isNutSplitsModalOpen && (
+
+ )}
+
diff --git a/src/styles/globals.css b/src/styles/globals.css
index d2ecb85..513230a 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -199,20 +199,6 @@ main {
font-weight: bold;
}
-.data-output {
- background-color: #111111;
- color: #ff9900;
- padding: 10px;
- border: 1px solid #ff9900;
- white-space: pre-wrap;
- overflow: scroll;
- max-width: 100%;
- height: 300px;
- max-height: 300px;
- word-wrap: break-word;
- margin: 10px 0px 10px 0px;
-}
-
@layer utilities {
.text-balance {
text-wrap: balance;
@@ -463,6 +449,20 @@ main {
background-color: rgba(0, 0, 0, 0.8);
}
+ .nut_splits_modal {
+ display: block;
+ justify-content: center;
+ align-items: center;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0, 0, 0, 0.8);
+ }
+
.input-container {
display: flex;
align-items: flex-start;
@@ -581,6 +581,40 @@ main {
left: 3px;
}
+ input[type="checkbox"] {
+ appearance: none;
+ -webkit-appearance: none; /* For Safari */
+ min-width: 21px; /* Set width */
+ min-height: 21px; /* Set height */
+ border: 2px solid #FF9900;
+ border-radius: 0px;
+ outline: none;
+ cursor: pointer;
+ background-color: #111111;
+ position: relative;
+
+}
+
+input[type="checkbox"]:checked {
+ background-color: #FF9900;
+}
+
+input[type="checkbox"]:checked::after {
+ content: '✓';
+ font-size: 16px; /* Adjust font size as needed */
+ color: #111111;
+ position: absolute;
+ top: -2px; /* Adjust position as needed */
+ left: 4px; /* Adjust position as needed */
+}
+
+/* Optional: Add a hover effect */
+input[type="checkbox"]:hover {
+ border-color: #FFCC00; /* Change border color on hover */
+}
+
+
+
.ecash_or_lightning_modal {
display: block;
justify-content: center;
@@ -603,6 +637,14 @@ main {
background-color: #111111;
}
+ #nut_splits_input {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #ff9900;
+ border-radius: 0px;
+ background-color: #111111;
+ }
+
/* Progress bar container */
.progress-bar-container {
width: 100%;