Browse Source

refactor: fix markdown formatting throughout, dynamic sidebar, start on includes

fix/enable-imgix
Thomas Osmonson 5 years ago
parent
commit
b38612035a
  1. 2
      babel.config.js
  2. 39
      next.config.js
  3. 21
      package.json
  4. 1368
      src/common/_data/cliRef.js
  5. 1290
      src/common/_data/cliRef.json
  6. 4
      src/common/_includes/architecture.md
  7. 112
      src/common/_includes/commandline.md
  8. 2
      src/common/constants.ts
  9. 47
      src/common/routes/get-routes.js
  10. 5
      src/common/routes/index.ts
  11. 52
      src/components/clarity-ref.tsx
  12. 45
      src/components/cli-reference.tsx
  13. 2
      src/components/code-block/components.tsx
  14. 5
      src/components/code-block/index.tsx
  15. 2
      src/components/content-wrapper.tsx
  16. 198
      src/components/docs-layout.tsx
  17. 2
      src/components/home/sections/hero.tsx
  18. 281
      src/components/layouts/docs-layout.tsx
  19. 5
      src/components/layouts/home.tsx
  20. 12
      src/components/layouts/index.tsx
  21. 6
      src/components/layouts/none.tsx
  22. 116
      src/components/mdx/components.tsx
  23. 111
      src/components/mdx/mdx-components.tsx
  24. 105
      src/components/mdx/typography.ts
  25. 48
      src/components/side-nav.tsx
  26. 40
      src/components/toc.tsx
  27. 41
      src/lib/remark-paragraph-alerts.js
  28. 2
      src/pages/404.tsx
  29. 10
      src/pages/_app.tsx
  30. 142
      src/pages/browser/todo-list.md
  31. 5
      src/pages/core/README.md
  32. 2
      src/pages/core/atlas/howitworks.md
  33. 2
      src/pages/core/atlas/overview.md
  34. 70
      src/pages/core/cmdLineRef.md
  35. 4
      src/pages/core/faq_developer.md
  36. 2
      src/pages/core/faq_technical.md
  37. 6
      src/pages/core/install-api.md
  38. 6
      src/pages/core/memcached.md
  39. 2
      src/pages/core/naming/architecture.md
  40. 8
      src/pages/core/naming/comparison.md
  41. 2
      src/pages/core/naming/creationhowto.md
  42. 2
      src/pages/core/naming/did.md
  43. 2
      src/pages/core/naming/forks.md
  44. 6
      src/pages/core/naming/introduction.md
  45. 24
      src/pages/core/naming/manage.md
  46. 4
      src/pages/core/naming/namespaces.md
  47. 56
      src/pages/core/naming/pickname.md
  48. 12
      src/pages/core/naming/register.md
  49. 6
      src/pages/core/naming/resolving.md
  50. 42
      src/pages/core/naming/search.md
  51. 4
      src/pages/core/naming/subdomains.md
  52. 60
      src/pages/core/naming/tutorial_subdomains.md
  53. 78
      src/pages/core/smart/clarityRef.md
  54. 6
      src/pages/core/smart/cli-wallet-quickstart.md
  55. 4
      src/pages/core/smart/overview.md
  56. 18
      src/pages/core/smart/principals.md
  57. 10
      src/pages/core/smart/tutorial-counter.md
  58. 2
      src/pages/core/smart/tutorial-test.md
  59. 20
      src/pages/core/smart/tutorial.md
  60. 1
      src/pages/core/subdomains.md
  61. 6
      src/pages/core/wire-format.md
  62. 57
      src/pages/develop/collections.md
  63. 8
      src/pages/develop/connect/get-started.md
  64. 20
      src/pages/develop/connect/overview.md
  65. 16
      src/pages/develop/connect/use-with-clarity.md
  66. 22
      src/pages/develop/overview_auth.md
  67. 12
      src/pages/develop/profiles.md
  68. 56
      src/pages/develop/radiks-collaborate.md
  69. 2
      src/pages/develop/radiks-intro.md
  70. 120
      src/pages/develop/radiks-models.md
  71. 12
      src/pages/develop/radiks-server-extras.md
  72. 12
      src/pages/develop/radiks-setup.md
  73. 90
      src/pages/develop/storage.md
  74. 9
      src/pages/index.tsx
  75. 6
      src/pages/ios/tutorial.md
  76. 8
      src/pages/org/wallet-install.md
  77. 2
      src/pages/org/wallet-use.md
  78. 47
      src/pages/storage/amazon-s3-deploy.md
  79. 11
      src/pages/storage/digital-ocean-deploy.md
  80. 8
      src/pages/storage/hello-hub-choice.md
  81. 2
      src/pages/storage/hub-operation.md
  82. 4
      src/pages/storage/overview.md
  83. 47
      src/pages/storage/write-to-read.md
  84. 751
      yarn.lock

2
babel.config.js

@ -1,4 +1,4 @@
module.exports = { module.exports = {
presets: ['next/babel'], presets: ['next/babel'],
plugins: [['styled-components', { ssr: true }]], plugins: ['babel-plugin-macros', ['styled-components', { ssr: true }]],
}; };

39
next.config.js

