WIP: Docusaurus website (#465)
* Docs: Introduce new Docusaurus-based website This commit is the result of running npx docusaurus-init, and nothing more. Further changes will happen on top of this, to make it easier to review changes and update to newer versions of Docusaurus in the future. * WIP: Add Bitcoin-S website Change the default Docusaurus template to a custom website. Goes off of existing documentation, and moves it into the new docs and website directories. Deletes some unused files, such as BUILD_README.md * Initial mdoc support * Add Scaladoc to website * Add SVG assets * Change colors, flesh out pages, correct Scaladoc links * Rename doc project to scripts, move security doc to website * Add copy buttons to website code snippets * Add doc and tasks for publishing website * Refactor how paths get copied after generating Scaladocs * Add Get Started button * Replace bitcoin-s logo with white text * Add Montserrat font for headers * flesh out user showcase and landing page * Change Scaladoc URL to bitcoins package
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
*/node_modules
|
||||
*.log
|
5
.gitignore
vendored
@ -68,3 +68,8 @@ build-aux/compile
|
||||
build-aux/test-driver
|
||||
src/stamp-h1
|
||||
libsecp256k1.pc
|
||||
|
||||
# Docusaurs
|
||||
node_modules
|
||||
website/build
|
||||
website/static/api
|
189
CONTRIBUTING.md
@ -1,188 +1 @@
|
||||
# Contributing to Bitcoin-S
|
||||
|
||||
Bitcoin-S is an open source project where anyone is welcome to contribute. All contributions are encouraged and appreciated, whether that is code, testing, documentation or something else entirely.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
It's possible to communicate with other developers through a variety of communication channels:
|
||||
|
||||
- [Suredbits Slack](https://join.slack.com/t/suredbits/shared_invite/enQtNDEyMjY3MTg1MTg3LTYyYjkwOGUzMDQ4NDAwZjE1M2I3MmQyNWNlZjNlYjg4OGRjYTRjNWUwNjRjNjg4Y2NjZjAxYjU1N2JjMTU1YWM) - Suredbits is a company monetizing APIs through the Lightning Network. Suredbits doesn't own Bitcoin-S, but the Suredbits CEO Chris Stewart is the maintainer of this library. There's a separate Bitcoin-S channel on their Slack, this is probably the easiest way of getting in touch with someone working on this project.
|
||||
- [Bitcoin-S Gitter](https://gitter.im/bitcoin-s-core/)
|
||||
- [#bitcoin-scala](https://webchat.freenode.net/?channels=bitcoin-scala) on IRC Freenode
|
||||
|
||||
## Developer productivity
|
||||
|
||||
### Bloop
|
||||
|
||||
If you're tired of waiting around for sbt all day, there's a new,
|
||||
cool kid on the block. It is called [Bloop](https://scalacenter.github.io/bloop/),
|
||||
and it makes compilations in general faster, and in particular
|
||||
incremental, small compilation units (which greatly help editor
|
||||
performance). Bloop is a server that runs in the background of
|
||||
your computer, and keeps several "hot" JVMs running at all
|
||||
times. These JVMs serve compilation requests. Because the JVMs
|
||||
are running in the background you avoid the startup lag, and you
|
||||
also get code that's already [JIT compiled](https://en.wikipedia.org/wiki/Just-in-time_compilation)
|
||||
for you.
|
||||
|
||||
The documentation on Bloops [site](https://scalacenter.github.io/bloop/) is good, but here is the highlights:
|
||||
|
||||
1. Install Bloop by doing step 1 & 2 in the [official guide](https://scalacenter.github.io/bloop/setup#universal)
|
||||
2. Enable the Bloop background daemon
|
||||
1. macOS:
|
||||
```bash
|
||||
$ brew services start bloop
|
||||
```
|
||||
2. Ubuntu:
|
||||
```bash
|
||||
$ systemctl --user enable $HOME/.bloop/systemd/bloop.service
|
||||
$ systemctl --user daemon-reload
|
||||
$ systemctl --user start bloop
|
||||
```
|
||||
3. Enable shell completion for the Bloop CLI
|
||||
1. Bash:
|
||||
```bash
|
||||
$ echo '. $HOME/.bloop/bash/bloop' >> $HOME/.bash_profile
|
||||
```
|
||||
2. Zsh:
|
||||
```bash
|
||||
$ echo 'autoload -U compinit' >> $HOME/.zshrc
|
||||
$ echo 'fpath=($HOME/.bloop/zsh $fpath)' >> $HOME/.bashrc
|
||||
$ echo 'compinit' >> $HOME/.bashrc
|
||||
```
|
||||
3. Fish:
|
||||
```bash
|
||||
$ ln -s $HOME/.bloop/fish/bloop.fish ~/.config/fish/completions/bloop.fish
|
||||
```
|
||||
4. Generate configuration files
|
||||
```bash
|
||||
$ sbt bloopInstall
|
||||
```
|
||||
5. Import Bitcoin-S into IntelliJ again, as a bsp (Build Server Protocol) project (instead of a sbt project). Make sure you're running on the most recent IntelliJ and Scala plugin. See [official docs](https://scalacenter.github.io/bloop/docs/ides/intellij) for details.
|
||||
6. _(Bonus step):_ Lightning fast recompilations on file save:
|
||||
```bash
|
||||
$ bloop compile --project <name of module your're working on> --watch
|
||||
```
|
||||
|
||||
Your editor should now be much faster and require less resources :tada:
|
||||
|
||||
## Documentation
|
||||
|
||||
One of the goals of Bitcoin-S is having useful and well-formatted Scaladoc comments on classes,
|
||||
objects and functions. Here are some useful resources on how to properly format your Scaladoc comments:
|
||||
|
||||
- [Scaladoc for library authors](https://docs.scala-lang.org/overviews/scaladoc/for-library-authors.html)
|
||||
- [Guidelines](https://docs.scala-lang.org/style/scaladoc.html) used by the official Scala language Scaladoc
|
||||
|
||||
### Bitcoin-S static site
|
||||
|
||||
Bitcoin-S comes bundles with it's own web site with documentation about the library. It consists if the generated Scaladoc of the project, as well as the content of `src/site`.
|
||||
|
||||
### Working with documentation locally
|
||||
|
||||
View generated site:
|
||||
|
||||
```bash
|
||||
$ sbt previewSite
|
||||
```
|
||||
|
||||
### Publishing Bitcoin-S site
|
||||
|
||||
```bash
|
||||
$ sbt ghpagesPushSite
|
||||
```
|
||||
|
||||
Read more on the [`sbt-ghpages`](https://github.com/sbt/sbt-ghpages) sbt plugin.
|
||||
|
||||
> Note: some setup is required before doing this the first time
|
||||
> From the `sbt-ghpages` documentation:
|
||||
|
||||
Before using sbt-ghpages, you must create the gh-pages branch in your repository and push the branch to GitHub. The quick steps are:
|
||||
|
||||
```bash
|
||||
# Using a fresh, temporary clone is safest for this procedure
|
||||
$ pushd /tmp
|
||||
$ git clone git@github.com:youruser/yourproject.git
|
||||
$ cd yourproject
|
||||
|
||||
# Create branch with no history or content
|
||||
$ git checkout --orphan gh-pages
|
||||
$ git rm -rf .
|
||||
|
||||
# Establish the branch existence
|
||||
$ git commit --allow-empty -m "Initialize gh-pages branch"
|
||||
$ git push origin gh-pages
|
||||
|
||||
# Return to original working copy clone, we're finished with the /tmp one
|
||||
$ popd
|
||||
$ rm -rf /tmp/yourproject
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Property based testing
|
||||
|
||||
This library aims to achieve high level of correctness via property based testing. At the simplest level, you can think of property based testing as specifying a invariant that must always hold true. [Here](https://github.com/bitcoin-s/bitcoin-s-core/blob/89fbf35d78046b7ed21fd93fec05bb57cba023bb/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala#L13-L17) is an example of a property in the bitcoin-s-core test suite
|
||||
|
||||
```scala
|
||||
property("Serialization symmetry") =
|
||||
Prop.forAll(TransactionGenerators.transactions) { tx =>
|
||||
Transaction(tx.hex) == tx
|
||||
}
|
||||
```
|
||||
|
||||
What this property says is that for every transaction we can generate with [`TransactionGenerators.transactions`](testkit/src/main/scala/org/bitcoins/core/gen/TransactionGenerators.scala) we _must_ be able to serialize it to hex format, then deserialize it back to a transaction and get the original `tx` back.
|
||||
|
||||
A more complex example of property based testing is checking that a multi signature transaction was signed correctly (see [`TransactionSignatureCreatorSpec`](core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala) line 29-34). First we generate a _supposedly_ validly signed multisig transaction with [`TransactionGenerators.signedMultiSigTransaction`](testkit/src/main/scala/org/bitcoins/core/gen/TransactionGenerators.scala) (line 102-108). These transactions have varying `m` of `n` requirements. An interesting corner case if when you have 0 of `n` signatures, which means no signature is required. Property based testing is really good at fleshing out these corner cases. We check to see if this transaction is valid by running it through our [`ScriptInterpreter`](core/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala). If we have built our functionality correctly the ScriptInterpreter should always return [`ScriptOk`](core/src/main/scala/org/bitcoins/core/script/result/ScriptResult.scala) indicating the script was valid.
|
||||
|
||||
```scala
|
||||
property("generate valid signatures for a multisignature transaction") =
|
||||
Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
|
||||
case (txSignatureComponent: TxSigComponent, _) =>
|
||||
//run it through the interpreter
|
||||
val program = ScriptProgram(txSignatureComponent)
|
||||
val result = ScriptInterpreter.run(program)
|
||||
result == ScriptOk
|
||||
}
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
To run the entire test suite all you need to do is run the following command
|
||||
|
||||
```scala
|
||||
$ sbt test
|
||||
[info] Elapsed time: 4 min 36.760 sec
|
||||
[info] ScalaCheck
|
||||
[info] Passed: Total 149, Failed 0, Errors 0, Passed 149
|
||||
[info] ScalaTest
|
||||
[info] Run completed in 4 minutes, 55 seconds.
|
||||
[info] Total number of tests run: 744
|
||||
[info] Suites: completed 97, aborted 0
|
||||
[info] Tests: succeeded 744, failed 0, canceled 0, ignored 0, pending 0
|
||||
[info] All tests passed.
|
||||
[info] Passed: Total 909, Failed 0, Errors 0, Passed 909
|
||||
[success] Total time: 297 s, completed Jul 20, 2017 10:34:16 AM
|
||||
```
|
||||
|
||||
To run a specific suite of tests you can specify the suite name in the following way
|
||||
|
||||
```scala
|
||||
$ sbt testOnly *ScriptInterpreterTest*
|
||||
[info] ScriptInterpreterTest:
|
||||
[info] ScriptInterpreter
|
||||
[info] - must evaluate all the scripts from the bitcoin core script_tests.json
|
||||
[info] Run completed in 8 seconds, 208 milliseconds.
|
||||
[info] Total number of tests run: 1
|
||||
[info] Suites: completed 1, aborted 0
|
||||
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
|
||||
[info] All tests passed.
|
||||
```
|
||||
|
||||
The command `sbt testQuick` can also be handy. It runs tests that either:
|
||||
|
||||
1. Failed previously
|
||||
2. Has not been run previously
|
||||
3. Either the test or one of its dependencies has been recompiled
|
||||
|
||||
For more information on `testQuick`, see the offical [sbt docs](https://www.scala-sbt.org/1.x/docs/Testing.html#testQuick).
|
||||
See the contribution guide on the Bitcoin-S [website](https://bitcoin-s.org/docs/contributing)
|
||||
|
10
Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM node:8.11.4
|
||||
|
||||
WORKDIR /app/website
|
||||
|
||||
EXPOSE 3000 35729
|
||||
COPY ./docs /app/docs
|
||||
COPY ./website /app/website
|
||||
RUN yarn install
|
||||
|
||||
CMD ["yarn", "start"]
|
@ -1,58 +1,2 @@
|
||||
[ ![Download](https://api.bintray.com/packages/bitcoin-s/bitcoin-s-core/bitcoin-s-bitcoind-rpc/images/download.svg) ](https://bintray.com/bitcoin-s/bitcoin-s-core/bitcoin-s-bitcoind-rpc/_latestVersion)
|
||||
|
||||
> Note: `bitcoin-s-bitcoind-rpc` requires you to have `bitcoind` (Bitcoin Core daemon) installed. Grab this at [bitcoincore.org](https://bitcoincore.org/en/download/)
|
||||
|
||||
## Usage
|
||||
|
||||
The Bitcoin Core RPC client in Bitcoin-S currently supports the Bitcoin Core 0.16 and 0.17
|
||||
version lines. It can be set up to work with both local and remote Bitcoin Core servers.
|
||||
|
||||
### Basic example
|
||||
```scala
|
||||
import org.bitcoins.rpc
|
||||
import rpc.client.common._
|
||||
import rpc.config.BitcoindInstance
|
||||
import akka.actor.ActorSystem
|
||||
|
||||
// data directory defaults to ~/.bitcoin on Linux and
|
||||
// ~/Library/Application Support/Bitcoin on macOS
|
||||
val bitcoindInstance = BitcoindInstance.fromDatadir()
|
||||
|
||||
// alternative:
|
||||
import java.io.File
|
||||
val dataDir = new File("/my/bitcoin/data/dir")
|
||||
val otherInstance = BitcoindInstance.fromDatadir(dataDir)
|
||||
|
||||
implicit val actorSystem: ActorSystem = ActorSystem.create()
|
||||
|
||||
val client = new BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
for {
|
||||
_ <- client.start()
|
||||
balance <- client.getBalance
|
||||
} yield balance
|
||||
```
|
||||
|
||||
### Advanced example
|
||||
|
||||
TODO: How to connect to remote bitcoind
|
||||
|
||||
## Testing
|
||||
|
||||
To test the Bitcoin-S RPC project you need both version 0.16 and 0.17 of Bitcoin Core. A list of current and previous releases can be found [here](https://bitcoincore.org/en/releases/).
|
||||
|
||||
You then need to set environment variables to indicate where Bitcoin-S can find the different versions:
|
||||
|
||||
```bash
|
||||
$ export BITCOIND_V16_PATH=/path/to/v16/bitcoind
|
||||
$ export BITCOIND_V17_PATH=/path/to/v17/bitcoind
|
||||
```
|
||||
|
||||
If you just run tests testing common functionality it's enough to have either version 0.16 or 0.17 on your `PATH`.
|
||||
|
||||
To run all RPC related tests:
|
||||
|
||||
```bash
|
||||
$ bash sbt bitcoindRpcTest/test
|
||||
```
|
||||
|
||||
See the `bitcoind`/Bitcoin Core section on the
|
||||
Bitcoin-S [website](https://bitcoin-s.org/docs/rpc/rpc-bitcoind).
|
||||
|
175
build.sbt
@ -16,6 +16,7 @@ lazy val commonCompilerOpts = {
|
||||
"128"
|
||||
)
|
||||
}
|
||||
|
||||
//https://docs.scala-lang.org/overviews/compiler-options/index.html
|
||||
lazy val compilerOpts = Seq(
|
||||
"-target:jvm-1.8",
|
||||
@ -36,13 +37,9 @@ lazy val compilerOpts = Seq(
|
||||
|
||||
lazy val testCompilerOpts = commonCompilerOpts
|
||||
|
||||
|
||||
|
||||
lazy val commonSettings = List(
|
||||
|
||||
organization := "org.bitcoin-s",
|
||||
homepage := Some(url("https://bitcoin-s.org")),
|
||||
|
||||
developers := List(
|
||||
Developer(
|
||||
"christewart",
|
||||
@ -51,27 +48,19 @@ lazy val commonSettings = List(
|
||||
url("https://twitter.com/Chris_Stewart_5")
|
||||
)
|
||||
),
|
||||
|
||||
scalacOptions in Compile := compilerOpts,
|
||||
|
||||
scalacOptions in Test := testCompilerOpts,
|
||||
|
||||
//show full stack trace of failed tests
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oF"),
|
||||
|
||||
//show duration of tests
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oD"),
|
||||
|
||||
assemblyOption in assembly := (assemblyOption in assembly).value
|
||||
.copy(includeScala = false),
|
||||
|
||||
licenses += ("MIT", url("http://opensource.org/licenses/MIT")),
|
||||
|
||||
|
||||
/**
|
||||
* Adding Ammonite REPL to test scope, can access both test and compile
|
||||
* sources. Docs: http://ammonite.io/#Ammonite-REPL
|
||||
* Creates an ad-hoc main file that can be run by doing
|
||||
* Creates an ad-hoc main file that can be run by doing
|
||||
* test:run (or test:runMain amm if there's multiple main files
|
||||
* in scope)
|
||||
*/
|
||||
@ -87,7 +76,7 @@ lazy val commonSettings = List(
|
||||
)
|
||||
|
||||
lazy val commonTestSettings = Seq(
|
||||
publish / skip := true,
|
||||
publish / skip := true
|
||||
) ++ commonSettings
|
||||
|
||||
lazy val commonProdSettings = Seq(
|
||||
@ -107,20 +96,84 @@ lazy val bitcoins = project
|
||||
eclairRpc,
|
||||
eclairRpcTest,
|
||||
testkit,
|
||||
doc
|
||||
scripts
|
||||
)
|
||||
.settings(commonSettings: _*)
|
||||
.settings(crossScalaVersions := Nil)
|
||||
.settings(libraryDependencies ++= Deps.root)
|
||||
.enablePlugins(ScalaUnidocPlugin, GhpagesPlugin, GitVersioning)
|
||||
.enablePlugins(ScalaUnidocPlugin, GitVersioning)
|
||||
.settings(
|
||||
// scaladoc settings
|
||||
// TODO this is not working properly
|
||||
inTask(unidoc)(
|
||||
scalacOptions in Compile ++= List(
|
||||
"-doc-title",
|
||||
"Bitcoin-S",
|
||||
"-doc-version",
|
||||
version.value
|
||||
)),
|
||||
// we modify the unidoc task to move the generated Scaladocs into the
|
||||
// website directory afterwards
|
||||
Compile / unidoc := {
|
||||
import java.nio.file._
|
||||
import scala.collection.JavaConverters._
|
||||
val logger = streams.value.log
|
||||
|
||||
def cleanPath(path: Path, isRoot: Boolean = true): Unit = {
|
||||
if (Files.isDirectory(path)) {
|
||||
path.toFile.list().map { file =>
|
||||
val toClean = path.resolve(file)
|
||||
cleanPath(toClean, isRoot = false)
|
||||
}
|
||||
if (isRoot) ()
|
||||
else Files.deleteIfExists(path)
|
||||
} else if (isRoot) {
|
||||
()
|
||||
} else if (path.toString.endsWith(".gitkeep")) {
|
||||
()
|
||||
} else {
|
||||
Files.deleteIfExists(path)
|
||||
}
|
||||
}
|
||||
|
||||
val websiteScaladocDir =
|
||||
Paths.get("website", "static", "api").toAbsolutePath
|
||||
|
||||
logger.info(s"Cleaning website Scaladoc directory $websiteScaladocDir")
|
||||
cleanPath(websiteScaladocDir)
|
||||
|
||||
// returned value is a list of files,
|
||||
// list has one element
|
||||
val generatedDir = (Compile / unidoc).value.head
|
||||
|
||||
logger.info(s"Moving files in $generatedDir to $websiteScaladocDir")
|
||||
|
||||
try {
|
||||
Files
|
||||
.walk(generatedDir.toPath)
|
||||
.iterator()
|
||||
.asScala
|
||||
.drop(1) // skip the root directory
|
||||
.foreach { child =>
|
||||
val pathDiff = generatedDir.toPath.relativize(child)
|
||||
Files.copy(child,
|
||||
websiteScaladocDir.resolve(pathDiff),
|
||||
StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
logger.err(
|
||||
"Error when copying Scaladocs to website folder: ${e.toString}")
|
||||
throw e
|
||||
}
|
||||
Seq(generatedDir)
|
||||
}
|
||||
)
|
||||
.settings(
|
||||
name := "bitcoin-s",
|
||||
ScalaUnidoc / siteSubdirName := "latest/api",
|
||||
addMappingsToSiteDir(ScalaUnidoc / packageDoc / mappings, ScalaUnidoc / siteSubdirName),
|
||||
gitRemoteRepo := "git@github.com:bitcoin-s/bitcoin-s-core.git"
|
||||
)
|
||||
|
||||
|
||||
lazy val secp256k1jni = project
|
||||
.in(file("secp256k1jni"))
|
||||
.settings(commonSettings: _*)
|
||||
@ -139,49 +192,48 @@ lazy val core = project
|
||||
.settings(commonProdSettings: _*)
|
||||
.dependsOn(
|
||||
secp256k1jni
|
||||
).enablePlugins(GitVersioning)
|
||||
)
|
||||
.enablePlugins(GitVersioning)
|
||||
|
||||
lazy val coreTest = project
|
||||
.in(file("core-test"))
|
||||
.settings(commonTestSettings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-core-test"
|
||||
).dependsOn(
|
||||
)
|
||||
.dependsOn(
|
||||
core,
|
||||
testkit,
|
||||
).enablePlugins()
|
||||
testkit
|
||||
)
|
||||
.enablePlugins()
|
||||
|
||||
lazy val zmq = project
|
||||
.in(file("zmq"))
|
||||
.settings(commonSettings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-zmq",
|
||||
libraryDependencies ++= Deps.bitcoindZmq)
|
||||
.settings(name := "bitcoin-s-zmq", libraryDependencies ++= Deps.bitcoindZmq)
|
||||
.dependsOn(
|
||||
core
|
||||
).enablePlugins(GitVersioning)
|
||||
)
|
||||
.enablePlugins(GitVersioning)
|
||||
|
||||
lazy val bitcoindRpc = project
|
||||
.in(file("bitcoind-rpc"))
|
||||
.settings(commonProdSettings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-bitcoind-rpc",
|
||||
libraryDependencies ++= Deps.bitcoindRpc)
|
||||
.settings(name := "bitcoin-s-bitcoind-rpc",
|
||||
libraryDependencies ++= Deps.bitcoindRpc)
|
||||
.dependsOn(core)
|
||||
.enablePlugins(GitVersioning)
|
||||
|
||||
lazy val bitcoindRpcTest = project
|
||||
.in(file("bitcoind-rpc-test"))
|
||||
.settings(commonTestSettings: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Deps.bitcoindRpcTest,
|
||||
name := "bitcoin-s-bitcoind-rpc-test")
|
||||
.settings(libraryDependencies ++= Deps.bitcoindRpcTest,
|
||||
name := "bitcoin-s-bitcoind-rpc-test")
|
||||
.dependsOn(testkit)
|
||||
.enablePlugins()
|
||||
|
||||
lazy val bench = project
|
||||
.in(file("bench"))
|
||||
|
||||
.settings(commonSettings: _*)
|
||||
.settings(assemblyOption in assembly := (assemblyOption in assembly).value
|
||||
.copy(includeScala = true))
|
||||
@ -196,20 +248,19 @@ lazy val bench = project
|
||||
lazy val eclairRpc = project
|
||||
.in(file("eclair-rpc"))
|
||||
.settings(commonProdSettings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-eclair-rpc",
|
||||
libraryDependencies ++= Deps.eclairRpc)
|
||||
.settings(name := "bitcoin-s-eclair-rpc",
|
||||
libraryDependencies ++= Deps.eclairRpc)
|
||||
.dependsOn(
|
||||
core,
|
||||
bitcoindRpc
|
||||
).enablePlugins(GitVersioning)
|
||||
)
|
||||
.enablePlugins(GitVersioning)
|
||||
|
||||
lazy val eclairRpcTest = project
|
||||
.in(file("eclair-rpc-test"))
|
||||
.settings(commonTestSettings: _*)
|
||||
.settings(libraryDependencies ++= Deps.eclairRpcTest,
|
||||
name := "bitcoin-s-eclair-rpc-test",
|
||||
)
|
||||
name := "bitcoin-s-eclair-rpc-test")
|
||||
.dependsOn(testkit)
|
||||
.enablePlugins()
|
||||
|
||||
@ -220,15 +271,27 @@ lazy val testkit = project
|
||||
core,
|
||||
bitcoindRpc,
|
||||
eclairRpc
|
||||
).enablePlugins(GitVersioning)
|
||||
)
|
||||
.enablePlugins(GitVersioning)
|
||||
|
||||
lazy val publishWebsite = taskKey[Unit]("Publish website")
|
||||
|
||||
lazy val doc = project
|
||||
.in(file("doc"))
|
||||
lazy val docs = project
|
||||
.in(file("bitcoin-s-docs")) // important: it must not be docs/
|
||||
.settings(commonTestSettings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-doc",
|
||||
libraryDependencies ++= Deps.doc,
|
||||
// come back to visit this setting later
|
||||
mdocExtraArguments := List("--no-link-hygiene"),
|
||||
name := "bitcoin-s-docs",
|
||||
mdocVariables := Map(
|
||||
"VERSION" -> version.value
|
||||
),
|
||||
publishWebsite := Def
|
||||
.sequential(
|
||||
bitcoins / Compile / unidoc,
|
||||
Compile / docusaurusPublishGhpages
|
||||
)
|
||||
.value
|
||||
)
|
||||
.dependsOn(
|
||||
bitcoindRpc,
|
||||
@ -238,20 +301,28 @@ lazy val doc = project
|
||||
testkit,
|
||||
zmq
|
||||
)
|
||||
.enablePlugins(MdocPlugin, DocusaurusPlugin)
|
||||
|
||||
lazy val scripts = project
|
||||
.in(file("scripts"))
|
||||
.dependsOn(core, bitcoindRpc, eclairRpc, zmq)
|
||||
.settings(commonTestSettings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-scripts",
|
||||
libraryDependencies ++= Deps.scripts
|
||||
)
|
||||
|
||||
// Ammonite is invoked through running
|
||||
// a main class it places in test sources
|
||||
// for us. This makes it a bit less awkward
|
||||
// to start the Ammonite shell. Sadly,
|
||||
// to start the Ammonite shell. Sadly,
|
||||
// prepending the project and then doing
|
||||
// `amm` (e.g. sbt coreTest/amm`) does not
|
||||
// work. For that you either have to do
|
||||
// `sbt coreTest/test:run` or:
|
||||
// `amm` (e.g. sbt coreTest/amm`) does not
|
||||
// work. For that you either have to do
|
||||
// `sbt coreTest/test:run` or:
|
||||
// sbt
|
||||
// project coreTest
|
||||
// amm
|
||||
addCommandAlias("amm", "test:run")
|
||||
|
||||
publishArtifact in bitcoins := false
|
||||
|
||||
previewSite / aggregate := false
|
||||
previewAuto / aggregate := false
|
||||
|
207
core/README.md
@ -1,205 +1,2 @@
|
||||
[ ![Download](https://api.bintray.com/packages/bitcoin-s/bitcoin-s-core/bitcoin-s-core/images/download.svg) ](https://bintray.com/bitcoin-s/bitcoin-s-core/bitcoin-s-core/_latestVersion)
|
||||
|
||||
# `core` module
|
||||
|
||||
This is the core functionality of Bitcoin-S. The goal is to provide basic data structures that are found in the Bitcoin and Lightning protocols while minimizing external depedencies for security purposes. We aim to have an extremely high level of test coverage in this module to flesh out bugs. We use [property based testing](http://www.scalatest.org/user_guide/property_based_testing) heavily in this library to ensure high quality of code.
|
||||
|
||||
## The basics
|
||||
|
||||
Every bitcoin protocol data structure (and some other data structures) extends [`NetworkElement`](src/main/scala/org/bitcoins/core/protocol/NetworkElement.scala). `NetworkElement` provides methods to convert the data structure to hex or byte representation. When paired with [`Factory`](src/main/scala/org/bitcoins/core/util/Factory.scala) we can easily serialize and deserialize data structures.
|
||||
|
||||
Most data structures have companion objects that extends `Factory` to be able to easily create protocol data structures. An example of this is the [`ScriptPubKey`](https://github.com/bitcoin-s/bitcoin-s-core/blob/1c7a7b9f46679a753248d9f55246c272bb3d63b9/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala#L462) companion object. You can use this companion object to create a `ScriptPubKey` from a hex string or a byte array.
|
||||
|
||||
## Main modules in `core`
|
||||
|
||||
1. [`protocol`](src/main/scala/org/bitcoins/core/protocol) - basic protocol data structures. Useful for serializing/deserializing things
|
||||
2. [`script`](src/main/scala/org/bitcoins/core/script) - an implementation of [Script](https://en.bitcoin.it/wiki/Script) - the programming language in Bitcoin
|
||||
3. [`wallet`](src/main/scala/org/bitcoins/core/wallet) - implements signing logic for Bitcoin transactions. This module is not named well as there is **NO** functionality to persist wallet state to disk as it stands. This will most likely be renamed in the future.
|
||||
4. [`config`](src/main/scala/org/bitcoins/core/config) - Contains information about a chain's genesis block and DNS seeds
|
||||
5. [`number`](src/main/scala/org/bitcoins/core/number) - Implements number types that are native in C, i.e. `UInt8`, `UInt32`, `UInt64`, etc.
|
||||
6. [`currency`](src/main/scala/org/bitcoins/core/currency) - Implements currency units in the Bitcoin protocol
|
||||
7. [`bloom`](src/main/scala/org/bitcoins/core/bloom) - Implements [Bloom filters](https://en.wikipedia.org/wiki/Bloom_filter) and [merkle blocks](https://bitcoin.org/en/glossary/merkle-block) needed for [BIP37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki)
|
||||
|
||||
## Examples
|
||||
|
||||
### Serializing and deserializing a `Transaction`
|
||||
|
||||
Here is an example scala console session with bitcoins-core
|
||||
|
||||
```scala
|
||||
$ sbt core/console
|
||||
[info] Loading global plugins from /home/chris/.sbt/0.13/plugins
|
||||
[info] Loading project definition from /home/chris/dev/bitcoin-s-core/project
|
||||
[info] Set current project to bitcoin-s-core (in build file:/home/chris/dev/bitcoin-s-core/)
|
||||
[info] Starting scala interpreter...
|
||||
[info]
|
||||
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151).
|
||||
Type in expressions to have them evaluated.
|
||||
Type :help for more information.
|
||||
|
||||
scala> import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
|
||||
scala> val hexTx = "0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f8$9c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1$a02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e$90c865c2f6f7a9710a474154ab1423abb5b9288ac00000000"
|
||||
hexTx: String = 0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f889c1$52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1fa02$45f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e690c$65c2f6f7a9710a474154ab1423abb5b9288ac00000000
|
||||
|
||||
scala> val tx = Transaction.fromHex(hexTx)
|
||||
tx: org.bitcoins.core.protocol.transaction.Transaction = BaseTransactionImpl(UInt32Impl(1),List(TransactionInputImpl(TransactionOutPointImpl$DoubleSha256DigestImpl(ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3),UInt32Impl(0)),P2PKHScriptSignatureImpl(6b483045022$008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf012102$1d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353),UInt32Impl(4294967295))),List(TransactionOutputImpl(SatoshisImpl(Int64Impl($9994000)),P2PKHScriptPubKeyImpl(1976a914b1d7591b69e9def0feb13254bace942923c7922d88ac)), TransactionOutputImpl(SatoshisImpl(Int64Impl(840)),P$PKHScriptPubKeyImpl(1976a9145e690c865c2f6f7a9710a474154ab1423abb5b9288ac))),UInt32Impl(0))
|
||||
|
||||
scala> val hexAgain = tx.hex
|
||||
hexAgain: String = 0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f88$c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1f$02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e6$0c865c2f6f7a9710a474154ab1423abb5b9288ac00000000
|
||||
|
||||
```
|
||||
|
||||
This gives us an example of a hex encoded Bitcoin transaction that is deserialized to a native Scala object called a [`Transaction`](https://github.com/bitcoin-s/bitcoin-s-core/blob/6358eb83067909771f989d615b422759222d060a/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala#L14-L42). You could also serialize the transaction to bytes using `tx.bytes` instead of `tx.hex`. These methods are available on every data structure that extends NetworkElement, like [`ECPrivateKey`](https://github.com/bitcoin-s/bitcoin-s-core/blob/6358eb83067909771f989d615b422759222d060a/src/main/scala/org/bitcoins/core/crypto/ECKey.scala#L23-L67), [`ScriptPubKey`](https://github.com/bitcoin-s/bitcoin-s-core/blob/6358eb83067909771f989d615b422759222d060a/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala#L23), [`ScriptWitness`](https://github.com/bitcoin-s/bitcoin-s-core/blob/6358eb83067909771f989d615b422759222d060a/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala#L13), and [`Block`](https://github.com/bitcoin-s/bitcoin-s-core/blob/6358eb83067909771f989d615b422759222d060a/src/main/scala/org/bitcoins/core/protocol/blockchain/Block.scala#L17).
|
||||
|
||||
#### Generating a BIP39 mnemonic phrase and an `xpriv`
|
||||
|
||||
BIP39 mnemonic phrases are the most common way of creating backups of wallets.
|
||||
They are between 12 and 24 words the user writes down, and can later be used to restore
|
||||
their bitcoins. From the mnemonic phrase we generate a wallet seed, and that seed
|
||||
can be used to generate what's called an extended private key
|
||||
([`ExtPrivateKey`](src/main/scala/org/bitcoins/core/crypto/ExtKey.scala) in Bitcoin-S).
|
||||
|
||||
Here's an example:
|
||||
|
||||
```scala
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.crypto._
|
||||
|
||||
// the length of the entropy bit vector determine
|
||||
// how long our phrase ends up being
|
||||
// 256 bits of entropy results in 24 words
|
||||
val entropy: BitVector = MnemonicCode.getEntropy256Bits
|
||||
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
|
||||
mnemonicCode.words // the phrase the user should write down
|
||||
|
||||
// the password argument is an optional, extra security
|
||||
// measure. all MnemonicCode instances will give you a
|
||||
// valid BIP39 seed, but different passwords will give
|
||||
// you different seeds. So you could have as many wallets
|
||||
// from the same seed as you'd like, by simply giving them
|
||||
// different passwords.
|
||||
val bip39Seed = BIP39Seed.fromMnemonic(mnemonicCode,
|
||||
password = "secret password")
|
||||
|
||||
val xpriv = ExtPrivateKey.fromBIP39Seed(ExtKeyVersion.SegWitMainNetPriv,
|
||||
bip39Seed)
|
||||
val xpub = xpriv.extPublicKey
|
||||
|
||||
// you can now use the generated xpriv to derive further
|
||||
// private or public keys
|
||||
|
||||
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
|
||||
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
|
||||
// alternatively:
|
||||
val otherSegwitPath =
|
||||
SegWitHDPath(HDCoinType.Bitcoin,
|
||||
accountIndex = 0,
|
||||
HDChainType.External,
|
||||
addressIndex = 0)
|
||||
|
||||
segwitPath == otherSegwitPath // true
|
||||
|
||||
// there's also paths available for legacy
|
||||
// addresses (LegacyHDPath) as well as nested
|
||||
// segwit paths (NestedSegWitPath)
|
||||
```
|
||||
|
||||
### Building a signed transaction
|
||||
|
||||
Bitcoin Core supports building unsigned transactions and then signing them with a set of private keys. The first important thing to look at is [`UTXOSpendingInfo`](src/main/scala/org/bitcoins/core/wallet/utxo/UTXOSpendingInfo.scala). This contains all of the information needed to create a validly signed [`ScriptSignature`](src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala) that spends this output.
|
||||
|
||||
Our [`TxBuilder`](src/main/scala/org/bitcoins/core/wallet/builder/TxBuilder.scala) class requires you to provide the following:
|
||||
|
||||
1. `destinations` - the places we are sending bitcoin to. These are [TransactionOutputs](src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutput.scala) you are sending coins too
|
||||
2. `utxos` - these are the [UTXOs](src/main/scala/org/bitcoins/core/wallet/utxo/UTXOSpendingInfo.scala) used to fund your transaction. These must exist in your wallet, and you must know how to spend them (i.e. have the private key)
|
||||
3. `feeRate` - the fee rate you want to pay for this transaction
|
||||
4. `changeSPK` - where the change (i.e. `creditingAmount - destinationAmount - fee`) from the transaction will be sent
|
||||
5. `network` - the [`Network`](src/main/scala/org/bitcoins/core/config/NetworkParameters.scala) we are transacting on
|
||||
|
||||
After providing this information, you can generate a validly signed bitcoin transaction by calling the `sign` method.
|
||||
|
||||
### The [`Sign` API](src/main/scala/org/bitcoins/core/crypto/Sign.scala)
|
||||
|
||||
This is the API we define to sign things with. It takes in an arbitrary byte vector and returns a `Future[ECDigitalSignature]`. The reason we incorporate `Future`s here is for extensibility of this API. We would like to provide implementations of this API for hardware devices, which need to be asynchrnous since they may require user input.
|
||||
|
||||
From [`core/src/main/scala/org/bitcoins/core/crypto/Sign.scala`](src/main/scala/org/bitcoins/core/crypto/Sign.scala):
|
||||
|
||||
```scala
|
||||
trait Sign {
|
||||
def signFunction: ByteVector => Future[ECDigitalSignature]
|
||||
|
||||
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
|
||||
signFunction(bytes)
|
||||
|
||||
def sign(bytes: ByteVector): ECDigitalSignature = {
|
||||
Await.result(signFuture(bytes), 30.seconds)
|
||||
}
|
||||
|
||||
def publicKey: ECPublicKey
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The `ByteVector` that is input to the `signFunction` should be the hash that is output from [`TransactionSignatureSerializer`](src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala)'s `hashForSignature` method. Our in-memory [`ECKey`](src/main/scala/org/bitcoins/core/crypto/ECKey.scala) types implement the `Sign` API.
|
||||
|
||||
If you wanted to implement a new `Sign` api for a hardware wallet, you can easily pass it into the `TxBuilder`/`Signer` classes to allow for you to use those devices to sign with Bitcoin-S.
|
||||
|
||||
This API is currently used to sign ordinary transactions with our [`Signer`](src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala)s. The `Signer` subtypes (i.e. `P2PKHSigner`) implement the specific functionality needed to produce a valid digital signature for their corresponding script type.
|
||||
|
||||
#### Complete `TxBuilder` example
|
||||
|
||||
For an example of how to use the `TxBuilder` please see [`TxBuilderExample.scala`](../doc/src/test/scala/TxBuilderExample.scala).
|
||||
|
||||
### Verifying a transaction's script is valid (does not check if UTXO is valid)
|
||||
|
||||
Transactions are run through the interpreter to check their validity. These are packaged up into an object called `ScriptProgram`, which contains the following:
|
||||
|
||||
- The transaction that is being checked
|
||||
- The specific input index that it is checking
|
||||
- The `scriptPubKey` for the crediting transaction
|
||||
- The flags used to verify the script
|
||||
|
||||
Here is an example of a transaction spending a `scriptPubKey` which is correctly evaluated with our interpreter implementation:
|
||||
|
||||
```scala
|
||||
chris@chris:~/dev/bitcoins-core$ sbt core/console
|
||||
|
||||
scala> import org.bitcoins.core.protocol.script._
|
||||
scala> import org.bitcoins.core.protocol.transaction._
|
||||
scala> import org.bitcoins.core.script._
|
||||
scala> import org.bitcoins.core.script.interpreter._
|
||||
scala> import org.bitcoins.core.policy._
|
||||
scala> import org.bitcoins.core.number._
|
||||
scala> import org.bitcoins.core.crypto._
|
||||
scala> import org.bitcoins.core.currency._
|
||||
|
||||
scala> val spendingTx = Transaction.fromHex("0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e690c865c2f6f7a9710a474154ab1423abb5b9288ac00000000")
|
||||
spendingTx: org.bitcoins.core.protocol.transaction.Transaction = ... // omitted for brevity
|
||||
|
||||
scala> val scriptPubKey = ScriptPubKey.fromAsmHex("76a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")
|
||||
scriptPubKey: org.bitcoins.core.protocol.script.ScriptPubKey = P2PKHScriptPubKeyImpl(1976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac)
|
||||
|
||||
scala> val output = TransactionOutput(CurrencyUnits.zero, scriptPubKey)
|
||||
output: org.bitcoins.core.protocol.transaction.TransactionOutput = ... // omitted for brevity
|
||||
|
||||
scala> val inputIndex = UInt32.zero
|
||||
inputIndex: org.bitcoins.core.number.UInt32 = UInt32Impl(0)
|
||||
|
||||
scala> val btxsc = BaseTxSigComponent(spendingTx,inputIndex,output,Policy.standardScriptVerifyFlags)
|
||||
btxsc: org.bitcoins.core.crypto.BaseTxSigComponent = ... // omitted for brevity
|
||||
|
||||
scala> val preExecution = PreExecutionScriptProgram(btxsc)
|
||||
preExecution: org.bitcoins.core.script.PreExecutionScriptProgram = ... // omitted for brevity
|
||||
|
||||
scala> val result = ScriptInterpreter.run(preExecution)
|
||||
result: org.bitcoins.core.script.result.ScriptResult = ScriptOk
|
||||
```
|
||||
|
||||
## Running `core` tests
|
||||
|
||||
See [`core-test/README.md`](../core-test/README.md).
|
||||
See the core module section on the
|
||||
Bitcoin-S [website](https://bitcoin-s.org/docs/core/core-intro).
|
||||
|
18
docker-compose.yml
Normal file
@ -0,0 +1,18 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
docusaurus:
|
||||
build: .
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 35729:35729
|
||||
volumes:
|
||||
- ./docs:/app/docs
|
||||
- ./website/blog:/app/website/blog
|
||||
- ./website/core:/app/website/core
|
||||
- ./website/i18n:/app/website/i18n
|
||||
- ./website/pages:/app/website/pages
|
||||
- ./website/static:/app/website/static
|
||||
- ./website/sidebars.json:/app/website/sidebars.json
|
||||
- ./website/siteConfig.js:/app/website/siteConfig.js
|
||||
working_dir: /app/website
|
82
docs/contributing-website.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
id: contributing-website
|
||||
title: Contributing to the website
|
||||
---
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/).
|
||||
|
||||
For simple changes to the documentation, click on the `Edit` button at the top
|
||||
of each page and submit those changes directly on GitHub.
|
||||
|
||||
## Scaladoc
|
||||
|
||||
One of the goals of Bitcoin-S is having useful and well-formatted Scaladoc comments on classes,
|
||||
objects and functions. Here are some useful resources on how to properly format your Scaladoc comments:
|
||||
|
||||
- [Scaladoc for library authors](https://docs.scala-lang.org/overviews/scaladoc/for-library-authors.html)
|
||||
- [Guidelines](https://docs.scala-lang.org/style/scaladoc.html) used by the official Scala language Scaladoc
|
||||
- [Alvin Alexander guide](https://alvinalexander.com/scala/how-to-generate-scala-documentation-scaladoc-command-examples) on writing Scaladoc
|
||||
|
||||
To generate Scaladocs:
|
||||
|
||||
```bash
|
||||
$ sbt
|
||||
> unidoc
|
||||
```
|
||||
|
||||
This gets placed in `website/static/api`. When viewing the Docusaurus site the generated Scaladocs
|
||||
appear under the API tab in the header bar,
|
||||
or in the "API reference" link in the footer.
|
||||
|
||||
## Running the site locally
|
||||
|
||||
For running the website locally, you'll need:
|
||||
|
||||
- `yarn` (https://yarnpkg.com/lang/en/docs/install-ci/)
|
||||
- `sbt` (https://www.scala-sbt.org/1.0/docs/Setup.html)
|
||||
|
||||
> In case you want to contribute substantial structural changes to the website,
|
||||
> we suggest to read
|
||||
> [Docusaurus' documentation](https://docusaurus.io/docs/en/installation.html)
|
||||
> first.
|
||||
|
||||
You can now build and launch the website using
|
||||
these commands:
|
||||
|
||||
```sh
|
||||
cd website
|
||||
yarn install # only the first time, to install the dependencies
|
||||
yarn start
|
||||
```
|
||||
|
||||
In a separate shell:
|
||||
|
||||
```bash
|
||||
$ sbt
|
||||
> doc/mdoc --watch
|
||||
```
|
||||
|
||||
The above commands compiles our Mdoc Markdown files every time you change
|
||||
them, and puts them in the correct directory for Docusaurus to find them.
|
||||
|
||||
Now visit http://localhost:3000/ and you should see a local version of
|
||||
the website.
|
||||
|
||||
## Adding a new page
|
||||
|
||||
Whenever you add a new markdown page to the documentation, you'll have to
|
||||
manually include it in the side menu.
|
||||
|
||||
You can do this by editing the `website/sidebars.json` file. The name to use is
|
||||
the `id` specified in the page metadata (see the existing pages for an example).
|
||||
|
||||
## Publishing the site
|
||||
|
||||
```bash
|
||||
$ sbt
|
||||
> docs/publishWebsite
|
||||
```
|
||||
|
||||
This command first generates Scaladocs, then invokes
|
||||
`docs/docusaurusPublishGhPages`, which in turn compile our mdoc
|
||||
files, build the site and push them to GH pages.
|
161
docs/contributing.md
Normal file
@ -0,0 +1,161 @@
|
||||
---
|
||||
id: contributing
|
||||
title: Contributing
|
||||
---
|
||||
|
||||
Bitcoin-S is an open source project where anyone is welcome to contribute. All contributions are encouraged and appreciated, whether that is code, testing, documentation or something else entirely.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
It's possible to communicate with other developers through a variety of communication channels:
|
||||
|
||||
- [Suredbits Slack](https://join.slack.com/t/suredbits/shared_invite/enQtNDEyMjY3MTg1MTg3LTYyYjkwOGUzMDQ4NDAwZjE1M2I3MmQyNWNlZjNlYjg4OGRjYTRjNWUwNjRjNjg4Y2NjZjAxYjU1N2JjMTU1YWM) - Suredbits is a company monetizing APIs through the Lightning Network. Suredbits doesn't own Bitcoin-S, but the Suredbits CEO Chris Stewart is the maintainer of this library. There's a separate Bitcoin-S channel on their Slack, this is probably the easiest way of getting in touch with someone working on this project.
|
||||
- [Bitcoin-S Gitter](https://gitter.im/bitcoin-s-core/)
|
||||
- [#bitcoin-scala](https://webchat.freenode.net/?channels=bitcoin-scala) on IRC Freenode
|
||||
|
||||
## Developer productivity
|
||||
|
||||
### Bloop
|
||||
|
||||
If you're tired of waiting around for sbt all day, there's a new,
|
||||
cool kid on the block. It is called [Bloop](https://scalacenter.github.io/bloop/),
|
||||
and it makes compilations in general faster, and in particular
|
||||
incremental, small compilation units (which greatly help editor
|
||||
performance). Bloop is a server that runs in the background of
|
||||
your computer, and keeps several "hot" JVMs running at all
|
||||
times. These JVMs serve compilation requests. Because the JVMs
|
||||
are running in the background you avoid the startup lag, and you
|
||||
also get code that's already [JIT compiled](https://en.wikipedia.org/wiki/Just-in-time_compilation)
|
||||
for you.
|
||||
|
||||
The documentation on Bloops [site](https://scalacenter.github.io/bloop/) is good, but here is the highlights:
|
||||
|
||||
1. Install Bloop by doing step 1 & 2 in the [official guide](https://scalacenter.github.io/bloop/setup#universal)
|
||||
2. Enable the Bloop background daemon
|
||||
1. macOS:
|
||||
```bash
|
||||
$ brew services start bloop
|
||||
```
|
||||
2. Ubuntu:
|
||||
```bash
|
||||
$ systemctl --user enable $HOME/.bloop/systemd/bloop.service
|
||||
$ systemctl --user daemon-reload
|
||||
$ systemctl --user start bloop
|
||||
```
|
||||
3. Enable shell completion for the Bloop CLI
|
||||
1. Bash:
|
||||
```bash
|
||||
$ echo '. $HOME/.bloop/bash/bloop' >> $HOME/.bash_profile
|
||||
```
|
||||
2. Zsh:
|
||||
```bash
|
||||
$ echo 'autoload -U compinit' >> $HOME/.zshrc
|
||||
$ echo 'fpath=($HOME/.bloop/zsh $fpath)' >> $HOME/.bashrc
|
||||
$ echo 'compinit' >> $HOME/.bashrc
|
||||
```
|
||||
3. Fish:
|
||||
```bash
|
||||
$ ln -s $HOME/.bloop/fish/bloop.fish ~/.config/fish/completions/bloop.fish
|
||||
```
|
||||
4. Generate configuration files
|
||||
```bash
|
||||
$ sbt bloopInstall
|
||||
```
|
||||
5. Import Bitcoin-S into IntelliJ again, as a bsp (Build Server Protocol) project (instead of a sbt project). Make sure you're running on the most recent IntelliJ and Scala plugin. See [official docs](https://scalacenter.github.io/bloop/docs/ides/intellij) for details.
|
||||
6. _(Bonus step):_ Lightning fast recompilations on file save:
|
||||
```bash
|
||||
$ bloop compile --project <name of module your're working on> --watch
|
||||
```
|
||||
|
||||
Your editor should now be much faster and require less resources :tada:
|
||||
|
||||
## Testing
|
||||
|
||||
### Property based testing
|
||||
|
||||
This library aims to achieve high level of correctness via property based
|
||||
testing. At the simplest level, you can think of property based testing as
|
||||
specifying a invariant that must always hold true.
|
||||
[Here](https://github.com/bitcoin-s/bitcoin-s-core/blob/89fbf35d78046b7ed21fd93fec05bb57cba023bb/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala#L13-L17)
|
||||
is an example of a property in the bitcoin-s-core test suite
|
||||
|
||||
```scala
|
||||
property("Serialization symmetry") =
|
||||
Prop.forAll(TransactionGenerators.transactions) { tx =>
|
||||
Transaction(tx.hex) == tx
|
||||
}
|
||||
```
|
||||
|
||||
What this property says is that for every transaction we can generate with
|
||||
[`TransactionGenerators.transactions`](/api/org/bitcoins/core/gen/TransactionGenerators)
|
||||
we _must_ be able to serialize it to hex format, then deserialize it back
|
||||
to a transaction and get the original `tx` back.
|
||||
|
||||
A more complex example of property based testing is checking that a
|
||||
multisignature transaction was signed correctly (see
|
||||
[`TransactionSignatureCreatorSpec`](core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala)
|
||||
line 29-34). First we generate a _supposedly_ validly signed multisig
|
||||
transaction with [`TransactionGenerators.signedMultiSigTransaction`](/api/org/bitcoins/testkit/core/gen/TransactionGenerators)
|
||||
(line 102-108). These transactions have varying `m` of `n` requirements.
|
||||
An interesting corner case if when you have 0 of `n` signatures, which
|
||||
means no signature is required. Property based testing is really good at
|
||||
fleshing out these corner cases. We check to see if this transaction is
|
||||
valid by running it through our [`ScriptInterpreter`](/api/org/bitcoins/core/script/interpreter/ScriptInterpreter).
|
||||
If we have built our functionality correctly the `ScriptInterpreter` should
|
||||
always return [`ScriptOk`](/api/org/bitcoins/core/script/result/ScriptResult)
|
||||
indicating the script was valid.
|
||||
|
||||
```scala
|
||||
property("generate valid signatures for a multisignature transaction") =
|
||||
Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
|
||||
case (txSignatureComponent: TxSigComponent, _) =>
|
||||
//run it through the interpreter
|
||||
val program = ScriptProgram(txSignatureComponent)
|
||||
val result = ScriptInterpreter.run(program)
|
||||
result == ScriptOk
|
||||
}
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
To run the entire test suite all you need to do is run the following command:
|
||||
|
||||
> This takes a long time, and runs a lot of tests that require IO. It may hog your computer at times.
|
||||
|
||||
```scala
|
||||
$ sbt test
|
||||
[info] Elapsed time: 4 min 36.760 sec
|
||||
[info] ScalaCheck
|
||||
[info] Passed: Total 149, Failed 0, Errors 0, Passed 149
|
||||
[info] ScalaTest
|
||||
[info] Run completed in 4 minutes, 55 seconds.
|
||||
[info] Total number of tests run: 744
|
||||
[info] Suites: completed 97, aborted 0
|
||||
[info] Tests: succeeded 744, failed 0, canceled 0, ignored 0, pending 0
|
||||
[info] All tests passed.
|
||||
[info] Passed: Total 909, Failed 0, Errors 0, Passed 909
|
||||
[success] Total time: 297 s, completed Jul 20, 2017 10:34:16 AM
|
||||
```
|
||||
|
||||
To run a specific suite of tests you can specify the suite name in the following way
|
||||
|
||||
```scala
|
||||
$ sbt testOnly *ScriptInterpreterTest*
|
||||
[info] ScriptInterpreterTest:
|
||||
[info] ScriptInterpreter
|
||||
[info] - must evaluate all the scripts from the bitcoin core script_tests.json
|
||||
[info] Run completed in 8 seconds, 208 milliseconds.
|
||||
[info] Total number of tests run: 1
|
||||
[info] Suites: completed 1, aborted 0
|
||||
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
|
||||
[info] All tests passed.
|
||||
```
|
||||
|
||||
The command `sbt testQuick` can also be handy. It runs tests that either:
|
||||
|
||||
1. Failed previously
|
||||
2. Has not been run previously
|
||||
3. Either the test or one of its dependencies has been recompiled
|
||||
|
||||
For more information on `testQuick`, see the offical
|
||||
[sbt docs](https://www.scala-sbt.org/1.x/docs/Testing.html#testQuick).
|
188
docs/core/core-intro.md
Normal file
@ -0,0 +1,188 @@
|
||||
---
|
||||
id: core-intro
|
||||
title: Core module
|
||||
---
|
||||
|
||||
The `core` module is the core (duh!) functionality of Bitcoin-S. The goal is to provide basic
|
||||
data structures that are found in the Bitcoin and Lightning protocols while
|
||||
minimizing external depedencies for security purposes. We aim to have an extremely
|
||||
high level of test coverage in this module to flesh out bugs. We use property based
|
||||
testing heavily in this library to ensure high quality of code.
|
||||
|
||||
## The basics
|
||||
|
||||
Every bitcoin protocol data structure (and some other data structures) extends [`NetworkElement`](/api/org/bitcoins/core/protocol/NetworkElement). `NetworkElement` provides methods to convert the data structure to hex or byte representation. When paired with [`Factory`](/api/org/bitcoins/core/util/Factory) we can easily serialize and deserialize data structures.
|
||||
|
||||
Most data structures have companion objects that extends `Factory` to be able to easily create protocol data structures. An example of this is the [`ScriptPubKey`](/api/org/bitcoins/core/protocol/script/ScriptPubKey) companion object. You can use this companion object to create a `ScriptPubKey` from a hex string or a byte array.
|
||||
|
||||
## Main modules in `core`
|
||||
|
||||
1. [`protocol`](/api/org/bitcoins/core/protocol) - basic protocol data structures. Useful for serializing/deserializing things
|
||||
1. [`crypto`](/api/org/bitcoins/core/crypto) - cryptograhic functionality used in Bitcoin and Lightning
|
||||
1. [`script`](/api/org/bitcoins/core/script) - an implementation of [Script](https://en.bitcoin.it/wiki/Script) - the programming language in Bitcoin
|
||||
1. [`wallet`](/api/org/bitcoins/core/wallet) - implements signing logic for Bitcoin transactions. This module is not named well as there is **NO** functionality to persist wallet state to disk as it stands. This will most likely be renamed in the future.
|
||||
1. [`config`](/api/org/bitcoins/core/config) - Contains information about a chain's genesis block and DNS seeds
|
||||
1. [`number`](/api/org/bitcoins/core/number) - Implements number types that are native in C, i.e. `UInt8`, `UInt32`, `UInt64`, etc.
|
||||
1. [`currency`](/api/org/bitcoins/core/currency) - Implements currency units in the Bitcoin protocol
|
||||
1. [`bloom`](/api/org/bitcoins/core/bloom) - Implements [Bloom filters](https://en.wikipedia.org/wiki/Bloom_filter) and [merkle blocks](https://bitcoin.org/en/glossary/merkle-block) needed for [BIP37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki)
|
||||
1. [`hd`](/api/org/bitcoins/core/hd) - Contains implementations of hierarchical deterministic (HD) paths, that when combined with `ExtPrivKey` and `ExtPubKey` in `crypto` can implement BIP32, BIP44, BIP49 and BIP84.
|
||||
|
||||
## Examples
|
||||
|
||||
### Serializing and deserializing a `Transaction`
|
||||
|
||||
Here is an example scala console session with bitcoins-core
|
||||
|
||||
```scala mdoc
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
|
||||
val hexTx = "0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f8$9c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1$a02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e$90c865c2f6f7a9710a474154ab1423abb5b9288ac00000000"
|
||||
|
||||
// val tx = Transaction.fromHex(hexTx)
|
||||
|
||||
// val hexAgain = tx.hex
|
||||
```
|
||||
|
||||
This gives us an example of a hex encoded Bitcoin transaction that is deserialized to a native Scala object called a [`Transaction`](/api/org/bitcoins/core/protocol/transaction/Transaction). You could also serialize the transaction to bytes using `tx.bytes` instead of `tx.hex`. These methods are available on every data structure that extends NetworkElement, like [`ECPrivateKey`](/api/org/bitcoins/core/crypto/ECPrivateKey), [`ScriptPubKey`](/api/org/bitcoins/core/protocol/script/ScriptPubKey), [`ScriptWitness`](/api/org/bitcoins/core/protocol/script/ScriptWitness), and [`Block`](/api/org/bitcoins/core/protocol/blockchain/Block).
|
||||
|
||||
#### Generating a BIP39 mnemonic phrase and an `xpriv`
|
||||
|
||||
BIP39 mnemonic phrases are the most common way of creating backups of wallets.
|
||||
They are between 12 and 24 words the user writes down, and can later be used to restore
|
||||
their bitcoins. From the mnemonic phrase we generate a wallet seed, and that seed
|
||||
can be used to generate what's called an extended private key
|
||||
([`ExtPrivateKey`](/api/org/bitcoins/core/crypto/ExtPrivateKey) in Bitcoin-S).
|
||||
|
||||
Here's an example:
|
||||
|
||||
```scala mdoc:to-string
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.hd._
|
||||
|
||||
// the length of the entropy bit vector determine
|
||||
// how long our phrase ends up being
|
||||
// 256 bits of entropy results in 24 words
|
||||
val entropy: BitVector = MnemonicCode.getEntropy256Bits
|
||||
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
|
||||
mnemonicCode.words // the phrase the user should write down
|
||||
|
||||
// the password argument is an optional, extra security
|
||||
// measure. all MnemonicCode instances will give you a
|
||||
// valid BIP39 seed, but different passwords will give
|
||||
// you different seeds. So you could have as many wallets
|
||||
// from the same seed as you'd like, by simply giving them
|
||||
// different passwords.
|
||||
val bip39Seed = BIP39Seed.fromMnemonic(mnemonicCode,
|
||||
password = "secret password")
|
||||
|
||||
val xpriv = ExtPrivateKey.fromBIP39Seed(ExtKeyVersion.SegWitMainNetPriv,
|
||||
bip39Seed)
|
||||
val xpub = xpriv.extPublicKey
|
||||
|
||||
// you can now use the generated xpriv to derive further
|
||||
// private or public keys
|
||||
|
||||
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
|
||||
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
|
||||
// alternatively:
|
||||
val otherSegwitPath =
|
||||
SegWitHDPath(HDCoinType.Bitcoin,
|
||||
accountIndex = 0,
|
||||
HDChainType.External,
|
||||
addressIndex = 0)
|
||||
|
||||
segwitPath == otherSegwitPath
|
||||
|
||||
// there's also paths available for legacy
|
||||
// addresses (LegacyHDPath) as well as nested
|
||||
// segwit paths (NestedSegWitPath)
|
||||
```
|
||||
|
||||
### Building a signed transaction
|
||||
|
||||
Bitcoin Core supports building unsigned transactions and then signing them with a set of private keys. The first important thing to look at is [`UTXOSpendingInfo`](/api/org/bitcoins/core/wallet/utxo/UTXOSpendingInfo). This contains all of the information needed to create a validly signed [`ScriptSignature`](/api/org/bitcoins/core/protocol/script/ScriptSignature) that spends this output.
|
||||
|
||||
Our [`TxBuilder`](/api/org/bitcoins/core/wallet/builder/TxBuilder) class requires you to provide the following:
|
||||
|
||||
1. `destinations` - the places we are sending bitcoin to. These are [TransactionOutputs](/api/org/bitcoins/core/protocol/transaction/TransactionOutput) you are sending coins too
|
||||
2. `utxos` - these are the [UTXOs](/api/org/bitcoins/core/wallet/utxo/UTXOSpendingInfo) used to fund your transaction. These must exist in your wallet, and you must know how to spend them (i.e. have the private key)
|
||||
3. `feeRate` - the fee rate you want to pay for this transaction
|
||||
4. `changeSPK` - where the change (i.e. `creditingAmount - destinationAmount - fee`) from the transaction will be sent
|
||||
5. `network` - the network(/api/org/bitcoins/core/config/NetworkParameters) we are transacting on
|
||||
|
||||
After providing this information, you can generate a validly signed bitcoin transaction by calling the `sign` method.
|
||||
|
||||
To see a complete example of this, see [our `TxBuilder` example](txbuilder.md)
|
||||
|
||||
### The [`Sign` API](/api/org/bitcoins/core/crypto/Sign)
|
||||
|
||||
This is the API we define to sign things with. It takes in an arbitrary byte vector and returns a `Future[ECDigitalSignature]`. The reason we incorporate `Future`s here is for extensibility of this API. We would like to provide implementations of this API for hardware devices, which need to be asynchrnous since they may require user input.
|
||||
|
||||
From [`core/src/main/scala/org/bitcoins/core/crypto/Sign.scala`](/api/org/bitcoins/core/crypto/Sign):
|
||||
|
||||
```scala mdoc
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
trait Sign {
|
||||
def signFunction: ByteVector => Future[ECDigitalSignature]
|
||||
|
||||
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
|
||||
signFunction(bytes)
|
||||
|
||||
def sign(bytes: ByteVector): ECDigitalSignature = {
|
||||
Await.result(signFuture(bytes), 30.seconds)
|
||||
}
|
||||
|
||||
def publicKey: ECPublicKey
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The `ByteVector` that is input to the `signFunction` should be the hash that is output from [`TransactionSignatureSerializer`](/api/org/bitcoins/core/crypto/TransactionSignatureSerializer)'s `hashForSignature` method. Our in-memory [`ECKey`](/api/org/bitcoins/core/crypto/ECKey) types implement the `Sign` API.
|
||||
|
||||
If you wanted to implement a new `Sign` api for a hardware wallet, you can easily pass it into the `TxBuilder`/`Signer` classes to allow for you to use those devices to sign with Bitcoin-S.
|
||||
|
||||
This API is currently used to sign ordinary transactions with our [`Signer`](/api/org/bitcoins/core/wallet/signer/Signer)s. The `Signer` subtypes (i.e. `P2PKHSigner`) implement the specific functionality needed to produce a valid digital signature for their corresponding script type.
|
||||
|
||||
### Verifying a transaction's script is valid (does not check if UTXO is valid)
|
||||
|
||||
Transactions are run through the interpreter to check their validity. These are packaged up into an object called `ScriptProgram`, which contains the following:
|
||||
|
||||
- The transaction that is being checked
|
||||
- The specific input index that it is checking
|
||||
- The `scriptPubKey` for the crediting transaction
|
||||
- The flags used to verify the script
|
||||
|
||||
Here is an example of a transaction spending a `scriptPubKey` which is correctly evaluated with our interpreter implementation:
|
||||
|
||||
```scala mdoc:silent
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.script._
|
||||
import org.bitcoins.core.script.interpreter._
|
||||
import org.bitcoins.core.policy._
|
||||
import org.bitcoins.core.number._
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.currency._
|
||||
|
||||
val spendingTx = Transaction.fromHex("0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e690c865c2f6f7a9710a474154ab1423abb5b9288ac00000000")
|
||||
|
||||
val scriptPubKey = ScriptPubKey.fromAsmHex("76a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")
|
||||
|
||||
val output = TransactionOutput(CurrencyUnits.zero, scriptPubKey)
|
||||
|
||||
val inputIndex = UInt32.zero
|
||||
|
||||
val btxsc = BaseTxSigComponent(spendingTx,inputIndex,output,Policy.standardScriptVerifyFlags)
|
||||
|
||||
val preExecution = PreExecutionScriptProgram(btxsc)
|
||||
```
|
||||
|
||||
```scala mdoc
|
||||
val result = ScriptInterpreter.run(preExecution)
|
||||
```
|
132
docs/core/txbuilder.md
Normal file
@ -0,0 +1,132 @@
|
||||
---
|
||||
id: txbuilder
|
||||
title: TxBuilder example
|
||||
---
|
||||
|
||||
Bitcoin-S features a transaction buidlder that constructs and signs Bitcoin
|
||||
transactions. Here's an example of how to use it
|
||||
|
||||
```scala mdoc:silent
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import org.bitcoins.core._
|
||||
import number._
|
||||
import config._
|
||||
import currency._
|
||||
import crypto._
|
||||
import script.crypto._
|
||||
import protocol.transaction._
|
||||
import protocol.script._
|
||||
|
||||
import wallet.builder._
|
||||
import wallet.fee._
|
||||
import wallet.utxo._
|
||||
|
||||
implicit val ec: ExecutionContext = ExecutionContext.Implicits.global
|
||||
|
||||
// generate a fresh private key that we are going to use in the scriptpubkey
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
// this is the script that the TxBuilder is going to create a
|
||||
// script signature that validly spends this scriptPubKey
|
||||
val creditingSpk = P2PKHScriptPubKey(pubKey = privKey.publicKey)
|
||||
val amount = 10000.satoshis
|
||||
|
||||
// this is the UTXO we are going to be spending
|
||||
val utxo =
|
||||
TransactionOutput(currencyUnit = amount, scriptPubKey = creditingSpk)
|
||||
|
||||
// the private key that locks the funds for the script we are spending too
|
||||
val destinationPrivKey = ECPrivateKey.freshPrivateKey
|
||||
|
||||
// the amount we are sending -- 5000 satoshis -- to the destinationSPK
|
||||
val destinationAmount = 5000.satoshis
|
||||
|
||||
// the script that corresponds to destination private key, this is what is protecting the money
|
||||
val destinationSPK =
|
||||
P2PKHScriptPubKey(pubKey = destinationPrivKey.publicKey)
|
||||
|
||||
// this is where we are sending money too
|
||||
// we could add more destinations here if we
|
||||
// wanted to batch transactions
|
||||
val destinations = {
|
||||
val destination1 = TransactionOutput(currencyUnit = destinationAmount,
|
||||
scriptPubKey = destinationSPK)
|
||||
|
||||
List(destination1)
|
||||
}
|
||||
|
||||
// we have to fabricate a transaction that contains the
|
||||
// UTXO we are trying to spend. If this were a real blockchain
|
||||
// we would need to reference the UTXO set
|
||||
val creditingTx = BaseTransaction(version = Int32.one,
|
||||
inputs = List.empty,
|
||||
outputs = List(utxo),
|
||||
lockTime = UInt32.zero)
|
||||
|
||||
// this is the information we need from the crediting TX
|
||||
// to properly "link" it in the transaction we are creating
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
// this contains all the information we need to
|
||||
// validly sign the UTXO above
|
||||
val utxoSpendingInfo = BitcoinUTXOSpendingInfo(outPoint = outPoint,
|
||||
output = utxo,
|
||||
signers = List(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType =
|
||||
HashType.sigHashAll)
|
||||
|
||||
// all of the UTXO spending information, since we are only
|
||||
//spending one UTXO, this is just one element
|
||||
val utxos: List[BitcoinUTXOSpendingInfo] = List(utxoSpendingInfo)
|
||||
|
||||
// this is how much we are going to pay as a fee to the network
|
||||
// for this example, we are going to pay 1 satoshi per byte
|
||||
val feeRate = SatoshisPerByte(1.satoshi)
|
||||
|
||||
val changePrivKey = ECPrivateKey.freshPrivateKey
|
||||
val changeSPK = P2PKHScriptPubKey(pubKey = changePrivKey.publicKey)
|
||||
|
||||
// the network we are on, for this example we are using
|
||||
// the regression test network. This is a network you control
|
||||
// on your own machine
|
||||
val networkParams = RegTest
|
||||
|
||||
// Yay! Now we have a TxBuilder object that we can use
|
||||
// to sign the TX.
|
||||
val txBuilder: BitcoinTxBuilder = {
|
||||
val builderF = BitcoinTxBuilder(
|
||||
destinations = destinations,
|
||||
utxos = utxos,
|
||||
feeRate = feeRate,
|
||||
changeSPK = changeSPK,
|
||||
network = networkParams)
|
||||
Await.result(builderF, 30.seconds)
|
||||
}
|
||||
|
||||
// Let's finally produce a validly signed tx!
|
||||
// The 'sign' method is going produce a validly signed transaction
|
||||
// This is going to iterate through each of the UTXOs and use
|
||||
// the corresponding UTXOSpendingInfo to produce a validly
|
||||
// signed input. This UTXO has:
|
||||
// 1: one input
|
||||
// 2: outputs (destination and change outputs)
|
||||
// 3: a fee rate of 1 satoshi/byte
|
||||
val signedTx: Transaction = {
|
||||
val signF = txBuilder.sign
|
||||
Await.result(signF, 30.seconds)
|
||||
}
|
||||
```
|
||||
|
||||
```scala mdoc
|
||||
signedTx.inputs.length
|
||||
|
||||
signedTx.outputs.length
|
||||
|
||||
//remember, you can call .hex on any bitcoin-s data structure to get the hex representation!
|
||||
signedTx.hex
|
||||
```
|
51
docs/getting-started.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
id: getting-started
|
||||
title: Add Bitcoin-S to your project
|
||||
---
|
||||
|
||||
## REPL
|
||||
|
||||
You can try out Bitcoin-S in a REPL in a matter of seconds. Run the provided
|
||||
["try bitcoin-s"](https://github.com/bitcoin-s/bitcoin-s-core/blob/master/try-bitcoin-s.sh)
|
||||
script, which has no dependencies other than an installed JDK. The script
|
||||
downloads and installs [Coursier](https://get-coursier.io/) and uses it to
|
||||
fetch the [Ammonite](https://ammonite.io) REPL and the latest version of
|
||||
Bitcoin-S. It then drops you into immediately into a REPL session.
|
||||
|
||||
```
|
||||
% curl -s https://raw.githubusercontent.com/bitcoin-s/bitcoin-s-core/master/try-bitcoin-s.sh | bash
|
||||
Loading...
|
||||
Welcome to the Ammonite Repl 1.0.3
|
||||
(Scala 2.12.4 Java 1.8.0_152)
|
||||
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
|
||||
@ 23 :: "foo" :: true :: HNil
|
||||
res0: Int :: String :: Boolean :: HNil = 23 :: "foo" :: true :: HNil
|
||||
%
|
||||
```
|
||||
|
||||
## Build tools
|
||||
|
||||
If you want to add Bitcoin-S to your project, follow the
|
||||
instructions for your build tool
|
||||
|
||||
### sbt
|
||||
|
||||
Add this to your `build.sbt`:
|
||||
|
||||
```scala
|
||||
libraryDependencies +="org.bitcoins" % "bitcoin-s-secp256k1jni" % "@VERSION@"
|
||||
|
||||
libraryDependencies += "org.bitcoins" %% "bitcoin-s-core" % "@VERSION@" withSources() withJavadoc()
|
||||
|
||||
libraryDependencies += "org.bitcoins" %% "bitcoin-s-bitcoind-rpc" % "@VERSION@" withSources() withJavadoc()
|
||||
|
||||
libraryDependencies += "org.bitcoins" %% "bitcoin-s-eclair-rpc" % "@VERSION@" withSources() withJavadoc()
|
||||
|
||||
libraryDependencies += "org.bitcoins" %% "bitcoin-s-testkit" % "@VERSION@" withSources() withJavadoc()
|
||||
|
||||
libraryDependencies += "org.bitcoins" %% "bitcoin-s-zmq" % "@VERSION@" withSources() withJavadoc()
|
||||
```
|
||||
|
||||
### Mill
|
||||
|
||||
TODO
|
116
docs/rpc/bitcoind.md
Normal file
@ -0,0 +1,116 @@
|
||||
---
|
||||
id: rpc-bitcoind
|
||||
title: bitcoind/Bitcoin Core
|
||||
---
|
||||
|
||||
> Note: `bitcoin-s-bitcoind-rpc` requires you to have `bitcoind` (Bitcoin Core daemon) installed. Grab this at [bitcoincore.org](https://bitcoincore.org/en/download/)
|
||||
|
||||
The Bitcoin Core RPC client in Bitcoin-S currently supports the Bitcoin Core 0.16 and 0.17
|
||||
version lines. It can be set up to work with both local and remote Bitcoin Core servers.
|
||||
|
||||
## Connecting to a local `bitcoind` instance
|
||||
|
||||
```scala mdoc:compile-only
|
||||
import scala.concurrent._
|
||||
import akka.actor.ActorSystem
|
||||
|
||||
import org.bitcoins.{rpc, core}
|
||||
import core.currency.Bitcoins
|
||||
import rpc.client.common._
|
||||
import rpc.config.BitcoindInstance
|
||||
|
||||
implicit val actorSystem: ActorSystem = ActorSystem.create()
|
||||
implicit val ec: ExecutionContext = actorSystem.dispatcher
|
||||
|
||||
// data directory defaults to ~/.bitcoin on Linux and
|
||||
// ~/Library/Application Support/Bitcoin on macOS
|
||||
val bitcoindInstance = BitcoindInstance.fromDatadir()
|
||||
|
||||
// alternative:
|
||||
import java.io.File
|
||||
val dataDir = new File("/my/bitcoin/data/dir")
|
||||
val otherInstance = BitcoindInstance.fromDatadir(dataDir)
|
||||
|
||||
|
||||
val client = new BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
val balance: Future[Bitcoins] = for {
|
||||
_ <- client.start()
|
||||
balance <- client.getBalance
|
||||
} yield balance
|
||||
```
|
||||
|
||||
## Connecting to a remote `bitcoind`
|
||||
|
||||
First, we create a secure connection to our `bitcoind` instance by setting
|
||||
up a SSH tunnel:
|
||||
|
||||
```bash
|
||||
$ ssh -L 8332:localhost:8332 \
|
||||
my-cool-user@my-cool-website.com
|
||||
```
|
||||
|
||||
> Note: the port number '8332' is the default for mainnet. If you want to
|
||||
> connect to a testnet `bitcoind`, the default port is '18332'
|
||||
|
||||
Now that we have a secure connection between our remote `bitcoind`, we're
|
||||
ready to create the connection with our RPC client
|
||||
|
||||
```scala mdoc:compile-only
|
||||
import akka.actor.ActorSystem
|
||||
import java.net.URI
|
||||
import scala.concurrent._
|
||||
|
||||
import org.bitcoins.core.config._
|
||||
import org.bitcoins.rpc.config._
|
||||
import org.bitcoins.rpc.client.common._
|
||||
|
||||
val username = "FILL_ME_IN" //this username comes from 'rpcuser' in your bitcoin.conf file
|
||||
val password = "FILL_ME_IN" //this password comes from your 'rpcpassword' in your bitcoin.conf file
|
||||
val rpcPort = 8332 //this is default port for mainnet, 18332 for testnet/regtest
|
||||
|
||||
|
||||
val authCredentials = BitcoindAuthCredentials(
|
||||
username = username,
|
||||
password = password,
|
||||
rpcPort = rpcPort
|
||||
)
|
||||
|
||||
val bitcoindInstance = {
|
||||
BitcoindInstance (
|
||||
network = MainNet,
|
||||
uri = new URI(s"http://localhost:${authCredentials.rpcPort + 1}"),
|
||||
rpcUri = new URI(s"http://localhost:${authCredentials.rpcPort}"),
|
||||
authCredentials = authCredentials
|
||||
)
|
||||
}
|
||||
|
||||
implicit val system: ActorSystem = ActorSystem.create()
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
val rpcCli = new BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
rpcCli.getBalance.onComplete { case balance =>
|
||||
println(s"Wallet balance=${balance}")
|
||||
system.terminate()
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test the Bitcoin-S RPC project you need both version 0.16 and 0.17 of Bitcoin Core. A list of current and previous releases can be found [here](https://bitcoincore.org/en/releases/).
|
||||
|
||||
You then need to set environment variables to indicate where Bitcoin-S can find the different versions:
|
||||
|
||||
```bash
|
||||
$ export BITCOIND_V16_PATH=/path/to/v16/bitcoind
|
||||
$ export BITCOIND_V17_PATH=/path/to/v17/bitcoind
|
||||
```
|
||||
|
||||
If you just run tests testing common functionality it's enough to have either version 0.16 or 0.17 on your `PATH`.
|
||||
|
||||
To run all RPC related tests:
|
||||
|
||||
```bash
|
||||
$ bash sbt bitcoindRpcTest/test
|
||||
```
|
27
docs/rpc/eclair.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
id: rpc-eclair
|
||||
title: Eclair
|
||||
---
|
||||
|
||||
This is a RPC client for [Eclair](https://github.com/acinq/eclair). It assumes that a bitcoind instance is running.
|
||||
|
||||
Currently this RPC client is written for [v0.2-beta8](https://github.com/ACINQ/eclair/releases/tag/v0.2-beta8) version of Eclair.
|
||||
|
||||
## Configuration of Eclair
|
||||
|
||||
Please see the configuration secion of the
|
||||
[Eclair README](https://github.com/acinq/eclair#configuring-eclair).
|
||||
|
||||
## Starting the jar
|
||||
|
||||
You need to download the jar from the [Eclair GitHub](https://github.com/ACINQ/eclair/releases/tag/v0.2-beta8).
|
||||
|
||||
To run Eclair you can use this command:
|
||||
|
||||
```bash
|
||||
$ java -jar eclair-node-0.2-beta8-52821b8.jar &
|
||||
```
|
||||
|
||||
Alternatively you can set the `ECLAIR_PATH` env variable and then you can start Eclair with the `start` method on `EclairRpcClient`.
|
||||
|
||||
**YOU NEED TO SET `ECLAIR_PATH` CORRECTLY TO BE ABLE TO RUN THE UNIT TESTS**
|
6
docs/rpc/rpc-clients-intro.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
id: rpc-clients-intro
|
||||
title: Introduction
|
||||
---
|
||||
|
||||
Bitcoin-S contains RPC clients for interacting with both Bitcoin Core and Eclair.
|
@ -1,6 +1,17 @@
|
||||
---
|
||||
id: security
|
||||
title: Security
|
||||
---
|
||||
|
||||
The Bitcoin-S developers take security very seriously. This library has
|
||||
very few dependencies (at least in the `core` module), which is for
|
||||
of security reasons.
|
||||
|
||||
## Disclosure
|
||||
|
||||
Please send an email to stewart.chris1234@gmail.com encrypted with this gpg key
|
||||
If you have any security disclosures related to Bitcoin-S, please send an
|
||||
email to [stewart.chris1234@gmail.com](mailto:stewart.chris1234@gmail.com?subject=Bitcoin-S%20Security%20Disclosure)
|
||||
encrypted with this GPG key:
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
@ -1,29 +1,2 @@
|
||||
[ ![Download](https://api.bintray.com/packages/bitcoin-s/bitcoin-s-core/bitcoin-s-eclair-rpc/images/download.svg) ](https://bintray.com/bitcoin-s/bitcoin-s-core/bitcoin-s-eclair-rpc/_latestVersion)
|
||||
|
||||
# Bitcoin-s Eclair RPC client
|
||||
|
||||
This is a RPC client for [Eclair](https://github.com/acinq/eclair). It assumes that a bitcoind instance is running.
|
||||
|
||||
Currently this RPC client is written for the latest official version of eclair which is [v0.2-beta8](https://github.com/ACINQ/eclair/releases/tag/v0.2-beta8)
|
||||
|
||||
## Configuration eclair
|
||||
|
||||
Please see the configuration secion of the
|
||||
[Eclair README](https://github.com/acinq/eclair#configuring-eclair).
|
||||
|
||||
|
||||
|
||||
## Starting the jar
|
||||
|
||||
You need to download the jar from the [Eclair GitHub](https://github.com/ACINQ/eclair/releases/tag/v0.2-beta8).
|
||||
|
||||
|
||||
To run Eclair you can use this command:
|
||||
|
||||
```bash
|
||||
$ java -jar eclair-node-0.2-beta8-52821b8.jar &
|
||||
```
|
||||
|
||||
Alternatively you can set the `ECLAIR_PATH` env variable and then you can start Eclair with the `start` method on `EclairRpcClient`.
|
||||
|
||||
**YOU NEED TO SET `ECLAIR_PATH` CORRECTLY TO BE ABLE TO RUN THE UNIT TESTS**
|
||||
See the Eclair section on the
|
||||
Bitcoin-S [website](https://bitcoin-s.org/docs/rpc/rpc-eclair).
|
||||
|
@ -139,9 +139,9 @@ object Deps {
|
||||
Test.ammonite
|
||||
)
|
||||
|
||||
val doc = List(
|
||||
val scripts = List(
|
||||
Compile.ammonite,
|
||||
"ch.qos.logback" % "logback-classic" % V.logback withSources () withJavadoc (),
|
||||
Compile.logback,
|
||||
Test.scalaTest,
|
||||
Test.logback
|
||||
)
|
||||
|
@ -13,12 +13,6 @@ addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
|
||||
// sbt plugin to unify scaladoc/javadoc across multiple projects
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.2")
|
||||
|
||||
// make static site through sbt
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.3.2")
|
||||
|
||||
// publish said site to GitHub pages
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2")
|
||||
|
||||
// ensure proper linkage across libraries in Scaladoc
|
||||
addSbtPlugin(
|
||||
"com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "latest.release")
|
||||
@ -29,3 +23,6 @@ addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.2.5")
|
||||
//tool to publish snapshots to sonatype after CI builds finish
|
||||
//https://github.com/olafurpg/sbt-ci-release
|
||||
addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.2.6")
|
||||
|
||||
// write markdown files with type-checked Scala
|
||||
addSbtPlugin("org.scalameta" % "sbt-mdoc" % "1.3.0" )
|
||||
|
19
try-bitcoin-s.sh
Normal file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
|
||||
# this script is cribbed from Shapeless
|
||||
# https://github.com/milessabin/shapeless/blob/master/scripts/try-shapeless.sh
|
||||
|
||||
COURSIER_URL=https://git.io/coursier-cli
|
||||
# check if coursier exists
|
||||
# TODO: check version? only new-ish works with latest.version
|
||||
test -e ~/.coursier/coursier || \
|
||||
# if not, download latest coursier-cli
|
||||
(mkdir -p ~/.coursier && curl -L -s --output ~/.coursier/coursier $COURSIER_URL && chmod +x ~/.coursier/coursier)
|
||||
|
||||
MSG="Welcome the Bitcoin-S REPL, powered by Ammonite
|
||||
Check out our documentation at https://bitcoin-s.org"
|
||||
# launch Ammonite with bitcoin-s on the classpath
|
||||
~/.coursier/coursier launch -q -P \
|
||||
com.lihaoyi:ammonite_2.12.4:latest.release \
|
||||
org.bitcoin-s:core.12:latest.release \
|
||||
-- --predef-code 'import org.bitcoins.core._' --banner $MSG < /dev/tty
|
86
website/core/Footer.js
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
|
||||
class Footer extends React.Component {
|
||||
docUrl(doc, language) {
|
||||
const baseUrl = this.props.config.baseUrl;
|
||||
const docsUrl = this.props.config.docsUrl;
|
||||
const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`;
|
||||
const langPart = `${language ? `${language}/` : ""}`;
|
||||
return `${baseUrl}${docsPart}${langPart}${doc}`;
|
||||
}
|
||||
|
||||
pageUrl(doc, language) {
|
||||
const baseUrl = this.props.config.baseUrl;
|
||||
return baseUrl + (language ? `${language}/` : "") + doc;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<footer className="nav-footer" id="footer">
|
||||
<section className="sitemap">
|
||||
<a href={this.props.config.baseUrl} className="nav-home">
|
||||
{this.props.config.footerIcon && (
|
||||
<img
|
||||
src={this.props.config.baseUrl + this.props.config.footerIcon}
|
||||
alt={this.props.config.title}
|
||||
width="66"
|
||||
height="58"
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
<div>
|
||||
<h5>Docs</h5>
|
||||
<a href={this.docUrl("getting-started", this.props.language)}>
|
||||
Getting Started
|
||||
</a>
|
||||
<a href={this.docUrl("core/core-intro", this.props.language)}>
|
||||
Guides
|
||||
</a>
|
||||
<a href={this.props.config.scaladocUrl}>API Reference</a>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Community</h5>
|
||||
<a href={this.pageUrl("users.html", this.props.language)}>
|
||||
User Showcase
|
||||
</a>
|
||||
<a
|
||||
href={this.props.config.suredbitsSlack}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Slack
|
||||
</a>
|
||||
<a href={this.props.config.gitterUrl}>Gitter chat</a>
|
||||
</div>
|
||||
<div>
|
||||
<h5>More</h5>
|
||||
<a href={`${this.props.config.baseUrl}blog`}>Blog</a>
|
||||
<a href={this.props.config.repoUrl}>GitHub</a>
|
||||
<a
|
||||
className="github-button"
|
||||
href={this.props.config.repoUrl}
|
||||
data-icon="octicon-star"
|
||||
data-count-href="/bitcoin-s/bitcoin-s-core/stargazers"
|
||||
data-show-count="true"
|
||||
data-count-aria-label="# stargazers on GitHub"
|
||||
aria-label="Star this project on GitHub"
|
||||
>
|
||||
Star
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="copyright">{this.props.config.copyright}</section>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Footer;
|
15
website/core/Image.js
Normal file
@ -0,0 +1,15 @@
|
||||
const React = require("react");
|
||||
|
||||
const Image = ({ src, style }) => (
|
||||
<img
|
||||
style={{
|
||||
maxWidth: "50%",
|
||||
// center the image
|
||||
margin: "0 25%",
|
||||
...style
|
||||
}}
|
||||
src={src}
|
||||
/>
|
||||
);
|
||||
|
||||
module.exports = Image;
|
50
website/i18n/en.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"_comment": "This file is auto-generated by write-translations.js",
|
||||
"localized-strings": {
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"tagline": "Bitcoin implementation in Scala",
|
||||
"docs": {
|
||||
"contributing-website": {
|
||||
"title": "Contributing to the website"
|
||||
},
|
||||
"contributing": {
|
||||
"title": "Contributing"
|
||||
},
|
||||
"core/core-intro": {
|
||||
"title": "Core module"
|
||||
},
|
||||
"core/txbuilder": {
|
||||
"title": "TxBuilder example"
|
||||
},
|
||||
"getting-started": {
|
||||
"title": "Add Bitcoin-S to your project"
|
||||
},
|
||||
"rpc/rpc-bitcoind": {
|
||||
"title": "bitcoind/Bitcoin Core"
|
||||
},
|
||||
"rpc/rpc-eclair": {
|
||||
"title": "Eclair"
|
||||
},
|
||||
"rpc/rpc-clients-intro": {
|
||||
"title": "Introduction"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"Docs": "Docs",
|
||||
"Help": "Help",
|
||||
"Blog": "Blog"
|
||||
},
|
||||
"categories": {
|
||||
"Getting started": "Getting started",
|
||||
"Core module": "Core module",
|
||||
"RPC clients": "RPC clients",
|
||||
"Contributing": "Contributing"
|
||||
}
|
||||
},
|
||||
"pages-strings": {
|
||||
"Help Translate|recruit community translators for your project": "Help Translate",
|
||||
"Edit this Doc|recruitment message asking to edit the doc source": "Edit",
|
||||
"Translate this Doc|recruitment message asking to translate the docs": "Translate"
|
||||
}
|
||||
}
|
15
website/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"examples": "docusaurus-examples",
|
||||
"start": "docusaurus-start",
|
||||
"build": "docusaurus-build",
|
||||
"publish-gh-pages": "docusaurus-publish",
|
||||
"write-translations": "docusaurus-write-translations",
|
||||
"version": "docusaurus-version",
|
||||
"rename-version": "docusaurus-rename-version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"docusaurus": "^1.9.0"
|
||||
}
|
||||
}
|
67
website/pages/en/help.js
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const CompLibrary = require("../../core/CompLibrary.js");
|
||||
const Image = require(process.cwd() + "/core/Image.js");
|
||||
|
||||
const Container = CompLibrary.Container;
|
||||
const GridBlock = CompLibrary.GridBlock;
|
||||
|
||||
function Help(props) {
|
||||
const { config: siteConfig, language = "" } = props;
|
||||
const { baseUrl, docsUrl } = siteConfig;
|
||||
const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`;
|
||||
const langPart = `${language ? `${language}/` : ""}`;
|
||||
const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`;
|
||||
|
||||
const supportLinks = [
|
||||
{
|
||||
content: `Read the [guides and docs on this site.](${docUrl(
|
||||
"core/core-intro"
|
||||
)})`,
|
||||
title: "Browse Docs"
|
||||
},
|
||||
{
|
||||
content: `Explore the [Scaladocs](${
|
||||
siteConfig.scaladocUrl
|
||||
}) for the complete guide to how Bitcoin-S work`,
|
||||
title: "Browse API reference"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
"If you have any questions about either the documentation or",
|
||||
"Bitcoin-S itself, the easiest way to get in touch with the",
|
||||
`developers is the \`#bitcoin-s\` channel in the [Suredbits Slack](${
|
||||
siteConfig.suredbitsSlack
|
||||
}).`,
|
||||
`There's also a [Gitter room](${
|
||||
siteConfig.gitterUrl
|
||||
}) you can join, although there's less`,
|
||||
"activity there."
|
||||
].join(" "),
|
||||
title: "Join the community"
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="docMainWrapper wrapper">
|
||||
<Container className="mainContainer documentContainer postContainer">
|
||||
<div className="post">
|
||||
<header className="postHeader">
|
||||
<h1>Need help?</h1>
|
||||
</header>
|
||||
<Image src={baseUrl + "img/undraw_questions_75e0.svg"} />
|
||||
<GridBlock contents={supportLinks} layout="threeColumn" />
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = Help;
|
248
website/pages/en/index.js
Normal file
@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const CompLibrary = require("../../core/CompLibrary.js");
|
||||
|
||||
const MarkdownBlock = CompLibrary.MarkdownBlock; /* Used to read markdown */
|
||||
const Container = CompLibrary.Container;
|
||||
const GridBlock = CompLibrary.GridBlock;
|
||||
|
||||
const Logo = props => (
|
||||
<div className="projectLogo">
|
||||
<img src={props.img_src} alt="Project Logo" />
|
||||
</div>
|
||||
);
|
||||
|
||||
class HomeSplash extends React.Component {
|
||||
render() {
|
||||
const { siteConfig, language = "" } = this.props;
|
||||
const { baseUrl, docsUrl } = siteConfig;
|
||||
const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`;
|
||||
const langPart = `${language ? `${language}/` : ""}`;
|
||||
const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`;
|
||||
|
||||
const SplashContainer = props => (
|
||||
<div className="homeContainer">
|
||||
<div className="homeSplashFade">
|
||||
<div className="wrapper homeWrapper">{props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ProjectTitle = () => (
|
||||
<h2 className="projectTitle">
|
||||
<small>{siteConfig.tagline}</small>
|
||||
</h2>
|
||||
);
|
||||
|
||||
const PromoSection = props => (
|
||||
<div className="section promoSection">
|
||||
<div className="promoRow">
|
||||
<div className="pluginRowBlock">{props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<SplashContainer>
|
||||
<div className="inner">
|
||||
<ProjectTitle siteConfig={siteConfig} />
|
||||
<PromoSection>
|
||||
{/*
|
||||
<Button href="#try">Try It Out</Button>
|
||||
<Button href={docUrl("doc1.html")}>Example Link</Button>
|
||||
<Button href={docUrl("doc2.html")}>Example Link 2</Button> */}
|
||||
</PromoSection>
|
||||
</div>
|
||||
</SplashContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Index extends React.Component {
|
||||
render() {
|
||||
const { config: siteConfig, language = "" } = this.props;
|
||||
const { baseUrl, docsUrl } = siteConfig;
|
||||
const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`;
|
||||
const langPart = `${language ? `${language}/` : ""}`;
|
||||
const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`;
|
||||
|
||||
const Block = props => (
|
||||
<Container
|
||||
padding={["bottom", "top"]}
|
||||
id={props.id}
|
||||
background={props.background}
|
||||
>
|
||||
<GridBlock
|
||||
align="center"
|
||||
contents={props.children}
|
||||
layout={props.layout}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
const Button = props => (
|
||||
<div style={props.style} className="pluginWrapper buttonWrapper">
|
||||
<a className="dark-button" href={props.href} target={props.target}>
|
||||
{props.children}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
// cribbed from Bloop site, https://github.com/scalacenter/bloop/blob/6b5384241d1bba4143315e66f668876d65a2e34f/website/pages/en/index.js#L92
|
||||
const Hero = ({ siteConfig }) => (
|
||||
<div className="hero">
|
||||
<div className="hero__container">
|
||||
<Logo img_src={`${siteConfig.baseUrl}img/bitcoin-s-logo.svg`} />
|
||||
<h1>{siteConfig.tagline}</h1>
|
||||
<Button href={docUrl("getting-started")}>Get started</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const TryOut = () => (
|
||||
<Block id="try">
|
||||
{[
|
||||
{
|
||||
content: [
|
||||
"Use our RPC clients for `bitcoind`/Bitcoin Core and Eclair, and get powerful",
|
||||
"static typing baked into your RPC calls. All returned values you get from `bitcoind`",
|
||||
"and Eclair are converted into native Bitcoin/Lightning data structures for you.",
|
||||
"Is that raw hex string you've been passing around a transaction or a Lightning invoice?",
|
||||
"With Bitcoin-S you get both confidence in your code _and_ powerful methods available",
|
||||
"on your data"
|
||||
].join(" "),
|
||||
image: `${baseUrl}img/undraw_target_kriv.svg`,
|
||||
imageAlign: "left",
|
||||
title: "Super-powered RPC clients"
|
||||
}
|
||||
]}
|
||||
</Block>
|
||||
);
|
||||
|
||||
const Description = () => (
|
||||
<Block background="dark">
|
||||
{[
|
||||
{
|
||||
content: [
|
||||
"Bitcoin-S is used in production today, facilitating transactions and systems handling",
|
||||
"millions of dollars each day. It has a very high degree of code coverage,",
|
||||
"with unit tests, property based tests and integration tests."
|
||||
].join(" "),
|
||||
image: `${baseUrl}img/undraw_Security_on_s9ym.svg`,
|
||||
imageAlign: "right",
|
||||
title: "Battle tested, high quality code"
|
||||
}
|
||||
]}
|
||||
</Block>
|
||||
);
|
||||
|
||||
const LearnHow = () => (
|
||||
<Block background="light">
|
||||
{[
|
||||
{
|
||||
content: [
|
||||
"We provide solid APIs for constructing and signing transactions.",
|
||||
"From small-scale 1-in 2-out transactions, to custom logic powering exchange withdrawals, we've got you covered.",
|
||||
"Check out our [`TxBuilder` example](docs/txbuilder) to see how."
|
||||
].join(" "),
|
||||
image: `${baseUrl}img/undraw_transfer_money_rywa.svg`,
|
||||
imageAlign: "right",
|
||||
title: "Construct and sign bitcoin transactions"
|
||||
}
|
||||
]}
|
||||
</Block>
|
||||
);
|
||||
|
||||
const Features = () => (
|
||||
<Block layout="fourColumn">
|
||||
{[
|
||||
{
|
||||
content: [
|
||||
"Bitcoin-S allows you to interact with data structures found in the",
|
||||
"Bitcoin and Lightning protocols as first-class citizens of your app.",
|
||||
"Go back and forth between hex, byte and JVM representation trivially,",
|
||||
"letting our abstractions focus on what you want to build"
|
||||
].join(" "),
|
||||
image: `${baseUrl}img/undraw_digital_currency_qpak.svg`,
|
||||
imageAlign: "top",
|
||||
title: "Deep protocol understanding"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
"Code with confidence, knowing your data won't change under you. All",
|
||||
"data structures in Bitcoin-S are immutable. This eliminates a big",
|
||||
"range of bugs right away, and enable you to write concurrent code",
|
||||
"much easier"
|
||||
].join(" "),
|
||||
image: `${baseUrl}img/undraw_code_review_l1q9.svg`,
|
||||
imageAlign: "top",
|
||||
title: "Immutable data structures"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
"Get the compiler to work for you, ensuring your logic covers all cases.",
|
||||
"Modelling your application with mathematically founded types enables greater confidence in the correctness of your code"
|
||||
].join(" "),
|
||||
image: `${baseUrl}img/undraw_mathematics_4otb.svg`,
|
||||
imageAlign: "top",
|
||||
title: "Algebraic data types"
|
||||
}
|
||||
]}
|
||||
</Block>
|
||||
);
|
||||
|
||||
const Showcase = () => {
|
||||
if ((siteConfig.users || []).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showcase = siteConfig.users
|
||||
.filter(user => user.pinned)
|
||||
.map(user => (
|
||||
<a href={user.infoLink} key={user.infoLink}>
|
||||
<img src={user.image} alt={user.caption} title={user.caption} />
|
||||
</a>
|
||||
));
|
||||
|
||||
const pageUrl = page => baseUrl + (language ? `${language}/` : "") + page;
|
||||
|
||||
return (
|
||||
<div className="productShowcaseSection paddingBottom">
|
||||
<h2>Who is using Bitcoin-S?</h2>
|
||||
<p>
|
||||
Bitcoin-S is used in production applications, by both small and
|
||||
large companies
|
||||
</p>
|
||||
<div className="logos">{showcase}</div>
|
||||
<div className="more-users">
|
||||
<a className="button" href={pageUrl("users.html")}>
|
||||
Read more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Hero siteConfig={siteConfig} />
|
||||
<div className="mainContainer">
|
||||
<Features />
|
||||
<LearnHow />
|
||||
<TryOut />
|
||||
<Description />
|
||||
<Showcase />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Index;
|
99
website/pages/en/users.js
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const CompLibrary = require("../../core/CompLibrary.js");
|
||||
const Markdown = CompLibrary.MarkdownBlock;
|
||||
const Image = require(process.cwd() + "/core/Image.js");
|
||||
|
||||
const Container = CompLibrary.Container;
|
||||
|
||||
class Users extends React.Component {
|
||||
render() {
|
||||
const { config: siteConfig } = this.props;
|
||||
if ((siteConfig.users || []).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const editUrl = `${siteConfig.repoUrl}/edit/master/website/siteConfig.js`;
|
||||
const showcase = siteConfig.users.map(user => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<a href={user.infoLink} key={user.infoLink}>
|
||||
<img src={user.image} alt={user.caption} title={user.caption} />
|
||||
</a>
|
||||
{user.description ? (
|
||||
<Container className="showcase-user-container">
|
||||
<Markdown>{user.description}</Markdown>
|
||||
</Container>
|
||||
) : null}
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="mainContainer">
|
||||
<Container padding={["bottom"]}>
|
||||
<div className="showcaseSection">
|
||||
<div className="prose">
|
||||
<h1>What is Bitcoin-S good for?</h1>
|
||||
<Image
|
||||
style={{ margin: "1em 0" }}
|
||||
src={siteConfig.baseUrl + "img/undraw_bitcoin2_ave7.svg"}
|
||||
/>
|
||||
<p>
|
||||
Bitcoin-S is a versatile and feature-rich Bitcoin framework that
|
||||
can power a vast array of Bitcoin and cryptocurrency
|
||||
applications. Some examples of what Bitcoin-S is used for in
|
||||
production today:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Construct and sign transactions for withdrawals from a crypto
|
||||
exchange
|
||||
</li>
|
||||
<li>
|
||||
Spend to and from{" "}
|
||||
<a href="https://en.bitcoin.it/wiki/Bech32">
|
||||
Bech32 addresses
|
||||
</a>
|
||||
, enabling full SegWit support for your application
|
||||
</li>
|
||||
<li>
|
||||
Parse{" "}
|
||||
<a href="https://suredbits.com/lightning-101-what-is-a-lightning-invoice/">
|
||||
Lightning Invoices
|
||||
</a>{" "}
|
||||
and other Lightning Network-native data structures
|
||||
</li>
|
||||
<li>
|
||||
Interact with the{" "}
|
||||
<a href="https://github.com/ACINQ/eclair">Eclair</a> Lightning
|
||||
client, fast-tracking your application onto the Lightning
|
||||
Network
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h4>Here are some examples of companies using Bitcoin-S:</h4>
|
||||
<div className="logos">{showcase}</div>
|
||||
<p style={{ textAlign: "center" }}>Are you using this project?</p>
|
||||
<a href={editUrl} className="button">
|
||||
Add your company
|
||||
</a>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Users;
|
13
website/sidebars.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"docs": {
|
||||
"Getting started": ["getting-started"],
|
||||
"Core module": ["core/core-intro", "core/txbuilder"],
|
||||
"RPC clients": [
|
||||
"rpc/rpc-clients-intro",
|
||||
"rpc/rpc-eclair",
|
||||
"rpc/rpc-bitcoind"
|
||||
],
|
||||
"Contributing": ["contributing", "contributing-website"],
|
||||
"Security": ["security"]
|
||||
}
|
||||
}
|
147
website/siteConfig.js
Normal file
@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// See https://docusaurus.io/docs/site-config for all the possible
|
||||
// site configuration options.
|
||||
|
||||
const baseUrl = "/";
|
||||
const scaladocUrl = baseUrl + "api/org/bitcoins";
|
||||
|
||||
// List of projects/orgs using your project for the users page.
|
||||
const users = [
|
||||
/*
|
||||
This is how a user description should look. The description field is optional.
|
||||
You can use markdown in your company description.
|
||||
{
|
||||
caption: "The name of your company",
|
||||
image: "/img/your-company-logo.png",
|
||||
description: "Describe how your company uses bitcoin-s",
|
||||
pinned: true
|
||||
},
|
||||
*/
|
||||
{
|
||||
caption: "Suredbits",
|
||||
image: `${baseUrl}img/suredbits-logo.png`,
|
||||
infoLink: "https://suredbits.com",
|
||||
description: "Suredbits uses Bitcoin-S to power their Lightning APIs.",
|
||||
pinned: true
|
||||
},
|
||||
{
|
||||
caption: "Gemini",
|
||||
image: `${baseUrl}img/gemini-logo.png`,
|
||||
infoLink: "https://gemini.com",
|
||||
description: [
|
||||
"Gemini uses Bitcoin-S to batch transactions and facilitate deposits and",
|
||||
"withdrawals with full SegWit support.",
|
||||
"Read more at [their blog](https://medium.com/gemini/gemini-upgrades-wallet-with-full-support-of-segwit-5bb8e4bc851b)"
|
||||
].join(" "),
|
||||
pinned: true
|
||||
}
|
||||
];
|
||||
|
||||
const siteConfig = {
|
||||
title: "bitcoin-s", // Title for your website.
|
||||
tagline: "Bitcoin implementation in Scala",
|
||||
url: "https://bitcoin-s.org", // Your website URL
|
||||
baseUrl, // Base URL for your project */
|
||||
// For github.io type URLs, you would set the url and baseUrl like:
|
||||
// url: 'https://facebook.github.io',
|
||||
// baseUrl: '/test-site/',
|
||||
|
||||
// URL for editing docs, has to be present for the
|
||||
// "Edit this Doc" button to appear
|
||||
editUrl: "https://github.com/bitcoin-s/bitcoin-s-core/docs",
|
||||
|
||||
// Used for publishing and more
|
||||
projectName: "bitcoin-s",
|
||||
organizationName: "bitcoin-s",
|
||||
// For top-level user or org sites, the organization is still the same.
|
||||
// e.g., for the https://JoelMarcey.github.io site, it would be set like...
|
||||
// organizationName: 'JoelMarcey'
|
||||
|
||||
// For no header links in the top nav bar -> headerLinks: [],
|
||||
headerLinks: [
|
||||
{ doc: "core/core-intro", label: "Docs" },
|
||||
{ href: scaladocUrl, label: "API" },
|
||||
{ page: "help", label: "Help" },
|
||||
{ blog: true, label: "Blog" }
|
||||
],
|
||||
|
||||
// If you have users set above, you add it here:
|
||||
users,
|
||||
|
||||
/* path to images for header/footer */
|
||||
headerIcon: "img/favicon.ico",
|
||||
footerIcon: "img/favicon.ico",
|
||||
favicon: "img/favicon.ico",
|
||||
|
||||
/* Colors for website */
|
||||
colors: {
|
||||
primaryColor: "#1f7a8c", // teal
|
||||
secondaryColor: "#bfdbf7" // light-ish blue
|
||||
},
|
||||
|
||||
/* Custom fonts for website */
|
||||
fonts: {
|
||||
headerFont: ["Montserrat", "sans-serif"]
|
||||
},
|
||||
|
||||
// This copyright info is used in /core/Footer.js and blog RSS/Atom feeds.
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Suredbits & the bitcoin-s developers`,
|
||||
|
||||
highlight: {
|
||||
// Highlight.js theme to use for syntax highlighting in code blocks.
|
||||
theme: "default"
|
||||
},
|
||||
|
||||
// Add custom scripts here that would be placed in <script> tags.
|
||||
scripts: [
|
||||
"https://buttons.github.io/buttons.js",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",
|
||||
"https://fonts.googleapis.com/css?family=Montserrat:500",
|
||||
`${baseUrl}js/code-block-buttons.js`
|
||||
],
|
||||
|
||||
stylesheets: [`${baseUrl}css/code-block-buttons.css`],
|
||||
|
||||
// On page navigation for the current documentation page.
|
||||
onPageNav: "separate",
|
||||
// No .html extensions for paths.
|
||||
cleanUrl: true,
|
||||
|
||||
// Open Graph and Twitter card images.
|
||||
ogImage: "img/undraw_online.svg",
|
||||
twitterImage: "img/undraw_tweetstorm.svg",
|
||||
|
||||
// Show documentation's last contributor's name.
|
||||
enableUpdateBy: true,
|
||||
|
||||
// Show documentation's last update time.
|
||||
enableUpdateTime: true,
|
||||
|
||||
// don't use Docusarus CSS for Scaladocs,
|
||||
// and don't let Scaladoc CSS influence
|
||||
// Docusaurus
|
||||
separateCss: ["api"],
|
||||
|
||||
// mdoc writes docs to this directory
|
||||
customDocsPath: "bitcoin-s-docs/target/mdoc",
|
||||
|
||||
////////////////////
|
||||
// custom keys begin
|
||||
repoUrl: "https://github.com/bitcoin-s/bitcoin-s-core",
|
||||
suredbitsSlack:
|
||||
"https://join.slack.com/t/suredbits/shared_invite/enQtNDEyMjY3MTg1MTg3LTYyYjkwOGUzMDQ4NDAwZjE1M2I3MmQyNWNlZjNlYjg4OGRjYTRjNWUwNjRjNjg4Y2NjZjAxYjU1N2JjMTU1YWM",
|
||||
gitterUrl: "https://gitter.im/bitcoin-s-core/",
|
||||
// avoid showing "root" as default Scaladoc page
|
||||
scaladocUrl
|
||||
|
||||
// custom keys end
|
||||
//////////////////
|
||||
};
|
||||
|
||||
module.exports = siteConfig;
|
39
website/static/css/code-block-buttons.css
Normal file
@ -0,0 +1,39 @@
|
||||
/* "Copy" code block button */
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
pre .btnIcon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
padding: 0;
|
||||
color: $primaryColor;
|
||||
background-color: transparent;
|
||||
height: 30px;
|
||||
transition: all 0.25s ease-out;
|
||||
}
|
||||
|
||||
pre .btnIcon:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btnIcon__body {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.btnIcon svg {
|
||||
fill: currentColor;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
.btnIcon__label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.btnClipboard {
|
||||
right: 10px;
|
||||
}
|
102
website/static/css/custom.css
Normal file
@ -0,0 +1,102 @@
|
||||
/* your custom css */
|
||||
|
||||
@media only screen and (min-device-width: 360px) and (max-device-width: 736px) {
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1023px) {
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1400px) {
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1500px) {
|
||||
}
|
||||
|
||||
.showcase-user-container {
|
||||
max-width: 15em;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dark-button {
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.2em;
|
||||
padding: 10px;
|
||||
text-decoration: none !important;
|
||||
text-transform: uppercase;
|
||||
transition: background 0.3s, color 0.3s;
|
||||
}
|
||||
.dark-button:hover {
|
||||
background-color: $secondaryColor;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* begin crib section from Bloop https://github.com/scalacenter/bloop/blob/d73e247b353a9a0df4a95528095811da391c4a12/website/static/css/index.css#L126 */
|
||||
.hero {
|
||||
background: $primaryColor;
|
||||
color: #f9f9f9;
|
||||
overflow: hidden;
|
||||
padding: 2rem 1rem 2rem;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 500;
|
||||
font-family: $headerFont;
|
||||
}
|
||||
.hero .projectLogo {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
background: url(../img/babel-black.svg) no-repeat center center;
|
||||
background-size: cover;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
left: 0;
|
||||
opacity: 0.3;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero__container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
/* end crib section from bloop */
|
||||
|
||||
.showcaseSection h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.showcaseSection p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.showcaseSection li {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.showcase-user-container p {
|
||||
text-align: center;
|
||||
}
|
121
website/static/img/bitcoin-s-logo.svg
Normal file
@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg3952"
|
||||
xml:space="preserve"
|
||||
width="481.07999"
|
||||
height="129.30467"
|
||||
viewBox="0 0 481.07999 129.30467"
|
||||
sodipodi:docname="bitcoin-s-white-background.svg"
|
||||
inkscape:version="0.92.4 (33fec40, 2019-01-16)"><metadata
|
||||
id="metadata3958"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3956"><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3968"><path
|
||||
d="M 0,96.978 H 360.81 V 0 H 0 Z"
|
||||
id="path3966"
|
||||
inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1024"
|
||||
inkscape:window-height="1088"
|
||||
id="namedview3954"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="1.8001165"
|
||||
inkscape:cx="240.53999"
|
||||
inkscape:cy="64.652336"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g3960" /><g
|
||||
id="g3960"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Dan Smith - Surebits_logo"
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,0,129.30467)"><g
|
||||
id="g3962"><g
|
||||
id="g3964"
|
||||
clip-path="url(#clipPath3968)"><g
|
||||
id="g3970"
|
||||
transform="translate(121.9384,32.058)"><path
|
||||
d="m 0,0 c 1.676,1.814 2.514,4.138 2.514,6.972 0,2.832 -0.838,5.145 -2.514,6.941 -1.676,1.795 -3.83,2.692 -6.463,2.692 -2.673,0 -4.847,-0.897 -6.523,-2.692 -1.675,-1.796 -2.513,-4.109 -2.513,-6.941 0,-2.873 0.838,-5.207 2.513,-7.002 1.676,-1.795 3.85,-2.693 6.523,-2.693 2.633,0 4.787,0.907 6.463,2.723 m 3.201,21.063 c 2.294,-1.376 4.07,-3.3 5.326,-5.774 C 9.784,12.815 10.412,9.963 10.412,6.731 10.412,3.54 9.793,0.728 8.558,-1.706 7.32,-4.14 5.565,-6.035 3.291,-7.391 1.018,-8.747 -1.616,-9.425 -4.607,-9.425 c -2.394,0 -4.51,0.468 -6.344,1.406 -1.836,0.937 -3.351,2.304 -4.548,4.099 v -5.206 h -7.779 v 44.402 h 7.779 V 17.623 c 1.157,1.795 2.642,3.161 4.458,4.099 1.815,0.937 3.919,1.406 6.313,1.406 2.993,0 5.635,-0.688 7.929,-2.065"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3972"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g3974"
|
||||
transform="translate(143.122,67.3939)"><path
|
||||
d="m 0,0 c 0.838,-0.878 1.257,-1.995 1.257,-3.352 0,-1.316 -0.419,-2.414 -1.257,-3.291 -0.838,-0.878 -1.896,-1.316 -3.172,-1.316 -1.276,0 -2.333,0.438 -3.171,1.316 -0.838,0.877 -1.257,1.975 -1.257,3.291 0,1.357 0.419,2.474 1.257,3.352 0.838,0.877 1.895,1.316 3.171,1.316 C -1.896,1.316 -0.838,0.877 0,0"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3976"
|
||||
inkscape:connector-curvature="0" /></g><path
|
||||
d="M 136.121,54.947 H 143.9 V 22.932 h -7.779 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3978"
|
||||
inkscape:connector-curvature="0" /><g
|
||||
id="g3980"
|
||||
transform="translate(169.2714,24.9066)"><path
|
||||
d="m 0,0 c -2.553,-1.556 -5.227,-2.334 -8.019,-2.334 -2.752,0 -5.006,0.809 -6.761,2.424 -1.756,1.616 -2.633,3.999 -2.633,7.151 v 15.798 h -4.488 l -0.06,5.685 h 4.548 v 8.797 h 7.72 v -8.797 h 9.155 V 23.039 H -9.693 V 8.617 c 0,-1.476 0.278,-2.523 0.837,-3.141 0.558,-0.62 1.396,-0.928 2.513,-0.928 1.197,0 2.713,0.438 4.548,1.316 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3982"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g3984"
|
||||
transform="translate(185.7284,48.3646)"><path
|
||||
d="m 0,0 c -2.594,0 -4.697,-0.878 -6.313,-2.633 -1.616,-1.756 -2.424,-4.03 -2.424,-6.822 0,-2.872 0.808,-5.187 2.424,-6.941 1.616,-1.756 3.719,-2.633 6.313,-2.633 3.83,0 6.702,1.256 8.617,3.769 l 4.847,-4.009 c -1.436,-2.075 -3.331,-3.67 -5.685,-4.788 -2.354,-1.117 -5.087,-1.674 -8.198,-1.674 -3.152,0 -5.954,0.677 -8.407,2.034 -2.454,1.355 -4.36,3.261 -5.715,5.715 -1.357,2.453 -2.035,5.275 -2.035,8.467 0,3.232 0.688,6.083 2.064,8.557 1.377,2.474 3.291,4.389 5.745,5.745 2.454,1.356 5.276,2.034 8.468,2.034 2.911,0 5.515,-0.508 7.809,-1.525 2.294,-1.018 4.159,-2.464 5.595,-4.339 L 8.378,-3.531 C 6.224,-1.178 3.431,0 0,0"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3986"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g3988"
|
||||
transform="translate(209.1259,45.8509)"><path
|
||||
d="m 0,0 c -1.676,-1.795 -2.514,-4.129 -2.514,-7.001 0,-2.873 0.838,-5.206 2.514,-7.002 1.676,-1.795 3.869,-2.692 6.583,-2.692 2.633,0 4.787,0.897 6.463,2.692 1.675,1.796 2.513,4.129 2.513,7.002 0,2.872 -0.838,5.206 -2.513,7.001 C 11.37,1.796 9.216,2.693 6.583,2.693 3.869,2.693 1.676,1.796 0,0 m 15.35,7.301 c 2.532,-1.357 4.507,-3.262 5.923,-5.715 1.416,-2.453 2.124,-5.276 2.124,-8.468 0,-3.231 -0.708,-6.084 -2.124,-8.556 -1.416,-2.475 -3.391,-4.39 -5.923,-5.746 -2.535,-1.356 -5.456,-2.034 -8.767,-2.034 -3.352,0 -6.305,0.678 -8.856,2.034 -2.554,1.356 -4.539,3.271 -5.956,5.746 -1.416,2.472 -2.124,5.325 -2.124,8.556 0,3.192 0.708,6.015 2.124,8.468 1.417,2.453 3.402,4.358 5.956,5.715 2.551,1.355 5.504,2.034 8.856,2.034 3.311,0 6.232,-0.679 8.767,-2.034"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3990"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g3992"
|
||||
transform="translate(243.2948,67.3939)"><path
|
||||
d="m 0,0 c 0.838,-0.878 1.256,-1.995 1.256,-3.352 0,-1.316 -0.418,-2.414 -1.256,-3.291 -0.838,-0.878 -1.896,-1.316 -3.172,-1.316 -1.277,0 -2.334,0.438 -3.172,1.316 -0.838,0.877 -1.257,1.975 -1.257,3.291 0,1.357 0.419,2.474 1.257,3.352 0.838,0.877 1.895,1.316 3.172,1.316 C -1.896,1.316 -0.838,0.877 0,0"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3994"
|
||||
inkscape:connector-curvature="0" /></g><path
|
||||
d="m 236.293,54.947 h 7.779 V 22.932 h -7.779 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path3996"
|
||||
inkscape:connector-curvature="0" /><g
|
||||
id="g3998"
|
||||
transform="translate(278.6601,51.9252)"><path
|
||||
d="m 0,0 c 2.114,-2.214 3.171,-5.217 3.171,-9.006 v -19.987 h -7.839 v 17.713 c 0,2.233 -0.639,3.999 -1.915,5.296 -1.277,1.296 -3.013,1.945 -5.206,1.945 -2.594,-0.041 -4.638,-0.919 -6.134,-2.634 -1.496,-1.716 -2.244,-3.91 -2.244,-6.582 v -15.738 h -7.779 V 3.021 h 7.779 v -5.983 c 2.193,4.108 6.023,6.202 11.489,6.283 C -5.007,3.321 -2.115,2.214 0,0"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path4000"
|
||||
inkscape:connector-curvature="0" /></g><path
|
||||
d="M 286.259,43.876 H 301.28 V 38.49 h -15.021 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path4002"
|
||||
inkscape:connector-curvature="0" /><g
|
||||
id="g4004"
|
||||
transform="translate(320.8769,48.4242)"><path
|
||||
d="m 0,0 c -1.656,0.519 -3.182,0.778 -4.578,0.778 -1.317,0 -2.363,-0.23 -3.142,-0.688 -0.778,-0.459 -1.167,-1.167 -1.167,-2.124 0,-0.998 0.489,-1.766 1.466,-2.304 0.978,-0.539 2.524,-1.107 4.638,-1.706 2.234,-0.679 4.069,-1.347 5.506,-2.005 1.436,-0.658 2.682,-1.636 3.74,-2.931 1.057,-1.297 1.586,-3.003 1.586,-5.117 0,-3.112 -1.197,-5.506 -3.591,-7.181 -2.394,-1.676 -5.406,-2.513 -9.036,-2.513 -2.474,0 -4.888,0.389 -7.24,1.167 -2.355,0.777 -4.35,1.885 -5.985,3.32 l 2.693,5.447 c 1.436,-1.238 3.151,-2.205 5.146,-2.903 1.994,-0.699 3.89,-1.048 5.685,-1.048 1.436,0 2.583,0.249 3.441,0.748 0.858,0.499 1.286,1.248 1.286,2.245 0,1.116 -0.499,1.954 -1.496,2.513 -0.997,0.558 -2.613,1.176 -4.847,1.855 -2.154,0.637 -3.91,1.266 -5.265,1.885 -1.358,0.619 -2.534,1.556 -3.531,2.813 -0.997,1.257 -1.496,2.902 -1.496,4.936 0,3.152 1.146,5.555 3.441,7.211 2.293,1.656 5.176,2.484 8.646,2.484 2.115,0 4.199,-0.299 6.254,-0.898 C 4.208,5.386 6.033,4.548 7.63,3.471 L 4.817,-2.154 C 3.261,-1.237 1.654,-0.519 0,0"
|
||||
style="fill:#ed5039;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path4006"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g4008"
|
||||
transform="translate(60.327,74.4056)"><path
|
||||
d="m 0,0 c -15.708,0 -28.442,-12.734 -28.442,-28.442 0,-9.185 4.357,-17.347 11.112,-22.546 3.014,1.79 8.559,4.834 10.472,4.2 2.675,-0.886 5.147,-1.348 7.414,-1.385 2.139,-0.034 3.845,0.313 5.121,1.035 1.276,0.726 1.926,1.866 1.951,3.419 0.027,1.621 -0.746,2.881 -2.32,3.781 -1.573,0.901 -4.068,1.866 -7.487,2.893 -3.611,1.162 -6.574,2.295 -8.889,3.402 -2.316,1.107 -4.314,2.728 -5.998,4.861 -1.682,2.134 -2.496,4.919 -2.44,8.351 0.082,5.055 2.089,8.911 6.022,11.57 3.931,2.659 8.847,3.938 14.742,3.842 4.018,-0.065 7.928,-0.76 11.729,-2.086 3.804,-1.325 5.115,-14.326 5.115,-14.326 -2.3,2.047 -5.06,3.664 -8.283,4.85 -3.22,1.188 -6.289,1.805 -9.205,1.852 -2.333,0.038 -4.201,-0.336 -5.608,-1.124 -1.406,-0.788 -2.123,-1.992 -2.149,-3.611 -0.03,-1.813 0.759,-3.187 2.363,-4.122 1.606,-0.933 4.214,-1.978 7.824,-3.141 3.482,-1.092 6.317,-2.16 8.503,-3.202 2.187,-1.039 4.075,-2.592 5.66,-4.661 1.587,-2.067 2.354,-4.753 2.3,-8.057 -0.058,-3.559 -1.028,-6.5 -2.867,-8.856 7.147,5.166 11.802,13.567 11.802,23.061 C 28.442,-12.734 15.708,0 0,0"
|
||||
style="fill:#ed5039;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path4010"
|
||||
inkscape:connector-curvature="0" /></g></g></g></g></svg>
|
After Width: | Height: | Size: 10 KiB |
BIN
website/static/img/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
website/static/img/gemini-logo.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
website/static/img/oss_logo.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
website/static/img/suredbits-logo.png
Normal file
After Width: | Height: | Size: 28 KiB |
1
website/static/img/undraw_Security_on_s9ym.svg
Normal file
After Width: | Height: | Size: 8.7 KiB |
1
website/static/img/undraw_bitcoin2_ave7.svg
Normal file
After Width: | Height: | Size: 21 KiB |
1
website/static/img/undraw_code_review.svg
Normal file
After Width: | Height: | Size: 17 KiB |
1
website/static/img/undraw_code_review_l1q9.svg
Normal file
After Width: | Height: | Size: 17 KiB |
1
website/static/img/undraw_digital_currency_qpak.svg
Normal file
After Width: | Height: | Size: 16 KiB |
1
website/static/img/undraw_mathematics_4otb.svg
Normal file
After Width: | Height: | Size: 8.1 KiB |
1
website/static/img/undraw_monitor.svg
Normal file
After Width: | Height: | Size: 32 KiB |
1
website/static/img/undraw_note_list.svg
Normal file
After Width: | Height: | Size: 15 KiB |
1
website/static/img/undraw_online.svg
Normal file
After Width: | Height: | Size: 26 KiB |
1
website/static/img/undraw_open_source.svg
Normal file
After Width: | Height: | Size: 16 KiB |
1
website/static/img/undraw_operating_system.svg
Normal file
After Width: | Height: | Size: 36 KiB |
1
website/static/img/undraw_questions_75e0.svg
Normal file
After Width: | Height: | Size: 24 KiB |
1
website/static/img/undraw_react.svg
Normal file
After Width: | Height: | Size: 24 KiB |
1
website/static/img/undraw_security_o890.svg
Normal file
After Width: | Height: | Size: 22 KiB |
1
website/static/img/undraw_target_kriv.svg
Normal file
After Width: | Height: | Size: 25 KiB |
1
website/static/img/undraw_transfer_money_rywa.svg
Normal file
After Width: | Height: | Size: 16 KiB |
1
website/static/img/undraw_tweetstorm.svg
Normal file
After Width: | Height: | Size: 9.2 KiB |
1
website/static/img/undraw_wallet_aym5.svg
Normal file
After Width: | Height: | Size: 41 KiB |
1
website/static/img/undraw_youtube_tutorial.svg
Normal file
After Width: | Height: | Size: 28 KiB |
45
website/static/js/code-block-buttons.js
Normal file
@ -0,0 +1,45 @@
|
||||
window.addEventListener("load", function() {
|
||||
function button(label, ariaLabel, icon, className) {
|
||||
const btn = document.createElement("button");
|
||||
btn.classList.add("btnIcon", className);
|
||||
btn.setAttribute("type", "button");
|
||||
btn.setAttribute("aria-label", ariaLabel);
|
||||
btn.innerHTML =
|
||||
'<div class="btnIcon__body">' +
|
||||
icon +
|
||||
'<strong class="btnIcon__label">' +
|
||||
label +
|
||||
"</strong>" +
|
||||
"</div>";
|
||||
return btn;
|
||||
}
|
||||
|
||||
function addButtons(codeBlockSelector, btn) {
|
||||
document.querySelectorAll(codeBlockSelector).forEach(function(code) {
|
||||
code.parentNode.appendChild(btn.cloneNode(true));
|
||||
});
|
||||
}
|
||||
|
||||
const copyIcon =
|
||||
'<svg width="12" height="12" viewBox="340 364 14 15" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M342 375.974h4v.998h-4v-.998zm5-5.987h-5v.998h5v-.998zm2 2.994v-1.995l-3 2.993 3 2.994v-1.996h5v-1.995h-5zm-4.5-.997H342v.998h2.5v-.997zm-2.5 2.993h2.5v-.998H342v.998zm9 .998h1v1.996c-.016.28-.11.514-.297.702-.187.187-.422.28-.703.296h-10c-.547 0-1-.452-1-.998v-10.976c0-.546.453-.998 1-.998h3c0-1.107.89-1.996 2-1.996 1.11 0 2 .89 2 1.996h3c.547 0 1 .452 1 .998v4.99h-1v-2.995h-10v8.98h10v-1.996zm-9-7.983h8c0-.544-.453-.996-1-.996h-1c-.547 0-1-.453-1-.998 0-.546-.453-.998-1-.998-.547 0-1 .452-1 .998 0 .545-.453.998-1 .998h-1c-.547 0-1 .452-1 .997z" fill-rule="evenodd"/></svg>';
|
||||
|
||||
addButtons(
|
||||
".hljs",
|
||||
button("Copy", "Copy code to clipboard", copyIcon, "btnClipboard")
|
||||
);
|
||||
|
||||
const clipboard = new ClipboardJS(".btnClipboard", {
|
||||
target: function(trigger) {
|
||||
return trigger.parentNode.querySelector("code");
|
||||
}
|
||||
});
|
||||
|
||||
clipboard.on("success", function(event) {
|
||||
event.clearSelection();
|
||||
const textEl = event.trigger.querySelector(".btnIcon__label");
|
||||
textEl.textContent = "Copied";
|
||||
setTimeout(function() {
|
||||
textEl.textContent = "Copy";
|
||||
}, 2000);
|
||||
});
|
||||
});
|