Browse Source

Merge branch 'blockstack-connect-release' of https://github.com/blockstack/docs.blockstack into blockstack-connect-release

blockstack-connect-release
Mark M. Hendrickson 5 years ago
parent
commit
c6ac47f19e
  1. BIN
      _android/images/blockstack-install.png
  2. BIN
      _android/images/blockstack-signin.png
  3. BIN
      _android/images/chrome-prompt.png
  4. BIN
      _android/images/configure-activity.png
  5. BIN
      _android/images/connect-response.png
  6. BIN
      _android/images/connect-ui.png
  7. BIN
      _android/images/create-restore.png
  8. BIN
      _android/images/final-app.png
  9. BIN
      _android/images/helloandroid.zip
  10. BIN
      _android/images/initial-build.png
  11. BIN
      _android/images/new-interface.png
  12. BIN
      _android/images/oreo-api.png
  13. BIN
      _android/images/q-api.png
  14. BIN
      _android/images/running-app.png
  15. BIN
      _android/images/select-hdw.png
  16. BIN
      _android/images/sync-project.png
  17. BIN
      _android/images/sync-success.png
  18. 493
      _android/tutorial.md
  19. 251
      _browser/blockstack_storage.md
  20. BIN
      _browser/images/display-complete.png
  21. BIN
      _browser/images/initial-app.png
  22. BIN
      _browser/images/login-choice.png
  23. BIN
      _browser/images/login-no-auth.png
  24. BIN
      _browser/images/login.gif
  25. BIN
      _browser/images/login.png
  26. BIN
      _browser/images/make-a-list.png
  27. BIN
      _browser/images/multi-player-storage-status.png
  28. BIN
      _browser/images/multiple-lists.png
  29. BIN
      _browser/images/network-connections.gif
  30. BIN
      _browser/images/publish-data-perm.png
  31. BIN
      _browser/images/saving-status.png
  32. BIN
      _browser/images/todo-app.png
  33. BIN
      _browser/images/todo-sign-in.png
  34. 28
      _browser/todo-list.md
  35. 19
      _common/core_ref.md
  36. 24
      _core/smart/cli-wallet-quickstart.md
  37. 93
      _core/smart/neon-node.md
  38. 18
      _core/smart/overview.md
  39. 209
      _core/smart/principals.md
  40. 2
      _core/smart/sdk-quickstart.md
  41. 167
      _core/smart/testnet-node.md
  42. 14
      _core/smart/tutorial-counter.md
  43. 4
      _core/smart/tutorial.md
  44. 2
      _develop/cliDocs.md
  45. 45
      _develop/connect/get-started.md
  46. 206
      _develop/connect/use-with-clarity.md
  47. 2
      _develop/radiks-intro.md
  48. 4
      _includes/sign_in.md
  49. 14
      _ios/tutorial.md

BIN
_android/images/blockstack-install.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

BIN
_android/images/blockstack-signin.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
_android/images/chrome-prompt.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

BIN
_android/images/configure-activity.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

BIN
_android/images/connect-response.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
_android/images/connect-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
_android/images/create-restore.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
_android/images/final-app.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
_android/images/helloandroid.zip

Binary file not shown.

BIN
_android/images/initial-build.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

BIN
_android/images/new-interface.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
_android/images/oreo-api.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

BIN
_android/images/q-api.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
_android/images/running-app.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 59 KiB

BIN
_android/images/select-hdw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 70 KiB

BIN
_android/images/sync-project.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
_android/images/sync-success.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

493
_android/tutorial.md