@ -2,8 +2,11 @@ const withMdxEnhanced = require('next-mdx-enhanced');
const withBundleAnalyzer = require('@next/bundle-analyzer')({ const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true', enabled: process.env.ANALYZE === 'true',
}); });
const remark = require('remark');
const strip = require('strip-markdown');
const remarkPlugins = [ const remarkPlugins = [
require('./src/lib/remark-paragraph-alerts'),
require('remark-external-links'), require('remark-external-links'),
require('remark-emoji'), require('remark-emoji'),
require('remark-images'), require('remark-images'),
@ -11,6 +14,30 @@ const remarkPlugins = [
require('remark-slug'), require('remark-slug'),
]; ];
const processMdxContent = mdxContent => {
const regex = /\n(#+)(.*)/gm;
const found = mdxContent.match(regex);
const getLevel = string => string.split('#');
const headings =
found && found.length
? found.map(f => {
const md = f.split('# ')[1];
let content = md;
remark()
.use(strip)
.process(md, (err, file) => {
if (err) throw err;
content = file.contents.toString().trim();
});
const level = getLevel(f).length;
return { content, level };
})
: [];
return {
headings,
};
};
module.exports = withBundleAnalyzer( module.exports = withBundleAnalyzer(
withMdxEnhanced({ withMdxEnhanced({
layoutPath: 'src/components/layouts', layoutPath: 'src/components/layouts',
@ -18,14 +45,7 @@ module.exports = withBundleAnalyzer(
fileExtensions: ['mdx', 'md'], fileExtensions: ['mdx', 'md'],
remarkPlugins, remarkPlugins,
extendFrontMatter: { extendFrontMatter: {
process: mdxContent => { process: processMdxContent,
const regex = /\n(#+)(.*)/gm;
const found = mdxContent.match(regex);
const headings = found && found.length ? found.map(f => f.split('# ')[1]) : [];
return {
headings,
};
},
}, },
})({ })({
experimental: { experimental: {
@ -33,9 +53,6 @@ module.exports = withBundleAnalyzer(
polyfillsOptimization: true, polyfillsOptimization: true,
jsconfigPaths: true, jsconfigPaths: true,
}, },
env: {
FATHOM_ID: 'XHIKWSQD',
},
pageExtensions: ['ts', 'tsx', 'md', 'mdx'], pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
webpack: (config, options) => { webpack: (config, options) => {
if (!options.isServer) { if (!options.isServer) {

21
package.json

@ -7,6 +7,7 @@
"@mdx-js/loader": "1.6.6", "@mdx-js/loader": "1.6.6",
"@mdx-js/mdx": "^1.6.6", "@mdx-js/mdx": "^1.6.6",
"@mdx-js/react": "^1.6.6", "@mdx-js/react": "^1.6.6",
"@mdx-js/runtime": "^1.6.7",
"@next/mdx": "^9.4.4", "@next/mdx": "^9.4.4",
"@philpl/buble": "^0.19.7", "@philpl/buble": "^0.19.7",
"@reach/tooltip": "^0.10.5", "@reach/tooltip": "^0.10.5",
@ -16,20 +17,25 @@
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/reach__tooltip": "^0.2.0", "@types/reach__tooltip": "^0.2.0",
"algoliasearch": "^4.3.0", "algoliasearch": "^4.3.0",
"babel-plugin-macros": "^2.8.0",
"docsearch.js": "^2.6.3", "docsearch.js": "^2.6.3",
"fathom-client": "^3.0.0", "fathom-client": "^3.0.0",
"front-matter": "^4.0.2",
"fs-extra": "^9.0.1",
"jsx-to-string": "^1.4.0", "jsx-to-string": "^1.4.0",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"mdi-react": "7.3.0", "mdi-react": "7.3.0",
"next": "^9.4.4", "next": "^9.4.5-canary.34",
"next-google-fonts": "^1.1.0", "next-google-fonts": "^1.1.0",
"next-mdx-enhanced": "^3.0.0", "next-mdx-enhanced": "^3.0.0",
"next-optimized-images": "^2.6.1",
"nookies": "^2.3.2", "nookies": "^2.3.2",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"preact": "^10.4.4", "preact": "^10.4.4",
"preact-render-to-string": "^5.1.4", "preact-render-to-string": "^5.1.4",
"preact-ssr-prepass": "^1.0.1", "preact-ssr-prepass": "^1.0.1",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"preval.macro": "^5.0.0",
"prism-react-renderer": "^1.0.2", "prism-react-renderer": "^1.0.2",
"prismjs": "^1.20.0", "prismjs": "^1.20.0",
"react-children-utilities": "^2.1.2", "react-children-utilities": "^2.1.2",
@ -39,15 +45,20 @@
"react-live": "^2.2.2", "react-live": "^2.2.2",
"react-simple-code-editor": "^0.11.0", "react-simple-code-editor": "^0.11.0",
"react-waypoint": "^9.0.3", "react-waypoint": "^9.0.3",
"remark": "^12.0.0",
"remark-emoji": "2.1.0", "remark-emoji": "2.1.0",
"remark-external-links": "^6.1.0", "remark-external-links": "^6.1.0",
"remark-frontmatter": "^2.0.0",
"remark-images": "2.0.0", "remark-images": "2.0.0",
"remark-slug": "6.0.0", "remark-slug": "6.0.0",
"remark-unwrap-images": "2.0.0", "remark-unwrap-images": "2.0.0",
"store": "^2.0.12", "store": "^2.0.12",
"strip-markdown": "^3.1.2",
"swr": "^0.2.3", "swr": "^0.2.3",
"touchable-hook": "^1.3.0", "touchable-hook": "^1.3.0",
"typescript": "^3.9.5", "typescript": "^3.9.5",
"unist-util-is": "^4.0.2",
"unist-util-visit": "^2.0.3",
"use-events": "^1.4.2" "use-events": "^1.4.2"
}, },
"devDependencies": { "devDependencies": {
@ -57,15 +68,17 @@
"next-transpile-modules": "^3.3.0", "next-transpile-modules": "^3.3.0",
"react": "^16.13.0", "react": "^16.13.0",
"react-dom": "^16.13.0", "react-dom": "^16.13.0",
"rimraf": "^3.0.2",
"styled-components": "^5.0.1", "styled-components": "^5.0.1",
"tsconfig-paths-webpack-plugin": "^3.2.0" "tsconfig-paths-webpack-plugin": "^3.2.0"
}, },
"private": true, "private": true,
"scripts": { "scripts": {
"build": "next telemetry disable && next build && next export -o build", "build": "yarn clean:build-files && next telemetry disable && next build && next export -o build",
"build:analyze": "next telemetry disable && ANALYZE=true next build", "build:analyze": "yarn clean:build-files && next telemetry disable && ANALYZE=true next build",
"start": "next telemetry disable && NODE_ENV=production next start", "start": "next telemetry disable && NODE_ENV=production next start",
"dev": "next dev", "clean:build-files": "rimraf .next",
"dev": "yarn clean:build-files && next dev",
"export": "next export", "export": "next export",
"lint": "yarn lint:eslint && yarn lint:prettier", "lint": "yarn lint:eslint && yarn lint:prettier",
"lint:eslint": "eslint \"src/**/*.{ts,tsx}\" -f unix", "lint:eslint": "eslint \"src/**/*.{ts,tsx}\" -f unix",

1368
src/common/_data/cliRef.js

File diff suppressed because it is too large

1290
src/common/_data/cliRef.json

File diff suppressed because it is too large

4
src/common/_includes/architecture.md

@ -1,3 +1,7 @@
---
layout: none
---
![Blockstack Architecture](/common/images/architecture.png) ![Blockstack Architecture](/common/images/architecture.png)
Blockchains require consensus among large numbers of people, so they can be slow. Additionally, a blockchain is not designed to hold a lot of data. This means using a blockchain for every bit of data a user might write and store is expensive. For example, imagine if an application were storing every tweet in the chain. Blockchains require consensus among large numbers of people, so they can be slow. Additionally, a blockchain is not designed to hold a lot of data. This means using a blockchain for every bit of data a user might write and store is expensive. For example, imagine if an application were storing every tweet in the chain.

112
src/common/_includes/commandline.md

@ -1,112 +0,0 @@
The command line is intended for developers only. Developers can use the command
line to test and debug Blockstack applications in ways that the Blockstack
Browser does not yet support. Using the command line, developers can:
- Generate and Broadcast all supported types of Blockstack transactions
- Load, store, and list data in Gaia hubs
- Generate owner, payment and application keys from a seed phrase
- Query Stacks Nodes
- Implement a minimum viable authentication flow
{% include warning.html content="Many of the commands operate on unencrypted
private keys. For this reason, DO NOT use this tool for day-to-day tasks as you
risk the security of your keys." %}
You must <a href="#installCommandLine">install the command line</a> before you
can use the commands.
## List of commands
{:.no_toc}
To see the usage and options for the command in general, enter `blockstack-cli` without any subcommands. To see a list of subcommands enter `blockstack-cli help`. Enter `blockstack-cli SUBCOMMAND_NAME help` to see a subcommand with its usage. The following are the available subcommands:
- TOC
{:toc}
{% for entry in site.data.cliRef %}
## {{ entry.command }}
**Group**: {{ entry.group }}
{{ entry.usage }}
### Arguments
{:.no_toc}
<table class="uk-table uk-table-small uk-table-striped">
<tr>
<th>Name</th>
<th>Type</th>
<th>Value</th>
<th>Format</th>
</tr>
{% for arg in entry.args %}
<tr>
<td class="uk-text-bold">{{ arg.name }}</td>
<td>{{ arg.type }}</td>
<td>{{ arg.value }}</td>
<td><code style="font-size:10px">{{ arg.format }}</code></td>
</tr>
{% endfor %}
</table>
{% endfor %}
<p><a name="installCommandLine">&nbsp;</a></p>
## How to install the command line
{:.no_toc}
You must have [Node.js](https://nodejs.org/en/download/) v8 or higher (v10 recommended). macOS and Linux users can avoid `sudo` or [permissions problems](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally) or by using [`nvm`](https://github.com/nvm-sh/nvm). These instructions assume you are using a macOS or Linux system.
To install the command line, do the following:
1. <a href="https://github.com/blockstack/cli-blockstack" target="\_blank">Download or `git clone` the command line repository code</a>.
Downloading or cloning the repo creates a `cli-blockstack` repository on your system.
2. Change directory into the `cli-blockstack` directory.
```
cd cli-blockstack
```
```
3. Install the dependencies with `npm`.
```
npm install
```
4. Build the command line command.
```
npm run build
```
5. Link the command.
```
sudo npm link
```
### Troubleshooting the CLI installation
If you run into `EACCES` permissions errors, try the following:
* See https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally.
* Use [`Node Version Manager`](https://github.com/nvm-sh/nvm).
```

2
src/common/constants.ts

@ -1,3 +1,3 @@
export const SIDEBAR_WIDTH = 280; export const SIDEBAR_WIDTH = 280;
export const TOC_WIDTH = 200; export const TOC_WIDTH = 280;
export const CONTENT_MAX_WIDTH = 1104; export const CONTENT_MAX_WIDTH = 1104;

47
src/common/routes.ts → src/common/routes/get-routes.js

@ -1,4 +1,17 @@
export const routes = [ /**
* Routes
*
* This file contains our paths for all of our markdown files and is pre evaluated at runtime to get the content
* from all the markdown: front matter and extracts all the headings from the document.
*
* This data is used to dynamically generate the sidenav.
*
*/
const fm = require('front-matter');
const fs = require('fs-extra');
const path = require('path');
const sections = [
{ {
title: 'Authentication', title: 'Authentication',
routes: [ routes: [
@ -14,7 +27,7 @@ export const routes = [
{ path: 'develop/storage' }, { path: 'develop/storage' },
{ path: 'storage/authentication' }, { path: 'storage/authentication' },
{ path: 'storage/write-to-read' }, { path: 'storage/write-to-read' },
{ path: 'storage/hub-choice' }, // { path: 'storage/hub-choice' },
], ],
}, },
{ {
@ -24,7 +37,7 @@ export const routes = [
{ path: 'develop/storage' }, { path: 'develop/storage' },
{ path: 'storage/authentication' }, { path: 'storage/authentication' },
{ path: 'storage/write-to-read' }, { path: 'storage/write-to-read' },
{ path: 'storage/hub-choice' }, // { path: 'storage/hub-choice' },
], ],
}, },
{ {
@ -124,3 +137,31 @@ export const routes = [
], ],
}, },
]; ];
const getHeadings = mdContent => {
const regex = /(#+)(.*)/gm;
const found = mdContent.match(regex);
return found && found.length
? found.map(f => f && f.split('# ')[1]).filter(f => typeof f !== 'undefined')
: undefined;
};
const routes = sections.map(section => {
const _routes = section.routes.map(route => {
const fileContent = fs.readFileSync(path.join('./src/pages/', route.path + '.md'), 'utf8');
const data = fm(fileContent);
const headings = getHeadings(data.body);
return {
...route,
...data.attributes,
headings,
};
});
return {
...section,
routes: _routes,
directory: process.cwd(),
};
});
module.exports = routes || [];

5
src/common/routes/index.ts

@ -0,0 +1,5 @@
import preval from 'preval.macro';
const routes = preval`module.exports = require('./get-routes')`;
export default routes;

52
src/components/clarity-ref.tsx

@ -0,0 +1,52 @@
import React from 'react';
import clarityRefData from '@common/_data/clarityRef.json';
import MDX from '@mdx-js/runtime';
import { MDXComponents } from '@components/mdx/mdx-components';
import { slugify } from '@common/utils';
export const ClarityKeywordReference = () =>
clarityRefData.keywords.map((entry, key) => {
return (
<React.Fragment key={key}>
<MDXComponents.h3 id={slugify(entry.name)}>{entry.name}</MDXComponents.h3>
<MDXComponents.p>
<MDXComponents.inlineCode>{entry.output_type}</MDXComponents.inlineCode>
</MDXComponents.p>
<MDXComponents.p>
<MDX components={MDXComponents}>{entry.description}</MDX>
</MDXComponents.p>
<MDXComponents.h4 id={slugify(entry.name) + '-example'}>Example</MDXComponents.h4>
<MDXComponents.pre>
{/* @ts-ignore*/}
<MDXComponents.code children={entry.example.toString()} />
</MDXComponents.pre>
</React.Fragment>
);
});
export const ClarityFunctionReference = () =>
clarityRefData.functions.map((entry, key) => {
return (
<React.Fragment key={key}>
<MDXComponents.h3 id={slugify(entry.name)}>{entry.name}</MDXComponents.h3>
<MDXComponents.p>
<MDXComponents.inlineCode>{entry.signature}</MDXComponents.inlineCode>
</MDXComponents.p>
<MDXComponents.p>
<strong>INPUT:</strong>{' '}
<MDXComponents.inlineCode>{entry.input_type}</MDXComponents.inlineCode>
</MDXComponents.p>
<MDXComponents.p>
<strong>OUTPUT:</strong>{' '}
<MDXComponents.inlineCode>{entry.output_type}</MDXComponents.inlineCode>
</MDXComponents.p>
<MDXComponents.p>
<MDX components={MDXComponents}>{entry.description}</MDX>
</MDXComponents.p>
<MDXComponents.h4 id={slugify(entry.name) + '-example'}>Example</MDXComponents.h4>
<MDXComponents.pre>
{/* @ts-ignore*/}
<MDXComponents.code children={entry.example.toString() as string} />
</MDXComponents.pre>
</React.Fragment>
);
});

45
src/components/cli-reference.tsx

@ -0,0 +1,45 @@
import React from 'react';
import { cliReferenceData } from '@common/_data/cliRef';
import { H2, P } from '@components/mdx/mdx-components';
import { Text } from '@components/typography';
import MDX from '@mdx-js/runtime';
import { MDXComponents } from '@components/mdx/mdx-components';
export const CLIReferenceTable = () => {
return (
<>
{cliReferenceData.map((entry, key) => {
return (
<React.Fragment key={key}>
<H2>{entry.command}</H2>
<P>
<Text fontWeight="bold">Group</Text>: {entry.group}
</P>
<MDX components={MDXComponents}>{entry.usage}</MDX>
<MDXComponents.h3>Arguments</MDXComponents.h3>
<MDXComponents.table>
<tr>
<MDXComponents.th>Name</MDXComponents.th>
<MDXComponents.th>Type</MDXComponents.th>
<MDXComponents.th>Value</MDXComponents.th>
<MDXComponents.th>Format</MDXComponents.th>
</tr>
{entry.args.map((arg, argKey) => (
<tr key={argKey}>
<MDXComponents.td>{arg.name}</MDXComponents.td>
<MDXComponents.td>{arg.type}</MDXComponents.td>
<MDXComponents.td>{arg.value}</MDXComponents.td>
<MDXComponents.td>
<MDXComponents.inlineCode>{arg.format}</MDXComponents.inlineCode>
</MDXComponents.td>
</tr>
))}
</MDXComponents.table>
</React.Fragment>
);
})}
</>
);
};

2
src/components/code-block/components.tsx

@ -9,10 +9,8 @@ import {
useClipboard, useClipboard,
BoxProps, BoxProps,
} from '@blockstack/ui'; } from '@blockstack/ui';
import 'prismjs/components/prism-jsx';
import { CodeEditor } from '@components/code-editor'; import { CodeEditor } from '@components/code-editor';
import { Text } from '@components/typography'; import { Text } from '@components/typography';
import 'prismjs/components/prism-tsx';
import { border } from '@common/utils'; import { border } from '@common/utils';
const Error = () => { const Error = () => {

5
src/components/code-block/index.tsx

@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-css'; import 'prismjs/components/prism-css';
import 'prismjs/components/prism-jsx'; import 'prismjs/components/prism-jsx';
import 'prismjs/components/prism-tsx'; import 'prismjs/components/prism-tsx';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-toml';
import 'prismjs/components/prism-python';
import { SimpleCodeBlock } from '@components/code-block/components'; import { SimpleCodeBlock } from '@components/code-block/components';
import { useForceUpdate } from '@blockstack/ui'; import { useForceUpdate } from '@blockstack/ui';

2
src/components/content-wrapper.tsx

@ -3,7 +3,7 @@ import { Flex, FlexProps, space } from '@blockstack/ui';
const ContentWrapper: React.FC<FlexProps> = props => ( const ContentWrapper: React.FC<FlexProps> = props => (
<Flex <Flex
flexShrink={1} flexShrink={0}
px={space('base')} px={space('base')}
pt={space(['base', 'base', 'extra-loose'])} pt={space(['base', 'base', 'extra-loose'])}
mt={space('extra-loose')} mt={space('extra-loose')}

198
src/components/docs-layout.tsx

@ -1,198 +0,0 @@
import React from 'react';
import { Flex, color, space } from '@blockstack/ui';
import { SideNav } from './side-nav';
import { Header } from './header';
import { Main } from './main';
import { Footer } from './footer';
import { useRouter } from 'next/router';
import { ContentWrapper } from './content-wrapper';
import NotFoundPage from '@pages/404';
import { createGlobalStyle } from 'styled-components';
import { TableOfContents } from '@components/toc';
import { css } from '@styled-system/css';
import { SIDEBAR_WIDTH, TOC_WIDTH } from '@common/constants';
export const MdxOverrides = createGlobalStyle`
.DocSearch-Container{
z-index: 99999;
}
:root{
--docsearch-modal-background: ${color('bg')};
--docsearch-primary-color-R: 84;
--docsearch-primary-color-G: 104;
--docsearch-primary-color-B: 255;
--docsearch-primary-color: ${color('accent')};
--docsearch-input-color: ${color('text-title')};
--docsearch-highlight-color: var(--docsearch-primary-color);
--docsearch-placeholder-color: ${color('text-caption')};
--docsearch-container-background: rgba(22,22,22,0.75);
--docsearch-modal-shadow: inset 1px 1px 0px 0px hsla(0,0%,100%,0.5),0px 3px 8px 0px #555a64;
--docsearch-searchbox-background: var(--ifm-color-emphasis-300);
--docsearch-searchbox-focus-background: #fff;
--docsearch-searchbox-shadow: inset 0px 0px 0px 2px rgba(var(--docsearch-primary-color-R),var(--docsearch-primary-color-G),var(--docsearch-primary-color-B),0.5);
--docsearch-hit-color: var(--ifm-color-emphasis-800);
--docsearch-hit-active-color: #fff;
--docsearch-hit-background: #fff;
--docsearch-hit-shadow: 0px 1px 3px 0px #d4d9e1;
--docsearch-key-gradient: linear-gradient(-225deg,#d5dbe4,#f8f8f8);
--docsearch-key-shadow: inset 0px -2px 0px 0px #cdcde6,inset 0px 0px 1px 1px #fff,0px 1px 2px 1px rgba(30,35,90,0.4);
--docsearch-footer-background: #fff;
--docsearch-footer-shadow: 0px -1px 0px 0px #e0e3e8;
--docsearch-logo-color: #5468ff;
--docsearch-muted-color: #969faf;
--docsearch-modal-width: 560px;
--docsearch-modal-height: 600px;
--docsearch-searchbox-height: 56px;
--docsearch-hit-height: 56px;
--docsearch-footer-height: 44px;
--docsearch-spacing: 12px;
--docsearch-icon-stroke-width: 1.4;
}
pre{
display: inline-block;
}
p, ul, ol, table {
color: ${color('text-body')};
a > pre {
color: ${color('accent')} !important;
}
}
`;
const DocsLayout: React.FC<{ headings?: string[] }> = ({ children, headings }) => {
const router = useRouter();
let isErrorPage = false;
// get if NotFoundPage
React.Children.forEach(children, (child: any) => {
if (child?.type === NotFoundPage) {
isErrorPage = true;
}
});
return (
<Flex minHeight="100vh" flexDirection="column">
<Header />
<Flex width="100%" flexGrow={1}>
<SideNav display={['none', 'none', 'block']} />
<Flex
flexGrow={1}
maxWidth={[
'100%',
'100%',
`calc(100% - ${SIDEBAR_WIDTH}px)`,
`calc(100% - ${SIDEBAR_WIDTH}px)`,
]}
mt={'50px'}
flexDirection="column"
>
<Main mx="unset" width={'100%'}>
<Flex
flexDirection={['column', 'column', 'row', 'row']}
maxWidth="108ch"
mx="auto"
flexGrow={1}
>
<ContentWrapper
width={
headings?.length
? ['100%', '100%', `calc(100% - ${TOC_WIDTH}px)`, `calc(100% - ${TOC_WIDTH}px)`]
: '100%'
}
mx="unset"
px="unset"
pt="unset"
pr={space('base-tight')}
css={css({
'& > *:not(pre):not(ul):not(ol)': {
px: space('extra-loose'),
},
'& > ul, & > ol': {
pr: space('extra-loose'),
pl: '64px ',
},
p: {
'& + p': {
mt: space('extra-loose'),
},
},
'& > *:not(pre) a > code': {
color: color('accent'),
'&:hover': {
textDecoration: 'underline',
},
},
pre: {
'& + h2': {
mt: space('base'),
},
'& + h3': {
mt: space('base'),
},
},
h1: {
'& + *': {
mt: space('base-loose'),
},
},
h2: {
'& + h3': {
mt: 0,
},
},
'h1, h2, h3, h4, h5, h6': {
'& + pre': {
mt: '0',
},
'& + ul, & + ol': {
mt: '0',
},
'& + blockquote': {
mt: '0',
},
},
blockquote: {
'& + pre': {
mt: '0',
},
'& + h2': {
mt: '0',
},
},
'& > pre > *:not(pre)': {
border: 'none',
px: space(['extra-loose', 'extra-loose', 'none', 'none']),
},
'& > pre > div[style]': {
px: space(['base-loose', 'base-loose', 'none', 'none']),
},
'& > pre > .code-editor': {
pl: space(['base', 'base', 'none', 'none']),
},
'& > pre': {
px: space(['none', 'none', 'extra-loose', 'extra-loose']),
border: 'none',
boxShadow: 'none',
my: space('extra-loose'),
},
'li pre': {
display: 'block',
my: space('base'),
},
})}
>
{children}
</ContentWrapper>
{headings?.length && headings.length > 1 ? (
<TableOfContents headings={headings} />
) : null}
</Flex>
</Main>
<Footer justifySelf="flex-end" />
</Flex>
</Flex>
</Flex>
);
};
export { DocsLayout };

2
src/components/home/sections/hero.tsx

@ -11,7 +11,7 @@ import { Body, H1, BodyLarge, SubHeading } from '@components/home/text';
export const Hero = ({ cards }: { cards?: any }) => { export const Hero = ({ cards }: { cards?: any }) => {
return ( return (
<> <>
<Grid pb="64px" pt="128px" style={{ placeItems: 'center' }} mt="50px"> <Grid pb="64px" pt="64px" style={{ placeItems: 'center' }} mt="50px">
<Box maxWidth="62ch" textAlign="center"> <Box maxWidth="62ch" textAlign="center">
<H1 mb={space('base')}>Easily build decentralized apps</H1> <H1 mb={space('base')}>Easily build decentralized apps</H1>
<BodyLarge maxWidth="42ch" mt="64px" mx="auto"> <BodyLarge maxWidth="42ch" mt="64px" mx="auto">

281
src/components/layouts/docs-layout.tsx

@ -0,0 +1,281 @@
import React from 'react';
import { Flex, color, space } from '@blockstack/ui';
import { SideNav } from '../side-nav';
import { Header } from '../header';
import { Main } from '../main';
import { Footer } from '../footer';
import { useRouter } from 'next/router';
import { ContentWrapper } from '../content-wrapper';
import NotFoundPage from '@pages/404';
import { createGlobalStyle } from 'styled-components';
import { TableOfContents } from '@components/toc';
import { getHeadingStyles } from '@components/mdx/typography';
import { css } from '@styled-system/css';
import { SIDEBAR_WIDTH, TOC_WIDTH } from '@common/constants';
export const MdxOverrides = createGlobalStyle`
.DocSearch-Container{
z-index: 99999;
}
:root{
--docsearch-modal-background: ${color('bg')};
--docsearch-primary-color-R: 84;
--docsearch-primary-color-G: 104;
--docsearch-primary-color-B: 255;
--docsearch-primary-color: ${color('accent')};
--docsearch-input-color: ${color('text-title')};
--docsearch-highlight-color: var(--docsearch-primary-color);
--docsearch-placeholder-color: ${color('text-caption')};
--docsearch-container-background: rgba(22,22,22,0.75);
--docsearch-modal-shadow: inset 1px 1px 0px 0px hsla(0,0%,100%,0.5),0px 3px 8px 0px #555a64;
--docsearch-searchbox-background: var(--ifm-color-emphasis-300);
--docsearch-searchbox-focus-background: #fff;
--docsearch-searchbox-shadow: inset 0px 0px 0px 2px rgba(var(--docsearch-primary-color-R),var(--docsearch-primary-color-G),var(--docsearch-primary-color-B),0.5);
--docsearch-hit-color: var(--ifm-color-emphasis-800);
--docsearch-hit-active-color: #fff;
--docsearch-hit-background: #fff;
--docsearch-hit-shadow: 0px 1px 3px 0px #d4d9e1;
--docsearch-key-gradient: linear-gradient(-225deg,#d5dbe4,#f8f8f8);
--docsearch-key-shadow: inset 0px -2px 0px 0px #cdcde6,inset 0px 0px 1px 1px #fff,0px 1px 2px 1px rgba(30,35,90,0.4);
--docsearch-footer-background: #fff;
--docsearch-footer-shadow: 0px -1px 0px 0px #e0e3e8;
--docsearch-logo-color: #5468ff;
--docsearch-muted-color: #969faf;
--docsearch-modal-width: 560px;
--docsearch-modal-height: 600px;
--docsearch-searchbox-height: 56px;
--docsearch-hit-height: 56px;
--docsearch-footer-height: 44px;
--docsearch-spacing: 12px;
--docsearch-icon-stroke-width: 1.4;
}
pre{
display: inline-block;
}
p, ul, ol, table {
color: ${color('text-body')};
a > pre {
color: ${color('accent')} !important;
}
}
`;
const styleOverwrites = {
'& > *:not(pre):not(ul):not(ol):not(img)': {
px: space('extra-loose'),
},
'& > ul, & > ol': {
pr: space('extra-loose'),
pl: '64px ',
},
p: {
'& + p': {
mt: space('extra-loose'),
},
'&, ol, ul': {
'& code': {
fontSize: '14px',
},
'&, & > *': {
display: 'inline-block',
fontSize: '16.5px',
lineHeight: '28px',
':before': {
content: "''",
marginTop: '-0.4878787878787879em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.4878787878787879em',
display: 'block',
height: 0,
},
},
},
},
'& > *:not(pre) a > code': {
color: color('accent'),
'&:hover': {
textDecoration: 'underline',
},
},
pre: {
'& + h2, & + h3': {
mt: space('extra-loose'),
},
'& + h4, & + h5, & + h6': {
mt: 0,
},
},
h2: {
mt: '64px',
'&, & > *': {
...getHeadingStyles('h2'),
},
'& > code': {
px: space('tight'),
py: space('extra-tight'),
mr: '2px',
fontSize: '22px',
},
'& + h3': {
mt: 0,
},
},
h3: {
mt: '64px',
'&, & > *': {
...getHeadingStyles('h3'),
},
'& + h4': {
mt: 0,
},
},
h4: {
mt: space('extra-loose'),
'&, & > *': {
...getHeadingStyles('h4'),
},
'& + h5': {
mt: 0,
},
},
h5: {
mt: space('extra-loose'),
'&, & > *': {
...getHeadingStyles('h5'),
},
},
h6: {
mt: space('extra-loose'),
'&, & > *': {
...getHeadingStyles('h6'),
},
},
h1: {
mt: space('extra-loose'),
'&, & > *': {
...getHeadingStyles('h1'),
},
'& + *': {
mt: space('extra-loose'),
},
},
'h1, h2, h3, h4, h5, h6': {
mb: space('extra-loose'),
'& + pre': {
mt: '0',
},
'& + ul, & + ol': {
mt: '0',
},
'& + blockquote': {
mt: '0',
},
},
'ol, ul': {
'& + blockquote': {
mt: space('extra-tight'),
},
},
blockquote: {
'& > div > div > *:first-child': {
mt: 0,
},
'& + pre': {
mt: '0',
},
'& + h2': {
mt: '0',
},
},
img: {
my: space('extra-loose'),
},
'& > pre > *:not(pre)': {
border: 'none',
px: space(['extra-loose', 'extra-loose', 'none', 'none']),
},
'& > pre > div[style]': {
px: space(['base-loose', 'base-loose', 'none', 'none']),
},
'& > pre > .code-editor': {
pl: space(['base', 'base', 'none', 'none']),
},
'& > pre': {
px: space(['none', 'none', 'extra-loose', 'extra-loose']),
border: 'none',
boxShadow: 'none',
my: space('extra-loose'),
},
'li pre': {
display: 'block',
my: space('base'),
},
};
export const Contents = ({ headings, children }) => (
<>
<ContentWrapper
width={
headings?.length
? ['100%', '100%', `calc(100% - ${TOC_WIDTH}px)`, `calc(100% - ${TOC_WIDTH}px)`]
: '100%'
}
mx="unset"
px="unset"
pt="unset"
pr={space('base-tight')}
css={css(styleOverwrites)}
>
{children}
</ContentWrapper>
{headings?.length && headings.length > 1 ? <TableOfContents headings={headings} /> : null}
</>
);
const DocsLayout: React.FC<{ isHome?: boolean }> = ({ children, isHome }) => {
let isErrorPage = false;
// get if NotFoundPage
React.Children.forEach(children, (child: any) => {
if (child?.type === NotFoundPage) {
isErrorPage = true;
}
});
return (
<Flex minHeight="100vh" flexDirection="column">
<Header />
<Flex width="100%" flexGrow={1}>
{!isHome && <SideNav display={['none', 'none', 'block']} />}
<Flex
flexGrow={1}
maxWidth={[
'100%',
'100%',
`calc(100% - ${isHome ? 0 : SIDEBAR_WIDTH}px)`,
`calc(100% - ${isHome ? 0 : SIDEBAR_WIDTH}px)`,
]}
mt={'50px'}
flexDirection="column"
>
<Main mx="unset" width={'100%'}>
<Flex
flexDirection={['column', 'column', 'row', 'row']}
maxWidth="108ch"
mx="auto"
flexGrow={1}
>
{children}
</Flex>
</Main>
<Footer justifySelf="flex-end" />
</Flex>
</Flex>
</Flex>
);
};
export { DocsLayout };

5
src/components/layouts/home.tsx

@ -1,16 +1,11 @@
import React from 'react'; import React from 'react';
import { Flex, color, space } from '@blockstack/ui';
import { Main } from '@components/main'; import { Main } from '@components/main';
import { Header } from '@components/header';
const HomeLayout: React.FC<any> = ({ children }) => { const HomeLayout: React.FC<any> = ({ children }) => {
return ( return (
<Flex minHeight="100vh" flexDirection="column">
<Header />
<Main mx="unset" width={'100%'}> <Main mx="unset" width={'100%'}>
{children} {children}
</Main> </Main>
</Flex>
); );
}; };

12
src/components/layouts/index.tsx

@ -1,22 +1,22 @@
import React from 'react'; import React from 'react';
import { DocsLayout } from '@components/docs-layout'; import { Contents } from '@components/layouts/docs-layout';
import Head from 'next/head'; import Head from 'next/head';
export default function Layout(frontMatter) { const Layout = frontMatter => props => {
const { const {
title, title,
description = 'The Blockstack design system, built with React and styled-system.', description = 'The Blockstack design system, built with React and styled-system.',
headings, headings,
} = frontMatter; } = frontMatter;
return ({ children }) => {
return ( return (
<> <>
<Head> <Head>
<title>{title || headings[0]} | Blockstack Docs</title> <title>{title || headings[0].content} | Blockstack Docs</title>
<meta name="description" content={description} /> <meta name="description" content={description} />
</Head> </Head>
<DocsLayout headings={headings}>{children}</DocsLayout> <Contents headings={headings}>{props.children}</Contents>
</> </>
); );
}; };
}
export default Layout;

6
src/components/layouts/none.tsx

@ -0,0 +1,6 @@
import React from 'react';
export default function Layout(frontMatter) {
return ({ children }) => {
return <>{children}</>;
};
}

116
src/components/mdx/components.tsx

@ -1,4 +1,4 @@
import { Box, FlexProps, BoxProps, color, useClipboard, space } from '@blockstack/ui'; import { Box, Flex, FlexProps, BoxProps, color, useClipboard, space } from '@blockstack/ui';
import NextLink from 'next/link'; import NextLink from 'next/link';
import React, { forwardRef, Ref } from 'react'; import React, { forwardRef, Ref } from 'react';
import LinkIcon from 'mdi-react/LinkVariantIcon'; import LinkIcon from 'mdi-react/LinkVariantIcon';
@ -9,7 +9,7 @@ import { useActiveHeading } from '@common/hooks/use-active-heading';
import { Text, Title } from '@components/typography'; import { Text, Title } from '@components/typography';
import { border } from '@common/utils'; import { border } from '@common/utils';
import { css } from '@styled-system/css'; import { css } from '@styled-system/css';
import { useRouter } from 'next/router'; import { getHeadingStyles, baseTypeStyles } from '@components/mdx/typography';
const preProps = { const preProps = {
display: 'inline-block', display: 'inline-block',
@ -160,112 +160,6 @@ export const TextItem = (props: any) => (
</Text> </Text>
); );
const baseStyles = {
letterSpacing: '-0.01em',
dispay: 'flex',
fontFeatureSettings: `'ss01' on`,
};
const h1 = {
fontWeight: 'bolder',
fontSize: '44px',
lineHeight: '52px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.2284090909090909em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.22840909090909092em',
display: 'block',
height: 0,
},
};
const h2 = {
fontWeight: 600,
fontSize: '27.5px',
lineHeight: '34px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.25636363636363635em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.2563636363636364em',
display: 'block',
height: 0,
},
};
const h3 = {
fontWeight: 500,
fontSize: '22px',
lineHeight: '32px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.3659090909090909em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.3659090909090909em',
display: 'block',
height: 0,
},
};
const h4 = {
fontSize: '19.25px',
lineHeight: '28px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.36623376623376624em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.36623376623376624em',
display: 'block',
height: 0,
},
};
const h5 = {
fontSize: '16px',
lineHeight: '28px',
fontWeight: 'bold',
};
const h6 = {
fontSize: '14px',
lineHeight: '28px',
fontWeight: 'bold',
};
const headings = {
h1,
h2,
h3,
h4,
h5,
h6,
};
const getHeadingStyles = (as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6') => {
return {
...headings[as],
};
};
const LinkButton = React.memo(({ link, onClick, ...rest }: BoxProps & { link: string }) => { const LinkButton = React.memo(({ link, onClick, ...rest }: BoxProps & { link: string }) => {
const url = const url =
typeof document !== 'undefined' && document.location.origin + document.location.pathname + link; typeof document !== 'undefined' && document.location.origin + document.location.pathname + link;
@ -335,7 +229,7 @@ export const Heading = ({ as, children, id, ...rest }: FlexProps) => {
as={as} as={as}
{...bind} {...bind}
css={css({ css={css({
...baseStyles, ...baseTypeStyles,
...styles, ...styles,
color: isActive ? color('accent') : (color('text-title') as any), color: isActive ? color('accent') : (color('text-title') as any),
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
@ -347,10 +241,12 @@ export const Heading = ({ as, children, id, ...rest }: FlexProps) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore // @ts-ignore
display: 'flex', display: 'flex',
...rest, justifyContent: 'flex-start',
})} })}
> >
<Box as="span" display="inline-block">
{children} {children}
</Box>
<AnchorOffset id={id} /> <AnchorOffset id={id} />
{isActive && <Hashtag />} {isActive && <Hashtag />}
<LinkButton opacity={isHovered ? 1 : 0} onClick={handleLinkClick} link={link} /> <LinkButton opacity={isHovered ? 1 : 0} onClick={handleLinkClick} link={link} />

111
src/components/mdx/mdx-components.tsx

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Box, space, BoxProps, color } from '@blockstack/ui'; import { Box, space, BoxProps, color, themeColor } from '@blockstack/ui';
import css from '@styled-system/css'; import css from '@styled-system/css';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { import {
@ -20,25 +20,15 @@ const BaseHeading: React.FC<BoxProps> = React.memo(props => (
<Heading width="100%" mt={space('base-loose')} {...props} /> <Heading width="100%" mt={space('base-loose')} {...props} />
)); ));
const H1: React.FC<BoxProps> = props => <BaseHeading as="h1" mb={space('loose')} {...props} />; export const H1: React.FC<BoxProps> = props => <BaseHeading as="h1" {...props} />;
const H2: React.FC<BoxProps> = props => ( export const H2: React.FC<BoxProps> = props => <BaseHeading as="h2" {...props} />;
<BaseHeading as="h2" mt={'48px'} mb={space('loose')} {...props} /> export const H3: React.FC<BoxProps> = props => <BaseHeading as="h3" {...props} />;
); export const H4: React.FC<BoxProps> = props => <BaseHeading as="h4" {...props} />;
const H3: React.FC<BoxProps> = props => ( export const H5: React.FC<BoxProps> = props => <BaseHeading as="h5" {...props} />;
<BaseHeading as="h3" mt={space('extra-loose')} mb={space('loose')} {...props} /> export const H6: React.FC<BoxProps> = props => <BaseHeading as="h6" {...props} />;
);
const H4: React.FC<BoxProps> = props => (
<BaseHeading as="h4" mt={space('extra-loose')} mb={space('loose')} {...props} />
);
const H5: React.FC<BoxProps> = props => (
<BaseHeading as="h5" mt={space('extra-loose')} mb={space('loose')} {...props} />
);
const H6: React.FC<BoxProps> = props => (
<BaseHeading as="h6" mt={space('extra-loose')} mb={space('loose')} {...props} />
);
const Br: React.FC<BoxProps> = props => <Box height="24px" {...props} />; export const Br: React.FC<BoxProps> = props => <Box height="24px" {...props} />;
const Hr: React.FC<BoxProps> = props => ( export const Hr: React.FC<BoxProps> = props => (
<Box <Box
as="hr" as="hr"
borderTopWidth="1px" borderTopWidth="1px"
@ -49,63 +39,74 @@ const Hr: React.FC<BoxProps> = props => (
/> />
); );
const P: React.FC<BoxProps> = props => ( export const P: React.FC<BoxProps> = props => <Text as="p" {...props} />;
<Text export const Ol: React.FC<BoxProps> = props => (
as="p"
css={css({
display: 'block',
fontSize: '16.5px',
lineHeight: '28px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.4878787878787879em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.4878787878787879em',
display: 'block',
height: 0,
},
})}
{...props}
/>
);
const Ol: React.FC<BoxProps> = props => (
<Box pl={space('base')} mt={space('base')} mb={space('base-tight')} as="ol" {...props} /> <Box pl={space('base')} mt={space('base')} mb={space('base-tight')} as="ol" {...props} />
); );
const Ul: React.FC<BoxProps> = props => ( export const Ul: React.FC<BoxProps> = props => (
<Box pl={space('base-loose')} mt={space('base')} mb={space('base-tight')} as="ul" {...props} /> <Box pl={space('base-loose')} mt={space('base')} mb={space('base-tight')} as="ul" {...props} />
); );
const Li: React.FC<BoxProps> = props => <Box as="li" pb={space('tight')} {...props} />; export const Li: React.FC<BoxProps> = props => <Box as="li" pb={space('tight')} {...props} />;
const BlockQuote: React.FC<BoxProps> = ({ children, ...rest }) => (
<Box as="blockquote" display="block" my={space('extra-loose')} {...rest}> const getAlertStyles = (className: string) => {
if (className?.includes('alert-success')) {
return {};
}
if (className?.includes('alert-info')) {
return {};
}
if (className?.includes('alert-warning')) {
return {
bg: '#FEF0E3',
borderColor: '#F7AA00',
accent: '#F7AA00',
};
}
if (className?.includes('alert-danger')) {
return {
bg: '#FCEBEC',
borderColor: themeColor('red'),
accent: themeColor('red'),
};
}
return {};
};
export const BlockQuote: React.FC<BoxProps> = ({ children, className, ...rest }) => {
const { accent, ...styles } = getAlertStyles(className);
return (
<Box as="blockquote" display="block" my={space('extra-loose')} className={className} {...rest}>
<Box <Box
border="1px solid" border="1px solid"
css={css({ css={css({
border: border(), border: border(),
borderRadius: 'md', borderRadius: 'md',
boxShadow: 'mid', boxShadow: 'mid',
py: space('base-tight'), py: space('base'),
px: space('base'), px: space('base'),
bg: color('bg-light'), bg: color('bg-light'),
'> *:first-of-type': { ...styles,
})}
>
<Box
css={css({
marginTop: 0, marginTop: 0,
borderLeft: '4px solid', py: space('base-tight'),
borderColor: color('accent'), borderLeft: '2px solid',
borderRadius: '2px',
borderColor: accent || color('accent'),
pl: space('base'), pl: space('base'),
},
})} })}
> >
{children} {children}
</Box> </Box>
</Box> </Box>
</Box>
); );
};
const Img: React.FC<BoxProps> = props => ( export const Img: React.FC<BoxProps> = props => (
<Box display="block" mx="auto" my={space('extra-loose')} as="img" {...props} /> <Box display="block" mx="auto" as="img" {...props} />
); );
export const MDXComponents = { export const MDXComponents = {

105
src/components/mdx/typography.ts

@ -0,0 +1,105 @@
export const baseTypeStyles = {
letterSpacing: '-0.01em',
dispay: 'flex',
fontFeatureSettings: `'ss01' on`,
};
const h1 = {
fontWeight: 'bolder',
fontSize: '44px',
lineHeight: '52px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.2284090909090909em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.22840909090909092em',
display: 'block',
height: 0,
},
};
const h2 = {
fontWeight: 600,
fontSize: '27.5px',
lineHeight: '34px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.25636363636363635em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.2563636363636364em',
display: 'block',
height: 0,
},
};
const h3 = {
fontWeight: 500,
fontSize: '22px',
lineHeight: '32px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.3659090909090909em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.3659090909090909em',
display: 'block',
height: 0,
},
};
const h4 = {
fontSize: '19.25px',
lineHeight: '28px',
padding: '0.05px 0',
':before': {
content: "''",
marginTop: '-0.36623376623376624em',
display: 'block',
height: 0,
},
':after': {
content: "''",
marginBottom: '-0.36623376623376624em',
display: 'block',
height: 0,
},
};
const h5 = {
fontSize: '16px',
lineHeight: '28px',
fontWeight: 'bold',
};
const h6 = {
fontSize: '14px',
lineHeight: '28px',
fontWeight: 'bold',
};
const headings = {
h1,
h2,
h3,
h4,
h5,
h6,
};
export const getHeadingStyles = (as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6') => {
return {
...headings[as],
};
};

48
src/components/side-nav.tsx

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { Box, color, space } from '@blockstack/ui'; import { Flex, Box, color, space, ChevronIcon } from '@blockstack/ui';
import { border, slugify } from '@common/utils'; import { border } from '@common/utils';
import { Text, Caption } from '@components/typography'; import { Text, Caption } from '@components/typography';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { routes } from '@common/routes'; import routes from '@common/routes';
import { useMobileMenuState } from '@common/hooks/use-mobile-menu'; import { useMobileMenuState } from '@common/hooks/use-mobile-menu';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
const SearchBox = dynamic(() => import('./search')); const SearchBox = dynamic(() => import('./search'));
@ -61,16 +61,17 @@ const LinkItem = React.forwardRef(({ isActive, ...rest }: any, ref) => (
const Links = ({ routes, prefix = '', ...rest }: any) => { const Links = ({ routes, prefix = '', ...rest }: any) => {
const router = useRouter(); const router = useRouter();
const { handleClose } = useMobileMenuState(); const { handleClose } = useMobileMenuState();
const { pathname } = router;
return routes.map((link, linkKey) => { return routes.map((route, linkKey) => {
// const slug = slugify(link); const isActive = pathname === `/${route.path}`;
// const isActive =
// router.pathname.includes(slug) || (router.pathname === '/' && slug === 'getting-started');
return ( return (
<Box width="100%" px="base" py="1px" key={linkKey} onClick={handleClose} {...rest}> <Box width="100%" px="base" py="1px" key={linkKey} onClick={handleClose} {...rest}>
<Link href={`/${link.path}`} passHref> <Link href={`/${route.path}`} passHref>
<LinkItem width="100%" href={`/${link.path}`}> <LinkItem isActive={isActive} width="100%" href={`/${route.path}`}>
{link.path} {route.title ||
(route.headings && route.headings.length && route.headings[0]) ||
route.path}
</LinkItem> </LinkItem>
</Link> </Link>
</Box> </Box>
@ -86,17 +87,36 @@ const SectionTitle = ({ children, textStyles, ...rest }: any) => (
</Box> </Box>
); );
const Section = ({ section, isLast, ...rest }: any) => ( const Section = ({ section, isLast, ...rest }: any) => {
const router = useRouter();
const { pathname } = router;
const isActive = section.routes.find(route => pathname === `/${route.path}`);
const [visible, setVisible] = React.useState(isActive);
return (
<Box width="100%" pt={space('base')} {...rest}> <Box width="100%" pt={space('base')} {...rest}>
{section.title ? <SectionTitle>{section.title}</SectionTitle> : null} {section.title ? (
<Links routes={section.routes} /> <Flex
align="center"
pr={space('base')}
justify="space-between"
onClick={() => setVisible(!visible)}
_hover={{
cursor: 'pointer',
}}
>
<SectionTitle>{section.title}</SectionTitle>
<ChevronIcon size="24px" direction={visible ? 'up' : 'down'} />
</Flex>
) : null}
{visible && <Links routes={section.routes} />}
</Box> </Box>
); );
};
const SideNav = ({ ...rest }: any) => { const SideNav = ({ ...rest }: any) => {
return ( return (
<Wrapper {...rest}> <Wrapper {...rest}>
<SearchBox />
{routes.map((section, sectionKey, arr) => ( {routes.map((section, sectionKey, arr) => (
<Section key={sectionKey} section={section} isLast={sectionKey === arr.length - 1} /> <Section key={sectionKey} section={section} isLast={sectionKey === arr.length - 1} />
))} ))}

40
src/components/toc.tsx

@ -1,17 +1,29 @@
import React from 'react'; import React from 'react';
import { Box, color, space, useSafeLayoutEffect } from '@blockstack/ui'; import { Box, color, space } from '@blockstack/ui';
import { TOC_WIDTH } from '@common/constants';
import { slugify } from '@common/utils'; import { slugify } from '@common/utils';
import { Text } from '@components/typography'; import { Text } from '@components/typography';
import { Link } from '@components/mdx'; import { Link } from '@components/mdx';
import { useActiveHeading } from '@common/hooks/use-active-heading'; import { useActiveHeading } from '@common/hooks/use-active-heading';
const Item = ({ slug, label }) => { const getLevelPadding = (level: number) => {
switch (level) {
case 2:
return space('base-loose');
case 3:
return space('extra-loose');
default:
return 0;
}
};
const Item = ({ slug, label, level }) => {
const { isActive: _isActive, doChangeActiveSlug, slugInView } = useActiveHeading(slug); const { isActive: _isActive, doChangeActiveSlug, slugInView } = useActiveHeading(slug);
const isOnScreen = slugInView === slug; const isOnScreen = slugInView === slug;
const isActive = isOnScreen || _isActive; const isActive = isOnScreen || _isActive;
return ( return (
<Box py={space('extra-tight')}> <Box pl={getLevelPadding(level - 2)} py={space('extra-tight')}>
<Link <Link
href={`#${slug}`} href={`#${slug}`}
fontSize="14px" fontSize="14px"
@ -25,20 +37,27 @@ const Item = ({ slug, label }) => {
}} }}
pointerEvents={isActive ? 'none' : 'unset'} pointerEvents={isActive ? 'none' : 'unset'}
> >
{label} <Box as="span" dangerouslySetInnerHTML={{ __html: label }} />
</Link> </Link>
</Box> </Box>
); );
}; };
export const TableOfContents = ({ headings }: { headings?: string[] }) => { export const TableOfContents = ({
headings,
}: {
headings?: {
content: string;
level: number;
}[];
}) => {
return ( return (
<Box position="relative"> <Box position="relative">
<Box <Box
mt="50px" mt="50px"
flexShrink={0} flexShrink={0}
display={['none', 'none', 'block', 'block']} display={['none', 'none', 'block', 'block']}
minWidth={['100%', '200px', '200px']} minWidth={['100%', `${TOC_WIDTH}px`, `${TOC_WIDTH}px`]}
position="sticky" position="sticky"
top="118px" top="118px"
pr={space('base')} pr={space('base')}
@ -49,7 +68,14 @@ export const TableOfContents = ({ headings }: { headings?: string[] }) => {
</Text> </Text>
</Box> </Box>
{headings.map((heading, index) => { {headings.map((heading, index) => {
return index > 0 ? <Item slug={slugify(heading)} label={heading} key={index} /> : null; return index > 0 ? (
<Item
level={heading.level}
slug={slugify(heading.content)}
label={heading.content}
key={index}
/>
) : null;
})} })}
</Box> </Box>
</Box> </Box>

41
src/lib/remark-paragraph-alerts.js

@ -0,0 +1,41 @@
const is = require('unist-util-is');
const visit = require('unist-util-visit');
const sigils = {
'=>': 'success',
'->': 'info',
'~>': 'warning',
'!>': 'danger',
};
module.exports = function paragraphCustomAlertsPlugin() {
return function transformer(tree) {
visit(tree, 'paragraph', (pNode, _, parent) => {
visit(pNode, 'text', textNode => {
Object.keys(sigils).forEach(symbol => {
if (textNode.value.startsWith(`${symbol} `)) {
// Remove the literal sigil symbol from string contents
textNode.value = textNode.value.replace(`${symbol} `, '');
// Wrap matched nodes with <div> (containing proper attributes)
parent.children = parent.children.map(node => {
return is(pNode, node)
? {
type: 'wrapper',
children: [node],
data: {
hName: 'blockquote',
hProperties: {
className: ['alert', `alert-${sigils[symbol]}`],
role: 'alert',
},
},
}
: node;
});
}
});
});
});
};
};

2
src/pages/404.tsx

@ -4,7 +4,7 @@ import { ContentWrapper } from '@components/content-wrapper';
import { Text, Title, Link } from '@components/typography'; import { Text, Title, Link } from '@components/typography';
import FileDocumentEditOutlineIcon from 'mdi-react/FileDocumentEditOutlineIcon'; import FileDocumentEditOutlineIcon from 'mdi-react/FileDocumentEditOutlineIcon';
import { border } from '@common/utils'; import { border } from '@common/utils';
import { DocsLayout } from '@components/docs-layout'; import { DocsLayout } from '@components/layouts/docs-layout';
import Head from 'next/head'; import Head from 'next/head';
const toPx = (number: number): string => `${number}px`; const toPx = (number: number): string => `${number}px`;

10
src/pages/_app.tsx

@ -3,13 +3,14 @@ import { CSSReset, ThemeProvider, theme, ColorModeProvider } from '@blockstack/u
import { MDXProvider } from '@mdx-js/react'; import { MDXProvider } from '@mdx-js/react';
import { MDXComponents } from '@components/mdx'; import { MDXComponents } from '@components/mdx';
import { AppStateProvider } from '@components/app-state'; import { AppStateProvider } from '@components/app-state';
import { MdxOverrides } from '@components/docs-layout'; import { MdxOverrides } from '@components/layouts/docs-layout';
import { ProgressBar } from '@components/progress-bar'; import { ProgressBar } from '@components/progress-bar';
import engine from 'store/src/store-engine'; import engine from 'store/src/store-engine';
import cookieStorage from 'store/storages/cookieStorage'; import cookieStorage from 'store/storages/cookieStorage';
import GoogleFonts from 'next-google-fonts'; import GoogleFonts from 'next-google-fonts';
import Head from 'next/head'; import Head from 'next/head';
import '@docsearch/react/dist/style.css'; import '@docsearch/react/dist/style.css';
import { DocsLayout } from '@components/layouts/docs-layout';
const COLOR_MODE_COOKIE = 'color_mode'; const COLOR_MODE_COOKIE = 'color_mode';
@ -17,7 +18,7 @@ const cookieSetter = engine.createStore([cookieStorage]);
const handleColorModeChange = (mode: string) => cookieSetter.set(COLOR_MODE_COOKIE, mode); const handleColorModeChange = (mode: string) => cookieSetter.set(COLOR_MODE_COOKIE, mode);
const AppWrapper = ({ children, colorMode = 'light' }: any) => ( const AppWrapper = ({ children, colorMode = 'light', isHome }: any) => (
<> <>
<GoogleFonts href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Inter:wght@400;500;600;700&display=swap" /> <GoogleFonts href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Inter:wght@400;500;600;700&display=swap" />
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
@ -31,7 +32,7 @@ const AppWrapper = ({ children, colorMode = 'light' }: any) => (
<link rel="preconnect" href="https://cdn.usefathom.com" crossOrigin="true" /> <link rel="preconnect" href="https://cdn.usefathom.com" crossOrigin="true" />
</Head> </Head>
<CSSReset /> <CSSReset />
{children} <DocsLayout isHome={isHome}>{children}</DocsLayout>
</AppStateProvider> </AppStateProvider>
</MDXProvider> </MDXProvider>
</ColorModeProvider> </ColorModeProvider>
@ -40,8 +41,9 @@ const AppWrapper = ({ children, colorMode = 'light' }: any) => (
); );
const MyApp = ({ Component, pageProps, colorMode, ...rest }: any) => { const MyApp = ({ Component, pageProps, colorMode, ...rest }: any) => {
const { isHome } = pageProps;
return ( return (
<AppWrapper> <AppWrapper isHome={isHome}>
<Component {...pageProps} /> <Component {...pageProps} />
</AppWrapper> </AppWrapper>
); );

142
src/pages/browser/todo-list.md

@ -1,13 +1,15 @@
--- ---
title: 'Integrate Blockstack'
description: Single-page application with Blockstack description: Single-page application with Blockstack
redirect_from: redirect_from:
- /develop/zero_to_dapp_1.html - /develop/zero_to_dapp_1.html
- /browser/hello-blockstack.html - /browser/hello-blockstack.html
--- ---
# Tutorial for App Integration # Integrating Blockstack
In this tutorial, you will learn about Blockstack authentication and storage by installing, running and reviewing the code for a "Todos" web app built with Blockstack and [React](https://reactjs.org/). In this tutorial, you will learn about Blockstack authentication and storage by installing,
running and reviewing the code for a "Todos" web app built with Blockstack and [React](https://reactjs.org/).
This app highlights the following platform functionality: This app highlights the following platform functionality:
@ -20,19 +22,20 @@ This app highlights the following platform functionality:
Existing familiarity with React is recommended for reviewing this app's code. Existing familiarity with React is recommended for reviewing this app's code.
### Install and run the app ## Install and run the app
You must have recent versions of Git and [Node.js](https://nodejs.org/en/download/) (v12.10.0 or greater) installed already. You must have recent versions of Git and [Node.js](https://nodejs.org/en/download/)
(v12.10.0 or greater) installed already.
1. Install the code and its dependencies: ### Step 1: Install the code and its dependencies
``` ```bash
git clone https://github.com/blockstack/blockstack-todos git clone https://github.com/blockstack/blockstack-todos
cd blockstack-todos cd blockstack-todos
npm install npm install
``` ```
2. Run the application: ### Step 2: Run the application:
```bash ```bash
$ npm run start $ npm run start
@ -51,23 +54,28 @@ You must have recent versions of Git and [Node.js](https://nodejs.org/en/downloa
To create a production build, use npm run build. To create a production build, use npm run build.
``` ```
3. Open your local browser to [`http://localhost:3000`](http://localhost:3000) if it doesn't open automatically. ### Step 3: Open your local browser to [`http://localhost:3000`](http://localhost:3000) if it doesn't open automatically.
You should see the app's landing page: You should see the app's landing page:
![](images/todos-home.png) ![](images/todos-home.png)
### Onboard into your first Blockstack app ## Onboard into your first Blockstack app
1. Choose **Get started** to start onboarding into the app. ### Step 1: Choose **Get started** to start onboarding into the app.
The app displays a standardized introductory modal using [Blockstack Connect](https://github.com/blockstack/ux/tree/master/packages/connect), a JavaScript library that makes it easy to integrate Blockstack into the UI of any web app. The app displays a standardized introductory modal using
[Blockstack Connect](https://github.com/blockstack/ux/tree/master/packages/connect), a JavaScript
library that makes it easy to integrate Blockstack into the UI of any web app.
![](images/todos-intro.png) ![](images/todos-intro.png)
Below, you can see the relevant parts of the [React component](https://reactjs.org/docs/react-component.html) that triggers this modal in [`src/components/Signin.jsx`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/Signin.jsx): Below, you can see the relevant parts of the [React component](https://reactjs.org/docs/react-component.html)
that triggers this modal in [`src/components/Signin.jsx`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/Signin.jsx):
```js ```js
// src/components/Signin.jsx
import { useConnect } from '@blockstack/connect'; import { useConnect } from '@blockstack/connect';
export const Signin = () => { export const Signin = () => {
@ -77,15 +85,23 @@ You must have recent versions of Git and [Node.js](https://nodejs.org/en/downloa
}; };
``` ```
This component imports the [React hook](https://reactjs.org/docs/hooks-overview.html) [`useConnect`](https://github.com/blockstack/ux/blob/master/packages/connect/src/react/hooks/use-connect.ts) from the Blockstack Connect library. This component imports the [React hook](https://reactjs.org/docs/hooks-overview.html)
[`useConnect`](https://github.com/blockstack/ux/blob/master/packages/connect/src/react/hooks/use-connect.ts)
from the Blockstack Connect library.
`useConnect` returns many helper functions such as [`doOpenAuth`](https://github.com/blockstack/ux/blob/master/packages/connect/src/react/hooks/use-connect.ts#L33), which triggers this modal upon click of the "Get started" button. `useConnect` returns many helper functions such as
[`doOpenAuth`](https://github.com/blockstack/ux/blob/master/packages/connect/src/react/hooks/use-connect.ts#L33),
which triggers this modal upon click of the "Get started" button.
The modal is designed to prepare new users for a different type of relationship with Blockstack apps, one in which they authenticate with a _Secret Key_ that's used to encrypt their private data. The modal is designed to prepare new users for a different type of relationship with
Blockstack apps, one in which they authenticate with a _Secret Key_ that's used to encrypt
their private data.
The modal displays the app's name and icon as configured in [`src/components/App.jsx`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/App.jsx#L26): The modal displays the app's name and icon as configured in
[`src/components/App.jsx`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/App.jsx#L26):
```js ```jsx
// src/components/App.jsx
appDetails: { appDetails: {
name: 'Blockstack App', name: 'Blockstack App',
@ -94,7 +110,10 @@ You must have recent versions of Git and [Node.js](https://nodejs.org/en/downloa
``` ```
This component loads the [`UserSession`](https://blockstack.github.io/blockstack.js/classes/usersession.html) module from a second Blockstack library called [blockstack.js](https://github.com/blockstack/blockstack.js/), which complements Blockstack Connect by providing an API for many protocol-level operations, such as for authentication and storage. This component loads the [`UserSession`](https://blockstack.github.io/blockstack.js/classes/usersession.html)
module from a second Blockstack library called [blockstack.js](https://github.com/blockstack/blockstack.js/),
which complements Blockstack Connect by providing an API for many protocol-level operations, such as for
authentication and storage.
```js ```js
import { UserSession } from 'blockstack'; import { UserSession } from 'blockstack';
@ -105,15 +124,24 @@ You must have recent versions of Git and [Node.js](https://nodejs.org/en/downloa
const userSession = new UserSession({ appConfig }); const userSession = new UserSession({ appConfig });
``` ```
This module handles user session operations and is initiated using the [`appConfig`](https://github.com/blockstack/blockstack-todos/blob/master/src/assets/constants.js#L3) object, which contains an array of [scopes](/develop/overview_auth.html#scopes) that indicate just what permissions to grant during authentication: This module handles user session operations and is initiated using the
[`appConfig`](https://github.com/blockstack/blockstack-todos/blob/master/src/assets/constants.js#L3) object,
which contains an array of [scopes](/develop/overview_auth.html#scopes) that indicate just what permissions
to grant during authentication:
```js ```js
// src/assets/constants.js
export const appConfig = new AppConfig(['store_write', 'publish_data']); export const appConfig = new AppConfig(['store_write', 'publish_data']);
``` ```
The `appDetails` and `userSession` objects are joined by the callback function [`finished`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/App.jsx#L31) in configuring Blockstack Connect for authentication with the `authOptions` object: The `appDetails` and `userSession` objects are joined by the callback function
[`finished`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/App.jsx#L31)
in configuring Blockstack Connect for authentication with the `authOptions` object:
```js ```js
// src/components/App.jsx
finished: ({ userSession }) => { finished: ({ userSession }) => {
this.setState({ userData: userSession.loadUserData() }); this.setState({ userData: userSession.loadUserData() });
}; };
@ -121,9 +149,14 @@ You must have recent versions of Git and [Node.js](https://nodejs.org/en/downloa
This function simply saves data about the user into the app's state upon authentication. This function simply saves data about the user into the app's state upon authentication.
Further down in the component we see in [`componentDidMount`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/App.jsx#L46) that it checks upon mount to either process completion of authentication with `userSession.handlePendingSignIn()` or otherwise load session data into app state as above with `userSession.isUserSignedIn()`: Further down in the component we see in
[`componentDidMount`](https://github.com/blockstack/blockstack-todos/blob/master/src/components/App.jsx#L46)
that it checks upon mount to either process completion of authentication with `userSession.handlePendingSignIn()`
or otherwise load session data into app state as above with `userSession.isUserSignedIn()`:
```js ```js
// src/components/App.jsx
componentDidMount() { componentDidMount() {
if (userSession.isSignInPending()) { if (userSession.isSignInPending()) {
userSession.handlePendingSignIn().then((userData) => { userSession.handlePendingSignIn().then((userData) => {
@ -136,41 +169,49 @@ You must have recent versions of Git and [Node.js](https://nodejs.org/en/downloa
} }
``` ```
2) Choose **Get started** to generate a _Secret Key_. ### Step 2: Choose **Get started** to generate a _Secret Key_.
The app triggers a popup window in which [the Blockstack App](https://github.com/blockstack/ux/tree/master/packages/app) is loaded from [`app.blockstack.org`](http://app.blockstack.org/) and begins generating a new _Secret Key_. The app triggers a popup window in which [the Blockstack App](https://github.com/blockstack/ux/tree/master/packages/app)
is loaded from [`app.blockstack.org`](http://app.blockstack.org/) and begins generating a new _Secret Key_.
![](images/todos-generation.svg) ![](images/todos-generation.svg)
3) Choose **Copy Secret Key** to copy your _Secret Key_ to the clipboard. ### Step 3: Choose **Copy Secret Key** to copy your _Secret Key_ to the clipboard.
The _Secret Key_ is a unique 12-word [mnemonic phrase](https://en.bitcoinwiki.org/wiki/Mnemonic_phrase) that empowers the user not only to access Blockstack apps securely and independently. It's also used to encrypt all of the private data they create and manage with Blockstack apps. The _Secret Key_ is a unique 12-word [mnemonic phrase](https://en.bitcoinwiki.org/wiki/Mnemonic_phrase) that
empowers the user not only to access Blockstack apps securely and independently. It's also used to encrypt
all of the private data they create and manage with Blockstack apps.
_Secret Keys_ are like strong passwords. However, they can never be recovered if lost or reset if stolen. As such, it's paramount that users handle them with great care. _Secret Keys_ are like strong passwords. However, they can never be recovered if lost or reset if stolen.
As such, it's paramount that users handle them with great care.
![](images/todos-copy-secret-key.svg) ![](images/todos-copy-secret-key.svg)
4) Choose **I've saved it** to confirm you've secured your _Secret Key_ in a suitable place. ### Step 4: Choose **I've saved it** to confirm you've secured your _Secret Key_ in a suitable place.
![](images/todos-ive-saved-it.svg) ![](images/todos-ive-saved-it.svg)
5) Enter a username value and choose **Continue** ### Step 5: Enter a username value and choose **Continue**
The username will be used by the app to generate a URL for sharing your todos, should you choose to make them public. The username will be used by the app to generate a URL for sharing your todos, should you choose to make them public.
It is registered on the Stacks blockchain with the [Blockstack Naming System (BNS)](/core/naming/introduction.html) and associated with your _Secret Key_. It is registered on the Stacks blockchain with the [Blockstack Naming System (BNS)](/core/naming/introduction)
and associated with your _Secret Key_.
![](images/todos-username.svg) ![](images/todos-username.svg)
6) You've now completed onboarding into the app! ### Done: You've now completed onboarding into the app!
### Add, edit and delete todos privately ## Add, edit and delete todos privately
Once you've authenticated the app, you can can start adding todos by entering values into the "Write your to do" field and hitting "Enter". Once you've authenticated the app, you can can start adding todos by entering values into the "Write your to do"
field and hitting "Enter".
![](images/todos-home-authenticated.svg) ![](images/todos-home-authenticated.svg)
The data for all todos are saved as JSON to the Gaia hub linked to your Secret Key using the [`putFile`](http://blockstack.github.io/blockstack.js/globals.html#putfile) method of the `userSession` object in the [`src/assets/data-store.js`](https://github.com/blockstack/blockstack-todos/blob/master/src/assets/data-store.js#L26) module: The data for all todos are saved as JSON to the Gaia hub linked to your Secret Key using the
[`putFile`](http://blockstack.github.io/blockstack.js/globals.html#putfile) method of the `userSession` object in the
[`src/assets/data-store.js`](https://github.com/blockstack/blockstack-todos/blob/master/src/assets/data-store.js#L26) module:
```js ```js
export const saveTasks = async (userSession, tasks, isPublic) => { export const saveTasks = async (userSession, tasks, isPublic) => {
@ -180,7 +221,8 @@ export const saveTasks = async (userSession, tasks, isPublic) => {
}; };
``` ```
These todos are subsequently loaded using the [`getFile`](http://blockstack.github.io/blockstack.js/globals.html#getfile) method of the same object in the same module: These todos are subsequently loaded using the [`getFile`](http://blockstack.github.io/blockstack.js/globals.html#getfile)
method of the same object in the same module:
```js ```js
export const fetchTasks = async (userSession, username) => { export const fetchTasks = async (userSession, username) => {
@ -192,11 +234,12 @@ export const fetchTasks = async (userSession, username) => {
}; };
``` ```
By default, the `putFile` and `getFile` methods automatically encrypt data when saved and decrypt it when retrieved, using the user's Secret Key. This ensures that only the user has the ability to view this data. By default, the `putFile` and `getFile` methods automatically encrypt data when saved and decrypt it when retrieved,
using the user's Secret Key. This ensures that only the user has the ability to view this data.
When deleting a todo, the same `putFile` method is used to save a new JSON array of todos that excludes the deleted todo. When deleting a todo, the same `putFile` method is used to save a new JSON array of todos that excludes the deleted todo.
### Publish your todos publicly ## Publish your todos publicly
Select "Make public" to make your todos accessible to the public for sharing via URL. Select "Make public" to make your todos accessible to the public for sharing via URL.
@ -206,19 +249,27 @@ This will call `saveTasks` with the `isPublic` parameter set to `true`, which is
The app will now show all of your todos to anyone who visits the URL displayed with your Blockstack username as a suffix. The app will now show all of your todos to anyone who visits the URL displayed with your Blockstack username as a suffix.
### Sign out and see your public tasks ## Sign out and see your public tasks
Select "Sign out" to deauthenticate the app with your Blockstack account. Select "Sign out" to deauthenticate the app with your Blockstack account.
This triggers an event, which [under the hood](https://github.com/blockstack/blockstack-todos/blob/master/src/components/Header.jsx#L47) calls the [`signUserOut` method](https://blockstack.github.io/blockstack.js/classes/usersession.html#signuserout) of the `UserSession` object. This triggers an event, which
[under the hood](https://github.com/blockstack/blockstack-todos/blob/master/src/components/Header.jsx#L47)
calls the [`signUserOut` method](https://blockstack.github.io/blockstack.js/classes/usersession.html#signuserout)
of the `UserSession` object.
Now, visit the URL that was provided to you when you made your tasks public. This url is of the format `/todos/:username`, so if your username is `jane_doe.id.blockstack`, the URL would be [`localhost:3000/todos/jane_doe.id.blockstack`](http://localhost:3000/todos/jane_doe.id.blockstack). Now, visit the URL that was provided to you when you made your tasks public. This url is of the format `/todos/:username`,
so if your username is `jane_doe.id.blockstack`, the URL would be
[`localhost:3000/todos/jane_doe.id.blockstack`](http://localhost:3000/todos/jane_doe.id.blockstack).
When you visit this page, the `TodoList.jsx` component detects that there is a username in the URL. When there is a username, it calls `fetchTasks`, this time providing the `username` argument. This `username` option is then passed to `getFile`, which will lookup where that user's tasks are stored. When you visit this page, the `TodoList.jsx` component detects that there is a username in the URL.
When there is a username, it calls `fetchTasks`, this time providing the `username` argument. This `username`
option is then passed to `getFile`, which will lookup where that user's tasks are stored.
### Sign back in ## Sign back in
At this point, you will be logged out from the app but not you'll still have an active session with the Blockstack app itself on [app.blockstack.org](https://app.blockstack.org). Navigate to app.blockstack.org and select "Sign out" there if you want to deauthenticate the Blockstack app as well. At this point, you will be logged out from the app but not you'll still have an active session with the Blockstack
app itself on [app.blockstack.org](https://app.blockstack.org). Navigate to app.blockstack.org and select "Sign out" there if you want to deauthenticate the Blockstack app as well.
Once signed out, select "Sign in" to sign back in with your _Secret Key_. Once signed out, select "Sign in" to sign back in with your _Secret Key_.
@ -228,7 +279,8 @@ If you've previously deauthenticated the Blockstack app, you'll see a prompt to
The above screen will be ommitted if you have an active session with the Blockstack app already. The above screen will be ommitted if you have an active session with the Blockstack app already.
Then you'll be presented with the option to select an existing username associated with your _Secret Key_ or create a new one if you wish to authenticate the app with a different identity and data set: Then you'll be presented with the option to select an existing username associated with your _Secret Key_ or
create a new one if you wish to authenticate the app with a different identity and data set:
![](images/todos-choose-account.svg) ![](images/todos-choose-account.svg)
@ -236,4 +288,6 @@ You'll now see your todos as an authenticated user for the username you've chose
## Learn more ## Learn more
Read [the Blockstack Connect guide](/develop/connect/get-started.html) and [the blockstack.js reference](https://blockstack.github.io/blockstack.js/) to learn more about the libraries used in this tutorial. Read [the Blockstack Connect guide](/develop/connect/get-started) and
[the blockstack.js reference](https://blockstack.github.io/blockstack.js/) to learn more about the
libraries used in this tutorial.

5
src/pages/core/README.md

@ -1,5 +0,0 @@
# About this `docs` directory
The contents of this directory acts as source for material for
[docs.blockstack.org](https://docs.blockstack.org/), a searchable documentation
site for all things Blockstack.

2
src/pages/core/atlas/howitworks.md

@ -42,7 +42,7 @@ for this with the following tweaks:
but only reports a random subset of peers that have met a minimium health threshold. but only reports a random subset of peers that have met a minimium health threshold.
- A new neighbor is only selected if it belongs to the same [BNS - A new neighbor is only selected if it belongs to the same [BNS
fork-set]({{site.baseurl}}/core/naming/introduction.html#bns-forks) (i.e. it reports fork-set](/core/naming/introduction.html#bns-forks) (i.e. it reports
as having a recent valid consensus hash). as having a recent valid consensus hash).
The algorithm was adapted from the work from [Lee, Xu, and The algorithm was adapted from the work from [Lee, Xu, and

2
src/pages/core/atlas/overview.md

@ -13,7 +13,7 @@ all chunks are available to clients.
This document is aimed at developers and technical users. The following This document is aimed at developers and technical users. The following
concepts are discussed: concepts are discussed:
The reader of this document is expected to be familiar with the [Blockstack Naming Service]({{site.baseurl}}/core/naming/introduction.html)(BNS), as well as Blockstack's The reader of this document is expected to be familiar with the [Blockstack Naming Service](/core/naming/introduction)(BNS), as well as Blockstack's
storage system [Gaia](https://github.com/blockstack/gaia). We advise the reader storage system [Gaia](https://github.com/blockstack/gaia). We advise the reader
to familiarize themselves with both systems before approaching this document. to familiarize themselves with both systems before approaching this document.

70
src/pages/core/cmdLineRef.md

@ -1,3 +1,69 @@
# blockstack_cli reference ---
title: Blockstack CLI Reference
---
{% include commandline.md %} import { CLIReferenceTable } from '@components/cli-reference'
# Blockstack CLI reference
The command line is intended for developers only. Developers can use the command
line to test and debug Blockstack applications in ways that the Blockstack
Browser does not yet support. Using the command line, developers can:
- Generate and Broadcast all supported types of Blockstack transactions
- Load, store, and list data in Gaia hubs
- Generate owner, payment and application keys from a seed phrase
- Query Stacks Nodes
- Implement a minimum viable authentication flow
!> Many of the commands operate on unencrypted private keys. For this reason,
DO NOT use this tool for day-to-day tasks as you risk the security of your keys.
You must install the command line before you can use the commands.
## How to install the command line
You must have [Node.js](https://nodejs.org/en/download/) v8 or higher (v10 recommended). macOS and Linux users can avoid `sudo` or [permissions problems](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally) or by using [`nvm`](https://github.com/nvm-sh/nvm). These instructions assume you are using a macOS or Linux system.
To install the command line, do the following:
### Step 1: [Download or `git clone` the command line repository code](https://github.com/blockstack/cli-blockstack).
Downloading or cloning the repo creates a `cli-blockstack` repository on your system.
### Step 2: Change directory into the `cli-blockstack` directory.
```bash
cd cli-blockstack
```
### Step 3: Install the dependencies with `npm`.
```bash
npm install
```
### Step 4: Build the command line command.
```bash
npm run build
```
### Step 5: Link the command.
```bash
sudo npm link
```
### Troubleshooting the CLI installation
If you run into `EACCES` permissions errors, try the following:
- See https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally.
- Use [`Node Version Manager`](https://github.com/nvm-sh/nvm).
## List of commands
To see the usage and options for the command in general, enter `blockstack-cli` without any subcommands. To see a list of subcommands enter `blockstack-cli help`. Enter `blockstack-cli SUBCOMMAND_NAME help` to see a subcommand with its usage. The following are the available subcommands:
<CLIReferenceTable />

4
src/pages/core/faq_developer.md

@ -4,9 +4,9 @@ description: Blockstack DApp technical FAQs
# DApp Developer FAQs # DApp Developer FAQs
This document lists frequently-asked questions developers about Blockstack application development. If you are new to Blockstack, you should read the [general questions]({{site.baseurl}}/faqs/allFAQs.html) first. This document lists frequently-asked questions developers about Blockstack application development. If you are new to Blockstack, you should read the [general questions](/faqs/allFAQs) first.
For more technical FAQs about Stacks nodes, the Stacks blockchain, and other architectural elements, see the [entire set of technical FAQs]({{site.baseurl}}/core/faq_technical.html). For more technical FAQs about Stacks nodes, the Stacks blockchain, and other architectural elements, see the [entire set of technical FAQs](/core/faq_technical).
If you have a technical question that gets frequently asked on the If you have a technical question that gets frequently asked on the
[forum](https://forum.blockstack.org) or [Slack](https://blockstack.slack.com), [forum](https://forum.blockstack.org) or [Slack](https://blockstack.slack.com),

2
src/pages/core/faq_technical.md

@ -1,6 +1,6 @@
# Technical FAQ # Technical FAQ
This document lists frequently-asked questions by developers interested in working with Blockstack application and core components. If you are new to Blockstack, you should read the [general questions]({{site.baseurl}}/faqs/allFAQs.html) first. This document lists frequently-asked questions by developers interested in working with Blockstack application and core components. If you are new to Blockstack, you should read the [general questions](/faqs/allFAQs) first.
If you have a technical question that gets frequently asked on the If you have a technical question that gets frequently asked on the
[forum](https://forum.blockstack.org) or [Slack](https://blockstack.slack.com), [forum](https://forum.blockstack.org) or [Slack](https://blockstack.slack.com),

6
src/pages/core/install-api.md

@ -7,7 +7,7 @@ Step-by-step instructions for deploying a Blockstack API node on Debian or Ubunt
- **Step 2:** Make sure you have [virtualenv installed](http://docs.python-guide.org/en/latest/dev/virtualenvs/). - **Step 2:** Make sure you have [virtualenv installed](http://docs.python-guide.org/en/latest/dev/virtualenvs/).
Then, setup the API: Then, setup the API:
``` ```bash
$ sudo apt-get install -y python-pip memcached rng-tools python-dev libmemcached-dev zlib1g-dev libgmp-dev libffi-dev libssl-dev $ sudo apt-get install -y python-pip memcached rng-tools python-dev libmemcached-dev zlib1g-dev libgmp-dev libffi-dev libssl-dev
$ sudo service memcached start $ sudo service memcached start
$ sudo pip install virtualenv $ sudo pip install virtualenv
@ -34,7 +34,7 @@ For a production deployment we recommend using nginx and uwsgi:
- **Step 1:** Install nginx and uWSGI: - **Step 1:** Install nginx and uWSGI:
``` ```bash
$ sudo apt-get install -y nginx $ sudo apt-get install -y nginx
$ sudo pip install uwsgi $ sudo pip install uwsgi
``` ```
@ -46,7 +46,7 @@ $ sudo pip install uwsgi
and edit the paths depending on the uwsgi blockstack_api socket directory (defaults to /tmp/blockstack_api.sock) and edit the paths depending on the uwsgi blockstack_api socket directory (defaults to /tmp/blockstack_api.sock)
You can test your nginx settings: You can test your nginx settings:
``` ```bash
$ sudo nginx -t $ sudo nginx -t
``` ```

6
src/pages/core/memcached.md

@ -6,7 +6,7 @@ running locally.
### Memcached on Debian & Ubuntu: ### Memcached on Debian & Ubuntu:
``` ```bash
$ sudo apt-get install -y python-dev libmemcached-dev zlib1g-dev $ sudo apt-get install -y python-dev libmemcached-dev zlib1g-dev
$ pip install pylibmc $ pip install pylibmc
``` ```
@ -17,7 +17,7 @@ Easiest way to install memcached on macOS is by using [Homebrew](https://brew.sh
After installing Homebrew: After installing Homebrew:
``` ```bash
$ brew install memcached $ brew install memcached
$ brew install libmemcached $ brew install libmemcached
$ pip install pylibmc --install-option="--with-libmemcached=/usr/local/Cellar/libmemcached/1.0.18_1/" $ pip install pylibmc --install-option="--with-libmemcached=/usr/local/Cellar/libmemcached/1.0.18_1/"
@ -25,7 +25,7 @@ $ pip install pylibmc --install-option="--with-libmemcached=/usr/local/Cellar/li
After installing, you can start memcached and check if it's running properly: After installing, you can start memcached and check if it's running properly:
``` ```bash
$ memcached -d $ memcached -d
$ echo stats | nc localhost 11211 $ echo stats | nc localhost 11211
``` ```

2
src/pages/core/naming/architecture.md

@ -76,7 +76,7 @@ Browser](https://github.com/blockstack/blockstack-browser).
Developers who want to make their own client programs that do not use Developers who want to make their own client programs that do not use
the reference client library code should read the the reference client library code should read the
[BNS transaction wire format]({{ site.baseurl }}/core/wire-format.html) document for generating and [BNS transaction wire format](/core/wire-format) document for generating and
sending their own transactions. sending their own transactions.
The examples in this document focus on resolving names using `curl`. We refer The examples in this document focus on resolving names using `curl`. We refer

8
src/pages/core/naming/comparison.md

@ -14,7 +14,7 @@ comparison to Blockstack:
Blockstack and DNS both implement naming systems, but in fundamentally Blockstack and DNS both implement naming systems, but in fundamentally
different ways. Blockstack _can be used_ for resolving host names to IP different ways. Blockstack _can be used_ for resolving host names to IP
addresses, but this is not its default use-case. The [Blockstack Naming addresses, but this is not its default use-case. The [Blockstack Naming
Service]({{ site.baseurl }}/core/naming/introduction.html) (BNS) instead behaves Service](/core/naming/introduction) (BNS) instead behaves
more like a decentralized more like a decentralized
[LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) system for [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) system for
resolving user names to user data. resolving user names to user data.
@ -45,9 +45,9 @@ than DNS:
of `bar.baz` in both DNS and BNS). of `bar.baz` in both DNS and BNS).
More details can be found in the [Blockstack vs More details can be found in the [Blockstack vs
DNS]({{ site.baseurl }}/core/naming/comparison.html) document. A feature DNS](/core/naming/comparison) document. A feature
comparison can be found at the end of the [Blockstack Naming comparison can be found at the end of the [Blockstack Naming
Service]({{ site.baseurl }}/core/naming/introduction.html) document. Service](/core/naming/introduction) document.
## Blockstack vs Namecoin ## Blockstack vs Namecoin
@ -60,7 +60,7 @@ blockchains, so that if Blockstack's underlying blockchain (currently Bitcoin)
ever became insecure, the system could migrate to a more secure blockchain. ever became insecure, the system could migrate to a more secure blockchain.
A feature comparison can be found at the end of the [Blockstack Naming A feature comparison can be found at the end of the [Blockstack Naming
Service]({{ site.baseurl }}/core/naming/introduction.html) document. Service](/core/naming/introduction) document.
## Blockstack vs ENS ## Blockstack vs ENS

2
src/pages/core/naming/creationhowto.md

@ -4,7 +4,7 @@ description: 'Blockstack naming service (BNS)'
# Creating a Namespace # Creating a Namespace
Making a namespace is very expensive. Given the large amount of cryptocurrency at stake in name creation, developers wanting to create their own namespaces should read [Understand Namespaces]({{ site.baseurl }}/core/naming/namespaces.html) first. You should also read this document thoroughly before creating a namespace. Making a namespace is very expensive. Given the large amount of cryptocurrency at stake in name creation, developers wanting to create their own namespaces should read [Understand Namespaces](/core/naming/namespaces) first. You should also read this document thoroughly before creating a namespace.
## Creation process ## Creation process

2
src/pages/core/naming/did.md

@ -38,7 +38,7 @@ name:
- The name must exist - The name must exist
- The name's zone file hash must be the hash of a well-formed DNS zone file - The name's zone file hash must be the hash of a well-formed DNS zone file
- The DNS zone file must be present in the BNS [Atlas Network]({{ site.baseurl }}/core/atlas/overview.html) - The DNS zone file must be present in the BNS [Atlas Network](/core/atlas/overview)
- The DNS zone file must contain a `URI` resource record that points to a signed - The DNS zone file must contain a `URI` resource record that points to a signed
JSON Web Token JSON Web Token
- The public key that signed the JSON Web Token (and is included with it) must - The public key that signed the JSON Web Token (and is included with it) must

2
src/pages/core/naming/forks.md

@ -65,7 +65,7 @@ accepted if it included a recent valid consensus hash.
This means that all BNS nodes in the client's desired fork-set will accept This means that all BNS nodes in the client's desired fork-set will accept
the transaction, and all other BNS nodes not in the fork-set will ignore it. the transaction, and all other BNS nodes not in the fork-set will ignore it.
You can see where the consensus hash is included in blockchain transactions by reading You can see where the consensus hash is included in blockchain transactions by reading
the [transaction wire format]({{ site.baseurl }}/core/wire-format.html) document. the [transaction wire format](/core/wire-format) document.
## Fork-set Selection ## Fork-set Selection

6
src/pages/core/naming/introduction.md

@ -32,7 +32,7 @@ Internally, a BNS node implements a replicated name database. Each BNS node keep
synchronized to all of the other ones in the world, so queries on one BNS node synchronized to all of the other ones in the world, so queries on one BNS node
will be the same on other nodes. BNS nodes allow a name's owner to bind will be the same on other nodes. BNS nodes allow a name's owner to bind
up to 40Kb of off-chain state to their name, which will be replicated to all up to 40Kb of off-chain state to their name, which will be replicated to all
BNS nodes via the [Atlas network]({{ site.baseurl }}/core/atlas/overview.html). BNS nodes via the [Atlas network](/core/atlas/overview).
BNS nodes extract the name database log from an underlying blockchain (Blockstack BNS nodes extract the name database log from an underlying blockchain (Blockstack
Core currently uses Bitcoin, and had used Namecoin in the past). Core currently uses Bitcoin, and had used Namecoin in the past).
@ -113,11 +113,11 @@ layers in this hierarchy related to naming:
blockchain transactions. Example names include `verified.podcast` and blockchain transactions. Example names include `verified.podcast` and
`muneeb.id`. Anyone can create a BNS name, as long as the namespace that `muneeb.id`. Anyone can create a BNS name, as long as the namespace that
contains it exists already. The state for BNS names is usually stored in the [Atlas contains it exists already. The state for BNS names is usually stored in the [Atlas
network]({{ site.baseurl }}/core/atlas/overview.html). network](/core/atlas/overview).
- **BNS subdomains**. These are names whose records are stored off-chain, - **BNS subdomains**. These are names whose records are stored off-chain,
but are collectively anchored to the blockchain. The ownership and state for but are collectively anchored to the blockchain. The ownership and state for
these names lives within the [Atlas network]({{ site.baseurl }}/core/atlas/overview.html). While BNS these names lives within the [Atlas network](/core/atlas/overview). While BNS
subdomains are owned by separate private keys, a BNS name owner must subdomains are owned by separate private keys, a BNS name owner must
broadcast their subdomain state. Example subdomains include `jude.personal.id` broadcast their subdomain state. Example subdomains include `jude.personal.id`
and `podsaveamerica.verified.podcast`. Unlike BNS namespaces and names, the and `podsaveamerica.verified.podcast`. Unlike BNS namespaces and names, the

24
src/pages/core/naming/manage.md

@ -30,7 +30,9 @@ The reference BNS clients---
Browser](https://github.com/blockstack/blockstack-browser)---can handle creating Browser](https://github.com/blockstack/blockstack-browser)---can handle creating
and sending all of these transactions for you. and sending all of these transactions for you.
## NAME_UPDATE ([live example](https://www.blocktrail.com/BTC/tx/e2029990fa75e9fc642f149dad196ac6b64b9c4a6db254f23a580b7508fc34d7)) ## NAME_UPDATE
See [live example](https://www.blocktrail.com/BTC/tx/e2029990fa75e9fc642f149dad196ac6b64b9c4a6db254f23a580b7508fc34d7).
A `NAME_UPDATE` transaction changes the name's zone file hash. You would send A `NAME_UPDATE` transaction changes the name's zone file hash. You would send
one of these transactions if you wanted to change the name's zone file contents. one of these transactions if you wanted to change the name's zone file contents.
@ -39,10 +41,12 @@ hub](https://github.com/blockstack/gaia) and want other people to read from it.
A `NAME_UPDATE` transaction is generated from the name, a recent [consensus A `NAME_UPDATE` transaction is generated from the name, a recent [consensus
hash](#bns-forks), and the new zone file hash. The reference clients gather hash](#bns-forks), and the new zone file hash. The reference clients gather
this information automatically. See the [transaction format]({{ site.baseurl }}/core/wire-format.html) this information automatically. See the [transaction format](/core/wire-format)
document for details on how to construct this transaction. document for details on how to construct this transaction.
## NAME_TRANSFER ([live example](https://www.blocktrail.com/BTC/tx/7a0a3bb7d39b89c3638abc369c85b5c028d0a55d7804ba1953ff19b0125f3c24)) ## NAME_TRANSFER
See [live example](https://www.blocktrail.com/BTC/tx/7a0a3bb7d39b89c3638abc369c85b5c028d0a55d7804ba1953ff19b0125f3c24).
A `NAME_TRANSFER` transaction changes the name's public key hash. You would A `NAME_TRANSFER` transaction changes the name's public key hash. You would
send one of these transactions if you wanted to: send one of these transactions if you wanted to:
@ -57,10 +61,12 @@ recipient's name does not resolve to your zone file.
The `NAME_TRANSFER` transaction is generated from the name, a recent [consensus The `NAME_TRANSFER` transaction is generated from the name, a recent [consensus
hash](#bns-forks), and the new public key hash. The reference clients gather hash](#bns-forks), and the new public key hash. The reference clients gather
this information automatically. See the [transaction format]({{ site.baseurl }}/core/wire-format.html) this information automatically. See the [transaction format](/core/wire-format)
document for details on how to construct this transaction. document for details on how to construct this transaction.
## NAME_REVOKE ([live example](https://www.blocktrail.com/BTC/tx/eb2e84a45cf411e528185a98cd5fb45ed349843a83d39fd4dff2de47adad8c8f)) ## NAME_REVOKE
See [live example](https://www.blocktrail.com/BTC/tx/eb2e84a45cf411e528185a98cd5fb45ed349843a83d39fd4dff2de47adad8c8f).
A `NAME_REVOKE` transaction makes a name unresolvable. The BNS consensus rules A `NAME_REVOKE` transaction makes a name unresolvable. The BNS consensus rules
stipulate that once a name is revoked, no one can change its public key hash or stipulate that once a name is revoked, no one can change its public key hash or
@ -71,10 +77,12 @@ You should only do this if your private key is compromised, or if you want to
render your name unusable for whatever reason. It is rarely used in practice. render your name unusable for whatever reason. It is rarely used in practice.
The `NAME_REVOKE` operation is generated using only the name. See the The `NAME_REVOKE` operation is generated using only the name. See the
[transaction format]({{ site.baseurl }}/core/wire-format.html) document for details on how to construct [transaction format](/core/wire-format) document for details on how to construct
it. it.
## NAME_RENEWAL ([live example](https://www.blocktrail.com/BTC/tx/e543211b18e5d29fd3de7c0242cb017115f6a22ad5c6d51cf39e2b87447b7e65)) ## NAME_RENEWAL
See [live example](https://www.blocktrail.com/BTC/tx/e543211b18e5d29fd3de7c0242cb017115f6a22ad5c6d51cf39e2b87447b7e65).
Depending in the namespace rules, a name can expire. For example, names in the Depending in the namespace rules, a name can expire. For example, names in the
`.id` namespace expire after 2 years. You need to send a `NAME_RENEWAL` every `.id` namespace expire after 2 years. You need to send a `NAME_RENEWAL` every
@ -93,5 +101,5 @@ If your name is in a namespace where names do not expire, then you never need to
use this transaction. use this transaction.
When you send a `NAME_RENEWAL`, you have the option of also setting a new public When you send a `NAME_RENEWAL`, you have the option of also setting a new public
key hash and a new zone file hash. See the [transaction format]({{ site.baseurl }}/core/wire-format.html) key hash and a new zone file hash. See the [transaction format](/core/wire-format)
document for details on how to construct this transaction. document for details on how to construct this transaction.

4
src/pages/core/naming/namespaces.md

@ -51,11 +51,11 @@ layers in this hierarchy related to naming:
blockchain transactions. Example names include `verified.podcast` and blockchain transactions. Example names include `verified.podcast` and
`muneeb.id`. Anyone can create a BNS name, as long as the namespace that `muneeb.id`. Anyone can create a BNS name, as long as the namespace that
contains it exists already. The state for BNS names is usually stored in the [Atlas contains it exists already. The state for BNS names is usually stored in the [Atlas
network]({{ site.baseurl }}/core/atlas/overview.html). network](/core/atlas/overview).
- **BNS subdomains**. These are names whose records are stored off-chain, - **BNS subdomains**. These are names whose records are stored off-chain,
but are collectively anchored to the blockchain. The ownership and state for but are collectively anchored to the blockchain. The ownership and state for
these names lives within the [Atlas network]({{ site.baseurl }}/core/atlas/overview.html). While BNS these names lives within the [Atlas network](/core/atlas/overview). While BNS
subdomains are owned by separate private keys, a BNS name owner must subdomains are owned by separate private keys, a BNS name owner must
broadcast their subdomain state. Example subdomains include `jude.personal.id` broadcast their subdomain state. Example subdomains include `jude.personal.id`
and `podsaveamerica.verified.podcast`. Unlike BNS namespaces and names, the and `podsaveamerica.verified.podcast`. Unlike BNS namespaces and names, the

56
src/pages/core/naming/pickname.md

@ -33,7 +33,9 @@ gives them a way to measure economic activity.
Developers can query individual namespaces and look up names within them using Developers can query individual namespaces and look up names within them using
the BNS API. the BNS API.
## List all namespaces in existence ([reference](https://core.blockstack.org/#namespace-operations-get-all-namespaces)). ## List all namespaces in existence
See [reference](https://core.blockstack.org/#namespace-operations-get-all-namespaces).
```bash ```bash
$ curl https://core.blockstack.org/v1/namespaces $ curl https://core.blockstack.org/v1/namespaces
@ -44,7 +46,9 @@ $ curl https://core.blockstack.org/v1/namespaces
] ]
``` ```
## List all names within a namespace ([reference](https://core.blockstack.org/#namespace-operations-get-all-namespaces)). ## List all names within a namespace
See [reference](https://core.blockstack.org/#namespace-operations-get-all-namespaces).
```bash ```bash
$ curl https://core.blockstack.org/v1/namespaces/id/names?page=0 $ curl https://core.blockstack.org/v1/namespaces/id/names?page=0
@ -75,7 +79,9 @@ $ curl https://core.blockstack.org/v1/namespaces/id/names?page=0
Each page returns a batch of 100 names. Each page returns a batch of 100 names.
## Get the Cost to Register a Namespace (<a href="https://core.blockstack.org/#price-checks-get-namespace-price" target="\_blank">reference</a>). ## Get the Cost to Register a Namespace
See [reference](https://core.blockstack.org/#price-checks-get-namespace-price).
```bash ```bash
$ curl https://core.blockstack.org/v1/prices/namespaces/test $ curl https://core.blockstack.org/v1/prices/namespaces/test
@ -84,9 +90,11 @@ $ curl https://core.blockstack.org/v1/prices/namespaces/test
} }
``` ```
If you want to register a namespace, please see the [namespace creation section]({{ site.baseurl }}/core/naming/namespaces.html). If you want to register a namespace, please see the [namespace creation section](/core/naming/namespaces).
## Getting the Current Consensus Hash
## Getting the Current Consensus Hash (<a href="https://core.blockstack.org/#blockchain-operations-get-consensus-hash" target="\_blank">reference</a>). See [https://core.blockstack.org/#blockchain-operations-get-consensus-hash](reference).
```bash ```bash
$ curl -sL https://core.blockstack.org/v1/blockchains/bitcoin/consensus $ curl -sL https://core.blockstack.org/v1/blockchains/bitcoin/consensus
@ -96,22 +104,24 @@ $ curl -sL https://core.blockstack.org/v1/blockchains/bitcoin/consensus
``` ```
A recent consensus hash is required to create a `NAMESPACE_PREORDER` transaction. The reference A recent consensus hash is required to create a `NAMESPACE_PREORDER` transaction. The reference
BNS clients do this automatically. See the [transaction format]({{ site.baseurl }}/core/wire-format.html) BNS clients do this automatically. See the [transaction format](/core/wire-format)
document for details on how the consensus hash is used to construct the document for details on how the consensus hash is used to construct the transaction.
transaction.
## Create a namespace ## Create a namespace
There are four steps to creating a namespace: ### There are four steps to creating a namespace:
#### Step 1: Send a `NAMESPACE_PREORDER` transaction
1. **Send a `NAMESPACE_PREORDER` transaction** ([live example](https://www.blocktrail.com/BTC/tx/5f00b8e609821edd6f3369ee4ee86e03ea34b890e242236cdb66ef6c9c6a1b28)).
This is the first step. This registers the _salted hash_ of the namespace with BNS nodes, and burns the This is the first step. This registers the _salted hash_ of the namespace with BNS nodes, and burns the
requisite amount of cryptocurrency. In addition, it proves to the requisite amount of cryptocurrency. In addition, it proves to the BNS nodes that user has honored the
BNS nodes that user has honored the BNS consensus rules by including BNS consensus rules by including a recent _consensus hash_ in the transaction (see the section on
a recent _consensus hash_ in the transaction [BNS forks](#bns-forks) for details).
(see the section on [BNS forks](#bns-forks) for details).
See `NAMESPACE_PREORDER` ([live example](https://www.blocktrail.com/BTC/tx/5f00b8e609821edd6f3369ee4ee86e03ea34b890e242236cdb66ef6c9c6a1b28)).
#### Step 2: Send a `NAMESPACE_REVEAL` transaction
2. **Send a `NAMESPACE_REVEAL` transaction** ([live example](https://www.blocktrail.com/BTC/tx/ab54b1c1dd5332dc86b24ca2f88b8ca0068485edf0c322416d104c5b84133a32)).
This is the second step. This reveals the salt and the namespace ID (pairing it with its This is the second step. This reveals the salt and the namespace ID (pairing it with its
`NAMESPACE_PREORDER`), it reveals how long names last in this namespace before `NAMESPACE_PREORDER`), it reveals how long names last in this namespace before
they expire or must be renewed, and it sets a _price function_ for the namespace they expire or must be renewed, and it sets a _price function_ for the namespace
@ -122,17 +132,27 @@ There are four steps to creating a namespace:
has the option to collect name registration fees for the first year of the has the option to collect name registration fees for the first year of the
namespace's existence by setting a _namespace creator address_. namespace's existence by setting a _namespace creator address_.
3. **Seed the namespace with `NAME_IMPORT` transactions** ([live example](https://www.blocktrail.com/BTC/tx/c698ac4b4a61c90b2c93dababde867dea359f971e2efcf415c37c9a4d9c4f312)). See `NAMESPACE_REVEAL` ([live example](https://www.blocktrail.com/BTC/tx/ab54b1c1dd5332dc86b24ca2f88b8ca0068485edf0c322416d104c5b84133a32)).
#### Step 3: Seed the namespace with `NAME_IMPORT` transactions
Once the namespace has been revealed, the user has the option to populate it with a set of Once the namespace has been revealed, the user has the option to populate it with a set of
names. Each imported name is given both an owner and some off-chain state. names. Each imported name is given both an owner and some off-chain state.
This step is optional---namespace creators are not required to import names. This step is optional---namespace creators are not required to import names.
4. **Send a `NAMESPACE_READY` transaction** ([live example](https://www.blocktrail.com/BTC/tx/2bf9a97e3081886f96c4def36d99a677059fafdbd6bdb6d626c0608a1e286032)). See `NAME_IMPORT` ([live example](https://www.blocktrail.com/BTC/tx/c698ac4b4a61c90b2c93dababde867dea359f971e2efcf415c37c9a4d9c4f312)).
#### Step 4: Send a `NAMESPACE_READY` transaction
This is the final step of the process. It _launches_ the namespace, which makes it available to the This is the final step of the process. It _launches_ the namespace, which makes it available to the
public. Once a namespace is ready, anyone can register a name in it if they public. Once a namespace is ready, anyone can register a name in it if they
pay the appropriate amount of cryptocurrency (according to the price funtion pay the appropriate amount of cryptocurrency (according to the price funtion
revealed in step 2). revealed in step 2).
See `NAMESPACE_READY` ([live example](https://www.blocktrail.com/BTC/tx/2bf9a97e3081886f96c4def36d99a677059fafdbd6bdb6d626c0608a1e286032)).
---
The reason for the `NAMESPACE_PREORDER/NAMESPACE_REVEAL` pairing is to prevent The reason for the `NAMESPACE_PREORDER/NAMESPACE_REVEAL` pairing is to prevent
frontrunning. The BNS consensus rules require a `NAMESPACE_REVEAL` to be frontrunning. The BNS consensus rules require a `NAMESPACE_REVEAL` to be
paired with a previous `NAMESPACE_PREORDER` sent within the past 24 hours. paired with a previous `NAMESPACE_PREORDER` sent within the past 24 hours.
@ -149,6 +169,6 @@ Once the user issues the `NAMESPACE_PREORDER` and `NAMESPACE_REVEAL`, they have
do this, then the namespace they created disappears (along with all the names do this, then the namespace they created disappears (along with all the names
they imported). they imported).
Developers wanting to create their own namespaces should read the [namespace creation section](/core/naming/namespaces.html) document. It is highly recommended that Developers wanting to create their own namespaces should read the [namespace creation section](/core/naming/namespaces) document. It is highly recommended that
developers request individual support before creating their own space, given the large amount of developers request individual support before creating their own space, given the large amount of
cryptocurrency at stake. cryptocurrency at stake.

12
src/pages/core/naming/register.md

@ -65,7 +65,9 @@ the characters `a-z`, `0-9`, `+`, `-`, `_`, and `.`. This is to prevent
`NAME_REGISTRATION` transactions that do not conform to this requirement will be `NAME_REGISTRATION` transactions that do not conform to this requirement will be
ignored. ignored.
## Getting a Name's Registration Fee ([reference](https://core.blockstack.org/#price-checks-get-name-price)) ## Getting a Name's Registration Fee
See [reference](https://core.blockstack.org/#price-checks-get-name-price).
```bash ```bash
$ curl -sL https://core.blockstack.org/v1/prices/names/helloworld.id | jq -r ".name_price" $ curl -sL https://core.blockstack.org/v1/prices/names/helloworld.id | jq -r ".name_price"
@ -79,7 +81,9 @@ Note the use of `jq -r` to select the `"name_price"` field. This API
endpoint may return other ancilliary data regarding transaction fee estimation, endpoint may return other ancilliary data regarding transaction fee estimation,
but this is the only field guaranteed by this specification to be present. but this is the only field guaranteed by this specification to be present.
## Getting the Current Consensus Hash ([reference](https://core.blockstack.org/#blockchain-operations-get-consensus-hash)) ## Getting the Current Consensus Hash
See [reference](https://core.blockstack.org/#blockchain-operations-get-consensus-hash).
```bash ```bash
$ curl -sL https://core.blockstack.org/v1/blockchains/bitcoin/consensus $ curl -sL https://core.blockstack.org/v1/blockchains/bitcoin/consensus
@ -90,7 +94,7 @@ $ curl -sL https://core.blockstack.org/v1/blockchains/bitcoin/consensus
The consensus hash must be included in the `NAME_PREORDER` transaction. The BNS The consensus hash must be included in the `NAME_PREORDER` transaction. The BNS
clients do this automatically. See the [transaction format clients do this automatically. See the [transaction format
document]({{ site.baseurl }}/core/wire-format.html) for details as to how to include this in the document](/core/wire-format) for details as to how to include this in the
transaction. transaction.
## Registering a Name ## Registering a Name
@ -103,4 +107,4 @@ dynamically and automatically, and broadcast both the `NAME_PREORDER` and
`NAME_REGISTRATION` transactions at the right times. `NAME_REGISTRATION` transactions at the right times.
If you want to make your own registration client, you should see the If you want to make your own registration client, you should see the
[transaction format]({{ site.baseurl }}/core/wire-format.html) document. [transaction format](/core/wire-format) document.

6
src/pages/core/naming/resolving.md

@ -47,7 +47,7 @@ since they can be authenticated using the hashes discovered by indexing the
blockchain under the BNS consensus rules. blockchain under the BNS consensus rules.
BNS nodes implement a decentralized storage system for zone files called the BNS nodes implement a decentralized storage system for zone files called the
[Atlas network]({{ site.baseurl }}/core/atlas/overview.html). In this system, BNS nodes eagerly replicate [Atlas network](/core/atlas/overview). In this system, BNS nodes eagerly replicate
all the zone files they know about to one another, so that eventually every BNS all the zone files they know about to one another, so that eventually every BNS
node has a full replica of all zone files. node has a full replica of all zone files.
@ -229,7 +229,9 @@ scope are:
The name's _entire_ history is returned. This includes the history of the name The name's _entire_ history is returned. This includes the history of the name
under its previous owner, if the name expired and was reregistered. under its previous owner, if the name expired and was reregistered.
## Look up the list of names owned by a given public key hash ([reference](https://core.blockstack.org/#name-querying-get-names-owned-by-address)) ## Look up the list of names owned by a given public key hash
See [reference](https://core.blockstack.org/#name-querying-get-names-owned-by-address).
```bash ```bash
$ curl https://core.blockstack.org/v1/addresses/bitcoin/16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg $ curl https://core.blockstack.org/v1/addresses/bitcoin/16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg

42
src/pages/core/naming/search.md

@ -1,5 +1,5 @@
--- ---
description: 'Blockstack naming service (BNS)' description: Blockstack naming service (BNS)
--- ---
# How to build a Profile Search Index # How to build a Profile Search Index
@ -31,7 +31,7 @@ This document describes how to setup the search subsystem to respond at that end
- **Step 1:** First, make sure you have [virtualenv installed](http://docs.python-guide.org/en/latest/dev/virtualenvs/). - **Step 1:** First, make sure you have [virtualenv installed](http://docs.python-guide.org/en/latest/dev/virtualenvs/).
Then, setup the API and search subsystem: Then, setup the API and search subsystem:
``` ```bash
$ sudo apt-get install -y mongodb memcached python-dev libmemcached-dev zlib1g-dev nginx $ sudo apt-get install -y mongodb memcached python-dev libmemcached-dev zlib1g-dev nginx
$ sudo pip install uwsgi $ sudo pip install uwsgi
$ git clone https://github.com/blockstack/blockstack-core.git --branch api $ git clone https://github.com/blockstack/blockstack-core.git --branch api
@ -47,7 +47,7 @@ $ sudo mkdir /var/blockstack-search && sudo chown $USER:$USER /var/blockstack-se
- **Step 3:** Fetch the data for the .id namespace and respective profiles. Note, you may want to redirect stderr to a file, as there is a lot of debug output. - **Step 3:** Fetch the data for the .id namespace and respective profiles. Note, you may want to redirect stderr to a file, as there is a lot of debug output.
``` ```bash
$ cd api/ $ cd api/
$ python -m search.fetch_data --fetch_namespace $ python -m search.fetch_data --fetch_namespace
@ -57,13 +57,13 @@ $ python -m search.fetch_data --fetch_profiles
- **Step 4:** Create the search index: - **Step 4:** Create the search index:
``` ```bash
python -m search.basic_index --refresh python -m search.basic_index --refresh
``` ```
- **Step 5:** Enable search API endpoint: - **Step 5:** Enable search API endpoint:
``` ```bash
$ sed -i 's/SEARCH_API_ENDPOINT_ENABLED \= False/SEARCH_API_ENDPOINT_ENABLED \= True/' config.py $ sed -i 's/SEARCH_API_ENDPOINT_ENABLED \= False/SEARCH_API_ENDPOINT_ENABLED \= True/' config.py
``` ```
@ -71,18 +71,20 @@ $ sed -i 's/SEARCH_API_ENDPOINT_ENABLED \= False/SEARCH_API_ENDPOINT_ENABLED \=
You can quickly test the search index from the command line: You can quickly test the search index from the command line:
``` ```bash
python -m search.substring_search --search_name "Fred Wil" python -m search.substring_search --search_name "Fred Wil"
python -m search.substring_search --search_twitter fredwil python -m search.substring_search --search_twitter fredwil
``` ```
You can also use the search API end-point: You can also use the search API end-point:
> curl -G {machine_ip}:port/search/name -d "query=muneeb" ```bash
curl -G {machine_ip}:port/search/name -d "query=muneeb"
```
Sample Response: Sample Response:
``` ```json
{ {
"people": [ "people": [
{ {
@ -131,7 +133,6 @@ Sample Response:
}, },
"username": "muneebali1" "username": "muneebali1"
} }
] ]
} }
``` ```
@ -140,7 +141,7 @@ Sample Response:
### Requirements: ### Requirements:
``` ```bash
sudo apt-get install mongodb sudo apt-get install mongodb
sudo apt-get install memcached libmemcached-dev sudo apt-get install memcached libmemcached-dev
sudo apt-get install python2.7-dev sudo apt-get install python2.7-dev
@ -153,25 +154,29 @@ Elastic Search library is not in github and resides at unix/lib/elastic
the current version we're using is _0.90.2_. Download from: the current version we're using is _0.90.2_. Download from:
> wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.2.zip ```bash
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.2.zip
```
before installing pylimbmc make sure [memcached]({{ site.baseurl }}/core/memcached.html) is installed. before installing pylimbmc make sure [memcached](/core/memcached) is installed.
Ensure that mongodb and elastic search are running Ensure that mongodb and elastic search are running
starting elastic search: starting elastic search:
``` ```bash
$elasticsearch (on mac) $elasticsearch (on mac)
bin/elasticsearch -d (on linux) bin/elasticsearch -d (on linux)
``` ```
To test if elastic search is running: To test if elastic search is running:
> curl -X GET http://localhost:9200/ ```bash
curl -X GET http://localhost:9200/
```
returns: returns:
``` ```json
{ {
"ok": true, "ok": true,
"status": 200, "status": 200,
@ -180,9 +185,12 @@ returns:
"number": "0.90.2", "number": "0.90.2",
"snapshot_build": false, "snapshot_build": false,
"lucene_version": "4.3.1" "lucene_version": "4.3.1"
}, }
}
``` ```
Create Index: Create Index:
> python create_search_index.py --create_index ```bash
python create_search_index.py --create_index
```

4
src/pages/core/naming/subdomains.md

@ -31,7 +31,7 @@ cheaply, because they are broadcast to the
BNS network in batches. A single blockchain transaction can send up to 120 BNS network in batches. A single blockchain transaction can send up to 120
subdomain operations. subdomain operations.
This is achieved by storing subdomain records in the [Atlas Network]({{ site.baseurl }}/core/atlas/overview.html). This is achieved by storing subdomain records in the [Atlas Network](/core/atlas/overview).
An on-chain name owner broadcasts subdomain operations by encoding them as An on-chain name owner broadcasts subdomain operations by encoding them as
`TXT` records within a DNS zone file. To broadcast the zone file, `TXT` records within a DNS zone file. To broadcast the zone file,
the name owner sets the new zone file hash with a `NAME_UPDATE` transaction and the name owner sets the new zone file hash with a `NAME_UPDATE` transaction and
@ -241,5 +241,5 @@ implementation of a [BNS Subdomain Registrar](https://github.com/blockstack/subd
to help developers broadcast subdomain operations. Users would still own their to help developers broadcast subdomain operations. Users would still own their
subdomain names; the registrar simply gives developers a convenient way for them subdomain names; the registrar simply gives developers a convenient way for them
to register and manage them in the context of a particular application. to register and manage them in the context of a particular application.
Please see the [tutorial on running a subdomain registrar]({{ site.baseurl }}/core/naming/tutorial_subdomains.html) for Please see the [tutorial on running a subdomain registrar](/core/naming/tutorial_subdomains) for
details on how to use it. details on how to use it.

60
src/pages/core/naming/tutorial_subdomains.md

@ -49,7 +49,7 @@ for the TXT entry. We'll have the following strings with identifiers:
4. **seqn**: the sequence number 4. **seqn**: the sequence number
5. **sig**: signature of the above data. 5. **sig**: signature of the above data.
``` ```bash
$ORIGIN bar.id $ORIGIN bar.id
$TTL 3600 $TTL 3600
pubkey TXT "pubkey:data:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" pubkey TXT "pubkey:data:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
@ -69,7 +69,7 @@ At 4kb zonefile size, we can only fit around 20 updates per zonefile.
The directory `subdomain_registrar/` contains our code for running a The directory `subdomain_registrar/` contains our code for running a
subdomain registrar. It can be executed by running: subdomain registrar. It can be executed by running:
``` ```bash
$ blockstack-subdomain-registrar start foo.id $ blockstack-subdomain-registrar start foo.id
``` ```
@ -88,33 +88,31 @@ You can change the location of the config file (and the database), by setting th
Subdomain registrations can be submitted to this endpoint using a REST Subdomain registrations can be submitted to this endpoint using a REST
API. API.
``` ```bash
POST /register POST /register
``` ```
The schema for registration is: The schema for registration is:
``` ```json
{ {
'type' : 'object', "type": "object",
'properties' : { "properties": {
'name' : { "name": {
'type': 'string', "type": "string",
'pattern': '([a-z0-9\-_+]{3,36})$' "pattern": "([a-z0-9-_+]{3,36})$"
}, },
'owner_address' : { "owner_address": {
'type': 'string', "type": "string",
'pattern': schemas.OP_ADDRESS_PATTERN "pattern": schemas.OP_ADDRESS_PATTERN
}, },
'zonefile' : { "zonefile": {
'type' : 'string', "type": "string",
'maxLength' : blockstack_constants.RPC_MAX_ZONEFILE_LEN "maxLength": blockstack_constants.RPC_MAX_ZONEFILE_LEN
} }
}, },
'required':[ "required": ["name", "owner_address", "zonefile"],
'name', 'owner_address', 'zonefile' "additionalProperties": True
],
'additionalProperties' : True
} }
``` ```
@ -125,7 +123,7 @@ The registrar will:
On success, this returns `202` and the message On success, this returns `202` and the message
``` ```json
{ "status": "true", "message": "Subdomain registration queued." } { "status": "true", "message": "Subdomain registration queued." }
``` ```
@ -146,19 +144,21 @@ GET /status/{subdomain}
The registrar checks if the subdomain has propagated (i.e., the The registrar checks if the subdomain has propagated (i.e., the
registration is completed), in which case the following is returned: registration is completed), in which case the following is returned:
``` ```json
{ "status": "Subdomain already propagated" } { "status": "Subdomain already propagated" }
``` ```
Or, if the subdomain has already been submitted in a transaction: Or, if the subdomain has already been submitted in a transaction:
``` ```json
{"status": "Your subdomain was registered in transaction 09a40d6ea362608c68da6e1ebeb3210367abf7aa39ece5fd57fd63d269336399 -- it should propagate on the network once it has 6 confirmations."} {
"status": "Your subdomain was registered in transaction 09a40d6ea362608c68da6e1ebeb3210367abf7aa39ece5fd57fd63d269336399 -- it should propagate on the network once it has 6 confirmations."
}
``` ```
If the subdomain still hasn't been submitted yet: If the subdomain still hasn't been submitted yet:
``` ```json
{ "status": "Subdomain is queued for update and should be announced within the next few blocks." } { "status": "Subdomain is queued for update and should be announced within the next few blocks." }
``` ```
@ -193,7 +193,7 @@ This means that search is _not_ yet supported.
The lookups work just like normal -- it returns the user's The lookups work just like normal -- it returns the user's
profile object: profile object:
``` ```bash
$ curl -H "Authorization: bearer blockstack_integration_test_api_password" -H "Origin: http://localhost:3000" http://localhost:16268/v1/users/bar.foo.id -v -s | python -m json.tool $ curl -H "Authorization: bearer blockstack_integration_test_api_password" -H "Origin: http://localhost:3000" http://localhost:16268/v1/users/bar.foo.id -v -s | python -m json.tool
* Trying 127.0.0.1... * Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 16268 (#0) * Connected to localhost (127.0.0.1) port 16268 (#0)
@ -224,7 +224,7 @@ $ curl -H "Authorization: bearer blockstack_integration_test_api_password" -H "O
Name info lookups are also supported (this should enable authenticating logins Name info lookups are also supported (this should enable authenticating logins
with `blockstack.js`, but I will need to double check). with `blockstack.js`, but I will need to double check).
``` ```bash
$ curl -H "Authorization: bearer XXXX" -H "Origin: http://localhost:3000" http://localhost:6270/v1/names/created_equal.self_evident_truth.id -s | python -m json.tool $ curl -H "Authorization: bearer XXXX" -H "Origin: http://localhost:3000" http://localhost:6270/v1/names/created_equal.self_evident_truth.id -s | python -m json.tool
{ {
"address": "1AYddAnfHbw6bPNvnsQFFrEuUdhMhf2XG9", "address": "1AYddAnfHbw6bPNvnsQFFrEuUdhMhf2XG9",
@ -261,13 +261,13 @@ correct environment variables for it to run).
Once this environment has started, you can issue a registration request from curl: Once this environment has started, you can issue a registration request from curl:
``` ```bash
curl -X POST -H 'Content-Type: application/json' --data '{"zonefile": "$ORIGIN baz\n$TTL 3600\n_file URI 10 1 \"file:///tmp/baz.profile.json\"\n", "name": "baz", "owner_address": "14x2EMRz1gf16UzGbxZh2c6sJg4A8wcHLD"}' http://localhost:3000/register/ curl -X POST -H 'Content-Type: application/json' --data '{"zonefile": "$ORIGIN baz\n$TTL 3600\n_file URI 10 1 \"file:///tmp/baz.profile.json\"\n", "name": "baz", "owner_address": "14x2EMRz1gf16UzGbxZh2c6sJg4A8wcHLD"}' http://localhost:3000/register/
``` ```
This registers `baz.foo.id` -- you can check the registrar's status with This registers `baz.foo.id` -- you can check the registrar's status with
``` ```bash
curl http://localhost:3000/status/baz curl http://localhost:3000/status/baz
``` ```
@ -276,13 +276,13 @@ The API endpoints `/v1/users/<foo.bar.tld>`,
For example: For example:
``` ```bash
curl http://localhost:6270/v1/names/baz.foo.id | python -m json.tool curl http://localhost:6270/v1/names/baz.foo.id | python -m json.tool
``` ```
Will return: Will return:
``` ```json
{ {
"address": "1Nup2UcbVuVoDZeZCtR4vjSkrvTi8toTqc", "address": "1Nup2UcbVuVoDZeZCtR4vjSkrvTi8toTqc",
"blockchain": "bitcoin", "blockchain": "bitcoin",

78
src/pages/core/smart/clarityRef.md

@ -2,6 +2,8 @@
description: 'Clarity: Language Reference' description: 'Clarity: Language Reference'
--- ---
import { ClarityKeywordReference, ClarityFunctionReference } from '@components/clarity-ref'
# Clarity Language Reference # Clarity Language Reference
This file contains the reference for the Clarity language. This file contains the reference for the Clarity language.
@ -61,22 +63,25 @@ called smart contract function.
We distinguish 2 different types of `contract-call?`: We distinguish 2 different types of `contract-call?`:
- Static dispatch: the callee is a known, invariant contract available ### Static dispatch
The callee is a known, invariant contract available
on-chain when the caller contract is deployed. In this case, the on-chain when the caller contract is deployed. In this case, the
callee's principal is provided as the first argument, followed by the callee's principal is provided as the first argument, followed by the
name of the method and its arguments: name of the method and its arguments:
```scheme ```clarity
(contract-call? (contract-call?
.registrar .registrar
register-name register-name
name-to-register) name-to-register)
``` ```
- Dynamic dispatch: the callee is passed as an argument, and typed ### Dynamic dispatch
as a trait reference (`<A>`).
The callee is passed as an argument, and typed as a trait reference (`<A>`).
```scheme ```clarity
(define-public (swap (token-a <can-transfer-tokens>) (define-public (swap (token-a <can-transfer-tokens>)
(amount-a uint) (amount-a uint)
(owner-a principal) (owner-a principal)
@ -90,14 +95,14 @@ We distinguish 2 different types of `contract-call?`:
Traits can either be locally defined: Traits can either be locally defined:
```scheme ```clarity
(define-trait can-transfer-tokens ( (define-trait can-transfer-tokens (
(transfer-from? (principal principal uint) (response uint))) (transfer-from? (principal principal uint) (response uint)))
``` ```
Or imported from an existing contract: Or imported from an existing contract:
```scheme ```clarity
(use-trait can-transfer-tokens (use-trait can-transfer-tokens
.contract-defining-trait.can-transfer-tokens) .contract-defining-trait.can-transfer-tokens)
``` ```
@ -107,7 +112,7 @@ They can either be "compatible" with a trait by defining methods
matching some of the methods defined in a trait, or explicitely declare matching some of the methods defined in a trait, or explicitely declare
conformance using the `impl-trait` statement: conformance using the `impl-trait` statement:
```scheme ```clarity
(impl-trait .contract-defining-trait.can-transfer-tokens) (impl-trait .contract-defining-trait.can-transfer-tokens)
``` ```
@ -129,55 +134,8 @@ The following limitations are imposed on contract calls:
## Keyword reference ## Keyword reference
<!-- TODO: make work with react --> <ClarityKeywordReference />
<!-- {% capture keyword_list %} -->
<!-- {% for entry in site.data.clarityRef.keywords %} --> ## Function reference
<!-- {{ entry.name }}||{{ entry.output_type }}||{{ entry.description }}||{{ entry.example }} -->
<!-- {% if forloop.last == false %}::{% endif%} --> <ClarityFunctionReference />
<!-- {% endfor %} -->
<!-- {% endcapture %} -->
<!-- {% assign keyword_array = keyword_list | split: '::' | sort %} -->
<!-- {% for keyword in keyword_array %} -->
<!-- {% assign keyword_vals = keyword | split: '||' %} -->
<!-- ### {{keyword_vals[0] | lstrip | rstrip}} -->
<!-- <code>{{keyword_vals[1] | lstrip | rstrip }}</code> -->
<!-- {{keyword_vals[2]}} -->
<!-- **Example** -->
<!-- ```cl -->
<!-- {{keyword_vals[3] | lstrip | rstrip }} -->
<!-- ``` -->
<!-- <hr class="uk-divider-icon"> -->
<!-- {% endfor %} -->
<!-- ## Function reference -->
<!-- {% capture function_list %} -->
<!-- {% for entry in site.data.clarityRef.functions %} -->
<!-- {{ entry.name }}||{{ entry.signature }}||{{ entry.input_type }}||{{ entry.output_type }}||{{ entry.description }}||{{ entry.example }} -->
<!-- {% if forloop.last == false %}::{% endif%} -->
<!-- {% endfor %} -->
<!-- {% endcapture %} -->
<!-- {% assign function_array = function_list | split: '::' | sort %} -->
<!-- {% for function in function_array %} -->
<!-- {% assign function_vals = function | split: '||' %} -->
<!-- ### {{function_vals[0] | lstrip | rstrip}} -->
<!-- **Syntax** -->
<!-- ```{{function_vals[1] | lstrip | rstrip }} ``` -->
<!-- INPUT: <code>{{function_vals[2] | lstrip | rstrip }}</code><br> -->
<!-- OUTPUT: <code>{{function_vals[3] | lstrip | rstrip }}</code> -->
<!-- {{function_vals[4]}} -->
<!-- **Example** -->
<!-- ```cl -->
<!-- {{function_vals[5] | lstrip | rstrip }} -->
<!-- ``` -->
<!-- <hr class="uk-divider-icon"> -->
<!-- {% endfor %} -->

6
src/pages/core/smart/cli-wallet-quickstart.md

@ -20,7 +20,7 @@ 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. 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.
``` ```bash
$ blockstack make_keychain -t $ blockstack make_keychain -t
{ {
@ -53,7 +53,7 @@ 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. Once you’ve requested Testnet Stacks tokens from the faucet, you can check the balance of your account using the following command.
``` ```bash
$ blockstack balance ST1BG7MHW2R524WMF7X8PGG3V45ZN040EB9EW0GQJ -t $ blockstack balance ST1BG7MHW2R524WMF7X8PGG3V45ZN040EB9EW0GQJ -t
{ {
@ -86,7 +86,7 @@ In order to send tokens, we will need the 5 parameters below.
Once we have the parameters, we can use the `send_tokens` command: Once we have the parameters, we can use the `send_tokens` command:
``` ```bash
$ blockstack send_tokens ST2KMMVJAB00W5Z6XWTFPH6B13JE9RJ2DCSHYX0S7 1000 200 0 381314da39a45f43f45ffd33b5d8767d1a38db0da71fea50ed9508e048765cf301 -t $ blockstack send_tokens ST2KMMVJAB00W5Z6XWTFPH6B13JE9RJ2DCSHYX0S7 1000 200 0 381314da39a45f43f45ffd33b5d8767d1a38db0da71fea50ed9508e048765cf301 -t
d32de0d66b4a07e0d7eeca320c37a10111c8c703315e79e17df76de6950c622c d32de0d66b4a07e0d7eeca320c37a10111c8c703315e79e17df76de6950c622c

4
src/pages/core/smart/overview.md

@ -45,6 +45,6 @@ Note some of the key Clarity language rules and limitations.
## Learning Clarity ## Learning Clarity
The tutorials are ordered from "beginner" to "advanced." Start with the [Hello World tutorial](tutorial.html), then learn how to construct [a counter](tutorial-counter.html), and finally, learn how to [test your smart contracts](tutorial-test.html) using Mocha. The tutorials are ordered from "beginner" to "advanced." Start with the [Hello World tutorial](tutorial), then learn how to construct [a counter](tutorial-counter), and finally, learn how to [test your smart contracts](tutorial-test) using Mocha.
Once you've got the hang of the general workflow, environment, and language syntax, you should be ready to start writing contracts, referring to the [Clarity language reference](clarityRef.html) as you go. Once you've got the hang of the general workflow, environment, and language syntax, you should be ready to start writing contracts, referring to the [Clarity language reference](clarityRef) as you go.

18
src/pages/core/smart/principals.md

@ -23,7 +23,7 @@ obtain the current principal. The following example defines a transaction
type that transfers `amount` microSTX from the sender to a recipient if amount type that transfers `amount` microSTX from the sender to a recipient if amount
is a multiple of 10, otherwise returning a 400 error code. is a multiple of 10, otherwise returning a 400 error code.
```scheme ```clarity
(define-public (transfer-to-recipient! (recipient principal) (amount uint)) (define-public (transfer-to-recipient! (recipient principal) (amount uint))
(if (is-eq (mod amount 10) 0) (if (is-eq (mod amount 10) 0)
(stx-transfer? amount tx-sender recipient) (stx-transfer? amount tx-sender recipient)
@ -41,7 +41,7 @@ contracts A, B, and C, each with an `invoke` function such that
When a user Bob issues a transaction that calls `A::invoke`, the value 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: of `contract-caller` in each successive invoke function's body would change:
``` ```clarity
in A::invoke, contract-caller = Bob in A::invoke, contract-caller = Bob
in B::invoke, contract-caller = A in B::invoke, contract-caller = A
in C::invoke, contract-caller = B in C::invoke, contract-caller = B
@ -62,7 +62,7 @@ Smart contracts themselves are principals and are represented by the
smart contract's identifier -- which is the publishing address of the smart contract's identifier -- which is the publishing address of the
contract _and_ the contract's name, e.g.: contract _and_ the contract's name, e.g.:
```scheme ```clarity
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-name 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-name
``` ```
@ -74,7 +74,7 @@ same publisher key, `SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR`, is
publishing two contracts, `contract-A` and `contract-B`, the fully publishing two contracts, `contract-A` and `contract-B`, the fully
qualified identifier for the contracts would be: qualified identifier for the contracts would be:
```scheme ```clarity
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-A 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-A
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-B 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-B
``` ```
@ -83,7 +83,7 @@ But, in the contract source code, if the developer wishes
to call a function from `contract-A` in `contract-B`, they can to call a function from `contract-A` in `contract-B`, they can
write write
```scheme ```clarity
(contract-call? .contract-A public-function-foo) (contract-call? .contract-A public-function-foo)
``` ```
@ -100,7 +100,7 @@ set to the contract's principal, rather than the current sender. The
For example, a smart contract that implements something like a "token For example, a smart contract that implements something like a "token
faucet" could be implemented as so: faucet" could be implemented as so:
```scheme ```clarity
(define-public (claim-from-faucet) (define-public (claim-from-faucet)
(if (is-none? (map-get? claimed-before (tuple (sender tx-sender)))) (if (is-none? (map-get? claimed-before (tuple (sender tx-sender))))
(let ((requester tx-sender)) ;; set a local variable requester = tx-sender (let ((requester tx-sender)) ;; set a local variable requester = tx-sender
@ -154,7 +154,7 @@ protects users from unknowingly calling a function on a malicious
contract that subsequently tries to call sensitive functions on contract that subsequently tries to call sensitive functions on
another contract. another contract.
```scheme ```clarity
;; ;;
;; rockets-base.clar ;; rockets-base.clar
;; ;;
@ -239,7 +239,7 @@ around calling that function.
For example, we can create a contract that calls `fly-ship` For example, we can create a contract that calls `fly-ship`
for multiple rocket-ships in a single transaction: for multiple rocket-ships in a single transaction:
``` ```clarity
;; ;;
;; rockets-multi.clar ;; rockets-multi.clar
;; ;;
@ -262,7 +262,7 @@ 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 to the current contract principal. We can use this to, for example, create
a smart contract rocket-ship-line: a smart contract rocket-ship-line:
``` ```clarity
;; ;;
;; rockets-ship-line.clar ;; rockets-ship-line.clar
;; ;;

10
src/pages/core/smart/tutorial-counter.md

@ -17,7 +17,7 @@ In this tutorial, you learn how to implement a smart contract that stores and ma
## Prerequisites ## Prerequisites
Before you get started, you should complete the [Hello World tutorial](tutorial.html). Before you get started, you should complete the [Hello World tutorial](tutorial).
## Step 1: Downloading counter starter project ## Step 1: Downloading counter starter project
@ -36,7 +36,7 @@ You must now select a template. Type `counter` and hit ENTER:
? Template - one of [hello-world, counter]: counter ? Template - one of [hello-world, counter]: 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. Finally, the project dependencies are installed and your project is ready for development. Because you already completed the [Hello World tutorial](tutorial), the project structure is familiar to you. The main difference is that we have additional tests for a new counter smart contract.
## Step 2: Running tests ## Step 2: Running tests
@ -75,7 +75,7 @@ Let's get familiar with the tests to understand what the new smart contract shou
1. Take a quick look at the test file associated with the counter smart contract: 1. Take a quick look at the test file associated with the counter smart contract:
```shell ```bash
cat test/counter.ts cat test/counter.ts
``` ```
@ -112,7 +112,7 @@ Let's get familiar with the tests to understand what the new smart contract shou
3. Run the tests and review the results: 3. Run the tests and review the results:
```shell ```bash
npm test npm test
``` ```
@ -137,7 +137,7 @@ Let's get familiar with the tests to understand what the new smart contract shou
Done? Great! Run the tests and make sure all of them are passing. You are looking for 4 passed tests: Done? Great! Run the tests and make sure all of them are passing. You are looking for 4 passed tests:
```shell ```bash
counter contract test suite counter contract test suite
✓ should have a valid syntax (39ms) ✓ should have a valid syntax (39ms)
deploying an instance of the contract deploying an instance of the contract

2
src/pages/core/smart/tutorial-test.md

@ -20,7 +20,7 @@ Clarity, Blockstack's smart contracting language, is based on [LISP](<https://en
To complete the tutorial, you should have [NodeJS](https://nodejs.org/en/download/) installed on your workstation. To install and run the starter project, you need to have at least version `8.12.0`. You can verify your installation by opening up your terminal and run the following command: To complete the tutorial, you should have [NodeJS](https://nodejs.org/en/download/) installed on your workstation. To install and run the starter project, you need to have at least version `8.12.0`. You can verify your installation by opening up your terminal and run the following command:
```shell ```bash
node --version node --version
``` ```

20
src/pages/core/smart/tutorial.md

@ -11,7 +11,7 @@ description: Get Started Writing Smart Contracts with Clarity
In the world of smart contracts, everything is a blockchain transaction. You use tokens in your wallet to deploy a smart contract in a transaction, and each call to that contract after it's published is a transaction, too. That means that at each step, tokens are being exchanged as transaction fees. This tutorial introduces you to this mode of programming, which transforms blockchains into powerful state machines capable of executing complex logic. In the world of smart contracts, everything is a blockchain transaction. You use tokens in your wallet to deploy a smart contract in a transaction, and each call to that contract after it's published is a transaction, too. That means that at each step, tokens are being exchanged as transaction fees. This tutorial introduces you to this mode of programming, which transforms blockchains into powerful state machines capable of executing complex logic.
Clarity, Blockstack's smart contracting language, is based on LISP and uses its parenthesized notation. Clarity is an [interpreted language](https://en.wikipedia.org/wiki/Interpreted_language), and [decidable](https://en.wikipedia.org/wiki/Recursive_language). To learn more basics about the language, see the [Introduction to Clarity](overview.html) topic. Clarity, Blockstack's smart contracting language, is based on LISP and uses its parenthesized notation. Clarity is an [interpreted language](https://en.wikipedia.org/wiki/Interpreted_language), and [decidable](https://en.wikipedia.org/wiki/Recursive_language). To learn more basics about the language, see the [Introduction to Clarity](overview) topic.
By the end of this tutorial, you will: By the end of this tutorial, you will:
@ -26,7 +26,7 @@ By the end of this tutorial, you will:
You will need [NodeJS](https://nodejs.org/en/download/) `8.12.0` or higher to complete this tutorial. You can verify your installation by opening up your terminal and run the following command: You will need [NodeJS](https://nodejs.org/en/download/) `8.12.0` or higher to complete this tutorial. You can verify your installation by opening up your terminal and run the following command:
```shell ```bash
node --version node --version
``` ```
@ -63,7 +63,7 @@ Select **File** > **Add Folder to Workspace** in VS Code, and add the `hello-wor
You will see that the program and each statement is enclosed in `()` (parentheses), and the smart contract consists of two functions. You will see that the program and each statement is enclosed in `()` (parentheses), and the smart contract consists of two functions.
```cl ```clarity
(define-public (say-hi) (define-public (say-hi)
(ok "hello world")) (ok "hello world"))
@ -156,43 +156,43 @@ The following set of commands will achieve the same goals as the above workflow.
Install an early release of the new Blockstack CLI for Stacks 2.0. Install an early release of the new Blockstack CLI for Stacks 2.0.
```shell ```bash
sudo npm install -g "https://github.com/blockstack/cli-blockstack#feature/stacks-2.0-tx" sudo npm install -g "https://github.com/blockstack/cli-blockstack#feature/stacks-2.0-tx"
``` ```
Create a new STX address and save keychain details, using the `-t` flag to target Testnet. Create a new STX address and save keychain details, using the `-t` flag to target Testnet.
```shell ```bash
blockstack make_keychain -t > new_keychain.txt blockstack make_keychain -t > new_keychain.txt
``` ```
See your new STX address. See your new STX address.
```shell ```bash
cat new_keychain.txt cat new_keychain.txt
``` ```
Call the Testnet faucet to get STX tokens; replace `<stx_address>` with the address you obtained in the previous step. Call the Testnet faucet to get STX tokens; replace `<stx_address>` with the address you obtained in the previous step.
```shell ```bash
curl -XPOST "https://sidecar.staging.blockstack.xyz/sidecar/v1/faucets/stx?address=<stx_address>" | json_pp curl -XPOST "https://sidecar.staging.blockstack.xyz/sidecar/v1/faucets/stx?address=<stx_address>" | json_pp
``` ```
Confirm faucet transaction. Confirm faucet transaction.
```shell ```bash
blockstack balance <stx_address> -t blockstack balance <stx_address> -t
``` ```
Deploy a contract file to Testnet. Deploy a contract file to Testnet.
```shell ```bash
blockstack deploy_contract ./hello-world.clar hello-world 2000 0 <stx_private_key> -t blockstack deploy_contract ./hello-world.clar hello-world 2000 0 <stx_private_key> -t
``` ```
Call the `echo-number` method of the contract. Call the `echo-number` method of the contract.
```shell ```bash
blockstack call_contract_func <stx_address> hello-world echo-number 2000 1 <stx_private_key> -t blockstack call_contract_func <stx_address> hello-world echo-number 2000 1 <stx_private_key> -t
``` ```

1
src/pages/core/subdomains.md

@ -1 +0,0 @@
The documentation has been moved to [docs.blockstack.org](https://docs.blockstack.org/), please update your bookmarks.

6
src/pages/core/wire-format.md

@ -165,7 +165,7 @@ Op: `+`
Description: This transaction sets the name state for a name to the given Description: This transaction sets the name state for a name to the given
`value`. In practice, this is used to announce new DNS zone file hashes to the [Atlas `value`. In practice, this is used to announce new DNS zone file hashes to the [Atlas
network]({{ site.baseurl }}/core/atlas/overview.html). network](/core/atlas/overview).
Example: [e2029990fa75e9fc642f149dad196ac6b64b9c4a6db254f23a580b7508fc34d7](https://www.blocktrail.com/BTC/tx/e2029990fa75e9fc642f149dad196ac6b64b9c4a6db254f23a580b7508fc34d7) Example: [e2029990fa75e9fc642f149dad196ac6b64b9c4a6db254f23a580b7508fc34d7](https://www.blocktrail.com/BTC/tx/e2029990fa75e9fc642f149dad196ac6b64b9c4a6db254f23a580b7508fc34d7)
@ -270,7 +270,7 @@ received, the following must be true:
- The BNS nodes must list the sender's BNS name as being a "trusted message - The BNS nodes must list the sender's BNS name as being a "trusted message
sender" sender"
- The message must have already been propagated through the [Atlas - The message must have already been propagated through the [Atlas
network]({{ site.baseurl }}/core/atlas/overview.html). This transaction references it by content hash. network](/core/atlas/overview). This transaction references it by content hash.
`OP_RETURN` wire format: `OP_RETURN` wire format:
@ -391,7 +391,7 @@ Op: `;`
Description: This transaction registers a name and some name state into a Description: This transaction registers a name and some name state into a
namespace that has been revealed, but not been launched. Only the namespace namespace that has been revealed, but not been launched. Only the namespace
creator can import names. See the [namespace creation section]({{ site.baseurl }}/core/naming/namespaces.html) for details. creator can import names. See the [namespace creation section](/core/naming/namespaces) for details.
Example: [c698ac4b4a61c90b2c93dababde867dea359f971e2efcf415c37c9a4d9c4f312](https://www.blocktrail.com/BTC/tx/c698ac4b4a61c90b2c93dababde867dea359f971e2efcf415c37c9a4d9c4f312) Example: [c698ac4b4a61c90b2c93dababde867dea359f971e2efcf415c37c9a4d9c4f312](https://www.blocktrail.com/BTC/tx/c698ac4b4a61c90b2c93dababde867dea359f971e2efcf415c37c9a4d9c4f312)

57
src/pages/develop/collections.md

@ -3,19 +3,35 @@
# Work with Collections (Preview) # Work with Collections (Preview)
Collections is the feature designed to make data portable among Blockstack applications. Sharing is accomplished by storing a user's data in a standardized format at a known, Gaia storage location. Collections associate user data with a user's decentralized ID. When users move among apps, the same data is available to each application the user authorizes. Collections is the feature designed to make data portable among Blockstack applications. Sharing is accomplished by
storing a user's data in a standardized format at a known, Gaia storage location. Collections associate user data with
a user's decentralized ID. When users move among apps, the same data is available to each application the user authorizes.
On this page, you learn what collections are and how to use them. You'll learn about the `Contact` collection in particular. The following topics are covered: On this page, you learn what collections are and how to use them. You'll learn about the `Contact` collection in
particular. The following topics are covered:
{% include note.html content="This is a preview release of the <code>Contact</code> collections. This release allows developers to try out the new collections functionality and we are interested in collecting feedback. Please feel free to report issues or request enhancements with collections or <code>Contacts</code> themselves on the <a href='https://github.com/blockstack/blockstack-collections/issues/new' target='_blank'>blockstack/blockstack-collections</a> repository. If you encounter problems with <code>blockstack.js</code> you can <a href='https://github.com/blockstack/blockstack.js/issues/new' target='_blank'>file issues or request enhancements on its repo</a>." %} > #### Preview release
>
> This is a preview release of the `Contact` collections. This release allows developers to try out the new collections
> functionality and we are interested in collecting feedback. Please feel free to report issues or request enhancements
> with collections or `Contacts` themselves on the [blockstack-collections repository](https://github.com/blockstack/blockstack-collections/issues/new).
>
> If you encounter problems with `blockstack.js` you can [file issues or request enhancements on its repo](https://github.com/blockstack/blockstack.js/issues/new).
## Understand how collections work ## Understand how collections work
One of Blockstack's goals is to give users true data ownership by enabling _data portability_. Data portability allows users to login with their digital ID on any app and have access to the same data. For example, if a user adds a photo of a Hawaiian vacation in one app, that photo enters the user's data pool. Then, when the user opens a second app, that same photo is available to the second app because the user data, including the photo, is shared via the user's decentralized ID. One of Blockstack's goals is to give users true data ownership by enabling _data portability_. Data portability allows
users to login with their digital ID on any app and have access to the same data. For example, if a user adds a photo of
a Hawaiian vacation in one app, that photo enters the user's data pool. Then, when the user opens a second app, that
same photo is available to the second app because the user data, including the photo, is shared via the user's
decentralized ID.
How do collections work? Blockstack builds a library containing commonly used data schemes. Developers use these classes and objects instead of creating their own, unique data schemes. Using a class from the collections library guarantees class data is stored in Gaia in that format; And, when retrieved, guarantees the same format is returned. This pre-release provides the `Contact` collection. A contact schema produces this structure: How do collections work? Blockstack builds a library containing commonly used data schemes. Developers use these classes
and objects instead of creating their own, unique data schemes. Using a class from the collections library guarantees
class data is stored in Gaia in that format; And, when retrieved, guarantees the same format is returned. This
pre-release provides the `Contact` collection. A contact schema produces this structure:
``` ```json
{ {
"lastName": "jeffries", "lastName": "jeffries",
"firstName": "sally", "firstName": "sally",
@ -29,13 +45,20 @@ How do collections work? Blockstack builds a library containing commonly used da
A collection schema is neither validated or enforced. The goal is to incentivize collection use rather that enforce use. A collection schema is neither validated or enforced. The goal is to incentivize collection use rather that enforce use.
Because malicious apps or apps with poor security controls may damage user data, Blockstack believes collections should include the ability for users to roll-back changes. For this reason, Blockstack supports an event log and rollback mechanisms in collections. To support this rollback in the pre-release, collections data store is conceptually an event log. Every data write an app makes is stored as a separate file. By placing data in files it ensures that data is never lost and files can be returned back to any previous state. Because malicious apps or apps with poor security controls may damage user data, Blockstack believes collections should
include the ability for users to roll-back changes. For this reason, Blockstack supports an event log and rollback
mechanisms in collections. To support this rollback in the pre-release, collections data store is conceptually an event
log. Every data write an app makes is stored as a separate file. By placing data in files it ensures that data is never
lost and files can be returned back to any previous state.
##### The Future of Collections Envisioned
<div class="uk-card uk-card-default uk-card-body"> Blockstack believes that collections should enable true data portability across applications for each decentralized ID.
<h5 class="uk-card-title">The Future of Collections Envisioned</h5> The goal is to develop simple user interfaces to allow users to manage of application access and permissions to collection
<p>Blockstack believes that collections should enable true data portability across applications for each decentralized ID. The goal is to develop simple user interfaces to allow users to manage of application access and permissions to collection data. For example, in the future, users can rollback data to previous versions using management interfaces.</p> data. For example, in the future, users can rollback data to previous versions using management interfaces.
<p>For developers, collections can incentivize user adoption by reducing user friction. Users can easily try new apps and move to them without the overhead or barrier of re-entering data. You are <a href="https://forum.blockstack.org/t/feedback-wanted-collections-design/7752" target="_blank">welcome to review and comment</a> on the current design document.</p> For developers, collections can incentivize user adoption by reducing user friction. Users can easily try new apps and
</div> move to them without the overhead or barrier of re-entering data. You are [welcome to review and comment](https://forum.blockstack.org/t/feedback-wanted-collections-design/7752)
on the current design document.
## Build a Contact Manager demo app ## Build a Contact Manager demo app
@ -135,7 +158,7 @@ In this section, you learn how to add `Contact` collection functionality to an e
This scope grants your app permission to read and write to the user’s `Contact` collection. This scope grants your app permission to read and write to the user’s `Contact` collection.
```javascript ```jsx
import { UserSession, AppConfig, makeAuthRequest } from 'blockstack'; import { UserSession, AppConfig, makeAuthRequest } from 'blockstack';
import { Contact } from '`blockstack-collections'; import { Contact } from '`blockstack-collections';
@ -151,7 +174,7 @@ Collection storage was designed around an ORM-like interface. This approach ensu
### Example: Create and save a Contact object ### Example: Create and save a Contact object
```javascript ```jsx
const newContact = { const newContact = {
lastName: 'Stackerson', lastName: 'Stackerson',
firstName: 'Blocky', firstName: 'Blocky',
@ -169,7 +192,7 @@ contact.save().then(contactID => {
### Example: Read a Contact object ### Example: Read a Contact object
```javascript ```jsx
let contactID = 'Blocky Stackerson'; let contactID = 'Blocky Stackerson';
Contact.get(contactID).then(contact => { Contact.get(contactID).then(contact => {
// Do something with the contact object // Do something with the contact object
@ -179,7 +202,7 @@ Contact.get(contactID).then(contact => {
### Example: List Contact objects ### Example: List Contact objects
```javascript ```jsx
let contacts = []; let contacts = [];
Contact.list(contactID => { Contact.list(contactID => {
// This callback is invoked for each contact identifier // This callback is invoked for each contact identifier
@ -193,7 +216,7 @@ Contact.list(contactID => {
### Example: Delete a Contact ### Example: Delete a Contact
```javascript ```jsx
var contact = new Contact(newContact); var contact = new Contact(newContact);
contact.delete().then(() => { contact.delete().then(() => {
// contact deleted successfully // contact deleted successfully

8
src/pages/develop/connect/get-started.md

@ -49,7 +49,7 @@ If you're using `connect` in a React app, then the best option is to include `co
First, setup the `Connect` provider at the "top-level" of your app - probably next to wherever you would put a Redux provider, for example. First, setup the `Connect` provider at the "top-level" of your app - probably next to wherever you would put a Redux provider, for example.
```javascript ```jsx
import { Connect } from '@blockstack/connect'; import { Connect } from '@blockstack/connect';
const authOptions = { const authOptions = {
@ -68,7 +68,7 @@ const App = () => <Connect authOptions={authOptions}>// the rest of your app's c
Later, when you want to begin the onboarding process, use the `useConnect` hook to get `connect`'s `doOpenAuth` method. Later, when you want to begin the onboarding process, use the `useConnect` hook to get `connect`'s `doOpenAuth` method.
```javascript ```jsx
import { useConnect } from '@blockstack/connect'; import { useConnect } from '@blockstack/connect';
const SignInButton = () => { const SignInButton = () => {
@ -86,7 +86,7 @@ To send the user straight to sign in, call `doOpenAuth(true)`.
If you aren't using React, or just want a simpler API, then you can use the `showBlockstackConnect` method. If you aren't using React, or just want a simpler API, then you can use the `showBlockstackConnect` method.
```javascript ```jsx
import { showBlockstackConnect } from '@blockstack/connect'; import { showBlockstackConnect } from '@blockstack/connect';
const authOptions = { const authOptions = {
@ -117,7 +117,7 @@ First, include the script in your HTML:
Then, you can use API methods under the `blockstackConnect` global variable: Then, you can use API methods under the `blockstackConnect` global variable:
```javascript ```jsx
const authOptions = { const authOptions = {
/** See docs above for options */ /** See docs above for options */
}; };

20
src/pages/develop/connect/overview.md

@ -22,7 +22,7 @@ Although [`blockstack.js`](https://github.com/blockstack/blockstack.js) can also
## Start building with Blockstack Connect ## Start building with Blockstack Connect
Head over to the [Tutorial for App Integration](/browser/todo-list.html) to learn how to build apps with Blockstack Connect. Head over to the [Tutorial for App Integration](/browser/todo-list) to learn how to build apps with Blockstack Connect.
## Installation ## Installation
@ -46,7 +46,7 @@ Every major method you'll use with `connect` requires you to pass some options,
The exact interface you'll use [is defined as](https://github.com/blockstack/connect/blob/master/src/auth.ts#L12:L24): The exact interface you'll use [is defined as](https://github.com/blockstack/connect/blob/master/src/auth.ts#L12:L24):
```typescript ```tsx
export interface AuthOptions { export interface AuthOptions {
redirectTo: string; redirectTo: string;
finished: (payload: FinishedData) => void; finished: (payload: FinishedData) => void;
@ -73,7 +73,7 @@ If you're using `connect` in a React app, then the best option is to include `co
First, setup the `Connect` provider at the "top-level" of your app - probably next to wherever you would put a Redux provider, for example. First, setup the `Connect` provider at the "top-level" of your app - probably next to wherever you would put a Redux provider, for example.
```javascript ```tsx
import { Connect } from '@blockstack/connect'; import { Connect } from '@blockstack/connect';
const authOptions = { const authOptions = {
@ -87,12 +87,14 @@ const authOptions = {
}, },
}; };
const App = () => <Connect authOptions={authOptions}>// the rest of your app's components</Connect>; const App: React.FC<AppProps> = props => {
return <Connect authOptions={authOptions}>{/** the rest of your app's components */}</Connect>;
};
``` ```
Later, when you want to begin the onboarding process, use the `useConnect` hook to get `connect`'s `doOpenAuth` method. Later, when you want to begin the onboarding process, use the `useConnect` hook to get `connect`'s `doOpenAuth` method.
```javascript ```tsx
import { useConnect } from '@blockstack/connect'; import { useConnect } from '@blockstack/connect';
const SignInButton = () => { const SignInButton = () => {
@ -110,7 +112,7 @@ To send the user straight to sign in, call `doOpenAuth(true)`.
If you aren't using React, or just want a simpler API, then you can use the `showBlockstackConnect` method. If you aren't using React, or just want a simpler API, then you can use the `showBlockstackConnect` method.
```javascript ```tsx
import { showBlockstackConnect } from '@blockstack/connect'; import { showBlockstackConnect } from '@blockstack/connect';
const authOptions = { const authOptions = {
@ -136,12 +138,12 @@ If you aren't using ES6 imports, you can still use `connect`! We package the lib
First, include the script in your HTML: First, include the script in your HTML:
```html ```html
<script src="https://unpkg.com/@blockstack/connect" /> <script src="https://unpkg.com/@blockstack/connect">
``` ```
Then, you can use API methods under the `blockstackConnect` global variable: Then, you can use API methods under the `blockstackConnect` global variable:
```javascript ```jsx
const authOptions = { const authOptions = {
/** See docs above for options */ /** See docs above for options */
}; };
@ -158,7 +160,7 @@ To make sure your app handles this gracefully, you'll need to handle the case wh
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/). 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 ```jsx
const userSession = new UserSession(appConfig); const userSession = new UserSession(appConfig);
// ... call this code on page load // ... call this code on page load

16
src/pages/develop/connect/use-with-clarity.md

@ -41,7 +41,7 @@ with it.
To initiate a contract call transaction, use the `openContractCall` function. To initiate a contract call transaction, use the `openContractCall` function.
```ts ```tsx
import { openContractCall } from '@blockstack/connect'; import { openContractCall } from '@blockstack/connect';
// While in beta, you must provide this option: // While in beta, you must provide this option:
@ -76,7 +76,7 @@ await openContractCall(opts);
When calling `openContractCall`, you need to specify a few details. Here is the exact interface that describes what When calling `openContractCall`, you need to specify a few details. Here is the exact interface that describes what
options you have: options you have:
```ts ```tsx
interface ContractCallOptions { interface ContractCallOptions {
contractAddress: string; contractAddress: string;
functionName: string; functionName: string;
@ -111,12 +111,12 @@ These types are named the same as they are in Clarity. The `value` that you pass
- `bool` - can be "true", "false", "0" or "1" - `bool` - can be "true", "false", "0" or "1"
- `buff` - i.e. `"asdf"` - `buff` - i.e. `"asdf"`
- `principal` - This can be a contract principal, or a standard principal. - `principal` - This can be a contract principal, or a standard principal.
[Read more about principals](/core/smart/principals.html). Examples: `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ"` [Read more about principals](/core/smart/principals). Examples: `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ"`
or `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ.my-contract"`. or `"ST22T6ZS7HVWEMZHHFK77H4GTNDTWNPQAX8WZAKHJ.my-contract"`.
Using these types, each argument is an object with the keys `type` and `value`. For example: Using these types, each argument is an object with the keys `type` and `value`. For example:
```js ```tsx
const functionArguments = [ const functionArguments = [
{ {
type: 'buff', type: 'buff',
@ -135,7 +135,7 @@ If you're using Typescript, these Clarity types can be imported as `ContractCall
STX token transfers can be initiated with the `openSTXTransfer` function. STX token transfers can be initiated with the `openSTXTransfer` function.
```ts ```tsx
import { openSTXTransfer } from '@blockstack/connect'; import { openSTXTransfer } from '@blockstack/connect';
// While in beta, you must provide this option: // While in beta, you must provide this option:
@ -158,7 +158,7 @@ openSTXTransfer({
When calling `openSTXTransfer`, you need to specify a few details. Here are the options you have: When calling `openSTXTransfer`, you need to specify a few details. Here are the options you have:
```ts ```tsx
interface STXTransferOptions { interface STXTransferOptions {
recipient: string; recipient: string;
amount: string; amount: string;
@ -185,7 +185,7 @@ interface STXTransferOptions {
To allow your app's users to deploy arbitrary Clarity contracts, use the `openContractDeploy` method. To allow your app's users to deploy arbitrary Clarity contracts, use the `openContractDeploy` method.
```ts ```tsx
import { openContractDeploy } from '@blockstack/connect'; import { openContractDeploy } from '@blockstack/connect';
const codeBody = '(begin (print "hello, world"))'; const codeBody = '(begin (print "hello, world"))';
@ -208,7 +208,7 @@ openContractDeploy({
Here is the interface for the options you can provide to `openContractDeploy`: Here is the interface for the options you can provide to `openContractDeploy`:
```ts ```tsx
interface ContractDeployOptions { interface ContractDeployOptions {
codeBody: string; codeBody: string;
contractName: string; contractName: string;

22
src/pages/develop/overview_auth.md

@ -27,16 +27,18 @@ functions, the default is to request `['store_write']`.
Decentralized apps have a manifest file. This file is based on the [W3C web app manifest specification](https://w3c.github.io/manifest/). The following is an example manifest file. Decentralized apps have a manifest file. This file is based on the [W3C web app manifest specification](https://w3c.github.io/manifest/). The following is an example manifest file.
``` ```json
{ {
"name": "Todo App", "name": "Todo App",
"start_url": "http://blockstack-todos.appartisan.com", "start_url": "http://blockstack-todos.appartisan.com",
"description": "A simple todo app build on blockstack", "description": "A simple todo app build on blockstack",
"icons": [{ "icons": [
{
"src": "http://blockstack-todos.appartisan.com/logo.png", "src": "http://blockstack-todos.appartisan.com/logo.png",
"sizes": "400x400", "sizes": "400x400",
"type": "image/png" "type": "image/png"
}] }
]
} }
``` ```
@ -112,7 +114,7 @@ JWT libraries with support for this signing algorithm.
### Example: authRequest payload schema ### Example: authRequest payload schema
```JavaScript ```jsx
const requestPayload = { const requestPayload = {
jti, // UUID jti, // UUID
iat, // JWT creation time in seconds iat, // JWT creation time in seconds
@ -125,13 +127,13 @@ const requestPayload = {
version, // version tuple version, // version tuple
do_not_include_profile, // a boolean flag asking Blockstack App to send profile url instead of profile object do_not_include_profile, // a boolean flag asking Blockstack App to send profile url instead of profile object
supports_hub_url, // a boolean flag indicating gaia hub support supports_hub_url, // a boolean flag indicating gaia hub support
scopes // an array of string values indicating scopes requested by the app scopes, // an array of string values indicating scopes requested by the app
} };
``` ```
### Example: authResponse payload schema ### Example: authResponse payload schema
```JavaScript ```jsx
const responsePayload = { const responsePayload = {
jti, // UUID jti, // UUID
iat, // JWT creation time in seconds iat, // JWT creation time in seconds
@ -145,8 +147,8 @@ const requestPayload = {
email, // email if email scope is requested & email available email, // email if email scope is requested & email available
profile_url, // url to signed profile token profile_url, // url to signed profile token
hubUrl, // url pointing to user's gaia hub hubUrl, // url pointing to user's gaia hub
version // version tuple version, // version tuple
} };
``` ```
## Decode authRequest ## Decode authRequest
@ -155,7 +157,7 @@ To decode the token and see what information it holds:
1. Copy the `authRequest` string from the URL. 1. Copy the `authRequest` string from the URL.
<img src="{{site.baseurl}}/develop/images/copy-authRequest.png" alt="" /> <img src="/develop/images/copy-authRequest.png" alt="" />
2. Navigate to [jwt.io](https://jwt.io/). 2. Navigate to [jwt.io](https://jwt.io/).
3. Paste the full token there. 3. Paste the full token there.

12
src/pages/develop/profiles.md

@ -19,7 +19,7 @@ Follow these steps to create and register a profile for a Blockstack username (`
2. Split up the profile into tokens, sign the tokens, and put them in a token file 2. Split up the profile into tokens, sign the tokens, and put them in a token file
3. Create a zone file that points to the web location of the profile token file 3. Create a zone file that points to the web location of the profile token file
```js ```jsx
"account": [ "account": [
{ {
"@type": "Account", "@type": "Account",
@ -33,7 +33,7 @@ Follow these steps to create and register a profile for a Blockstack username (`
## Create a profile ## Create a profile
```es6 ```jsx
const profileOfNaval = { const profileOfNaval = {
'@context': 'http://schema.org/', '@context': 'http://schema.org/',
'@type': 'Person', '@type': 'Person',
@ -44,7 +44,7 @@ const profileOfNaval = {
## Sign a profile as a single token ## Sign a profile as a single token
```es6 ```jsx
import { makeECPrivateKey, wrapProfileToken, Person } from 'blockstack'; import { makeECPrivateKey, wrapProfileToken, Person } from 'blockstack';
const privateKey = makeECPrivateKey(); const privateKey = makeECPrivateKey();
@ -56,7 +56,7 @@ const tokenFile = [wrapProfileToken(token)];
## Verify an individual token ## Verify an individual token
```js ```jsx
import { verifyProfileToken } from 'blockstack'; import { verifyProfileToken } from 'blockstack';
try { try {
@ -68,12 +68,12 @@ try {
## Recover a profile from a token file ## Recover a profile from a token file
```js ```jsx
const recoveredProfile = Person.fromToken(tokenFile, publicKey); const recoveredProfile = Person.fromToken(tokenFile, publicKey);
``` ```
## Validate profile schema ## Validate profile schema
```js ```jsx
const validationResults = Person.validateSchema(recoveredProfile); const validationResults = Person.validateSchema(recoveredProfile);
``` ```

56
src/pages/develop/radiks-collaborate.md

@ -1,15 +1,22 @@
--- ---
title: Collaborate with groups
description: A key feature of Radiks is support for private collaboration between multiple users.
--- ---
# Collaborate with groups # Collaborate with groups
A key feature of Radiks is support for private collaboration between multiple users. Supporting collaboration with client-side encryption and user-owned storage can be complicated, but the patterns to implement it are generally the same among most applications. Radiks supplies interfaces for collaboration, making it easy to build private, collaborative apps. A key feature of Radiks is support for private collaboration between multiple users. Supporting collaboration with
client-side encryption and user-owned storage can be complicated, but the patterns to implement it are generally the
same among most applications. Radiks supplies interfaces for collaboration, making it easy to build private,
collaborative apps.
You use the <a href="https://github.com/blockstack/radiks/blob/master/src/models/user-group.ts" target="_blank"><code>UserGroup</code></a> class to build a collaborative group with Radiks. In this section, you learn about this class. You use the [`UserGroup`](https://github.com/blockstack/radiks/blob/master/src/models/user-group.ts) class to build a
collaborative group with Radiks. In this section, you learn about this class.
## Understand the UserGroup workflow ## Understand the UserGroup workflow
The key model behind a collaborative group is `UserGroup`. By default, it only has one attribute, `name`, which is encrypted. You can subclass `UserGroup` with different attributes as needed. The key model behind a collaborative group is `UserGroup`. By default, it only has one attribute, `name`, which is
encrypted. You can subclass `UserGroup` with different attributes as needed.
The general workflow for creating a collaborative group that can share and edit encrypted models is as follows: The general workflow for creating a collaborative group that can share and edit encrypted models is as follows:
@ -22,21 +29,27 @@ The general workflow for creating a collaborative group that can share and edit
3. When the invited user 'activates' an invitation, they create a `GroupMembership`. 3. When the invited user 'activates' an invitation, they create a `GroupMembership`.
They use this membership instance to reference information (such as private keys and signing keys) related to the group. They use this membership instance to reference information (such as private keys and signing keys) related to the group.
As they participate in a group, the group's members can create and update models that are related to the group. These models **must** contain a `userGroupId` attribute used to reference the group. This allows Radiks to know which keys to use for encryption and signing. As they participate in a group, the group's members can create and update models that are related to the group.
These models **must** contain a `userGroupId` attribute used to reference the group. This allows Radiks to know which
keys to use for encryption and signing.
When needed, the group admin can remove a user from a group. To remove a user from the group, the admin creates a new private key for signing and encryption. Then, the admin updates the `GroupMembership` of all users _except_ the user they just removed. This update-and-remove action is also known as rotating the key. When needed, the group admin can remove a user from a group. To remove a user from the group, the admin creates a
new private key for signing and encryption. Then, the admin updates the `GroupMembership` of all users _except_ the
user they just removed. This update-and-remove action is also known as rotating the key.
After a key is rotated, all new and updated models must use the new key for signing. Radiks-server validates all group-related models to ensure that they're signed with the most up-to-date key. After a key is rotated, all new and updated models must use the new key for signing. Radiks-server validates all
group-related models to ensure that they're signed with the most up-to-date key.
## Work with a UserGroup ## Work with a UserGroup
This section details the methods on the <a href="https://github.com/blockstack/radiks/blob/master/src/models/user-group.ts" target="_blank"><code>UserGroup</code></a> class you can use to create, add members to, and query a group. This section details the methods on the [`UserGroup`](https://github.com/blockstack/radiks/blob/master/src/models/user-group.ts)
class you can use to create, add members to, and query a group.
### Create a UserGroup ### Create a UserGroup
To create a `UserGroup`, you must import the class into your application from `radiks`: To create a `UserGroup`, you must import the class into your application from `radiks`:
```javascript ```jsx
import { UserGroup } from 'radiks'; import { UserGroup } from 'radiks';
// ... // ...
@ -44,7 +57,7 @@ import { UserGroup } from 'radiks';
Calling `create` on a new `UserGroup` will create the group and activate an invitation for the group's creator. Calling `create` on a new `UserGroup` will create the group and activate an invitation for the group's creator.
```javascript ```jsx
const group = new UserGroup({ name: 'My Group Name' }); const group = new UserGroup({ name: 'My Group Name' });
await group.create(); await group.create();
``` ```
@ -53,9 +66,10 @@ A group's creator is also the group's admin.
### Invite users to become members ### Invite users to become members
Use the `makeGroupMembership` method on a `UserGroup` instance to invite a user. The only argument passed to this method is the user's `username`. Use the `makeGroupMembership` method on a `UserGroup` instance to invite a user. The only argument passed to this
method is the user's `username`.
```javascript ```jsx
import { UserGroup } from 'radiks'; import { UserGroup } from 'radiks';
const group = await UserGroup.findById(myGroupId); const group = await UserGroup.findById(myGroupId);
@ -66,9 +80,11 @@ console.log(invitation._id); // the ID used to later activate an invitation
#### Generic invitation #### Generic invitation
You can also create a generic invitation that any user can activate, if they are provided with randomly generated secret key, which should be used to decrypt the invitation. The key is generated when the generic invitation is being created. You can also create a generic invitation that any user can activate, if they are provided with randomly generated
secret key, which should be used to decrypt the invitation. The key is generated when the generic invitation
is being created.
```javascript ```jsx
import { GenericGroupInvitation, UserGroup } from 'radiks'; import { GenericGroupInvitation, UserGroup } from 'radiks';
const group = await UserGroup.findById(myGroupId); const group = await UserGroup.findById(myGroupId);
// Creating generic invitation // Creating generic invitation
@ -81,7 +97,7 @@ console.log(genericInvitation.secretCode); // the secretCode used to later activ
Use the `activate` method on a `GroupInvitation` instance to activate an invitation on behalf of a user: Use the `activate` method on a `GroupInvitation` instance to activate an invitation on behalf of a user:
````javascript ```jsx
import { GroupInvitation, GenericGroupInvitation } from 'radiks'; import { GroupInvitation, GenericGroupInvitation } from 'radiks';
// For user-specific invitation // For user-specific invitation
@ -90,22 +106,24 @@ await invitation.activate();
// For generic invitation // For generic invitation
const genericInvitation = await GenericGroupInvitation.findById(myInvitationID); const genericInvitation = await GenericGroupInvitation.findById(myInvitationID);
await genericInvitation.activate(mySecretCode);``` await genericInvitation.activate(mySecretCode);
```
## View all activated UserGroups for the current user ## View all activated UserGroups for the current user
Call `UserGroup.myGroups` to fetch all groups that the current user is a member of: Call `UserGroup.myGroups` to fetch all groups that the current user is a member of:
```javascript ```jsx
import { UserGroup } from 'radiks'; import { UserGroup } from 'radiks';
const groups = await UserGroup.myGroups(); const groups = await UserGroup.myGroups();
```` ```
## Find a UserGroup ## Find a UserGroup
Use the method `UserGroup.find(id)` when fetching a specific UserGroup. This method has extra boilerplate to handle decrypting the model, because the private keys may need to be fetched from different models. Use the method `UserGroup.find(id)` when fetching a specific UserGroup. This method has extra boilerplate to handle
decrypting the model, because the private keys may need to be fetched from different models.
```javascript ```jsx
const group = await UserGroup.find('my-id-here'); const group = await UserGroup.find('my-id-here');
``` ```

2
src/pages/develop/radiks-intro.md

@ -63,4 +63,4 @@ Although Radiks applications rely on a centrally-hosted database, an application
</table> </table>
If you are not familiar with Gaia, see If you are not familiar with Gaia, see
[read the Gaia documentation](({{site.baseurl}}/storage/overview.html). [read the Gaia documentation]((/storage/overview).

120
src/pages/develop/radiks-models.md

@ -3,23 +3,31 @@
# Create and use models # Create and use models
Radiks allows you to model your client data. You can then query this data and display it for a user in multi-player applications. A social application where users want to see the comments of other users is an example of a multi-player application. This page explains how to create a model in your distributed application using Radiks. Radiks allows you to model your client data. You can then query this data and display it for a
user in multi-player applications. A social application where users want to see the comments of
other users is an example of a multi-player application. This page explains how to create a model
in your distributed application using Radiks.
## Overview of Model class extension ## Overview of Model class extension
Blockstack provides a `Model` class you should extend to easily create, save, and fetch models. To create a model class, import the `Model` class from `radiks` into your application. Blockstack provides a `Model` class you should extend to easily create, save, and fetch models.
To create a model class, import the `Model` class from `radiks` into your application.
```javascript ```jsx
import { Model, User } from 'radiks'; import { Model, User } from 'radiks';
``` ```
Then, create a class that extends this model, and provide a schema. Refer to <a href="https://github.com/blockstack/radiks/blob/master/src/model.ts" target="_blank">the <code>Model</code> class</a> in the `radiks` repo to get an overview of the class functionality. Then, create a class that extends this model, and provide a schema. Refer to
[the `Model` class](https://github.com/blockstack/radiks/blob/master/src/model.ts) in the
`radiks` repo to get an overview of the class functionality.
Your new class must define a static `className` property. This property is used when storing and querying information. If you fail to add a `className`, Radiks defaults to the actual model's class name (`foobar.ts`) and your application will behave unpredictably. Your new class must define a static `className` property. This property is used when
storing and querying information. If you fail to add a `className`, Radiks defaults to the
actual model's class name (`foobar.ts`) and your application will behave unpredictably.
The example class code extends `Model` to create a class named `Todo`: The example class code extends `Model` to create a class named `Todo`:
```javascript ```jsx
import { Model, User } from 'radiks'; import { Model, User } from 'radiks';
class Todo extends Model { class Todo extends Model {
@ -52,9 +60,10 @@ The following sections guide you through the steps in defining your own class.
### Define a class schema ### Define a class schema
Every class must have a static `schema` property which defines the attributes of a model using field/value pairs, for example: Every class must have a static `schema` property which defines the attributes of a model
using field/value pairs, for example:
```javascript ```jsx
class Todo extends Model { class Todo extends Model {
static className = 'Todo'; static className = 'Todo';
static schema = { static schema = {
@ -65,19 +74,28 @@ class Todo extends Model {
} }
``` ```
The `key` in this object is the field name and the value, for example, `String`, `Boolean`, or `Number`. In this case, the `title` is a `String` field. Alternatively, you can pass options instead of a type. The `key` in this object is the field name and the value, for example, `String`, `Boolean`, or
`Number`. In this case, the `title` is a `String` field. Alternatively, you can pass options instead of a type.
To define options, pass an object, with a mandatory `type` field. The only supported option right now is `decrypted`. This defaults to `false`, meaning the field is encrypted before the data is stored publicly. If you specify `true`, then the field is not encrypted. To define options, pass an object, with a mandatory `type` field. The only supported option right
now is `decrypted`. This defaults to `false`, meaning the field is encrypted before the data is stored
publicly. If you specify `true`, then the field is not encrypted.
Storing unencrypted fields is useful if you want to be able to query the field when fetching data. A good use-case for storing decrypted fields is to store a `foreignId` that references a different model, for a "belongs-to" type of relationship. Storing unencrypted fields is useful if you want to be able to query the field when fetching data.
A good use-case for storing decrypted fields is to store a `foreignId` that references a different model,
for a "belongs-to" type of relationship.
**Never add the `decrypted` option to fields that contain sensitive user data.** Blockstack data is stored in a decentralized Gaia storage and anyone can read the user's data. That's why encrypting it is so important. If you want to filter sensitive data, then you should do it on the client-side, after decrypting it. **Never add the `decrypted` option to fields that contain sensitive user data.** Blockstack data is
stored in a decentralized Gaia storage and anyone can read the user's data. That's why encrypting it
is so important. If you want to filter sensitive data, then you should do it on the client-side,
after decrypting it.
### Include defaults ### Include defaults
You may want to include an optional `defaults` static property for some field values. For example, in the class below, the `likesDogs` field is a `Boolean`, and the default is `true`. You may want to include an optional `defaults` static property for some field values. For example,
in the class below, the `likesDogs` field is a `Boolean`, and the default is `true`.
```javascript ```jsx
import { Model } from 'radiks'; import { Model } from 'radiks';
class Person extends Model { class Person extends Model {
@ -99,13 +117,15 @@ class Person extends Model {
} }
``` ```
If you wanted to add a default for `isHuman`, you would simply add it to the `defaults` as well. Separate each field with a comma. If you wanted to add a default for `isHuman`, you would simply add it to the `defaults` as well.
Separate each field with a comma.
### Extend the User model ### Extend the User model
Radiks also supplies <a href="https://github.com/blockstack/radiks/blob/master/src/models/user.ts" target="_blank">a default <code>User</code> model</a>. You can also extend this model to add your own attributes. Radiks also supplies [a default `User` model](https://github.com/blockstack/radiks/blob/master/src/models/user.ts).
You can also extend this model to add your own attributes.
```javascript ```jsx
import { User } from 'radiks'; import { User } from 'radiks';
// For example I want to add a public name on my user model // For example I want to add a public name on my user model
@ -120,7 +140,8 @@ class MyAppUserModel extends User {
} }
``` ```
The default `User` model defines a `username`, but you can add a `displayName` to allow the user to set unique name in your app. The default `User` model defines a `username`, but you can add a `displayName` to allow the user to
set unique name in your app.
## Use a model you have defined ## Use a model you have defined
@ -128,15 +149,18 @@ In this section, you learn how to use a model you have defined.
### About the \_id attribute ### About the \_id attribute
All model instances have an `_id` attribute. An `_id` is used as a primary key when storing data and is used for fetching a model. Radiks also creates a `createdAt` and `updatedAt` property when creating and saving models. All model instances have an `_id` attribute. An `_id` is used as a primary key when storing data and is used
for fetching a model. Radiks also creates a `createdAt` and `updatedAt` property when creating and saving models.
If, when constructing a model's instance, you don't pass an `_id`, Radiks creates an `_id` for you automatically. This automatically created id uses the [`uuid/v4`](https://github.com/kelektiv/node-uuid) format. This automatic `_id` is returned by the constructor. If, when constructing a model's instance, you don't pass an `_id`, Radiks creates an `_id` for you automatically.
This automatically created id uses the [`uuid/v4`](https://github.com/kelektiv/node-uuid) format. This
automatic `_id` is returned by the constructor.
### Construct a model instance ### Construct a model instance
To create an instance, pass some attributes to the constructor of that class: To create an instance, pass some attributes to the constructor of that class:
```javascript ```jsx
const person = new Person({ const person = new Person({
name: 'Hank', name: 'Hank',
isHuman: false, isHuman: false,
@ -146,9 +170,10 @@ const person = new Person({
### Fetch an instance ### Fetch an instance
To fetch an existing instance of an instance, you need the instance's `id` property. Then, call the `findById()` method or the `fetch()` method, which returns a promise. To fetch an existing instance of an instance, you need the instance's `id` property. Then, call the `findById()`
method or the `fetch()` method, which returns a promise.
```javascript ```jsx
const person = await Person.findById('404eab3a-6ddc-4ba6-afe8-1c3fff464d44'); const person = await Person.findById('404eab3a-6ddc-4ba6-afe8-1c3fff464d44');
``` ```
@ -158,7 +183,7 @@ After calling these methods, Radiks automatically decrypts all encrypted fields.
Other than `id`, all attributes are stored in an `attrs` property on the instance. Other than `id`, all attributes are stored in an `attrs` property on the instance.
```javascript ```jsx
const { name, likesDogs } = person.attrs; const { name, likesDogs } = person.attrs;
console.log(`Does ${name} like dogs?`, likesDogs); console.log(`Does ${name} like dogs?`, likesDogs);
``` ```
@ -167,7 +192,7 @@ console.log(`Does ${name} like dogs?`, likesDogs);
To quickly update multiple attributes of an instance, pass those attributes to the `update` method. To quickly update multiple attributes of an instance, pass those attributes to the `update` method.
```javascript ```jsx
const newAttributes = { const newAttributes = {
likesDogs: false, likesDogs: false,
age: 30, age: 30,
@ -179,9 +204,11 @@ Important, calling `update` does **not** save the instance.
### Save changes ### Save changes
To save an instance to Gaia and MongoDB, call the `save()` method, which returns a promise. This method encrypts all attributes that do not have the `decrypted` option in their schema. Then, it saves a JSON representation of the model in Gaia, as well as in the MongoDB. To save an instance to Gaia and MongoDB, call the `save()` method, which returns a promise. This method encrypts
all attributes that do not have the `decrypted` option in their schema. Then, it saves a JSON representation of
the model in Gaia, as well as in the MongoDB.
```javascript ```jsx
await person.save(); await person.save();
``` ```
@ -189,23 +216,25 @@ await person.save();
To delete an instance, just call the `destroy` method on it. To delete an instance, just call the `destroy` method on it.
```javascript ```jsx
await person.destroy(); await person.destroy();
``` ```
## Query a model ## Query a model
To fetch multiple records that match a certain query, use the class's `fetchList()` function. This method creates an HTTP query to Radiks-server, which then queries the underlying database. Radiks-server uses the `query-to-mongo` package to turn an HTTP query into a MongoDB query. To fetch multiple records that match a certain query, use the class's `fetchList()` function. This method creates an
HTTP query to Radiks-server, which then queries the underlying database. Radiks-server uses the `query-to-mongo`
package to turn an HTTP query into a MongoDB query.
Here are some examples: Here are some examples:
```javascript ```jsx
const dogHaters = await Person.fetchList({ likesDogs: false }); const dogHaters = await Person.fetchList({ likesDogs: false });
``` ```
Or, imagine a `Task` model with a `name`, a boolean for `completed`, and an `order` attribute. Or, imagine a `Task` model with a `name`, a boolean for `completed`, and an `order` attribute.
```javascript ```jsx
class Task extends Model { class Task extends Model {
static className = 'Task'; static className = 'Task';
@ -228,22 +257,24 @@ const tasks = await Task.fetchList({
}); });
``` ```
You can read the [`query-to-mongo`](https://github.com/pbatey/query-to-mongo) package documentation to learn how to do complex querying, sorting, limiting, and so forth. You can read the [`query-to-mongo`](https://github.com/pbatey/query-to-mongo) package documentation to learn how
to do complex querying, sorting, limiting, and so forth.
## Count models ## Count models
You can also get a model's `count` record directly. You can also get a model's `count` record directly.
```javascript ```jsx
const dogHaters = await Person.count({ likesDogs: false }); const dogHaters = await Person.count({ likesDogs: false });
// dogHaters is the count number // dogHaters is the count number
``` ```
## Fetch models created by the current user ## Fetch models created by the current user
Use the `fetchOwnList` method to find instances that were created by the current user. By using this method, you can preserve privacy, because Radiks uses a `signingKey` that only the current user knows. Use the `fetchOwnList` method to find instances that were created by the current user. By using this method,
you can preserve privacy, because Radiks uses a `signingKey` that only the current user knows.
```javascript ```jsx
const tasks = await Task.fetchOwnList({ const tasks = await Task.fetchOwnList({
completed: false, completed: false,
}); });
@ -251,9 +282,11 @@ const tasks = await Task.fetchOwnList({
## Manage relational data ## Manage relational data
It is common for applications to have multiple different models, where some reference another. For example, imagine a task-tracking application where a user has multiple projects, and each project has multiple tasks. Here's what those models might look like: It is common for applications to have multiple different models, where some reference another. For example,
imagine a task-tracking application where a user has multiple projects, and each project has multiple tasks.
Here's what those models might look like:
```javascript ```jsx
class Project extends Model { class Project extends Model {
static className = 'Project'; static className = 'Project';
static schema = { name: String } static schema = { name: String }
@ -274,7 +307,7 @@ class Task extends Model {
Whenever you save a task, you should save a reference to the project it's in: Whenever you save a task, you should save a reference to the project it's in:
```javascript ```jsx
const task = new Task({ const task = new Task({
name: 'Improve radiks documentation', name: 'Improve radiks documentation',
projectId: project._id, projectId: project._id,
@ -284,15 +317,16 @@ await task.save();
Then, later you'll want to fetch all tasks for a certain project: Then, later you'll want to fetch all tasks for a certain project:
```javascript ```jsx
const tasks = await Task.fetchList({ const tasks = await Task.fetchList({
projectId: project._id, projectId: project._id,
}); });
``` ```
Radiks lets you define an `afterFetch` method. Use this method to automatically fetch child records when you fetch the parent instance. Radiks lets you define an `afterFetch` method. Use this method to automatically fetch child records when you
fetch the parent instance.
```javascript ```jsx
class Project extends Model { class Project extends Model {
static className = 'Project'; static className = 'Project';
static schema = { name: String }; static schema = { name: String };
@ -305,5 +339,7 @@ class Project extends Model {
} }
const project = await Project.findById('some-id-here'); const project = await Project.findById('some-id-here');
console.log(project.tasks); // will already have fetched and decrypted all related tasks
// will already have fetched and decrypted all related tasks
console.log(project.tasks);
``` ```

12
src/pages/develop/radiks-server-extras.md

@ -9,7 +9,7 @@ In this section, you'll find some tips and tricks you can use to work with a Rad
Radiks-server keeps all models inside of a collection. You can use the `getDB` function to access this collection from inside your application. Radiks-server keeps all models inside of a collection. You can use the `getDB` function to access this collection from inside your application.
```js ```jsx
const { getDB } = require('radiks-server'); const { getDB } = require('radiks-server');
const mongo = await getDB(MONGODB_URL); const mongo = await getDB(MONGODB_URL);
@ -23,7 +23,7 @@ If you're using an [express.js](https://expressjs.com/) server to run your appli
Radiks-server includes an easy-to-use middleware that you can include in your application: Radiks-server includes an easy-to-use middleware that you can include in your application:
```javascript ```jsx
const express = require('express'); const express = require('express');
const { setup } = require('radiks-server'); const { setup } = require('radiks-server');
@ -39,7 +39,7 @@ The `setup` method returns a promise, and that promise resolves to the actual mi
The `setup` function accepts an `options` object as the first argument. If you aren't using environment variables, you can explicitly pass in a MongoDB URL here: The `setup` function accepts an `options` object as the first argument. If you aren't using environment variables, you can explicitly pass in a MongoDB URL here:
```javascript ```jsx
setup({ setup({
mongoDBUrl: 'mongodb://localhost:27017/my-custom-database', mongoDBUrl: 'mongodb://localhost:27017/my-custom-database',
}).then(RadiksController => { }).then(RadiksController => {
@ -119,14 +119,14 @@ migrate()
Before you can implement the websocket function, you must configure your `Radiks-Server` with [express-ws](https://github.com/HenningM/express-ws) Before you can implement the websocket function, you must configure your `Radiks-Server` with [express-ws](https://github.com/HenningM/express-ws)
```javascript ```jsx
const app = express(); const app = express();
expressWS(app); expressWS(app);
``` ```
Here's an example for how to use the API: Here's an example for how to use the API:
```javascript ```jsx
import Task from './models/task'; import Task from './models/task';
const streamCallback = task => { const streamCallback = task => {
@ -153,7 +153,7 @@ Task.removeStreamListener(streamCallback);
Sometimes, you need to save some data on behalf of the user that only the server is able to see. A common use case for this is when you want to notify a user, and you need to store, for example, their email. This should be updatable only by the user, and only the server (or that user) should be able to see it. Radiks provides the `Central` API to handle this: Sometimes, you need to save some data on behalf of the user that only the server is able to see. A common use case for this is when you want to notify a user, and you need to store, for example, their email. This should be updatable only by the user, and only the server (or that user) should be able to see it. Radiks provides the `Central` API to handle this:
```javascript ```jsx
import { Central } from 'radiks'; import { Central } from 'radiks';
const key = 'UserSettings'; const key = 'UserSettings';

12
src/pages/develop/radiks-setup.md

@ -47,7 +47,7 @@ The easiest way to run `radiks-server` is to use the pre-packaged `node.js` serv
2. Start the `radiks-server` in the command line to confirm your installation. 2. Start the `radiks-server` in the command line to confirm your installation.
``` ```bash
$ radiks-server $ radiks-server
(node:37750) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor. (node:37750) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
radiks-server is ready on http://localhost:1260 radiks-server is ready on http://localhost:1260
@ -89,7 +89,7 @@ If you are using `blockstack.js` version 18 or earlier, you must use the Radiks
1. Start the mongo shell application. 1. Start the mongo shell application.
``` ```bash
$ mongo $ mongo
MongoDB shell version v4.2.0 MongoDB shell version v4.2.0
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
@ -104,7 +104,7 @@ If you are using `blockstack.js` version 18 or earlier, you must use the Radiks
2. Create a new database for your application. 2. Create a new database for your application.
``` ```bash
> show dbs > show dbs
admin 0.000GB admin 0.000GB
config 0.000GB config 0.000GB
@ -121,7 +121,7 @@ If you are using `blockstack.js` version 18 or earlier, you must use the Radiks
3. Add a user with administrative rights to the database. 3. Add a user with administrative rights to the database.
``` ```bash
> db.createUser({user: "admin", pwd:"foobar1",roles: ["readWrite","dbAdmin"]}); > db.createUser({user: "admin", pwd:"foobar1",roles: ["readWrite","dbAdmin"]});
Successfully added user: { "user" : "admin", "roles" : [ "readWrite", "dbAdmin" ] } Successfully added user: { "user" : "admin", "roles" : [ "readWrite", "dbAdmin" ] }
``` ```
@ -189,7 +189,7 @@ After you have added Radiks to your application, build and run the application.
You can specify the `mongoDBUrl` or the `maxLimit` option when initiating the Radiks server in your application. You can specify the `mongoDBUrl` or the `maxLimit` option when initiating the Radiks server in your application.
```javascript ```jsx
const { setup } = require('radiks-server'); const { setup } = require('radiks-server');
setup({ setup({
@ -202,4 +202,4 @@ The `maxLimit` option is the maximum `limit` field used inside the mongo queries
## Where to go next ## Where to go next
Creating models for your application's data is where radiks truly becomes helpful. To learn how to use models, see the [Create and use models](radiks-models.html) section. Creating models for your application's data is where radiks truly becomes helpful. To learn how to use models, see the [Create and use models](radiks-models) section.

90
src/pages/develop/storage.md

@ -5,7 +5,8 @@
The Blockstack Platform stores application data in the Gaia Storage System. Transactional metadata is stored on the Blockstack blockchain and user application data is stored in Gaia storage. Storing data off of the blockchain ensures that Blockstack applications can provide users with high performance and high availability for data reads and writes without introducing central trust parties. The Blockstack Platform stores application data in the Gaia Storage System. Transactional metadata is stored on the Blockstack blockchain and user application data is stored in Gaia storage. Storing data off of the blockchain ensures that Blockstack applications can provide users with high performance and high availability for data reads and writes without introducing central trust parties.
{% include note.html content="<ul> <li>Blockstack Gaia Storage APIs and on-disk format will change in upcoming pre-releases breaking backward compatibility. File encryption is currently opt-in on a file by file basis.</li> <li>Certain storage features such as collections are not implemented in the current version. These features will be rolled out in future updates.</li> </ul>" %} > Blockstack Gaia Storage APIs and on-disk format will change in upcoming pre-releases breaking backward compatibility. File encryption is currently opt-in on a file by file basis.
> Certain storage features such as collections are not implemented in the current version. These features will be rolled out in future updates.
## How data is stored ## How data is stored
@ -15,49 +16,46 @@ Gaia storage is a key-value store.
You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#putfile" target="_blank">UserSession.putFile</a> You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#putfile" target="_blank">UserSession.putFile</a>
```JavaScript ```jsx
var userSession = new UserSession() const userSession = new UserSession();
let options = { const options = {
encrypt: false encrypt: false,
} };
userSession.putFile("/hello.txt", "hello world!", options) userSession.putFile('/hello.txt', 'hello world!', options).then(() => {
.then(() => {
// /hello.txt exists now, and has the contents "hello world!". // /hello.txt exists now, and has the contents "hello world!".
}) });
``` ```
## Creating an encrypted file ## Creating an encrypted file
You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#putfile" target="_blank"></a> You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#putfile" target="_blank"></a>
```JavaScript ```jsx
var userSession = new UserSession() const userSession = new UserSession();
let options = { const options = {
encrypt: true encrypt: true,
} };
userSession.putFile("/message.txt", "Secret hello!", options) userSession.putFile('/message.txt', 'Secret hello!', options).then(() => {
.then(() => {
// message.txt exists now, and has the contents "hello world!". // message.txt exists now, and has the contents "hello world!".
}) });
``` ```
## Reading a file ## Reading a file
You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#getfile" target="_blank"></a> You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#getfile" target="_blank"></a>
```JavaScript ```jsx
var userSession = new UserSession() const userSession = new UserSession();
let options = { const options = {
decrypt: false decrypt: false,
} };
userSession.getFile("/hello.txt", options) userSession.getFile('/hello.txt', options).then(fileContents => {
.then((fileContents) => {
// get the contents of the file /hello.txt // get the contents of the file /hello.txt
assert(fileContents === "hello world!") assert(fileContents === 'hello world!');
}); });
``` ```
@ -65,17 +63,16 @@ var userSession = new UserSession()
You use the <a href="" target="_blank"></a> You use the <a href="" target="_blank"></a>
```JavaScript ```jsx
var userSession = new UserSession() const userSession = new UserSession();
let options = { const options = {
decrypt: true decrypt: true,
} };
userSession.getFile("/message.txt", options) userSession.getFile('/message.txt', options).then(fileContents => {
.then((fileContents) => {
// get & decrypt the contents of the file /message.txt // get & decrypt the contents of the file /message.txt
assert(fileContents === "Secret hello!") assert(fileContents === 'Secret hello!');
}); });
``` ```
@ -84,17 +81,16 @@ var userSession = new UserSession()
In order for files to be publicly readable, the app must request In order for files to be publicly readable, the app must request
the `publish_data` scope during authentication. the `publish_data` scope during authentication.
```JavaScript ```jsx
let options = { const options = {
user: 'ryan.id', // the Blockstack ID of the user for which to lookup the file user: 'ryan.id', // the Blockstack ID of the user for which to lookup the file
app: 'http://BlockstackApp.com' // origin of the app this file is stored for app: 'http://BlockstackApp.com', // origin of the app this file is stored for
} };
var userSession = new UserSession() const userSession = new UserSession();
userSession.putFile("/hello.txt", "hello world!", options) userSession.putFile('/hello.txt', 'hello world!', options).then(fileContents => {
.then((fileContents) => {
// get the contents of the file /message.txt // get the contents of the file /message.txt
assert(fileContents === "hello world!") assert(fileContents === 'hello world!');
}); });
``` ```
@ -102,16 +98,14 @@ userSession.putFile("/hello.txt", "hello world!", options)
You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#deletefile" target="_blank">UserSession.deleteFile</a> from the application's data store. You use the <a href="https://blockstack.github.io/blockstack.js/classes/usersession.html#deletefile" target="_blank">UserSession.deleteFile</a> from the application's data store.
```JavaScript ```jsx
const userSession = new UserSession();
var userSession = new UserSession() userSession.deleteFile('/hello.txt').then(() => {
userSession.deleteFile("/hello.txt")
.then(() => {
// /hello.txt is now removed. // /hello.txt is now removed.
}) });
``` ```
## Related Information ## Related Information
To learn more about the guarantees provided by Gaia, see [Storage write and read]({{ site.baseurl }}/storage/write-to-read.html#) To learn more about the guarantees provided by Gaia, see [Storage write and read](/storage/write-to-read)

9
src/pages/index.tsx

@ -5,6 +5,7 @@ import { Hero } from '@components/home/sections/hero';
import { AtomAltIcon } from '@components/icons/atom-alt'; import { AtomAltIcon } from '@components/icons/atom-alt';
import { BoxIcon } from '@components/icons/box'; import { BoxIcon } from '@components/icons/box';
import { EditIcon } from '@components/icons/edit'; import { EditIcon } from '@components/icons/edit';
interface Card { interface Card {
title: string; title: string;
subtitle: string; subtitle: string;
@ -63,4 +64,12 @@ const Homepage = () => (
</> </>
); );
export async function getStaticProps(context) {
return {
props: {
isHome: true,
}, // will be passed to the page component as props
};
}
export default Homepage; export default Homepage;

6
src/pages/ios/tutorial.md

@ -164,7 +164,7 @@ In this section, you build an initial React.js application called
5. Navigate to `http://localhost:8080` with your browser to display the 5. Navigate to `http://localhost:8080` with your browser to display the
application. application.
![]({{ site.baseurl }}/browser/images/initial-app.png) ![](/browser/images/initial-app.png)
This local instances is for testing your applications only. This local instances is for testing your applications only.
@ -172,7 +172,7 @@ In this section, you build an initial React.js application called
The system displays a prompt allowing you to create a new Blockstack ID or restore an existing one. The system displays a prompt allowing you to create a new Blockstack ID or restore an existing one.
![]({{ site.baseurl }}/browser/images/login-choice.png) ![](/browser/images/login-choice.png)
7. Follow the prompts appropriate to your situation. 7. Follow the prompts appropriate to your situation.
@ -181,7 +181,7 @@ In this section, you build an initial React.js application called
on your test server. So, you should see this single application, with your 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` display name, once you are signed in:
![]({{ site.baseurl }}/browser/images/hello-authd.png) ![](/browser/images/hello-authd.png)
### Add a redirect end point to your application ### Add a redirect end point to your application

8
src/pages/org/wallet-install.md

@ -30,13 +30,13 @@ by Blockstack PBC." %}
The default location is the **Downloads** folder, type the following into the terminal and press RETURN on your keyboard. The default location is the **Downloads** folder, type the following into the terminal and press RETURN on your keyboard.
``` ```bash
cd ~/Downloads cd ~/Downloads
``` ```
5. In the terminal window, type the following Command 5. In the terminal window, type the following Command
``` ```bash
shasum -a 512 Stacks-Wallet-macOS-3.0.0.dmg shasum -a 512 Stacks-Wallet-macOS-3.0.0.dmg
``` ```
@ -60,13 +60,13 @@ by Blockstack PBC." %}
The default location is the **Downloads** folder, type the following at the command prompt and press RETURN on your keyboard. The default location is the **Downloads** folder, type the following at the command prompt and press RETURN on your keyboard.
``` ```bash
cd <You-User-Directory>/Downloads cd <You-User-Directory>/Downloads
``` ```
4. In the command prompt window, type the following at the command prompt. 4. In the command prompt window, type the following at the command prompt.
``` ```bash
certUtil -hashfile Stacks-Wallet-win10-3.0.0.exe SHA512 certUtil -hashfile Stacks-Wallet-win10-3.0.0.exe SHA512
``` ```

2
src/pages/org/wallet-use.md

@ -6,7 +6,7 @@ description: 'Blockstack Network documentation'
This page describes how to use the Stacks Wallet software to manager your Stacks (STX) tokens. This page contains the following topics: This page describes how to use the Stacks Wallet software to manager your Stacks (STX) tokens. This page contains the following topics:
The Stacks Wallet software is installed on your computer, it is not a web application. You should have already [downloaded, verified, and installed the wallet software](wallet-install.html). The Stacks Wallet software is installed on your computer, it is not a web application. You should have already [downloaded, verified, and installed the wallet software](wallet-install).
## Key concepts you should understand ## Key concepts you should understand

47
src/pages/storage/amazon-s3-deploy.md

@ -4,32 +4,32 @@ description: 'Storing user data with Blockstack'
# Configure a hub on Amazon EC2 # Configure a hub on Amazon EC2
This teaches you how to run a Gaia hub on Amazon EC2. Amazon EC2 is an affordable and convenient cloud computing provider. This example uses Amazon EC2 instance together with an [EBS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html) disk for file storage. This teaches you how to run a Gaia hub on Amazon EC2. Amazon EC2 is an affordable and convenient cloud computing provider.
This example uses Amazon EC2 instance together with an [EBS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html)
<div class="uk-card uk-card-default uk-card-body"> disk for file storage.
<h5>Is this tutorial for you?</h5>
> #### Is this tutorial for you?
<p>This documentation is appropriate for advanced power users who are familiar with command line tools, <code>ssh</code>, and basic editing configuration files.</p> >
> This documentation is appropriate for advanced power users who are familiar with command line tools, `ssh`, and
<p>If you are planning on running an <em>open-membership hub</em> or an <em>application-specific hub</em>, see <a href="hub-operation.html">the section on Hub Operation</a>.</p> > basic editing configuration files.
>
</div> > If you are planning on running an _open-membership hub_ or an _application-specific hub_, see
> [the section on Hub Operation](hub-operation).
## Prerequisites you need ## Prerequisites you need
This procedure uses Amazon AWS to choose and configure an Amazon Machine Image This procedure uses Amazon AWS to choose and configure an Amazon Machine Image
(AMI) running a Gaia service. For this reason, you should have an AWS account (AMI) running a Gaia service. For this reason, you should have an AWS account
on the <a href="https://aws.amazon.com/free/" target="\_blank">Amazon AWS free on the [Amazon AWS free tier](https://aws.amazon.com/free/), personal account,
tier</a>, personal account, or corporate account. These instructions assume you or corporate account. These instructions assume you are using a free tier account.
are using a free tier account.
These instructions assume you have already created a free <a These instructions assume you have already created a free [domain through the freenom service](https://www.freenom.com).
href="https://www.freenom.com" target="\_blank">domain through the freenom If you have another domain, you can use that instead.
service</a>. If you have another domain, you can use that instead.
Finally, setting up the SSL certificates on your EC2 instance requires you to use the terminal command line on your workstation. Make sure you have the `watch` command installed using the `which` command. Finally, setting up the SSL certificates on your EC2 instance requires you to use the terminal
command line on your workstation. Make sure you have the `watch` command installed using the `which` command.
``` ```bash
$ which watch $ which watch
/usr/local/bin/watch /usr/local/bin/watch
``` ```
@ -38,7 +38,7 @@ If `watch` is not located, install it on your workstation.
## Task 1: Launch an EC2 instance ## Task 1: Launch an EC2 instance
1. Visit the <a href="https://aws.amazon.com/free/" target="\_blank">AWS Free Tier page</a> and choose **Sign in to the Console**. 1. Visit the [AWS Free Tier page](https://aws.amazon.com/free/) and choose **Sign in to the Console**.
![](/storage/images/aws-console.png) ![](/storage/images/aws-console.png)
@ -67,11 +67,14 @@ If `watch` is not located, install it on your workstation.
You can choose an image that uses **ephemeral** or **EBS** storage. The ephemeral You can choose an image that uses **ephemeral** or **EBS** storage. The ephemeral
storage is very small but free. Only choose this if you plan to test or use storage is very small but free. Only choose this if you plan to test or use
a personal hub. Otherwise, choose the AMI for elastic block storage (EBS) which provides a persistent data store on a separate disk backed by [EBS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html). a personal hub. Otherwise, choose the AMI for elastic block storage (EBS) which provides a persistent data store on
a separate disk backed by [EBS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html).
So, the `blockstack-gaia_hub-ephemeral-2.5.3-hvm - ami-0c8fc48c10a42737e` image uses ephemeral storage, is at version `2.5.3` and has the `0c8fc48c10a42737e` tag. So, the `blockstack-gaia_hub-ephemeral-2.5.3-hvm - ami-0c8fc48c10a42737e` image uses ephemeral storage, is at
version `2.5.3` and has the `0c8fc48c10a42737e` tag.
6. Select the most recent version image with the storage you want. The images are not sorted; The most recent images is not necessarily at the top of the list. 6. Select the most recent version image with the storage you want. The images are not sorted; The most recent images
is not necessarily at the top of the list.
After you select an image, the system displays **Step 2: Choose an Instance Type** page. After you select an image, the system displays **Step 2: Choose an Instance Type** page.

11
src/pages/storage/digital-ocean-deploy.md

@ -78,7 +78,7 @@ On your local workstation, create a bucket policy to grant read permission on yo
2. <a href="https://www.digitalocean.com/docs/spaces/resources/s3cmd/" target="_blank">Install and configure the <strong>s3cmd</strong></a>. 2. <a href="https://www.digitalocean.com/docs/spaces/resources/s3cmd/" target="_blank">Install and configure the <strong>s3cmd</strong></a>.
3. In the current directory, use the `touch` command to create a file called `gaiahub-policy`. 3. In the current directory, use the `touch` command to create a file called `gaiahub-policy`.
``` ```bash
touch gaiahub-policy touch gaiahub-policy
``` ```
@ -396,7 +396,7 @@ You should have the console open as `root` on your Droplet. In this section, you
"json": true "json": true
} }
} }
``` ````
15. Save your config file and close the `vim` editor. 15. Save your config file and close the `vim` editor.
@ -408,11 +408,12 @@ While your console is still in the the `gaia/hub` folder, build the `gaia.hub` i
1. Enter the following `docker` command at the console command line. 1. Enter the following `docker` command at the console command line.
```` ```
docker build -t gaia.hub . docker build -t gaia.hub .
``` ```
This build users the `Dockerfile` already in the `gaia/hub` folder. The output of the command is similar to the following: This build users the `Dockerfile` already in the `gaia/hub` folder. The output of the command is similar to the following:
``` ```
@ -435,13 +436,13 @@ Removing intermediate container ae459cc0865b
Successfully built b1ced6c39784 Successfully built b1ced6c39784
Successfully tagged gaia.hub:latest Successfully tagged gaia.hub:latest
```` ```
2. Run your Gaia hub image. 2. Run your Gaia hub image.
```bash ```bash
docker run --restart=always -v ~/gaia/hub/config.json:/src/hub/config.json -p 3000:3000 -e CONFIG_PATH=/src/hub/config.json gaia.hub docker run --restart=always -v ~/gaia/hub/config.json:/src/hub/config.json -p 3000:3000 -e CONFIG_PATH=/src/hub/config.json gaia.hub
```` ```
This runs your Gaia hub on port `3000`. If everything runs successfully, the last line outputted from this command should be: This runs your Gaia hub on port `3000`. If everything runs successfully, the last line outputted from this command should be:

8
src/pages/storage/hello-hub-choice.md

@ -6,13 +6,13 @@ description: 'Storing user data with Blockstack'
{% include note.html content="The functionality described in this tutorial has been deprecated with the Blockstack Browser. It will continue working only for apps that have not yet upgraded to Blockstack Connect." %} {% include note.html content="The functionality described in this tutorial has been deprecated with the Blockstack Browser. It will continue working only for apps that have not yet upgraded to Blockstack Connect." %}
In this tutorial, you build on the <a href="{{ site.baseurl }}/browser/hello-blockstack.html" target="\_blank">Hello, Blockstack Tutorial</a>. You'll modify the authentication code so that it prompts users who have not yet created a Blockstack identity, to choose a hub URL. In this tutorial, you build on the <a href="/browser/hello-blockstack.html" target="\_blank">Hello, Blockstack Tutorial</a>. You'll modify the authentication code so that it prompts users who have not yet created a Blockstack identity, to choose a hub URL.
{% include note.html content="This tutorial was written on macOS High Sierra 10.13.4. If you use a Windows or Linux system, you can still follow along. However, you will need to \"translate\" appropriately for your operating system. Additionally, this tutorial assumes you are accessing the Blockstack Browser web application via Chrome. The application you build will also work with a local installation and/or with browsers other than Chrome. " %} {% include note.html content="This tutorial was written on macOS High Sierra 10.13.4. If you use a Windows or Linux system, you can still follow along. However, you will need to \"translate\" appropriately for your operating system. Additionally, this tutorial assumes you are accessing the Blockstack Browser web application via Chrome. The application you build will also work with a local installation and/or with browsers other than Chrome. " %}
## About this tutorial and the prerequisites you need ## About this tutorial and the prerequisites you need
This tutorial assumes you already set up your environment and tooling as specified in the <a href="{{ site.baseurl }}/browser/hello-blockstack.html" target="\_blank">Hello, Blockstack Tutorial</a>. You should also review that tutorial for basic information about This tutorial assumes you already set up your environment and tooling as specified in the <a href="/browser/hello-blockstack.html" target="\_blank">Hello, Blockstack Tutorial</a>. You should also review that tutorial for basic information about
## Task 1: Generate an initial Blockstack application ## Task 1: Generate an initial Blockstack application
@ -148,7 +148,7 @@ To replace the default login, do the following:
2. Locate the `redirectToSignIn()` method at line 4. 2. Locate the `redirectToSignIn()` method at line 4.
3. Replace `redirectToSignIn()` method with the `blockstack.UserSession.redirectToSignInWithAuthRequest(authRequest)` method. 3. Replace `redirectToSignIn()` method with the `blockstack.UserSession.redirectToSignInWithAuthRequest(authRequest)` method.
```javascript ```jsx
var userSession = new UserSession(); var userSession = new UserSession();
userSession.redirectToSignInWithAuthRequest(authRequest); userSession.redirectToSignInWithAuthRequest(authRequest);
``` ```
@ -242,7 +242,7 @@ your application with a company-run Gaia hub.
To suggest a Gaia hub URL, provide an additional `recommendedGaiaHubUrl` value To suggest a Gaia hub URL, provide an additional `recommendedGaiaHubUrl` value
alongside the `solicitGaiaHubUrl`, for example: alongside the `solicitGaiaHubUrl`, for example:
```javascript ```jsx
import { makeAuthRequest, redirectToSignInWithAuthRequest } from 'blockstack'; import { makeAuthRequest, redirectToSignInWithAuthRequest } from 'blockstack';
var userSession = new UserSession(); var userSession = new UserSession();

2
src/pages/storage/hub-operation.md

@ -52,7 +52,7 @@ By default, the Gaia hub will validate that the supplied URL matches
`https://${config.servername}`, but if there are multiple valid URLs `https://${config.servername}`, but if there are multiple valid URLs
for clients to reach the hub at, you can include a list in your `config.json`: for clients to reach the hub at, you can include a list in your `config.json`:
```javascript ```jsx
{ {
.... ....
servername: "normalserver.com" servername: "normalserver.com"

4
src/pages/storage/overview.md

@ -2,6 +2,8 @@
description: 'Storing user data with Blockstack' description: 'Storing user data with Blockstack'
--- ---
import Architecture from '@common/\_includes/architecture.md'
# A decentralized storage architecture # A decentralized storage architecture
The Blockstack Network stores application data using a storage system called The Blockstack Network stores application data using a storage system called
@ -15,7 +17,7 @@ parties.
The following diagram depicts the Blockstack architecture and Gaia's place in it: The following diagram depicts the Blockstack architecture and Gaia's place in it:
{% include architecture.md %} <Architecture />
## User control or how is Gaia decentralized? ## User control or how is Gaia decentralized?

47
src/pages/storage/write-to-read.md

@ -4,7 +4,9 @@ description: 'Storing user data with Blockstack'
# Storage write and read # Storage write and read
Once a user authenticates and a DApp obtains authentication, the application interacts with Gaia through the blockstack.js library. There are two simple methods for working with data in Gaia hub: the `putFile()` and `getFile()` methods. This section goes into greater detail about the methods, how they interact with a hub, and how to use them. Once a user authenticates and a DApp obtains authentication, the application interacts with Gaia through the
blockstack.js library. There are two simple methods for working with data in Gaia hub: the `putFile()` and `getFile()`
methods. This section goes into greater detail about the methods, how they interact with a hub, and how to use them.
## Write-to and Read-from URL Guarantees ## Write-to and Read-from URL Guarantees
@ -21,18 +23,22 @@ be able to read from the `https://myreads.com/foo/bar` URL. Note that, while the
prefix in the write-to url (for example,`myhub.service.org/store`) and the read-from URL prefix in the write-to url (for example,`myhub.service.org/store`) and the read-from URL
(`https://myreads.com`) are different, the `foo/bar` suffixes are the same. (`https://myreads.com`) are different, the `foo/bar` suffixes are the same.
By default, `putFile()` encrypts information while `getFile()` decrypts it by default. Data stored in an encrypted format means only the user that stored it can view it. For applications that want other users to view data, the application should set the `encrypt` option to `false`. And, corresponding, the `decrypt` option on `getFile()` should also be `false`. By default, `putFile()` encrypts information while `getFile()` decrypts it by default. Data stored in an
encrypted format means only the user that stored it can view it. For applications that want other users to
view data, the application should set the `encrypt` option to `false`. And, corresponding, the `decrypt`
option on `getFile()` should also be `false`.
Consistent, identical suffixes allow an application to know _exactly_ where a Consistent, identical suffixes allow an application to know _exactly_ where a
written file can be read from, given the read prefix. The Gaia service defines a `hub_info` endpoint to obtain that read prefix: written file can be read from, given the read prefix. The Gaia service defines a `hub_info` endpoint to obtain
that read prefix:
``` ```bash
GET /hub_info/ GET /hub_info/
``` ```
The endpoint returns a JSON object with a `read_url_prefix`, for example, if my service returns: The endpoint returns a JSON object with a `read_url_prefix`, for example, if my service returns:
```javascript ```jsx
{ ..., { ...,
"read_url_prefix": "https://myservice.org/read/" "read_url_prefix": "https://myservice.org/read/"
} }
@ -50,7 +56,9 @@ The application is guaranteed that the profile is written with `putFile()` this
https://myservice.org/store/1DHvWDj834zPAkwMhpXdYbCYh4PomwQfzz/0/profile.json https://myservice.org/store/1DHvWDj834zPAkwMhpXdYbCYh4PomwQfzz/0/profile.json
``` ```
When you use the `putFile()` method it takes the user data and POSTs it to the user's Gaia storage hub. The data POSTs directly to the hub, the blockchain is not used and no data is stored there. The limit on file upload is currently 25mb. When you use the `putFile()` method it takes the user data and POSTs it to the user's Gaia storage hub.
The data POSTs directly to the hub, the blockchain is not used and no data is stored there. The limit on
file upload is currently 25mb.
## Address-based access-control ## Address-based access-control
@ -61,15 +69,22 @@ authentication token which is a message _signed_ by the private key associated
with that address. The message itself is a challenge text, returned via the with that address. The message itself is a challenge text, returned via the
`/hub_info/` endpoint. `/hub_info/` endpoint.
Reads can be done by everybody. The URLs to a user's app data are in a canonical location in their profile. For example, here's how you would get data from the Reads can be done by everybody. The URLs to a user's app data are in a canonical location in their profile.
 [Banter](https://banter.pub/) app, stored under the Blockstack ID `gavin.id`. For example, here's how you would get data from the [Banter](https://banter.pub/) app, stored under the
Blockstack ID `gavin.id`.
 1. Get the bucket URL
`bash  $ BUCKET_URL="$(curl -sL https://core.blockstack.org/v1/users/gavin.id | jq -r '."gavin.id"["profile"]["apps"]["https://banter.pub"]')"  $ echo "$BUCKET_URL"  https://gaia.blockstack.org/hub/16E485MVpR3QpmjVkRgej7ya2Vnzu3jyTR/ `
 2. Get the data
`bash  $ curl -sL "${BUCKET_URL%%/}/Message/3e866af471d0-4072-beba-06ad1e7ad4bd"  {"content":"Anyone here?","votes":[],"createdBy":"gavin.id",...} `
This data is public and unencrypted. The same works for encrypted data. ### Step 1: Get the bucket URL
Only the holder of the private key used for
encryption would be able to decrypt the data. ```bash
$ BUCKET_URL="$(curl -sL https://core.blockstack.org/v1/users/gavin.id | jq -r '."gavin.id"["profile"]["apps"]["https://banter.pub"]')" 
$ echo "$BUCKET_URL"  https://gaia.blockstack.org/hub/16E485MVpR3QpmjVkRgej7ya2Vnzu3jyTR/
```
### Step 2: Get the data
```bash
$ curl -sL "${BUCKET_URL%%/}/Message/3e866af471d0-4072-beba-06ad1e7ad4bd"  {"content":"Anyone here?","votes":[],"createdBy":"gavin.id",...}
```
This data is public and unencrypted. The same works for encrypted data. Only the holder of the private key used for encryption would be able to decrypt the data.

751
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save