diff --git a/README.md b/README.md
index 604035d..facb9ff 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-nostr-army-knife single-page app built with scalajs.
+# nostr army knife
diff --git a/build.js b/build.js
deleted file mode 100755
index e0e5bdb..0000000
--- a/build.js
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env node
-
-const esbuild = require('esbuild')
-const alias = require('esbuild-plugin-alias')
-const nodeGlobals = require('@esbuild-plugins/node-globals-polyfill').default
-
-const prod = process.argv.indexOf('prod') !== -1
-
-esbuild
- .build({
- entryPoints: ['globals.js'],
- outfile: 'globals.bundle.js',
- bundle: true,
- plugins: [
- alias({
- stream: require.resolve('readable-stream')
- }),
- nodeGlobals({buffer: true})
- ],
- define: {
- window: 'self',
- global: 'self'
- },
- sourcemap: prod ? false : 'inline',
- minify: prod
-})
- .then(() => console.log('build success.'))
diff --git a/build.sbt b/build.sbt
index 65a08e0..4e494a0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,14 +1,9 @@
enablePlugins(ScalaJSPlugin)
-name := "app"
+name := "nostr-army-knife"
scalaVersion := "2.13.7"
scalaJSUseMainModuleInitializer := true
-libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.1.0"
-libraryDependencies += "me.shadaj" %%% "slinky-core" % "0.7.0"
-libraryDependencies += "me.shadaj" %%% "slinky-web" % "0.7.0"
-libraryDependencies ++= Seq(
- "io.circe" %%% "circe-core",
- "io.circe" %%% "circe-parser"
-).map(_ % "0.14.1")
+libraryDependencies += "com.armanbilge" %%% "calico" % "0.2.0-RC2"
+libraryDependencies += "com.fiatjaf" %%% "snow" % "0.2.0-RC2"
diff --git a/globals.js b/globals.js
deleted file mode 100644
index 8b549f1..0000000
--- a/globals.js
+++ /dev/null
@@ -1,3 +0,0 @@
-window.React = require('react')
-window.ReactDOM = require('react-dom')
-window.Nostr = require('nostr-tools')
diff --git a/index.html b/index.html
index 8613978..54a4ee1 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,6 @@
-
nostr army knife - nak
-
-
-
+nostr army knife
+
+
+
+
diff --git a/package.json b/package.json
deleted file mode 100644
index 7d71c19..0000000
--- a/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "scripts": {
- "start": "./build.js && sbt ~fastLinkJS",
- "build": "./build.js prod && ./sbtx fullLinkJS && mkdir -p dist/target/scala-2.13/app-fastopt/ && cp index.html globals.bundle.js dist/ && cp target/scala-2.13/app-opt/main.js dist/target/scala-2.13/app-fastopt/"
- },
- "dependencies": {
- "@esbuild-plugins/node-globals-polyfill": "^0.1.1",
- "esbuild": "^0.14.23",
- "esbuild-plugin-alias": "^0.2.1",
- "events": "^3.3.0",
- "nostr-tools": "^0.22.2",
- "react": "^17.0.2",
- "react-dom": "^17.0.2"
- }
-}
diff --git a/project/build.properties b/project/build.properties
index 10fd9ee..22af262 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=1.5.5
+sbt.version=1.7.1
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 4931e02..dabefb2 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1 +1,2 @@
-addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.9.0")
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0")
+addSbtPlugin("com.fiatjaf" % "sbt-esbuild" % "0.1.1")
diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/main/scala/Main.scala
@@ -0,0 +1 @@
+
diff --git a/src/main/scala/app/Base.scala b/src/main/scala/app/Base.scala
deleted file mode 100644
index 2e9e261..0000000
--- a/src/main/scala/app/Base.scala
+++ /dev/null
@@ -1,65 +0,0 @@
-package app
-
-import scala.scalajs.js
-import org.scalajs.dom
-import slinky.core.FunctionalComponent
-import slinky.web.html._
-import slinky.core.facade.Hooks._
-
-import app.handlers.{Handler, Nothing, KeyHandling, EventSignatures}
-
-object Base {
- val handlers: List[Handler] = List(KeyHandling, EventSignatures, Nothing)
-
- val component = FunctionalComponent[Unit] { props =>
- val (typedValue, setTypedValue) = useState("")
-
- useEffect(
- () => {
- val saved = dom.window.localStorage.getItem("value")
- setTypedValue(saved match { case _: String => saved; case _ => "" })
- },
- Seq()
- )
-
- useEffect(
- () => {
- dom.window.localStorage.setItem("value", typedValue)
- },
- Seq(typedValue)
- )
-
- val Handler = handlers
- .find(handler => handler.handles(typedValue))
- .getOrElse(Nothing)
-
- div(
- style := js.Dynamic.literal(
- fontFamily = "monospace"
- )
- )(
- div(
- h1("nostr army knife"),
- p("paste something nostric"),
- textarea(
- value := typedValue,
- onChange := { ev => setTypedValue(ev.target.value) },
- style := js.Dynamic.literal(
- padding = "7px",
- width = "100%",
- minHeight = "200px"
- )
- )
- ),
- hr(style := js.Dynamic.literal(margin = "18px 0")),
- div(
- style := js.Dynamic.literal(
- width = "90%",
- margin = "auto"
- )
- )(
- Handler.component(typedValue)
- )
- )
- }
-}
diff --git a/src/main/scala/app/components/Item.scala b/src/main/scala/app/components/Item.scala
deleted file mode 100644
index fb3ee94..0000000
--- a/src/main/scala/app/components/Item.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-package app.components
-
-import scala.scalajs.js
-import slinky.core.{FunctionalComponent, CustomAttribute}
-import slinky.web.html._
-
-object Item {
- val wenkValue = CustomAttribute[String]("data-wenk")
- val wenkPos = CustomAttribute[String]("data-wenk-pos")
-
- case class props(label: String, hint: String, content: String)
-
- val component = FunctionalComponent[props] { props =>
- div(
- style := js.Dynamic.literal(
- marginBottom = "9px",
- whiteSpace = "pre-wrap",
- wordWrap = "break-word",
- wordBreak = "break-all"
- )
- )(
- b(wenkValue := props.hint, wenkPos := "right")(
- span(props.label)
- ),
- span(" "),
- span(props.content)
- )
- }
-}
diff --git a/src/main/scala/app/handlers/EventSignatures.scala b/src/main/scala/app/handlers/EventSignatures.scala
deleted file mode 100644
index ea391dd..0000000
--- a/src/main/scala/app/handlers/EventSignatures.scala
+++ /dev/null
@@ -1,204 +0,0 @@
-package app.handlers
-
-import scala.annotation.{nowarn, tailrec}
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
-import scala.util.{Failure, Success}
-import scala.scalajs.js
-import slinky.core.FunctionalComponent
-import slinky.web.html._
-import slinky.core.facade.Hooks._
-import slinky.core.facade.Fragment
-import io.circe.{Json, HCursor}
-import io.circe.parser.{parse}
-
-import app.modules.Nostr
-import app.handlers.{Handler}
-import app.components.{Item}
-
-object EventSignatures extends Handler {
- val keymatcher = "^[a-f0-9]{64}$".r
-
- def badProperties(c: HCursor): Seq[String] = Seq(
- (
- c.get[Double]("kind").getOrElse[Double](-1) >= 0 match {
- case true => None;
- case false => Some("kind")
- }
- ),
- (
- keymatcher.matches(
- c.get[String]("pubkey").getOrElse("").toLowerCase()
- ) match {
- case true => None;
- case false => Some("pubkey")
- }
- ),
- (
- c.get[String]("content").exists((_) => true) match {
- case true => None;
- case false => Some("content")
- }
- ),
- (
- c
- .get[List[List[String]]]("tags")
- .exists((_) => true) match {
- case true => None;
- case false => Some("tags")
- }
- )
- )
- .filter(res => res.isDefined)
- .map(res => res.get)
-
- override def handles(value: String): Boolean = parse(value) match {
- case Left(_) => false
- case Right(json) => {
- badProperties(json.hcursor).length < 4
- }
- }
-
- type MaybeItem = Future[
- Either[slinky.core.TagMod[Nothing], slinky.core.TagMod[Nothing]]
- ]
-
- @nowarn("cat=other")
- def itemWrongProperties(evtj: String): MaybeItem = Future {
- val c = parse(evtj).toOption.get.hcursor
- val bad = badProperties(c)
-
- if (bad.length > 0) {
- Left(
- Item.component(
- Item.props(
- "event missing or wrong properties",
- "",
- bad.mkString(", ")
- )
- )
- )
- } else {
- Right(div())
- }
- }
-
- @nowarn("cat=other")
- def itemSerializedEvent(evtj: String): MaybeItem = Future {
- val event: js.Dynamic = js.JSON.parse(evtj)
-
- Right(
- Item.component(
- Item.props(
- "serialized event",
- "according to nip-01 signature algorithm",
- Nostr.serializeEvent(event)
- )
- )
- )
- }
-
- @nowarn("cat=other")
- def itemEventId(evtj: String): MaybeItem = Future {
- val event: js.Dynamic = js.JSON.parse(evtj)
-
- Right(
- Item.component(
- Item.props(
- "event id",
- "sha256 hash of serialized event",
- Nostr.getEventHash(event)
- )
- )
- )
- }
-
- @nowarn("cat=other")
- def itemEventIdMatches(evtj: String): MaybeItem = Future {
- val c = parse(evtj).toOption.get.hcursor
- val event: js.Dynamic = js.JSON.parse(evtj)
-
- def render(result: Boolean) = Item.component(
- Item.props(
- "does the implied event id match the given event id?",
- "if not, that means you're hashing the event uncorrectly",
- f"${result match {
- case true => "yes"; case false => "no"
- }}"
- )
- )
-
- val hash = Nostr.getEventHash(event)
-
- c.get[String]("id") match {
- case Right(id) if id == hash => Right(render(true))
- case _ => Left(render(false))
- }
- }
-
- @nowarn("cat=other")
- def itemSignatureValid(evtj: String): MaybeItem = {
- val event: js.Dynamic = js.JSON.parse(evtj)
-
- def render(result: Boolean) = Item.component(
- Item.props(
- "is signature valid?",
- "",
- f"${result match {
- case true => "yes"; case false => "no"
- }}"
- )
- )
-
- Nostr.verifySignature(event).toFuture map {
- case true => Right(render(true))
- case false => Left(render(false))
- }
- }
-
- val protoElements = List[(String) => MaybeItem](
- itemWrongProperties,
- itemSerializedEvent,
- itemEventId,
- itemEventIdMatches,
- itemSignatureValid
- )
-
- @nowarn("cat=other")
- override val component = FunctionalComponent[String] { props =>
- val (elements, setElements) =
- useState(Seq.empty[slinky.core.TagMod[Nothing]])
-
- useEffect(
- () => {
- def runAndUnwrapUntilFirstLeft(
- remaining: List[String => Future[
- Either[slinky.core.TagMod[Nothing], slinky.core.TagMod[Nothing]]
- ]],
- acc: List[slinky.core.TagMod[Nothing]]
- ): Future[List[slinky.core.TagMod[Nothing]]] = remaining match {
- case fn :: tail => {
- fn(props) flatMap {
- {
- case Left(el) => runAndUnwrapUntilFirstLeft(Nil, el :: acc)
- case Right(el) => runAndUnwrapUntilFirstLeft(tail, el :: acc)
- }
- }
- }
- case Nil => Future { acc.reverse }
- }
-
- runAndUnwrapUntilFirstLeft(protoElements, List()) onComplete {
- case Success(x) => setElements(x)
- case Failure(err) =>
- println(f"failed to run through elements: ${err}")
- }
-
- () => {}
- },
- Seq(props)
- )
-
- Fragment(elements: _*)
- }
-}
diff --git a/src/main/scala/app/handlers/Handler.scala b/src/main/scala/app/handlers/Handler.scala
deleted file mode 100644
index 23eb638..0000000
--- a/src/main/scala/app/handlers/Handler.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package app.handlers
-
-import slinky.core.FunctionalComponent
-
-trait Handler {
- def handles(value: String): Boolean
- val component: FunctionalComponent[String]
-}
diff --git a/src/main/scala/app/handlers/KeyHandling.scala b/src/main/scala/app/handlers/KeyHandling.scala
deleted file mode 100644
index ffa322c..0000000
--- a/src/main/scala/app/handlers/KeyHandling.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-package app.handlers
-
-import scala.util.matching.Regex
-import scala.scalajs.js
-import slinky.core.{FunctionalComponent}
-import slinky.web.html._
-import slinky.core.facade.Hooks._
-import slinky.core.facade.Fragment
-
-import app.modules.Nostr
-import app.handlers.{Handler}
-import app.components.{Item}
-
-object KeyHandling extends Handler {
- val keymatcher = "^[a-f0-9]{64}$".r
-
- override def handles(value: String): Boolean =
- keymatcher.matches(value.toLowerCase())
-
- override val component = FunctionalComponent[String] { props =>
- Fragment(
- Item.component(Item.props("private key", "", props)),
- Item.component(
- Item.props("public key", "", Nostr.getPublicKey(props))
- )
- )
- }
-}
diff --git a/src/main/scala/app/handlers/Nothing.scala b/src/main/scala/app/handlers/Nothing.scala
deleted file mode 100644
index 7eeb587..0000000
--- a/src/main/scala/app/handlers/Nothing.scala
+++ /dev/null
@@ -1,34 +0,0 @@
-package app.handlers
-
-import scala.scalajs.js
-import slinky.core.FunctionalComponent
-import slinky.web.html._
-import slinky.core.facade.Hooks._
-import slinky.core.facade.Fragment
-
-import app.handlers.{Handler}
-
-object Nothing extends Handler {
- override def handles(value: String): Boolean = true
-
- override val component = FunctionalComponent[String] { props =>
- Fragment(
- p("you can paste here"),
- ul(
- li("an unsigned event to be hashed and signed"),
- li("a signed event to have its signature checked"),
- li("a nostr relay URL to be inspected"),
- li("a nostr event id we'll try to fetch"),
- li("a nip05 identifier to be checked"),
- li(
- span("contribute a new function: "),
- a(
- target := "_blank",
- href := "https://github.com/fiatjaf/nostr-army-knife",
- style := js.Dynamic.literal(color = "inherit")
- )("_______")
- )
- )
- )
- }
-}
diff --git a/src/main/scala/app/main.scala b/src/main/scala/app/main.scala
deleted file mode 100644
index 79da934..0000000
--- a/src/main/scala/app/main.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package app
-
-import org.scalajs.dom.document
-import slinky.web.ReactDOM
-import slinky.web.html._
-
-import app.Base._
-
-object Main {
- def main(args: Array[String]): Unit = {
- val div = document.createElement("div")
- div.id = "root"
- document.body.appendChild(div)
-
- ReactDOM.render(
- Base.component(),
- document.getElementById("root")
- )
- }
-}
diff --git a/src/main/scala/app/modules/Nostr.scala b/src/main/scala/app/modules/Nostr.scala
deleted file mode 100644
index b6954b4..0000000
--- a/src/main/scala/app/modules/Nostr.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package app.modules
-
-import scala.scalajs.js.annotation._
-import scala.scalajs.js
-
-@js.native
-@JSGlobal
-object Nostr extends js.Object {
- def getPublicKey(text: String): String = js.native
- def getEventHash(evt: js.Dynamic): String = js.native
- def serializeEvent(evt: js.Dynamic): String = js.native
- def verifySignature(evt: js.Dynamic): js.Promise[Boolean] = js.native
- def signEvent(evt: js.Dynamic, privateKey: String): String = js.native
-}