@ -3,21 +3,22 @@ layout: learn
description: Learn about the Android SDK
permalink: /:collection/:path.html
---
# Android DApps
{:.no_toc}
This tutorial is written for readers who are new to either or both Blockstack
and Android to create a decentralized application. It contains the following
content:
* TOC
TOC
{:toc}
This tutorial was extensively tested using Android Studio 3.1 on a MacBook Air
running High Sierra 10.13.4. If your environment is different, you may encounter
This tutorial was extensively tested using Android Studio 3.6 on a Dell XPS 13
running Ubuntu 19. If your environment is different, you may encounter
slight or even major discrepancies when performing the procedures in this
tutorial. Please [join the Blockstack community
Slack](https://slofile.com/slack/blockstack) and post questions or comments to
tutorial. Please [join the Blockstack discord server](https://chat.blockstack.org) and post questions or comments to
the `#support` channel.
Finally, this tutorial is written for all levels from the beginner to the most
@ -36,7 +37,7 @@ intended for user on an Android phone.
![](images/final-app.png)
Only users with an existing `blockstack.id` can run your
Only users with an existing blockstack id can run your
final sample application. When complete, users interact with the sample
application by doing the following:
@ -45,7 +46,7 @@ application by doing the following:
## Set up your environment
This sample application has two code bases, a Blockstack `hello-blockstack`
application and a `hello-andriod` Android application. Before you start
web application and a `hello-andriod` Android application. Before you start
developing the sample, there are a few elements you need in your environment.
### Install Android Studio
@ -56,7 +57,7 @@ step. However, you will need to adjust the remaining instructions for your
environment.
Follow the installation instructions to download and [Android Studio
3.1](https://developer.android.com/studio/install) for your operating system.
3.6](https://developer.android.com/studio/install) for your operating system.
Depending on your network connection, this can take between 15-30 minutes.
![](images/studio-download.png)
@ -75,27 +76,9 @@ $ which npm npx
If you don't have these installed, take a moment to install or upgrade as needed.
### Install the Blockstack test rig
Users interact with Blockstack-enabled applications through a web browser. You
can Blockstack in test mode, on `localhost` or you can interact with completed
apps through the Blockstack webapp which is available at
[https://browser.blockstack.org/].
If you have already installed Blockstack for testing locally and have an
existing Blockstack ID, skip this section. Otherwise, continue onto install
Blockstack.
1. Go to [Blockstack](https://blockstack.org/install)
![](images/blockstack-install.png)
## Build the Blockstack hello-world web app
2. Install the version appropriate for your operating system.
## Build the Blockstack hello-world
In this section, you build a Blockstack `hello-world` application. Then, you
modify the `hello-world` to interact with the Android app via a redirect.
In this section, you build a Blockstack `hello-world` web application that acts as the companion site to your Android application.
### Generate and launch your hello-blockstack application
@ -119,29 +102,23 @@ In this section, you build an initial React.js application called
3. Use the Blockstack application generator to create your initial `hello-blockstack` application.
```bash
$ npx generator-blockstack --react
npx: installed 338 in 13.792s
$ npx generator-blockstack --plain
create package.json
create .gitignore
create webpack.config.js
create netlify.toml
create firebase.json
...
I'm all done. Running npm install for you to install the required dependencies. If this fails, try running the command yourself.
npm WARN ajv-errors@1.0.1 requires a peer of ajv@>=5.0.0 but none is installed. You must install peer dependencies yourself.
> fsevents@1.2.9 install /private/tmp/testymc/node_modules/fsevents
> node install
added 775 packages from 455 contributors and audited 9435 packages in 20.934s
added 840 packages from 582 contributors and audited 843 packages in 18.84s
found 0 vulnerabilities
```
Depending on your environment you may have some warnings with the installation. Optionally, you can fix these before continuing to the next section.
```
4. Respond to the prompts to populate the initial app.
After the process completes successfully, you see a prompt similar to the following:
4. Depending on your environment, respond to the prompts to populate the initial app.
You might see a prompt similar to the following:
```bash
[fsevents] Success:
@ -150,29 +127,27 @@ In this section, you build an initial React.js application called
You should commit this file. added 1060 packages in 26.901s
```
5. Run the initial application.
5. After the initial setup you can now run the initial application.
```bash
$ npm run start
> hello-blockstack@0.0.0 start /Users/meepers/repos/hello-blockstack
> hello-blockstack-webpack@0.0.0 start /home/user/hello-blockstack
> webpack-dev-server
Project is running at http://localhost:8080/
webpack output is served from /
404s will fallback to /index.html
Hash: 4d2312ba236a4b95dc3a
Version: webpack 2.7.0
Time: 2969ms
ℹ 「wds」: Project is running at http://localhost:9000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /home/user/hello-blockstack/dist
ℹ 「wdm」: Hash: f5d88efe9c9194f66ddd
Version: webpack 4.43.0
Time: 2733ms
Built at: 05/19/2020 10:44:50 AM
Asset Size Chunks Chunk Names
main.js 5.39 MiB main [emitted] main
....
Child html-webpack-plugin for "index.html":
chunk {0} index.html 541 kB [entry] [rendered]
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] ./~/html-webpack-plugin/lib/loader.js!./src/index.html 533 bytes {0} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
webpack: Compiled successfully.
[./src/index.js] 1.8 KiB {main} [built]
+ 610 hidden modules
ℹ 「wdm」: Compiled successfully.
```
The system opens a browser displaying your running application.
@ -182,7 +157,7 @@ In this section, you build an initial React.js application called
At this point, the browser is running a Blockstack server on your local host.
This is for testing your applications only.
6. Choose **Sign in with Blockstack**
6. Choose **Get started**
The system displays a prompt allowing you to create a new Blockstack ID or restore an existing one.
@ -190,65 +165,12 @@ In this section, you build an initial React.js application called
7. Follow the prompts appropriate to your situation.
If you are restoring an existing ID, you may see a prompt about your user
being nameless, ignore it. At this point you have only a single application
At this point you have only a single application
on your test server. So, you should see this single application, with your
own `blockstack.id` display name, once you are signed in:
own `blockstack.id`, once you are signed in:
![](images/running-app.png)
### Add a redirect end point to your application
When a user opens the webapp from the Blockstack Browser on an Android phone,
you want the web app to redirect the user to your Android application. The work
you do here will allow it.
1. From the terminal command line, change directory to the root of your sample
application directory.
2. Use the `touch` command to add a redirect endpoint to your application.
This endpoint on the web version of your app will redirect Android users back
to your mobile app.
```bash
$ touch public/redirect.html
```
3. Open `redirect.html` and add code to the endpoint.
```html
<!DOCTYPE html>
<html>
<head>
<title>Hello, Blockstack!</title>
<script>
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
var authResponse = getParameterByName('authResponse')
window.location="myblockstackapp:" + authResponse
</script>
<body>
</body>
</html>
```
Blockstack apps are identified by their domain names. The endpoint will
receive a get request with the query parameter `authResponse=XXXX` and
should redirect the browser to `myblockstackapp:XXXX`.
`myblockstackapp:` is custom protocol handler. The handler should be unique
to your application. Your app's web-based authentication uses this handler
to redirect the user back to your Android app. Later, you'll add a reference
to this handler in your Android application.
5. Close and save the `redirect.html` file.
6. Ensure your Blockstack compiles successfully.
## Create the hello-android project
In this section, you'll create an Android application in Android Studio. You'll
@ -259,47 +181,43 @@ run the application in the emulator to test it.
In this section, you create an inital project. You'll validate the application's
iniatial state by creating an emulator to run it in. Open Android Studio and do the following:
1. Open Android Studio and choose **Start a new Andriod Studio project**.
If studio is already started, choose **File > New > New Project**.
2. Enter these fields in the **Create Android Project** page.
<table class="uk-table">
<tr>
<th>Application Name</th>
<td><code>hello-android</code></td>
</tr>
<tr>
<th>Company domain</th>
<td><code><i>USERNAME</i>.example.com</code></td>
</tr>
<tr>
<th>Project location</th>
<td><code>/Users/<i>USERNAME</i>/AndroidStudioProjects/helloandroid</code></td>
</tr>
<tr>
<th>Include Kotlin support</th>
<td>Set (checked)</td>
</tr>
</table>
2. Choose a project template: select **Empty Activity** and press **Next**
3. Press **Next** to display **Target Android Devices**.
4. Check **Phone and Tablet**.
5. Choose API 27: Andriod 8.1 (Oreo) for the target version.
6. Press **Next**.
7. Choose **Empty Activity** and press **Next**.
8. Leave the **Configure Activity** dialog with its defaults.
3. Enter these fields in the **Create Android Project** page.
![](images/configure-activity.png)
9. Press **Finish**.
<table class="uk-table">
<tr>
<th>Name</th>
<td><code>Hello Android</code></td>
</tr>
<tr>
<th>Package name</th>
<td><code>blockstack.id.<i>USERNAME</i>.hello</code></td>
</tr>
<tr>
<th>Project location</th>
<td><code>/home/<i>USERNAME</i>/AndroidStudioProjects/helloandroid</code></td>
</tr>
<tr>
<th>Language</th>
<td>Select (Kotlin)</td>
</tr>
<tr>
<th>Minimum SDK</th>
<td>Select (API 21: Android 5.0 (Lollipop))</td>
</tr>
</table>
![](images/configure-activity.png)
4. Press **Finish**.
Android studio builds your initial project. This can take a bit the first time you do it.
![](images/initial-build.png)
### Run the app in an emulator
In this section, you run the appliation and create an emulator when prompted.
@ -314,15 +232,15 @@ In this section, you run the appliation and create an emulator when prompted.
Studio prompts you to **Select Hardware**.
4. Choose a Phone running Pixel XL.
4. Choose a Phone device configuration like "Pixel".
![](images/select-hdw.png)
Studio prompts you for a system image.
5. Choose **Oreo** which is API level 27 and press **Next**.
5. Choose **Q** which is API level 29 and press **Next**.
![](images/oreo-api.png)
![](images/q-api.png)
Studio asks you to verify your new emulator configuration.
@ -332,26 +250,25 @@ In this section, you run the appliation and create an emulator when prompted.
![](images/hello-andriod-1.png)
### Configure your application with the Blockstack SDK
Now that you have created your initial project and verified it running in an emulator, you are ready to begin configuring the application for use with Blockstack.
1. In studio, open the `AndroidManifest.xml` file.
2. Add an `<intent-filter>` with the custom handler for Blockstack.
2. Add an `<intent-filter>` with the custom handler for Blockstack. Replace the host ("flamboyant-darwin-d11c17.netlify.app") with your own url once you have hosted your web app publicly.
```XML
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myblockstackapp" />
<data android:scheme="https" android:host="flamboyant-darwin-d11c17.netlify.app" />
</intent-filter>
```
2. Open the Project's `build.gradle` file.
3. Add the Jitpack repository `maven { url 'https://jitpack.io' }` to the `repositories` section.
3. Open the Project's `build.gradle` file ("Project: Hello Android").
4. Add the Jitpack repository `maven { url 'https://jitpack.io' }` to the `repositories` section. for all projects
When you finish, that section looks like this:
@ -365,23 +282,15 @@ Now that you have created your initial project and verified it running in an emu
}
```
4. Open the Module `build.gradle` file.
5. Set the `defaultConfig minSdkVersion` to `19`.
When you are done, you should see (within your own username not `meepers`):
5. Open the Module `build.gradle` file ("Module: app").
6. Fix a problem with duplicate meta data in the Android SDK: add `packagingOptions`
```JS
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.meepers.hello_android"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
...
packagingOptions {
exclude 'META-INF/*'
}
}
```
@ -391,19 +300,22 @@ Now that you have created your initial project and verified it running in an emu
```JS
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:27.1.1'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.github.blockstack:blockstack-android:0.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.github.blockstack:blockstack-android:7940940'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
}
```
**NOTE**: Ignore the warning on the appcompat` dependencies.
**NOTE**: Ignore the warning on the `junit` dependencies.
8. Sync your project.
@ -413,7 +325,7 @@ Now that you have created your initial project and verified it running in an emu
![](images/sync-success.png)
10. Run your app in the emulator.
9. Run your app in the emulator.
You've made a lot of changes, make sure the emulator is still running
correctly.
@ -424,48 +336,50 @@ Now that you have created your initial project and verified it running in an emu
The `activity_main.xml` file defines the graphical elements. Some elements are required before you can functionality to your `MainActivity.kt` code.
3. Replace the entire content of the file with the following code:
2. Replace the entire content of the file with the following code:
The new interface includes a `BlockstackSignInButton` which is provided by
the SDK. This SDK includes a themed "Sign in with Blockstack" button
the SDK. This SDK includes a themed "Get Started" button
(`BlockstackSignInButton`). You use this button here with the
`org.blockstack.android.sdk.ui.BlockstackSignInButton` class.
```XML
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context=".MainActivity">
<org.blockstack.android.sdk.ui.BlockstackSignInButton
android:id="@+id/signInButton"
android:layout_width="307dp"
android:layout_height="45dp"
android:layout_margin="4dp"
android:layout_marginEnd="185dp"
android:layout_marginStart="39dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="16dp" />
app:layout_constraintBottom_toTopOf="@id/userDataTextView"/>
<TextView
android:id="@+id/userDataTextView"
android:layout_width="370dp"
android:layout_height="27dp"
tools:layout_editor_absoluteX="6dp"
tools:layout_editor_absoluteY="70dp" />
</android>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/signInButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
```
This codes adds a button and some text to your application.
4. Choose **Run > Apply changes**.
3. Choose **Run > Apply changes**.
5. Choose **Run > Run app** in the emulator.
4. Choose **Run > Run app** in the emulator.
The emulator now contains a new interface with a button:
@ -474,27 +388,35 @@ Now that you have created your initial project and verified it running in an emu
### Add session & authentication code
1. Open the `MainActivity.kt` file.
2. Add some additional imports to the top below the `android.os.Bundle` import.
2. Add some additional imports to the top, replace the `android.os.Bundle` import.
When you are done, your imports should appear as follows:
```kotlin
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.blockstack.android.sdk.BlockstackSession
import org.blockstack.android.sdk.Scope
import org.blockstack.android.sdk.UserData
import org.blockstack.android.sdk.toBlockstackConfig
import org.blockstack.android.sdk.BlockstackSignIn
import org.blockstack.android.sdk.SessionStore
import org.blockstack.android.sdk.getBlockstackSharedPreferences
import org.blockstack.android.sdk.model.UserData
import org.blockstack.android.sdk.model.toBlockstackConfig
import org.blockstack.android.sdk.ui.SignInProvider
import org.blockstack.android.sdk.ui.showBlockstackConnect
```
3. Add a variable for the Blockstack session before `onCreate`.
3. Add one variable for the Blockstack sign-in flow and one for the Blockstack session that deals with all other function. This needs to be added before `onCreate`.
```kotlin
class MainActivity : AppCompatActivity() {
private var _blockstackSession: BlockstackSession? = null
private lateinit var blockstackSession: BlockstackSession
private lateinit var blockstackSignIn: BlockstackSignIn
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -503,42 +425,43 @@ Now that you have created your initial project and verified it running in an emu
}
```
4. Replace the existing the `onCreate` function with the following:
4. Replace the existing the `onCreate` function with the following, use your own domain if you are already hosting your web app somewhere:
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val scopes = arrayOf(Scope.StoreWrite)
val config = "https://flamboyant-darwin-d11c17.netlify.com"
.toBlockstackConfig(scopes)
val appConfig =
"https://flamboyant-darwin-d11c17.netlify.app".toBlockstackConfig()
val sessionStore = SessionStore(getBlockstackSharedPreferences())
blockstackSession = BlockstackSession(sessionStore, appConfig)
blockstackSignIn = BlockstackSignIn(sessionStore, appConfig)
BlockstackSignIn.shouldLaunchInCustomTabs = false
_blockstackSession = BlockstackSession(this, config)
signInButton.isEnabled = true
signInButton.setOnClickListener { view: View ->
blockstackSession().redirectUserToSignIn {
// only called on error
}
signInButton.setOnClickListener {
showBlockstackConnect()
}
if (intent?.action == Intent.ACTION_VIEW) {
// handle the redirect from sign in
userDataTextView.text = "Signing in now ..."
lifecycleScope.launch(Dispatchers.IO) {
handleAuthResponse(intent)
}
}
}
```
This new `onCreate` does several things:
* Define the initial state for the `signInButton`.
* Supply authentication information for connecting to your Blockstack app: `appDomain` and `scopes` (for `redirectURI`, `manifestURI` the default values are used)
* Add a listener for the button click.
- Supply authentication information for connecting to your Blockstack app: `appDomain` (for `scopes`, `redirectURI`, `manifestURI` the default values are used)
- Add a listener for the button click.
- Handles the redirect intent from the sign-in flow.
Notice that the application in this example is a URI you have not set up.
Registering and application name takes time, so in time's interest you'll
use an existing app that is identical to the `hello-world` you created
use an existing app (https://flamboyant-darwin-d11c17.netlify.app) that is identical to the `hello-world` you created
earlier. For a production version, you'll need to replace `appDomain`,
`redirectURI`, `manifestURI` and `scopes` with values appropriate for your
app.
@ -552,130 +475,107 @@ Now that you have created your initial project and verified it running in an emu
}
```
6. Handle sign in requests with an `onNewIntent` function if the activity was already opened when signing in
Retrieve the authentication token from the custom protocol handler call and
send it to the Blockstack session.
```kotlin
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.action == Intent.ACTION_VIEW) {
handleAuthResponse(intent)
}
}
```
7. Create a handler for the authentication response.
6. Create a handler for the authentication response.
```kotlin
private fun handleAuthResponse(intent: Intent) {
val response = intent.dataString
if (response != null) {
val authResponseTokens = response.split(':')
if (authResponseTokens.size > 1) {
val authResponse = authResponseTokens[1]
blockstackSession().handlePendingSignIn(authResponse, { userData ->
val authResponse = intent.data?.getQueryParameter("authResponse")
if (authResponse != null) {
val userData = blockstackSession.handlePendingSignIn(authResponse)
if (userData.hasValue) {
// The user is now signed in!
runOnUiThread {
onSignIn(userData.value!!)
}
}
})
}
}
}
```
8. Add the convenience method to access the blockstack session.
7. Add `SignInProvider` interface for better user onboarding. Let the activity implement the interface and implement the member function `getBlockstackSignIn`
```kotlin
fun blockstackSession() : BlockstackSession {
val session = _blockstackSession
if(session != null) {
return session
} else {
throw IllegalStateException("No session.")
class MainActivity : AppCompatActivity(), SignInProvider {
...
override fun provideBlockstackSignIn(): BlockstackSignIn {
return blockstackSignIn
}
}
```
9. Verify your final `MainActivity.kt` code looks like this:
8. Verify your final `MainActivity.kt` code looks like this:
```kotlin
class MainActivity : AppCompatActivity() {
package blockstack.id.user.hello
private var _blockstackSession: BlockstackSession? = null
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_main.\*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.blockstack.android.sdk.BlockstackSession
import org.blockstack.android.sdk.BlockstackSignIn
import org.blockstack.android.sdk.SessionStore
import org.blockstack.android.sdk.getBlockstackSharedPreferences
import org.blockstack.android.sdk.model.UserData
import org.blockstack.android.sdk.model.toBlockstackConfig
import org.blockstack.android.sdk.ui.SignInProvider
import org.blockstack.android.sdk.ui.showBlockstackConnect
class MainActivity : AppCompatActivity(), SignInProvider {
private lateinit var blockstackSession: BlockstackSession
private lateinit var blockstackSignIn: BlockstackSignIn
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
signInButton.isEnabled = false
val scopes = arrayOf(Scope.StoreWrite)
val config = "https://flamboyant-darwin-d11c17.netlify.com"
.toBlockstackConfig(scopes)
val appConfig =
"https://flamboyant-darwin-d11c17.netlify.app".toBlockstackConfig()
val sessionStore = SessionStore(getBlockstackSharedPreferences())
blockstackSession = BlockstackSession(sessionStore, appConfig)
blockstackSignIn = BlockstackSignIn(sessionStore, appConfig)
BlockstackSignIn.shouldLaunchInCustomTabs = false
_blockstackSession = BlockstackSession(this, config)
signInButton.isEnabled = true
signInButton.setOnClickListener { view: View ->
blockstackSession().redirectUserToSignIn {
// only called on error
}
signInButton.setOnClickListener {
showBlockstackConnect()
}
if (intent?.action == Intent.ACTION_VIEW) {
// handle the redirect from sign in
userDataTextView.text = "Signing in now ..."
lifecycleScope.launch(Dispatchers.IO) {
handleAuthResponse(intent)
}
}
}
private fun onSignIn(userData: UserData) {
userDataTextView.text = "Signed in as ${userData.decentralizedID}"
signInButton.isEnabled = false
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.action == Intent.ACTION_VIEW) {
handleAuthResponse(intent)
}
}
private fun handleAuthResponse(intent: Intent) {
val response = intent.dataString
if (response != null) {
val authResponseTokens = response.split(':')
if (authResponseTokens.size > 1) {
val authResponse = authResponseTokens[1]
blockstackSession().handlePendingSignIn(authResponse, { userData ->
private suspend fun handleAuthResponse(intent: Intent) {
val authResponse = intent.data?.getQueryParameter("authResponse")
if (authResponse != null) {
val userData = blockstackSession.handlePendingSignIn(authResponse)
if (userData.hasValue) {
// The user is now signed in!
runOnUiThread {
onSignIn(userData.value!!)
}
}
})
}
}
}
fun blockstackSession() : BlockstackSession {
val session = _blockstackSession
if(session != null) {
return session
} else {
throw IllegalStateException("No session.")
}
override fun provideBlockstackSignIn(): BlockstackSignIn {
return blockstackSignIn
}
}
```
@ -683,21 +583,22 @@ Now that you have created your initial project and verified it running in an emu
1. Choose **Run > Apply changes**.
2. Choose **Run > Run app** in the emulator.
3. When you see the application open, choose **Sign in with Blockstack**.
3. When you see the application open, choose **Get Started**.
4. A small information is presented about the sign-in flow
The system prompts you how to open.
![](images/connect-ui.png)
![](images/chrome-prompt.png)
5. Select **Get Started**
The system might prompt you how to select a browser.
4. Choose **Chrome** and click **ALWAYS**.
5. Move through the rest of the Chrome prompts.
6. Work through the Blockstack prompts to login.
7. Blockstack redirects you to a web site. Open it with your Android app: Select **Hello Android** and **Always**
The system presents you with your final application.
![](images/connect-response.png)
![](images/final-app.png)
6. Work through the Blockstack prompts to login.
8. The screen after the sign-in flow shows the owner address of the username that was entered during sign-in.
![](images/final-app.png)
## Where to go next

251
_browser/blockstack_storage.md

@ -14,7 +14,7 @@ topics:
* TOC
{:toc}
This tutorial does not teach you about authentication. That is covered in depth [in the hello-blockstack tutorial](hello-blockstack).
This tutorial does not teach you about authentication. That is covered in depth [in the guide to Blockstack Connect](/develop/connect/get-started).
<!--TODO: authentication tutorial-->
<!--Strictly speaking not sure it is necessary here to send them out-->
@ -32,8 +32,7 @@ to follow along, basic familiarity with React.js is helpful. When complete, the
- displaying statuses in the user profile
- looking up the profiles and statuses of other users
The basic identity and storage services are provided by `blockstack.js`. To test
the application, you need to have already [registered a Blockstack ID](ids-introduction).
The basic identity and storage services are provided by `blockstack.js`.
For this tutorial, you will use the following tools:
@ -220,48 +219,36 @@ These are the `UserSession.putFile`, `UserSession.getFile`, and `lookupProfile`
1. Open the `src/components/Profile.js` file.
2. Replace the initial state in the `constructor()` method so that it holds the key properties required by the app.
2. Replace the initial state in the component method so that it holds the key properties required by the app.
This code constructs a Blockstack `Person` object to hold the profile. Your constructor should look like this:
This code constructs a Blockstack `Person` object to hold the profile. Your component should look like this:
```javascript
constructor(props) {
super(props);
this.state = {
person: {
name() {
return 'Anonymous';
},
avatarUrl() {
return avatarFallbackImage;
},
},
username: "",
newStatus: "",
statuses: [],
statusIndex: 0,
isLoading: false
};
export const Profile = ({ userData, handleSignOut }) => {
const [newStatus, setNewStatus] = React.useState('');
const [statuses, setStatuses] = React.useState([]);
const [statusIndex, setStatusIndex] = React.useState(0);
const [isLoading, setLoading] = React.useState(false);
const [username, setUsername] = React.useState(userData.username);
const [person, setPerson] = React.useState(new Person(userData.profile));
const { authOptions } = useConnect();
const { userSession } = authOptions;
// ...
}
```
3. Locate the `render()` method.
4. Modify the `render()` method to add a text input and submit button to the
3. Modify the rendered result to add a text input and submit button to the
by replacing it with the code below:
The following code renders the `person.name` and `person.avatarURL`
properties from the profile on the display:
```javascript
render() {
const { handleSignOut, userSession } = this.props;
const { person } = this.state;
const { username } = this.state;
export const Profile = ({ userData, handleSignOut }) => {
// ... state setup from before
return (
!userSession.isSignInPending() && person ?
<div className="container">
<div className="row">
<div className="col-md-offset-3 col-md-6">
@ -289,15 +276,15 @@ These are the `UserSession.putFile`, `UserSession.getFile`, and `lookupProfile`
<div className="new-status">
<div className="col-md-12">
<textarea className="input-status"
value={this.state.newStatus}
onChange={e => this.handleNewStatusChange(e)}
value={newStatus}
onChange={handleNewStatus}
placeholder="Enter a status"
/>
</div>
<div className="col-md-12">
<button
className="btn btn-primary btn-lg"
onClick={e => this.handleNewStatusSubmit(e)}
onClick={handleNewStatusSubmit}
>
Submit
</button>
@ -306,7 +293,7 @@ These are the `UserSession.putFile`, `UserSession.getFile`, and `lookupProfile`
</div>
</div>
</div> : null
</div>
);
}
```
@ -315,61 +302,36 @@ These are the `UserSession.putFile`, `UserSession.getFile`, and `lookupProfile`
user's Blockstack ID. To display this, your app must extract the ID from the
user profile data.
Notice that the `userSession` property passed into our profile renderer contains
the `isSignInPending()` method which checks if a sign in operation is pending.
5. Locate the `componentWillMount()` method.
6. Add the `username` property below the `person` property.
You'll use the Blockstack `loadUserData()` method in our user session to access the `username`.
```javascript
componentWillMount() {
const { userSession } = this.props
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username
});
}
```
7. Add two methods in the `Profile` class to handle the status input events:
7. Add two methods in the `Profile` component to handle the status input events:
```javascript
handleNewStatusChange(event) {
this.setState({newStatus: event.target.value})
const handleNewStatus = (event) => {
setNewStatus(event.target.value);
}
handleNewStatusSubmit(event) {
this.saveNewStatus(this.state.newStatus)
this.setState({
newStatus: ""
})
const handleNewStatusSubmit = async (event) => {
await saveNewStatus(newStatus);
setNewStatus("");
}
```
8. Add a `saveNewStatus()` method to save the new statuses.
```javascript
saveNewStatus(statusText) {
const { userSession } = this.props
let statuses = this.state.statuses
const saveNewStatus = async (statusText) => {
const _statuses = statuses
let status = {
id: this.state.statusIndex++,
id: statusIndex + 1,
text: statusText.trim(),
created_at: Date.now()
}
statuses.unshift(status)
_statuses.unshift(status)
const options = { encrypt: false }
userSession.putFile('statuses.json', JSON.stringify(statuses), options)
.then(() => {
this.setState({
statuses: statuses
})
})
await userSession.putFile('statuses.json', JSON.stringify(_statuses), options);
setStatuses(_statuses);
setStatusIndex(statusIndex + 1);
}
```
@ -396,13 +358,12 @@ Update `Profile.js` again.
```javascript
<div className="col-md-12 statuses">
{this.state.isLoading && <span>Loading...</span>}
{this.state.statuses.map((status) => (
{isLoading && <span>Loading...</span>}
{statuses.map((status) => (
<div className="status" key={status.id}>
{status.text}
</div>
)
)}
))}
</div>
```
This displays existing state. Your code needs to fetch statuses on page load.
@ -410,34 +371,26 @@ Update `Profile.js` again.
4. Add a new method called `fetchData()` after the `saveNewStatus()` method.
```javascript
fetchData() {
const { userSession } = this.props
this.setState({ isLoading: true })
const fetchData = async () => {
setLoading(true);
const options = { decrypt: false }
userSession.getFile('statuses.json', options)
.then((file) => {
var statuses = JSON.parse(file || '[]')
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username,
statusIndex: statuses.length,
statuses: statuses,
})
})
.finally(() => {
this.setState({ isLoading: false })
})
const file = await userSession.getFile('statuses.json', options)
const _statuses = JSON.parse(file || '[]')
setStatusIndex(_statuses.length);
setStatuses(_statuses);
setLoading(false);
}
```
By default, `getFile()` this method decrypts data; because the default `putFile()` encrypts it. In this case, the app shares statuses publicly. So, there is no need to decrypt.
5. Call `fetchData()` from the `componentDidMount()` method.
5. Call `fetchData()` from the by using React's `useEffect` method, which will fetch data whenever the component's username state is changed..
```javascript
// after setting up your component's state
componentDidMount() {
this.fetchData()
}
React.useEffect(() => {
fetchData();
}, [username]);
```
6. Save the file.
@ -578,8 +531,13 @@ process URL paths that contain the `.` (dot) character for example,
3. Add a single method to the `Profile` class that determines if the app is viewing the local user's profile or another user's profile.
```javascript
isLocal() {
return this.props.match.params.username ? false : true
// Make sure you add this new prop!
export const Profile = ({ userData, handleSignOut, match }) => {
// ...
const isLocal = () => {
return match.params.username ? false : true
}
// ...
}
```
@ -588,37 +546,25 @@ process URL paths that contain the `.` (dot) character for example,
4. Modify the `fetchData()` method like so:
```javascript
fetchData() {
const { userSession } = this.props
this.setState({ isLoading: true })
if (this.isLocal()) {
const fetchData = async () => {
setLoading(true);
if (isLocal()) {
const options = { decrypt: false }
userSession.getFile('statuses.json', options)
.then((file) => {
var statuses = JSON.parse(file || '[]')
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username,
statusIndex: statuses.length,
statuses: statuses,
})
})
.finally(() => {
this.setState({ isLoading: false })
})
const file = await userSession.getFile('statuses.json', options)
const _statuses = JSON.parse(file || '[]')
setStatusIndex(_statuses.length);
setStatuses(_statuses);
setLoading(false);
} else {
const username = this.props.match.params.username
lookupProfile(username)
.then((profile) => {
this.setState({
person: new Person(profile),
username: username
})
})
.catch((error) => {
console.log('could not resolve profile')
})
const username = match.params.username
try {
const newProfile = await lookupProfile(username)
setPerson(new Person(newProfile));
setUsername(username);
} catch (error) {
console.log('Could not resolve profile');
}
}
}
```
@ -630,40 +576,25 @@ process URL paths that contain the `.` (dot) character for example,
documentation](http://blockstack.github.io/blockstack.js/#getfile) for
details.
5. Add the following block to `fetchData()` right after the call to `lookupProfile(username)... catch((error)=>{..}` block:
5. Add the following block to `fetchData()` right after the call to `setUsername(username)` block:
```javascript
const options = { username: username, decrypt: false }
userSession.getFile('statuses.json', options)
.then((file) => {
var statuses = JSON.parse(file || '[]')
this.setState({
statusIndex: statuses.length,
statuses: statuses
})
})
.catch((error) => {
console.log('could not fetch statuses')
})
.finally(() => {
this.setState({ isLoading: false })
})
const file = await userSession.getFile('statuses.json', options)
const _statuses = JSON.parse(file || '[]')
setStatusIndex(_statuses.length);
setStatuses(_statuses);
setLoading(false);
```
This fetches the user statuses.
Finally, you must conditionally render the logout button, status input textbox, and submit button so they don't show up when viewing another user's profile.
6. Replace the `render()` method with the following:
6. Replace the returned JSX in the `Profile` component with the following:
```javascript
render() {
const { handleSignOut, userSession } = this.props;
const { person } = this.state;
const { username } = this.state;
return (
!userSession.isSignInPending() && person ?
<div className="container">
<div className="row">
<div className="col-md-offset-3 col-md-6">
@ -680,28 +611,28 @@ process URL paths that contain the `.` (dot) character for example,
: 'Nameless Person' }</span>
</h1>
<span>{username}</span>
{this.isLocal() &&
{isLocal() &&
<span>
&nbsp;|&nbsp;
<a onClick={ handleSignOut.bind(this) }>(Logout)</a>
<a onClick={handleSignOut}>(Logout)</a>
</span>
}
</div>
</div>
</div>
{this.isLocal() &&
{isLocal() &&
<div className="new-status">
<div className="col-md-12">
<textarea className="input-status"
value={this.state.newStatus}
onChange={e => this.handleNewStatusChange(e)}
value={newStatus}
onChange={handleNewStatus}
placeholder="What's on your mind?"
/>
</div>
<div className="col-md-12 text-right">
<button
className="btn btn-primary btn-lg"
onClick={e => this.handleNewStatusSubmit(e)}
onClick={handleNewStatusSubmit}
>
Submit
</button>
@ -709,19 +640,17 @@ process URL paths that contain the `.` (dot) character for example,
</div>
}
<div className="col-md-12 statuses">
{this.state.isLoading && <span>Loading...</span>}
{this.state.statuses.map((status) => (
{isLoading && <span>Loading...</span>}
{statuses.map((status) => (
<div className="status" key={status.id}>
{status.text}
</div>
)
)}
))}
</div>
</div>
</div>
</div>
</div> : null
);
}
```
This checks to ensure that users are viewing their own profile, by wrapping the **Logout** button and inputs with the `{isLocal() && ...}` condition.

BIN
_browser/images/display-complete.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
_browser/images/initial-app.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
_browser/images/login-choice.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
_browser/images/login-no-auth.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
_browser/images/login.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
_browser/images/login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
_browser/images/make-a-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
_browser/images/multi-player-storage-status.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
_browser/images/multiple-lists.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
_browser/images/network-connections.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
_browser/images/publish-data-perm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
_browser/images/saving-status.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
_browser/images/todo-app.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
_browser/images/todo-sign-in.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

28
_browser/todo-list.md

@ -189,19 +189,27 @@ in and sign out is handled in each of these files:
| `components/Signin.js ` | Code for the initial sign on page. |
| `components/Profile.js` | Application data storage and user sign out. |
The `src/components/App.js` code configures an `AppConfig` object and then uses this to create a `UserSession`. Then, the application calls a [`redirectToSignIn()`](https://blockstack.github.io/blockstack.js#redirectToSignIn) function which generates the `authRequest` and redirects the user to the Blockstack authenticator:
<!-- The `src/components/App.js` code configures an `AppConfig` object and then uses this to create a `UserSession`. Then, the application calls a [`redirectToSignIn()`](https://blockstack.github.io/blockstack.js#redirectToSignIn) function which generates the `authRequest` and redirects the user to the Blockstack authenticator: -->
```js
...
const userSession = new UserSession({ appConfig })
The `src/components/App.js` code configures a `UserSession` and other `authOptions`, which are passed to the `Connect` component. The `Connect` component acts as a "provider" for the rest of your application, and essentially creates a re-usable configuration for you.
export default class App extends Component {
In the `src/components/Signin.js` component, we are then calling the `useConnect` hook. This hook returns many helper functions, one of which is `doOpenAuth`. Calling this method will being the authentication process. First, it injects a modal into your application, which acts as a way of "warming up" your user to Blockstack authentication. When the user continues, they are redirected to the Blockstack authenticator, where they can finish signing up.
handleSignIn(e) {
e.preventDefault();
userSession.redirectToSignIn();
}
...
```js
import React from 'react';
import { useConnect } from '@blockstack/connect';
export const Signin = () => {
const { doOpenAuth } = useConnect();
return (
<button
onClick={() => doOpenAuth()}
>
Sign In with Blockstack
</button>
)
};
```
Once the user authenticates, the application handles the `authResponse` in the `src/components/Profile.js` file. :

19
_common/core_ref.md

@ -1,5 +1,18 @@
---
layout: externalurl
redirect_url: https://core.blockstack.org/
title: "Stacks Node API"
layout: core
title: "Stacks Blockchain APIs"
description: Interacting with the Stacks 2.0 Blockchain
permalink: /:collection/:path.html
---
With the launch of Stacks 2.0, a new version of the Blockstack blockchain was released. There are two ways of interacting with the blockchain, either using the Stacks Blockchain API or by making RPC calls to a Stacks Core directly.
## Stacks Core API
The Stacks 2.0 blockchain's Rust implementation exposes RPC endpoints (in JSON format), which can be used to interface with the Stacks blockchain. [You can find the RPC API references here](https://docs.blockstack.org/core/smart/rpc-api.html).
## Stacks Blockchain API
The Stacks Blockchain API was built to maintain pageable materialized views of the Stacks 2.0 Blockchain. It is a server that exposes a RESTful JSON API, hosted by PBC. It introduces aidditonal functionality (e.g. get all transactions), as well as proxies calls directly to Stacks Node. [You can find the OpenAPI specification and documentation here](https://blockstack.github.io/stacks-blockchain-sidecar/).
*Note: Using this API requires you to trust the server, but provides a faster onboarding experience. It also addresses performance issues for which querying a node itself would be too slow or difficult.*
{% include note.html content="If you are looking for the Stacks 1.0 RPC endpoint references, please follow this <a href='https://core.blockstack.org/'>link</a>." %}

24
_core/smart/cli-wallet-quickstart.md

@ -22,13 +22,14 @@ To install the Blockstack CLI, run the following command in terminal.
First, we are going to generate a new wallet for Testnet. To generate a wallet use the `make_keychain` command with the `-t` option for Testnet.
```
$ blockstack-cli make_keychain -t
$ blockstack make_keychain -t
{
"mnemonic": "private unhappy random runway boil scissors remove harvest fatigue inherit inquiry still before mountain pet tail mad accuse second milk client rebuild salt chase",
"keyInfo": {
"privateKey": "381314da39a45f43f45ffd33b5d8767d1a38db0da71fea50ed9508e048765cf301",
"address": "ST1BG7MHW2R524WMF7X8PGG3V45ZN040EB9EW0GQJ",
"btcAddress": "n4X37UmRZYk9HawtS1w4xRtqJWhByxiz3c",
"index": 0
}
}
@ -54,18 +55,22 @@ Once the faucet transaction has been broadcasted, you will need to wait for the
Once you’ve requested Testnet Stacks tokens from the faucet, you can check the balance of your account using the following command.
```
$ blockstack-cli balance ST1BG7MHW2R524WMF7X8PGG3V45ZN040EB9EW0GQJ -H "http://neon.blockstack.org:20443"
$ blockstack balance ST1BG7MHW2R524WMF7X8PGG3V45ZN040EB9EW0GQJ -t
{
"balance": "1000",
"balance": "10000",
"nonce": 0
}
```
Using the `-H` option, we specify a connection to the testnet node at `http://neon.blockstack.org:20443`
By default, using the `-t` flag causes the CLI to connect to the neon testnet node at `http://neon.blockstack.org:20443`.
To specify a node to connect to, add the `-H` flag followed by the URL of the node `"http://localhost:20443"`. This flag can be used with all commands in this guide.
Verify that your account has tokens before continuing to the next step to send tokens.
Take note that the nonce for the account is `0`. We will be using this number in the next step.
## Sending Tokens
In order to send tokens, we will need the 5 parameters below.
@ -83,22 +88,21 @@ In order to send tokens, we will need the 5 parameters below.
Once we have the parameters, we can use the `send_tokens` command:
```
$ blockstack-cli send_tokens ST1WZ69T99RHQMQX3D91ZH2R37GV5NK8KDS5D5VDZ 1000 200 0 381314da39a45f43f45ffd33b5d8767d1a38db0da71fea50ed9508e048765cf301 -t -T "http://neon.blockstack.org:20443/v2/transactions"
$ blockstack send_tokens ST2KMMVJAB00W5Z6XWTFPH6B13JE9RJ2DCSHYX0S7 1000 200 0 381314da39a45f43f45ffd33b5d8767d1a38db0da71fea50ed9508e048765cf301 -t
d32de0d66b4a07e0d7eeca320c37a10111c8c703315e79e17df76de6950c622c
```
With this command we’re sending 1000 microstacks to the Stacks address `ST1WZ69T99RHQMQX3D91ZH2R37GV5NK8KDS5D5VDZ`.
With this command we’re sending 1000 microstacks to the Stacks address `ST2KMMVJAB00W5Z6XWTFPH6B13JE9RJ2DCSHYX0S7`.
We set the fee rate to `200` microstacks.
We set the fee rate to `200` microstacks. If you're not sure how much your transaction will cost. You can add the `-e` flag to estimate the transaction fee needed to get processed by the network, without broadcasting your transaction.
The nonce is set to `0` for this transaction, since it will be the first transaction we send from this account.
The nonce is set to `0` for this transaction, since it will be the first transaction we send from this account. For subsequent transactions, you will need to increment this number by `1` each time. You can check the current nonce for the account using the `balance` command.
Finally, the last parameter is the private key from earlier. `381314da39a45f43f45ffd33b5d8767d1a38db0da71fea50ed9508e048765cf301`
Once again, we’re using the `-t` option to indicate that this is a Testnet transaction, so it should be broadcasted to Testnet.
Using the `-T` option, we specify that we want to broadcast to the testnet node at `http://neon.blockstack.org:20443/v2/transactions`
If valid, your transaction will now be broadcasted to the network and you will see the transaction ID displayed on the screen.
To view the raw transaction without broadcasting, add the `-x` flag to your command.

93
_core/smart/neon-node.md

@ -1,93 +0,0 @@
---
layout: smart
description: "Run a Stacks Testnet Node"
permalink: /:collection/:path.html
---
# Running a Neon Testnet Node
{:.no_toc}
"Neon" is phase 1 of the Stacks 2.0 testnet. In Neon, you can run a node and connect it to a public network. This guide will walk you through downloading and running your own node in the Neon network.
* TOC
{:toc}
### Prerequisites
Note: If you use Linux, you may need to manually install [`libssl-dev`](https://wiki.openssl.org/index.php/Libssl_API) and other packages. In your command line, run the following to get all packages:
```bash
sudo apt-get install build-essential cmake libssl-dev pkg-config
```
### Download and install the `stacks-blockchain` repository
The first step is to ensure that you have Rust installed. If you are using macOS, Linux, or another Unix-like OS, run the following. If you are on a different OS, follow the [official Rust installation guide](https://www.rust-lang.org/tools/install).
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
If Rust is already installed, you might see this prompt. Select 'Proceed with Installation' to make sure you have the latest version installed.
![rustup prompt](/core/images/rust-install.png)
In case you just installed Rust, you will be prompted to run the following command to make the `cargo` command available:
```bash
source $HOME/.cargo/env
```
Next, clone this repository:
```bash
git clone https://github.com/blockstack/stacks-blockchain.git
cd stacks-blockchain
```
Install the Stacks node by running:
```bash
cargo install --path ./testnet
```
### Run your node
You're all set to run a node that connects to the Neon network.
Back in the command line, run:
```bash
stacks-node neon
```
The first time you run this, you'll see some logs indicating that the Rust code is being compiled. Once that's done, you should see some logs that look something like the this:
```
INFO [1588108047.585] [src/chainstate/stacks/index/marf.rs:732] First-ever block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
```
Awesome! Your node is now connected to the Neon network. Your node will receive new blocks when they are produced, and you can use your [node's RPC API](/core/smart/rpc-api) to send transactions, fetch information for contracts and accounts, and more.
### Creating an optimized binary
The steps above are great for trying to run a node temporarily. If you want to host a node on a server somewhere, you might want to generate an optimized binary. To do so, use the same configuration as above, but run:
```bash
cd testnet
cargo build --release --bin stacks-node
```
The above code will compile an optimized binary. To use it, run:
```bash
cd ..
./target/release/stacks-node start --config=./testnet/conf/neon-follower-conf.toml
```
### Enable debug logging
In case you are running into issues or would like to see verbose logging, you can run your node with debug logging enabled. In the command line, run:
```bash
BLOCKSTACK_DEBUG=1 stacks-node neon
```

18
_core/smart/overview.md

@ -6,20 +6,18 @@ permalink: /:collection/:path.html
# Introduction to Clarity
{:.no_toc}
Clarity is a smart contracting language for use with the Stacks 2.0 blockchain. It supports programmatic control over digital assets.
Clarity is a programming language for writing smart contracts on the Stacks 2.0 blockchain. It supports programmatic control over digital assets.
* TOC
{:toc}
## Smart contracts
Smart contracts encode and enforce the rules for modifying a particular set of data as shared among people and entities who don't necessarily trust each other. For example, a smart contract could allow creators to add a new tv show to a streaming service, but require that users first pay for a decryption key before viewing it. To do this, the smart contract could be written such that the movie creator had to publicly disclose the decryption key in order to receive payment. The movie creators would therefore be incentivized to work on tv show episodes that people want to watch, based on the viewers' upfront payments that are locked-in until the creator releases the episode and the decryption key.
Smart contracts encode and enforce rules for modifying a particular set of data that is shared among people and entities who don't necessarily trust each other. For example, a smart contract can hold funds in escrow until multiple parties agree to release them, create its own ledger and keep track of its own novel tokens (fungible or non-fungible), and even help make supply chains more transparent.
Because smart contracts run on top of a blockchain, anyone can query them, and anyone can submit transactions to execute them. A smart contract execution can result in new transactions being written to the blockchain.
Because smart contracts are programs that exist in a blockchain, anyone can query them, and anyone can submit transactions to execute them. A smart contract execution can result in new transactions being written to the blockchain.
Apps use the blockchain to manage a global state that is visible to the public. To get back to the streaming service example, the global state could include a list of user that paid for a specific tv show episode. This is possible because the blockchain stores the history of all accepted transactions.
Anyone can audit the blockchain in order to independently verify that an app's global shared state has been managed correctly according to the smart contract's rules.
Apps can take advantage of smart contracts to manage a global state that is visible to the public. Anyone can audit the blockchain in order to independently verify that an app's global shared state has been managed correctly according to the smart contract's rules.
## Use cases
@ -44,11 +42,11 @@ A Clarity smart contract is composed of two parts &mdash; a data space and a set
Note some of the key Clarity language rules and limitations.
* The only atomic types are booleans, integers, fixed length buffers, and principals
* Recursion is illegal and there is no lambda function.
* The only primitive types are booleans, integers, buffers, and principals
* Recursion is illegal and there are no anonymous functions.
* Looping may only be performed via `map`, `filter`, or `fold`
* There is support for lists of the atomic types, however, the only variable length lists in the language appear as function inputs; There is no support for list operations like append or join.
* Variables are created via `let` binding and there is no support for mutating functions like `set`.
* There is support for lists, however, the only variable length lists in the language appear as function inputs; There is no support for list operations like append or join.
* Variables are immutable.
## Learning Clarity

209
_core/smart/principals.md

@ -36,6 +36,32 @@ is a multiple of 10, otherwise returning a 400 error code.
(err u400)))
```
Clarity provides an additional variable to help smart contracts
authenticate a transaction sender. The keyword `contract-caller`
returns the principal that _called_ the current contract. If an
inter-contract call occurred, `contract-caller` returns the last
contract in the stack of callers. For example, suppose there are three
contracts A, B, and C, each with an `invoke` function such that
`A::invoke` calls `B::invoke` and `B::invoke` calls `C::invoke`.
When a user Bob issues a transaction that calls `A::invoke`, the value
of `contract-caller` in each successive invoke function's body would change:
```
in A::invoke, contract-caller = Bob
in B::invoke, contract-caller = A
in C::invoke, contract-caller = B
```
This allows contracts to make assertions and perform authorization
checks using not only the `tx-sender` (which in this example, would
always be "Bob"), but also using the `contract-caller`. This could be
used to ensure that a particular function is only ever called directly
and never called via an inter-contract call (by asserting that
`tx-sender` and `contract-caller` are equal). We provide an example of
a two different types of authorization checks in the rocket ship
example below.
## Smart contracts as principals
Smart contracts themselves are principals and are represented by the
@ -100,3 +126,186 @@ In this example, the public function `claim-from-faucet`:
Unlike other principals, there is no private key associated with a
smart contract. As it lacks a private key, a Clarity smart contract
cannot broadcast a signed transaction on the blockchain.
## Example: Authorization checks
The interactions between `tx-sender`, `contract-caller` and
`as-contract` are subtle, but are important when performing
authorization checks in a contract. In this example contract,
we'll show two different kinds of authorization checks
a contract may wish to perform, and then walk through how
different ways in which contract functions may be called
will pass or fail those checks.
This contract defines a "rocket-ship" non-fungible-token
that a principal may own and manage the authorized pilots.
Pilots are principals that are allowed to "fly" the
rocket ship.
This contract performs two different authorization checks:
1. Before a ship is allowed to fly, the contract checks whether or not
the transaction was created and signed by an authorized pilot. A
pilot, could, for example, call another contract, which then calls
the "fly-ship" public function on the pilot's behalf.
2. Before modifying the allowed-pilots for a given rocket ship, the
contract checks that the transaction was signed by the owner of the
rocket ship. Furthermore, the contract requires that this function
be called _directly_ by the ship's owner, rather than through a
inter-contract-call.
The second type of check is more restrictive than the first check,
and is helpful for guarding very sensitive routines --- it
protects users from unknowingly calling a function on a malicious
contract that subsequently tries to call sensitive functions on
another contract.
```scheme
;;
;; rockets-base.clar
;;
(define-non-fungible-token rocket-ship uint)
;; a map from rocket ships to their allowed
;; pilots
(define-map allowed-pilots
((rocket-ship uint)) ((pilots (list 10 principal))))
;; implementing a contains function via fold
(define-private (contains-check
(y principal)
(to-check { p: principal, result: bool }))
(if (get result to-check)
to-check
{ p: (get p to-check),
result: (is-eq (get p to-check) y) }))
(define-private (contains (x principal) (find-in (list 10 principal)))
(get result (fold contains-check find-in
{ p: x, result: false })))
(define-read-only (is-my-ship (ship uint))
(is-eq (some tx-sender) (nft-get-owner? rocket-ship ship)))
;; this function will print a message
;; (and emit an event) if the tx-sender was
;; an authorized flyer.
;;
;; here we use tx-sender, because we want
;; to allow the user to let other contracts
;; fly the ship on behalf of users
(define-public (fly-ship (ship uint))
(let ((pilots (default-to
(list)
(get pilots (map-get? allowed-pilots { rocket-ship: ship })))))
(if (contains tx-sender pilots)
(begin (print "Flew the rocket-ship!")
(ok true))
(begin (print "Tried to fly without permission!")
(ok false)))))
;;
;; Authorize a new pilot.
;;
;; here we want to ensure that this function
;; was called _directly_ by the user by
;; checking that tx-sender and contract-caller are equal.
;; if any other contract is in the call stack, contract-caller
;; would be updated to a different principal.
;;
(define-public (authorize-pilot (ship uint) (pilot principal))
(begin
;; sender must equal caller: an intermediate contract is
;; not issuing this call.
(asserts! (is-eq tx-sender contract-caller) (err u1))
;; sender must own the rocket ship
(asserts! (is-eq (some tx-sender)
(nft-get-owner? rocket-ship ship)) (err u2))
(let ((prev-pilots (default-to
(list)
(get pilots (map-get? allowed-pilots { rocket-ship: ship })))))
;; don't add a pilot already in the list
(asserts! (not (contains pilot prev-pilots)) (err u3))
;; append to the list, and check that it is less than
;; the allowed maximum
(match (as-max-len? (append prev-pilots pilot) u10)
next-pilots
(ok (map-set allowed-pilots {rocket-ship: ship} {pilots: next-pilots}))
;; too many pilots already
(err u4)))))
```
### Extending functionality: Multi-flyer contract
The authorization scheme for `fly-ship` allows pilots to fly rocket-ships from
other contracts. This allows other contracts to provide new functionality built
around calling that function.
For example, we can create a contract that calls `fly-ship`
for multiple rocket-ships in a single transaction:
```
;;
;; rockets-multi.clar
;;
(define-private (call-fly (ship uint))
(unwrap! (contract-call? .rockets-base fly-ship ship) false))
;; try to fly all the ships, returning a list of whether
;; or not we were able to fly the supplied ships
(define-public (fly-all (ships (list 10 uint)))
(ok (map call-fly ships)))
```
### Authorization for Contract-Owned Assets
The check in `authorize-pilot` protects users from malicious contracts,
but how would such a scheme support contract-owned assets? This is what the
`as-contract` function is used for. The `as-contract` function executes
the supplied closure as if the sender of the transaction was the current
contract, rather than the user -- it does this by updating `tx-sender`
to the current contract principal. We can use this to, for example, create
a smart contract rocket-ship-line:
```
;;
;; rockets-ship-line.clar
;;
(define-constant line-ceo 'SP19Y6GRV9X778VFS1V8WT2H502WFK33XZXADJWZ)
(define-data-var employed-pilots (list 10 principal) (list))
;; This function will:
;; * check that it is called by the line-ceo
;; * check that the rocket is owned by the contract
;; * authorize each employed pilot to the ship
(define-public (add-managed-rocket (ship uint))
(begin
;; only the ceo may call this function!
(asserts! (is-eq tx-sender contract-caller line-ceo) (err u1))
;; start executing as the contract
(as-contract (begin
;; make sure the contract owns the ship!
(asserts! (contract-call? .rockets-base is-my-ship ship) (err u2))
;; register all of our pilots on the ship
(add-pilots-to ship)))))
;; add all the pilots to a ship using fold --
;; the fold checks the return type of previous calls,
;; skipping subsequent contract-calls if one fails.
(define-private (add-pilot-via-fold (pilot principal) (prior-result (response uint uint)))
(let ((ship (try! prior-result)))
(try! (contract-call? .rockets-base authorize-pilot ship pilot))
(ok ship)))
(define-private (add-pilots-to (ship uint))
(fold add-pilot-via-fold (var-get employed-pilots) (ok ship)))
```
In order for the contract to add each pilot to a new ship, the contract must
call `authorize-pilot`. However, the contract wants to perform this action on
behalf of a ship the _contract_ owns, not the transaction sender. To do this,
the contract uses `as-contract`.

2
_core/smart/sdk-quickstart.md

@ -174,7 +174,7 @@ npm run test
✓ should have a valid syntax
deploying an instance of the contract
✓ should print hello world message
✓ should echo number
✓ should echo number (54ms)
3 passing (182ms)

167
_core/smart/testnet-node.md

@ -0,0 +1,167 @@
---
layout: smart
description: "Run a Stacks Testnet Node"
permalink: /:collection/:path.html
redirect_from:
- /core/smart/neon-node.html
---
# Running a Testnet Node
{:.no_toc}
The Stacks 2.0 testnet is currently in development. As part of the testnet, you can run a node and connect it to a public network. This guide will walk you through downloading and running your own node in the testnet network.
* TOC
{:toc}
### Prerequisites
Note: If you use Linux, you may need to manually install [`libssl-dev`](https://wiki.openssl.org/index.php/Libssl_API) and other packages. In your command line, run the following to get all packages:
```bash
sudo apt-get install build-essential cmake libssl-dev pkg-config
```
Ensure that you have Rust installed. If you are using macOS, Linux, or another Unix-like OS, run the following. If you are on a different OS, follow the [official Rust installation guide](https://www.rust-lang.org/tools/install).
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
If Rust is already installed, you might see this prompt. Select 'Proceed with Installation' to make sure you have the latest version installed.
![rustup prompt](/core/images/rust-install.png)
In case you just installed Rust, you will be prompted to run the following command to make the `cargo` command available:
```bash
source $HOME/.cargo/env
```
### Download and install the `stacks-blockchain` repository
Next, clone this repository:
```bash
git clone https://github.com/blockstack/stacks-blockchain.git
cd stacks-blockchain
```
Install the Stacks node by running:
```bash
cargo install --path ./testnet/stacks-node
```
### Run your node
You're all set to run a node that connects to the testnet network.
Back in the command line, run:
```bash
stacks-node argon
```
The first time you run this, you'll see some logs indicating that the Rust code is being compiled. Once that's done, you should see some logs that look something like the this:
```
INFO [1588108047.585] [src/chainstate/stacks/index/marf.rs:732] First-ever block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
```
Awesome! Your node is now connected to the testnet network. Your node will receive new blocks when they are produced, and you can use your [node's RPC API](/core/smart/rpc-api) to send transactions, fetch information for contracts and accounts, and more.
### Running a miner
Once you've followed the above steps to run a node, it's only a few more steps to run a proof-of-burn miner on the testnet.
First, we need to generate a keychain. With this keychain, we'll get some testnet BTC from a faucet, and then use that BTC to start mining.
To get a keychain, the simplest way is to use the `blockstack-cli`. We'll use the `make_keychain` command, and pass `-t` to indicate that we want a testnet keychain.
```bash
npx blockstack-cli@1.1.0-beta.1 make_keychain -t
```
After this runs, you'll probably see some installation logs, and at the end you should see some JSON that looks like this:
```json
{
"mnemonic": "exhaust spin topic distance hole december impulse gate century absent breeze ostrich armed clerk oak peace want scrap auction sniff cradle siren blur blur",
"keyInfo": {
"privateKey": "2033269b55026ff2eddaf06d2e56938f7fd8e9d697af8fe0f857bb5962894d5801",
"address": "STTX57EGWW058FZ6WG3WS2YRBQ8HDFGBKEFBNXTF",
"btcAddress": "mkRYR7KkPB1wjxNjVz3HByqAvVz8c4B6ND",
"index": 0
}
}
```
We need to get some testnet BTC to that address. Grab the `btcAddress` field, and head over to [the Stacks testnet website](https://testnet.blockstack.org/faucet). In the BTC faucet section, past in your `btcAddress`, and submit. You'll be sent 0.5 testnet BTC to that address. **Don't lose this information** - we'll need to use the `privateKey` field later on.
Now, we need to configure out node to use this Bitcoin keychain. In the `stacks-blockchain` folder, create a new file called `testnet/stacks-node/conf/testnet-miner-conf.toml`.
Paste in the following configuration:
```toml
[node]
rpc_bind = "0.0.0.0:20443"
p2p_bind = "0.0.0.0:20444"
bootstrap_node = "048dd4f26101715853533dee005f0915375854fd5be73405f679c1917a5d4d16aaaf3c4c0d7a9c132a36b8c5fe1287f07dad8c910174d789eb24bdfb5ae26f5f27@argon.blockstack.org:20444"
# Enter your private key here!
seed = "replace-with-your-private-key"
miner = true
[burnchain]
chain = "bitcoin"
mode = "argon"
peer_host = "argon.blockstack.org"
rpc_port = 18443
peer_port = 18444
[[mstx_balance]]
address = "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6"
amount = 10000000000000000
[[mstx_balance]]
address = "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y"
amount = 10000000000000000
[[mstx_balance]]
address = "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR"
amount = 10000000000000000
[[mstx_balance]]
address = "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP"
amount = 10000000000000000
```
Now, grab your `privateKey` from earlier, when you ran the `make_keychain` command. Replace the `seed` field with your private key. Save and close this configuration file.
To run your miner, run this in the command line:
```bash
stacks-node start --config=./testnet/stacks-node/conf/testnet-miner-conf.toml
```
Your node should start. It will take some time to sync, and then your miner will be running!
### Creating an optimized binary
The steps above are great for trying to run a node temporarily. If you want to host a node on a server somewhere, you might want to generate an optimized binary. To do so, use the same configuration as above, but run:
```bash
cd testnet/stacks-node
cargo build --release --bin stacks-node
```
The above code will compile an optimized binary. To use it, run:
```bash
cd ../..
./target/release/stacks-node start --config=./testnet/conf/argon-follower-conf.toml
```
### Enable debug logging
In case you are running into issues or would like to see verbose logging, you can run your node with debug logging enabled. In the command line, run:
```bash
BLOCKSTACK_DEBUG=1 stacks-node argon
```

14
_core/smart/tutorial-counter.md

@ -37,7 +37,6 @@ You have to select a template and a name for your local folder. For the counter
```bash
? Template - one of [hello-world, counter]: counter
? Project name: (clarity-counter)
```
Finally, the project dependencies are installed and your project is ready for development. Because you already completed the [Hello World tutorial](tutorial.html), the project structure is familiar to you. The main difference is that we have additional tests for a new counter smart contract.
@ -65,6 +64,10 @@ counter contract test suite
1 passing (734ms)
3 failing
... # error details
npm ERR! Test failed. See above for more details.
```
It looks like we see some failed tests! That is on purpose - we will implement the new smart contract in the next steps! After every step in this tutorial, we will rerun the tests to ensure we're on the right track.
@ -89,7 +92,7 @@ Let's get familiar with the tests to understand what the new smart contract shou
The file was already created during the project setup.
2. With the editor of your choice, open the file and add the following lines of code:
2. With the editor of your choice, open `contracts/counter.clar` and add the following lines of code:
```cl
(define-data-var counter int 0)
@ -151,16 +154,19 @@ Let's get familiar with the tests to understand what the new smart contract shou
```cl
(define-data-var counter int 0)
(define-public (get-counter)
(ok (var-get counter)))
(define-public (increment)
(begin
(var-set counter (+ (var-get counter) 1))
(ok (var-get counter))))
(define-public (decrement)
(begin
(var-set counter (- (var-get counter) 1))
(ok (var-get counter))))
(define-public (get-counter)
(ok (var-get counter)))
```
With the completion of this tutorial, you ...

4
_core/smart/tutorial.md

@ -44,15 +44,13 @@ In this step, you initialize a starter project for Clarity development:
```bash
? Template - one of [hello-world, counter]: (hello-world)
? Project name: (clarity-hello-world)
```
Finally, after the project dependencies have been installed, your project is ready for development.
3. The project is located in a new folder, `clarity-hello-world` by default. Jump into the folder and have a look at the file structure:
3. The project resources are created in your current folder. Have a look at the project structure:
```bash
cd clarity-hello-world
ls
```

2
_develop/cliDocs.md

@ -2,6 +2,6 @@
layout: learn
permalink: /:collection/:path.html
---
# blockstack_cli reference
# Blockstack CLI Reference
{% include commandline.md %}

45
_develop/connect/get-started.md

@ -5,11 +5,6 @@ permalink: /:collection/:path.html
# Guide to Blockstack Connect
{:.no_toc}
Blockstack Connect is a Javascript library for integrating your application with Stacks v2. With Connect, you get some big benefits:
<!-- - -->
* TOC
{:toc}
@ -143,3 +138,43 @@ Then, you can use API methods under the `blockstackConnect` global variable:
const authOptions = { /** See docs above for options */ };
blockstackConnect.showBlockstackConnect(authOptions);
```
## Handling redirect fallbacks
Connect is built to use popups with the [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) API, which provides a much better and seamless user experience. However, there are times when this flow can fail. For example, the popup may be blocked, or the `window.postMessage` API might not work properly (which often happens on mobile browsers).
To make sure your app handles this gracefully, you'll need to handle the case where authentication is performed through regular HTTP redirects. With redirects, your users will be sent back to your app at a URL like:
`${authOptions.redirectTo}?authResponse=....`
To finalize authentication with this flow, you'll need to utilize the `UserSession` methods `isSignInPending()` and `handlePendingSignIn()`. For more information, check out the [blockstack.js API reference](https://blockstack.github.io/blockstack.js/).
```js
const userSession = new UserSession(appConfig);
// ... call this code on page load
if (userSession.isSignInPending()) {
const userData = await userSession.handlePendingSignIn();
// your user is now logged in.
}
```
## Design Guidance
Blockstack is valuable to users, but it can also be a barrier to those unfamiliar with Blockstack. The following guidelines serve to remedy that and help you onboard as many new users as you can.
### Delay Blockstack onboarding as long as possible
People will often leave apps when things are asked of them before they experience the app. Give them a chance to try your app before you ask them to sign up with Blockstack. For example, a note taking app could let a new user write a couple of notes before prompting them to save their progress.
### Provide an easy way in for new users
Many new users to your app will not be familiar with Blockstack yet and will be hesitant to click a Blockstack-branded button. Provide a generic button for users that are new to your app and Blockstack. Blockstack Connect will introduce new users to Blockstack and recognize existing users.
![Design Guidance Example](./docs/call-to-action-branding.png)
### Provide a quick way for existing users to sign in
You can point users to a specific part of the Blockstack App. For instance, a “Sign in” button on your website can redirect users to the sign in flow of the Blockstack App. If you do this, make sure you also have an option that is explicitly for new users and that points to the sign up flow.
To implement this functionality, check out our section on sending users to sign in immediately.

206
_develop/connect/use-with-clarity.md

@ -7,19 +7,12 @@ permalink: /:collection/:path.html
With Connect, you can interact with the Stacks 2.0 blockchain. You can allow your users to send transactions and interact with smart contracts.
* TOC
{:toc}
## How it works
For your app's users to be able to execute a smart contract function, they need to sign and broadcast a transaction. It's important that users remain control of the private keys that sign these transactions. Connect provides an easy-to-use workflow that allows your users to securely sign transactions.
Connect allows you to open the authenticator with parameters indicating the details of the transaction - like the smart contract address, function name, and specific arguments. Your users get the chance to see these details, and then sign and broadcast the transaction in a single click. Their transaction will be securely signed and broadcasted onto the Stacks blockchain. After this is done, a callback is fired to allow you to update your app.
## Usage
<div class="uk-card uk-card-default uk-card-body">
<h5 class="uk-card-title">Transaction signing is still in progress</h5>
<p>
To use these features, make sure you install the <code>testnet</code> tag the <code>@blockstack/connect</code> NPM package. You can do this by running <code>npm install --save @blockstack/connect@testnet</code>, or by specifying <code>testnet</code> as the version in
your package.json file.
</p>
<p>
The Stacks 2.0 blockchain is still in testnet, and our web app integration is also still in beta. In order to use transaction signing in your application, you need to use the configuration `authOrigin` with `@blockstack/connect`.
</p>
@ -33,34 +26,81 @@ Connect allows you to open the authenticator with parameters indicating the deta
</pre>
</div>
### `ContractCallOptions`
* TOC
{:toc}
## How it works
For your app's users to be able to execute a smart contract function, they need to sign and broadcast a transaction. It's important that users remain in control of the private keys that sign these transactions. Connect provides an easy-to-use workflow that allows your users to securely sign transactions.
Connect allows you to open the authenticator with parameters indicating the details of the transaction - like the smart contract address, function name, and specific arguments. Your users get the chance to see these details, and then sign and broadcast the transaction in a single click. Their transaction will be securely signed and broadcasted onto the Stacks blockchain. After this is done, a callback is fired to allow you to update your app.
## Calling Clarity Contract Functions
Once you have a Clarity smart contract built and deployed, you'll naturally want to allow your app's users to interact with it.
To initiate a contract call transaction, use the `openContractCall` function.
```ts
import { openContractCall } from '@blockstack/connect';
// While in beta, you must provide this option:
const authOrigin = 'https://deploy-preview-301--stacks-authenticator.netlify.app';
// Here's an example of options:
const myStatus = 'hey there';
const options = {
contractAddress: 'ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ',
contractName: 'status',
functionName: 'write-status!',
functionArgs: [
{
type: 'buff',
value: myStatus,
}
],
authOrigin,
appDetails: {
name: 'SuperApp',
icon: 'https://example.com/icon.png'
},
finished: (data) => {
console.log('TX ID:', data.txId);
console.log('Raw TX:', data.txRaw);
},
};
await openContractCall(opts);
```
When signing a transaction, you need to specify a few details. Here is the exact interface that describes what options you have:
When calling `openContractCall`, you need to specify a few details. Here is the exact interface that describes what options you have:
```ts
export interface ContractCallOptions {
interface ContractCallOptions {
contractAddress: string;
functionName: string;
contractName: string;
functionArgs?: any[];
authOrigin?: string;
userSession?: UserSession;
appDetails?: AuthOptions['appDetails'];
finished?: (data: FinishedTxData) => void;
appDetails: {
name: string;
icon: string;
};
finished: (data: FinishedTxData) => void;
}
```
parameter | type | default | optional | description
---|---|---|---|---
contractAddress | string | | false | The Stacks address that published this contract
contractName | string | | false | The name that was used when publishing this contract
functionName | string | | false | The name of the function you're calling. This needs to be a [public function](/core/smart/clarityRef.html#define-public).
functionArgs | array | | false | The arguments you're calling the function with. You'll need to provide the Clarity type with each argument. See the below section for details.
userSession | UserSession | | true | A `UserSession` instance
appDetails | object | | false | A dictionary that includes `name` and `icon`
finished | function | | false | A callback that is fired when the transaction is signed and broadcasted. Your callback will receive an object back with a `txId` and a `txRaw`, both of which are strings.
parameter | type | optional | description
---|---|---|---
contractAddress | string | false | The Stacks address that published this contract
contractName | string | false | The name that was used when publishing this contract
functionName | string | false | The name of the function you're calling. This needs to be a [public function](/core/smart/clarityRef.html#define-public).
functionArgs | array | false | The arguments you're calling the function with. You'll need to provide the Clarity type with each argument. See the below section for details. Defaults to `[]`.
appDetails | object | false | A dictionary that includes `name` and `icon`
finished | function | false | A callback that is fired when the transaction is signed and broadcasted. Your callback will receive an object back with a `txId` and a `txRaw`, both of which are strings.
authOrigin | string | true | The location of the authenticator. This is only necessary when developing the authenticator locally, or when using beta features. Defaults to `"https://app.blockstack.org"`.
#### Passing Clarity types with function arguments
### Passing Clarity types with function arguments
To be able to serialize your transaction properly, you need to provide the appropriate Clarity type with each argument. These types are named the same as they are in Clarity. The `value` that you pass must be a string. The types you can pass are:
@ -68,7 +108,7 @@ To be able to serialize your transaction properly, you need to provide the appro
- `int` - i.e. `"12"`
- `bool` - can be "true", "false", "0" or "1"
- `buff` - i.e. `"asdf"`
- `principal` - This can be a contract principal, or a standard principal. Examples: `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ"` or `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ.my-contract"`.
- `principal` - This can be a contract principal, or a standard principal. [Read more about principals](/core/smart/principals.html). Examples: `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ"` or `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ.my-contract"`.
Using these types, each argument is an object with the keys `type` and `value`. For example:
@ -87,40 +127,110 @@ const functionArguments = [
If you're using Typescript, these Clarity types can be imported as `ContractCallArgumentType` from `@blockstack/connect`.
### Usage in ES6 (non-React) apps
## Stacks (STX) Token Transfers
STX token transfers can be initiated with the `openSTXTransfer` function.
```ts
import { openContractCall } from '@blockstack/connect';
import { openSTXTransfer } from '@blockstack/connect';
// Here's an example of options:
const myStatus = 'hey there';
const options = {
contractAddress: 'ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ',
contractName: 'status',
functionName: 'write-status!',
functionArgs: [
{
type: 'buff',
value: myStatus,
}
],
// While in beta, you must provide this option:
const authOrigin = 'https://deploy-preview-301--stacks-authenticator.netlify.app';
openSTXTransfer({
recipient: 'ST2EB9WEQNR9P0K28D2DC352TM75YG3K0GT7V13CV',
amount: '100',
memo: 'Testing STX Transfers!',
authOrigin,
appDetails: {
name: 'SuperApp',
icon: 'https://example.com/icon.png'
},
finished: (data) => {
console.log('TX ID:', data.txId);
console.log('Raw TX:', data.txRaw);
finished: data => {
console.log(data.txId);
},
};
});
```
await openContractCall(opts);
When calling `openSTXTransfer`, you need to specify a few details. Here are the options you have:
```ts
interface STXTransferOptions {
recipient: string;
amount: string;
memo?: string;
authOrigin?: string;
appDetails: {
name: string;
icon: string;
};
finished: (data: FinishedTxData) => void;
}
```
parameter | type | optional | description
---|---|---|---
recipient | string | false | The STX Address for the recipient of this STX transfer
amount | string | false | The amount of microstacks (µSTX) to be transferred. This argument is a string to prevent floating point errors. There are 1,000,000 µSTX per STX.
memo | string | true | An optional memo to include in the transaction.
appDetails | object | false | A dictionary that includes `name` and `icon`
finished | function | false | A callback that is fired when the transaction is signed and broadcasted. Your callback will receive an object back with a `txId` and a `txRaw`, both of which are strings.
authOrigin | string | true | The location of the authenticator. This is only necessary when developing the authenticator locally, or when using beta features. Defaults to `"https://app.blockstack.org"`.
## Deploying Clarity Contracts
To allow your app's users to deploy arbitrary Clarity contracts, use the `openContractDeploy` method.
```ts
import { openContractDeploy } from '@blockstack/connect';
const codeBody = '(begin (print "hello, world"))';
// While in beta, you must provide this option:
const authOrigin = 'https://deploy-preview-301--stacks-authenticator.netlify.app';
openContractDeploy({
contractName: 'my-contract-name',
codeBody,
authOrigin,
appDetails: {
name: 'SuperApp',
icon: 'https://example.com/icon.png'
},
finished: (data) => {
console.log(data.txId);
}
})
```
### Usage in React Apps
Here is the interface for the options you can provide to `openContractDeploy`:
```ts
interface ContractDeployOptions {
codeBody: string;
contractName: string;
authOrigin?: string;
appDetails: {
name: string;
icon: string;
};
finished: (data: FinishedTxData) => void;
}
```
parameter | type | optional | description
---|---|---|---
codeBody | string | false | The Clarity source code for this contract
contractName | string | false | The name for this contract
appDetails | object | false | A dictionary that includes `name` and `icon`
finished | function | false | A callback that is fired when the transaction is signed and broadcasted. Your callback will receive an object back with a `txId` and a `txRaw`, both of which are strings.
authOrigin | string | true | The location of the authenticator. This is only necessary when developing the authenticator locally, or when using beta features. Defaults to `"https://app.blockstack.org"`.
## Usage in React Apps
Make sure you follow the [setup instructions](/develop/connect/get-started.html#in-react-apps) first. When you're using `useConnect`, you don't have to specify `appDetails` - we'll pick that up from your existing configuration.
Each transaction signing method is exposed through the `useConnect` hook, but they're prefixed with `do` instead of `open`, to remain consistent with our React action naming standards.
```tsx
import { useConnect } from '@blockstack/connect';

2
_develop/radiks-intro.md

@ -29,7 +29,7 @@ Radiks consists of a database, a pre-built server, and a client. A developer add
2. Saves a raw JSON of this encrypted data in the user's Gaia storage.
3. Stores the encrypted data on the Radiks server.
Radiks can store both public and sensitive, non-public data since all data is encrypted by default before it leaves the client. Your application can query Radiks for public data and then decrypt the sensitive information on the client. Radix servers can only return queries for unencrypted data.
Radiks can store both public and sensitive, non-public data since all data is encrypted by default before it leaves the client. Your application can query Radiks for public data and then decrypt the sensitive information on the client. Radiks servers can only return queries for unencrypted data.
## How Radiks authorizes writes

4
_includes/sign_in.md

@ -4,9 +4,9 @@ A decentralized application (DApp) and the Blockstack Browser communicate during
![](/storage/images/app-sign-in.png)
When a user chooses to **Sign in with Blockstack** on a DApp, it calls the `redirectToSignIn()` method which sends an `authRequest` to the Blockstack Browser. Blockstack passes the token in via a URL query string in the `authRequest` parameter:
When a user chooses to **Sign in with Blockstack** on a DApp, it calls the `doOpenAuth()` method which sends an `authRequest` to the Blockstack Authenticator. Blockstack passes the token in via a URL query string in the `authRequest` parameter:
`https://browser.blockstack.org/auth?authRequest=j902120cn829n1jnvoa...`
`https://app.blockstack.org/#/sign-up?authRequest=j902120cn829n1jnvoa...`
When the Blockstack Browser receives the request, it generates an (`authResponse`) token to the application using an _ephemeral transit key_ . The ephemeral transit key is just used for the particular instance of the application, in this case, to sign the `authRequest`. The application stores the ephemeral transit key during the request generation. The public portion of the transit key is passed in the `authRequest` token. The Blockstack Browser uses the public portion of the key to encrypt an _app-private key_ which is returned via the `authResponse`.

14
_ios/tutorial.md

@ -71,15 +71,15 @@ $ which npm npx
If you don't have these installed, take a moment to install or upgrade as needed.
### Install the CocoaPods 1.6.0.beta.1 dependency manager
### Install the CocoaPods 1.6.0 dependency manager
The sample application only runs on devices with iOS 11.0 or higher. You install
the Blockstack iOS SDK through the CocoaPods. Cocoapods is a dependency manager
for Swift and Objective-C Cocoa projects. It’s a simple, user friendly way to
use libraries from the community in your project.
You must use the `1.6.0.beta.1` version of CocoaPods or newer to avoid an
incapability between Cocoapods and XCode. Before starting the tutorial, confirm
You must use CocoaPods version `1.6.0` or newer to avoid an
incompatibility between Cocoapods and XCode. Before starting the tutorial, confirm
you have installed CocoaPods.
```bash
@ -500,7 +500,7 @@ Rather than have you build up your own UI in the interface builder, this section
defines the graphical elements, and their functionality will be defined in
a respective `.swift` file.
3. Within the `<viewController>` element, replace the existing `<view>` subelement with the following:
3. Within the `<viewController>` element, replace the existing `<view>` sub-element with the following:
```xml
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
@ -619,7 +619,7 @@ registred it with Blockstack and the app would be available on the Web. This
example skips this registration step and uses an example application we've
already created for you:
`https://heuristic-brown-7a88f8.netlify.com/redirect.html`
`https://heuristic-brown-7a88f8.netlify.app/redirect.html`
This web application already has a redirect in place for you. You'll reference
this application in your mobile add for now. In XCode, do the following;
@ -687,8 +687,8 @@ this application in your mobile add for now. In XCode, do the following;
} else {
print("Currently signed out so signing in.")
// Address of deployed example web app
Blockstack.shared.signIn(redirectURI: URL(string: "https://heuristic-brown-7a88f8.netlify.com/redirect.html")!,
appDomain: URL(string: "https://heuristic-brown-7a88f8.netlify.com")!) { authResult in
Blockstack.shared.signIn(redirectURI: URL(string: "https://heuristic-brown-7a88f8.netlify.app/redirect.html")!,
appDomain: URL(string: "https://heuristic-brown-7a88f8.netlify.app")!) { authResult in
switch authResult {
case .success(let userData):
print("Sign in SUCCESS", userData.profile?.name as Any)

Loading…
Cancel
Save