mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-22 19:13:37 +08:00
Merge branch 'rc-0.14.3' of https://github.com/blockstack/blockstack-core into rc-0.14.3
This commit is contained in:
@@ -16,6 +16,7 @@ For more info on Blockstack see: http://github.com/blockstack/blockstack
|
||||
- [Quick Start](#quick-start)
|
||||
- [Development Status](#development-status)
|
||||
- [Blockstack Docs](#blockstack-docs)
|
||||
- [API Docs](#api-docs)
|
||||
- [Contributing](#contributing)
|
||||
- [Community](#community)
|
||||
|
||||
@@ -90,6 +91,11 @@ You can also read the Blockstack paper:
|
||||
|
||||
If you have high-level questions about Blockstack, try [searching our forum](https://forum.blockstack.org) and start a new question if your question is not answered there.
|
||||
|
||||
## API Docs
|
||||
|
||||
A local core node will expose a RESTful API after starting the api with `blockstack api start`.
|
||||
Documentation for that lives [here](https://kantai.github.io/blockstack-core/), which is generated from blueprint markdown [here](docs/api-specs.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome any small or big contributions! Please take a moment to
|
||||
|
||||
@@ -2153,7 +2153,7 @@ class BlockstackAPIEndpointHandler(SimpleHTTPRequestHandler):
|
||||
new_password = str(request['password'])
|
||||
|
||||
internal = self.server.get_internal_proxy()
|
||||
res = internal.cli_wallet_passwod(password, new_password, config_path=self.server.config_path, interactive=False)
|
||||
res = internal.cli_wallet_password(password, new_password, config_path=self.server.config_path, interactive=False)
|
||||
if 'error' in res:
|
||||
log.debug("Failed to change wallet password: {}".format(res['error']))
|
||||
return self._reply_json({'error': 'Failed to change password: {}'.format(res['error'])}, status_code=500)
|
||||
|
||||
11
build_docs.sh
Executable file
11
build_docs.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
PREVIOUS_BRANCH=$(git branch | grep ^\* | awk '{print $2}')
|
||||
aglio -i docs/api-specs.md --theme-template docs/aglio_templates/core.jade -o /tmp/index.html
|
||||
git checkout gh-pages
|
||||
cp /tmp/index.html .
|
||||
git add index.html
|
||||
git commit -m "updating generated doc outputs"
|
||||
git push
|
||||
git checkout $PREVIOUS_BRANCH
|
||||
36
docs/aglio_templates/core.jade
Normal file
36
docs/aglio_templates/core.jade
Normal file
@@ -0,0 +1,36 @@
|
||||
doctype
|
||||
|
||||
include mixins.jade
|
||||
|
||||
html
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
title= self.api.name || 'API Documentation'
|
||||
link(rel="stylesheet", href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css")
|
||||
style!= self.css
|
||||
body.preload
|
||||
#nav-background
|
||||
div.container-fluid.triple
|
||||
.row
|
||||
block nav
|
||||
+Nav(false)
|
||||
|
||||
.content
|
||||
#right-panel-background
|
||||
|
||||
block content
|
||||
+ContentTriple(false)
|
||||
|
||||
.middle
|
||||
p.text-muted(style="text-align: center;")
|
||||
|
||||
script: include scripts.js
|
||||
|
||||
if self.livePreview
|
||||
script(src="/socket.io/socket.io.js")
|
||||
script.
|
||||
var socket = io();
|
||||
socket.on('refresh', refresh);
|
||||
socket.on('reconnect', function () {
|
||||
socket.emit('request-refresh');
|
||||
});
|
||||
349
docs/aglio_templates/mixins.jade
Normal file
349
docs/aglio_templates/mixins.jade
Normal file
@@ -0,0 +1,349 @@
|
||||
mixin TryMe(action)
|
||||
//- Give a "try-me" link for the public api endpoint
|
||||
- var myUri = action.uriTemplate
|
||||
- action.parameters.forEach( function (x) { myUri = myUri.replace( "{" + x.name + "}", x.example) } )
|
||||
.title
|
||||
strong
|
||||
h4
|
||||
div
|
||||
span.method(class="badge get")
|
||||
a.method(href="https://core.blockstack.org" + myUri, style="color:white;font-size:12pt")
|
||||
= "Try It!"
|
||||
|
|
||||
p
|
||||
|
||||
mixin Badge(method)
|
||||
//- Draw a badge for a given HTTP method
|
||||
case method
|
||||
when 'GET'
|
||||
span.badge.get: i.fa.fa-arrow-down
|
||||
when 'HEAD'
|
||||
span.badge.head: i.fa.fa-info-circle
|
||||
when 'OPTIONS'
|
||||
span.badge.options: i.fa.fa-dot-circle-o
|
||||
when 'POST'
|
||||
span.badge.post: i.fa.fa-plus
|
||||
when 'PUT'
|
||||
span.badge.put: i.fa.fa-pencil
|
||||
when 'PATCH'
|
||||
span.badge.patch: i.fa.fa-pencil
|
||||
when 'DELETE'
|
||||
span.badge.delete: i.fa.fa-times
|
||||
default
|
||||
span.badge: i.fa.fa-dot-circle-o
|
||||
|
||||
mixin Nav(onlyPublic)
|
||||
//- Draw a navigation bar, which includes links to individual
|
||||
//- resources and actions.
|
||||
nav
|
||||
if self.api.navItems && self.api.navItems.length
|
||||
.resource-group
|
||||
.heading
|
||||
.chevron
|
||||
i.open.fa.fa-angle-down
|
||||
a(href='#top') Overview
|
||||
.collapse-content
|
||||
ul: each item in self.api.navItems
|
||||
li
|
||||
a(href=item[1])!= item[0]
|
||||
- if (onlyPublic){
|
||||
- myGroups = self.api.resourceGroups.filter( filter_public_resourcegroups )
|
||||
- }else{
|
||||
- myGroups = self.api.resourceGroups
|
||||
- }
|
||||
each resourceGroup in myGroups || []
|
||||
.resource-group
|
||||
.heading
|
||||
.chevron
|
||||
i.open.fa.fa-angle-down
|
||||
a(href=resourceGroup.elementLink)!= resourceGroup.name || 'Resource Group'
|
||||
.collapse-content
|
||||
ul
|
||||
each item in resourceGroup.navItems || []
|
||||
li
|
||||
a(href=item[1])!= item[0]
|
||||
- if (onlyPublic){
|
||||
- myResources = resourceGroup.resources.filter( filter_public_resources )
|
||||
- }else{
|
||||
- myResources = resourceGroup.resources
|
||||
- }
|
||||
each resource in myResources || []
|
||||
li
|
||||
- if (onlyPublic){
|
||||
- myActions = resource.actions.filter( filter_public_actions )
|
||||
- }else{
|
||||
- myActions = resource.actions
|
||||
- }
|
||||
if !self.condenseNav || (myActions.length != 1)
|
||||
a(href=resource.elementLink)!= resource.name || 'Resource'
|
||||
ul: each action in myActions || []
|
||||
li: a(href=resource.elementLink)
|
||||
+Badge(action.method)
|
||||
!= action.name || action.method + ' ' + (action.attributes && action.attributes.uriTemplate || resource.uriTemplate)
|
||||
else
|
||||
- var action = myActions[0]
|
||||
a(href=resource.elementLink)
|
||||
+Badge(action.method)
|
||||
!= action.name || resource.name || action.method + ' ' + (action.attributes && action.attributes.uriTemplate || resource.uriTemplate)
|
||||
//- Link to the API hostname, e.g. api.yourcompany.com
|
||||
each meta in self.api.metadata || {}
|
||||
if meta.name == 'HOST'
|
||||
p(style="text-align: center; word-wrap: break-word;")
|
||||
a(href=meta.value)= meta.value
|
||||
|
||||
mixin Parameters(params)
|
||||
//- Draw a definition list of parameter names, types, defaults,
|
||||
//- examples and descriptions.
|
||||
.title
|
||||
strong URI Parameters
|
||||
.collapse-button.show
|
||||
span.close Hide
|
||||
span.open Show
|
||||
.collapse-content
|
||||
dl.inner: each param in params || []
|
||||
dt= self.urldec(param.name)
|
||||
dd
|
||||
code= param.type || 'string'
|
||||
|
|
||||
if param.required
|
||||
span.required (required)
|
||||
else
|
||||
span (optional)
|
||||
|
|
||||
if param.default
|
||||
span.text-info.default
|
||||
strong Default:
|
||||
span= param.default
|
||||
|
|
||||
if param.example
|
||||
span.text-muted.example
|
||||
strong Example:
|
||||
span= param.example
|
||||
!= self.markdown(param.description)
|
||||
if param.values.length
|
||||
p.choices
|
||||
strong Choices:
|
||||
each value in param.values
|
||||
code= self.urldec(value.value)
|
||||
= ' '
|
||||
|
||||
mixin RequestResponse(title, request, collapse)
|
||||
.title
|
||||
strong
|
||||
= title
|
||||
if request.name
|
||||
|
|
||||
code= request.name
|
||||
if collapse && request.hasContent
|
||||
.collapse-button
|
||||
span.close Hide
|
||||
span.open Show
|
||||
+RequestResponseBody(request, collapse)
|
||||
|
||||
mixin RequestResponseBody(request, collapse, showBlank)
|
||||
if request.hasContent || showBlank
|
||||
div(class=collapse ? 'collapse-content' : ''): .inner
|
||||
if request.description
|
||||
.description!= self.markdown(request.description)
|
||||
|
||||
if Object.keys(request.headers).length
|
||||
h5 Headers
|
||||
pre: code
|
||||
each item, index in request.headers
|
||||
!= self.highlight(item.name + ': ' + item.value, 'http')
|
||||
if index < request.headers.length - 1
|
||||
br
|
||||
div(style="height: 1px;")
|
||||
if request.body
|
||||
h5 Body
|
||||
pre: code
|
||||
!= self.highlight(request.body, null, ['json', 'yaml', 'xml', 'javascript'])
|
||||
div(style="height: 1px;")
|
||||
if request.schema
|
||||
h5 Schema
|
||||
pre: code
|
||||
!= self.highlight(request.schema, null, ['json', 'yaml', 'xml'])
|
||||
div(style="height: 1px;")
|
||||
if !request.hasContent
|
||||
.description.text-muted This response has no content.
|
||||
div(style="height: 1px;")
|
||||
|
||||
mixin Examples(resourceGroup, resource, action)
|
||||
each example in action.examples
|
||||
each request in example.requests
|
||||
+RequestResponse('Request', request, true)
|
||||
each response in example.responses
|
||||
+RequestResponse('Response', response, true)
|
||||
|
||||
mixin Content()
|
||||
//- Page header and API description
|
||||
header
|
||||
h1#top!= self.api.name || 'API Documentation'
|
||||
|
||||
if self.api.descriptionHtml
|
||||
!= self.api.descriptionHtml
|
||||
|
||||
//- Loop through and display information about all the resource
|
||||
//- groups, resources, and actions.
|
||||
each resourceGroup in self.api.resourceGroups || []
|
||||
section.resource-group(id=resourceGroup.elementId)
|
||||
h2.group-heading
|
||||
!= resourceGroup.name || 'Resource Group'
|
||||
= " "
|
||||
a.permalink(href=resourceGroup.elementLink) ¶
|
||||
if resourceGroup.descriptionHtml
|
||||
!= resourceGroup.descriptionHtml
|
||||
|
||||
each resource in resourceGroup.resources || []
|
||||
.resource(id=resource.elementId)
|
||||
h3.resource-heading
|
||||
!= resource.name || ((resource.actions[0] != null) && resource.actions[0].name) || 'Resource'
|
||||
= " "
|
||||
a.permalink(href=resource.elementLink) ¶
|
||||
if resource.description
|
||||
!= self.markdown(resource.description)
|
||||
|
||||
each action in resource.actions || []
|
||||
.action(class=action.methodLower, id=action.elementId)
|
||||
h4.action-heading
|
||||
.name!= action.name
|
||||
a.method(class=action.methodLower, href=action.elementLink)
|
||||
= action.method
|
||||
code.uri= self.urldec(action.uriTemplate)
|
||||
if action.description
|
||||
!= self.markdown(action.description)
|
||||
|
||||
h4 Example URI
|
||||
.definition
|
||||
span.method(class=action.methodLower)= action.method
|
||||
|
|
||||
span.uri
|
||||
span.hostname= self.api.host
|
||||
!= action.colorizedUriTemplate
|
||||
|
||||
//- A list of sub-sections for parameters, requests
|
||||
//- and responses.
|
||||
if action.parameters.length
|
||||
+Parameters(action.parameters)
|
||||
if action.examples
|
||||
+Examples(resourceGroup, resource, action)
|
||||
|
||||
- function filter_public_actions(x){
|
||||
- return x.description.includes('+ Public Endpoint')
|
||||
- }
|
||||
- function filter_public_resources(x){
|
||||
- return (x.actions.filter( filter_public_actions ).length > 0)
|
||||
- }
|
||||
- function filter_public_resourcegroups(x){
|
||||
- return (x.resources.filter( filter_public_resources ).length > 0)
|
||||
- }
|
||||
|
||||
mixin ContentTriple(onlyPublic)
|
||||
.middle
|
||||
//- Page header and API description
|
||||
header
|
||||
h1#top!= self.api.name || 'API Documentation'
|
||||
|
||||
.right
|
||||
h5 API Endpoint
|
||||
a(href=self.api.host)= self.api.host
|
||||
.middle
|
||||
if self.api.descriptionHtml
|
||||
!= self.api.descriptionHtml
|
||||
|
||||
//- Loop through and display information about all the resource
|
||||
//- groups, resources, and actions.
|
||||
- if (onlyPublic){
|
||||
- myGroups = self.api.resourceGroups.filter( filter_public_resourcegroups )
|
||||
- }else{
|
||||
- myGroups = self.api.resourceGroups
|
||||
- }
|
||||
each resourceGroup in myGroups || []
|
||||
.middle
|
||||
section.resource-group(id=resourceGroup.elementId)
|
||||
h2.group-heading
|
||||
!= resourceGroup.name || 'Resource Group'
|
||||
= " "
|
||||
a.permalink(href=resourceGroup.elementLink) ¶
|
||||
if resourceGroup.descriptionHtml
|
||||
!= resourceGroup.descriptionHtml
|
||||
|
||||
- if (onlyPublic){
|
||||
- myResources = resourceGroup.resources.filter( filter_public_resources )
|
||||
- }else{
|
||||
- myResources = resourceGroup.resources
|
||||
- }
|
||||
each resource in myResources || []
|
||||
if resource.public != null
|
||||
.middle
|
||||
.resource(id=resource.elementId)
|
||||
a.permalink(href=resource.elementLink)
|
||||
h3.resource-heading
|
||||
!= resource.name || ((resource.actions[0] != null) && resource.actions[0].name) || 'Resource'
|
||||
= " "
|
||||
¶
|
||||
if resource.description
|
||||
!= self.markdown(resource.description)
|
||||
|
||||
- if (onlyPublic){
|
||||
- myActions = resource.actions.filter( filter_public_actions )
|
||||
- }else{
|
||||
- myActions = resource.actions
|
||||
- }
|
||||
each action in myActions || []
|
||||
if action.examples
|
||||
.right
|
||||
.definition
|
||||
span.method(class=action.methodLower)= action.method
|
||||
|
|
||||
span.uri
|
||||
span.hostname= self.api.host
|
||||
!= action.colorizedUriTemplate
|
||||
.tabs
|
||||
if action.hasRequest
|
||||
.example-names
|
||||
span Requests
|
||||
- var requestCount = 0
|
||||
each example in action.examples
|
||||
each request in example.requests
|
||||
- requestCount++
|
||||
span.tab-button= request.name || 'example ' + requestCount
|
||||
each example in action.examples
|
||||
each request in example.requests
|
||||
.tab
|
||||
+RequestResponseBody(request, false, true)
|
||||
.tabs
|
||||
.example-names
|
||||
span Responses
|
||||
each response in example.responses
|
||||
span.tab-button= response.name
|
||||
each response in example.responses
|
||||
.tab
|
||||
+RequestResponseBody(response, false, true)
|
||||
else
|
||||
each example in action.examples
|
||||
.tabs
|
||||
.example-names
|
||||
span Responses
|
||||
each response in example.responses
|
||||
span.tab-button= response.name
|
||||
each response in example.responses
|
||||
.tab
|
||||
+RequestResponseBody(response, false, true)
|
||||
.middle
|
||||
.action(class=action.methodLower, id=action.elementId)
|
||||
h4.action-heading
|
||||
.name!= action.name
|
||||
a.method(class=action.methodLower, href=action.elementLink)
|
||||
= action.method
|
||||
code.uri= self.urldec(action.uriTemplate)
|
||||
if action.description
|
||||
!= self.markdown(action.description)
|
||||
|
||||
//- A list of sub-sections for parameters, requests
|
||||
//- and responses.
|
||||
if action.parameters.length
|
||||
+Parameters(action.parameters)
|
||||
if onlyPublic
|
||||
+TryMe(action)
|
||||
hr.split
|
||||
36
docs/aglio_templates/public.jade
Normal file
36
docs/aglio_templates/public.jade
Normal file
@@ -0,0 +1,36 @@
|
||||
doctype
|
||||
|
||||
include mixins.jade
|
||||
|
||||
html
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
title= self.api.name || 'API Documentation'
|
||||
link(rel="stylesheet", href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css")
|
||||
style!= self.css
|
||||
body.preload
|
||||
#nav-background
|
||||
div.container-fluid.triple
|
||||
.row
|
||||
block nav
|
||||
+Nav(true)
|
||||
|
||||
.content
|
||||
#right-panel-background
|
||||
|
||||
block content
|
||||
+ContentTriple(true)
|
||||
|
||||
.middle
|
||||
p.text-muted(style="text-align: center;")
|
||||
|
||||
script: include scripts.js
|
||||
|
||||
if self.livePreview
|
||||
script(src="/socket.io/socket.io.js")
|
||||
script.
|
||||
var socket = io();
|
||||
socket.on('refresh', refresh);
|
||||
socket.on('reconnect', function () {
|
||||
socket.emit('request-refresh');
|
||||
});
|
||||
223
docs/aglio_templates/scripts.js
Normal file
223
docs/aglio_templates/scripts.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/* eslint-env browser */
|
||||
/* eslint quotes: [2, "single"] */
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Determine if a string ends with another string.
|
||||
*/
|
||||
function endsWith(str, suffix) {
|
||||
return str.indexOf(suffix, str.length - suffix.length) !== -1;
|
||||
}
|
||||
|
||||
/*
|
||||
Get a list of direct child elements by class name.
|
||||
*/
|
||||
function childrenByClass(element, name) {
|
||||
var filtered = [];
|
||||
|
||||
for (var i = 0; i < element.children.length; i++) {
|
||||
var child = element.children[i];
|
||||
var classNames = child.className.split(' ');
|
||||
if (classNames.indexOf(name) !== -1) {
|
||||
filtered.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/*
|
||||
Get an array [width, height] of the window.
|
||||
*/
|
||||
function getWindowDimensions() {
|
||||
var w = window,
|
||||
d = document,
|
||||
e = d.documentElement,
|
||||
g = d.body,
|
||||
x = w.innerWidth || e.clientWidth || g.clientWidth,
|
||||
y = w.innerHeight || e.clientHeight || g.clientHeight;
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
/*
|
||||
Collapse or show a request/response example.
|
||||
*/
|
||||
function toggleCollapseButton(event) {
|
||||
var button = event.target.parentNode;
|
||||
var content = button.parentNode.nextSibling;
|
||||
var inner = content.children[0];
|
||||
|
||||
if (button.className.indexOf('collapse-button') === -1) {
|
||||
// Clicked without hitting the right element?
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.style.maxHeight && content.style.maxHeight !== '0px') {
|
||||
// Currently showing, so let's hide it
|
||||
button.className = 'collapse-button';
|
||||
content.style.maxHeight = '0px';
|
||||
} else {
|
||||
// Currently hidden, so let's show it
|
||||
button.className = 'collapse-button show';
|
||||
content.style.maxHeight = inner.offsetHeight + 12 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTabButton(event) {
|
||||
var i, index;
|
||||
var button = event.target;
|
||||
|
||||
// Get index of the current button.
|
||||
var buttons = childrenByClass(button.parentNode, 'tab-button');
|
||||
for (i = 0; i < buttons.length; i++) {
|
||||
if (buttons[i] === button) {
|
||||
index = i;
|
||||
button.className = 'tab-button active';
|
||||
} else {
|
||||
buttons[i].className = 'tab-button';
|
||||
}
|
||||
}
|
||||
|
||||
// Hide other tabs and show this one.
|
||||
var tabs = childrenByClass(button.parentNode.parentNode, 'tab');
|
||||
for (i = 0; i < tabs.length; i++) {
|
||||
if (i === index) {
|
||||
tabs[i].style.display = 'block';
|
||||
} else {
|
||||
tabs[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Collapse or show a navigation menu. It will not be hidden unless it
|
||||
is currently selected or `force` has been passed.
|
||||
*/
|
||||
function toggleCollapseNav(event, force) {
|
||||
var heading = event.target.parentNode;
|
||||
var content = heading.nextSibling;
|
||||
var inner = content.children[0];
|
||||
|
||||
if (heading.className.indexOf('heading') === -1) {
|
||||
// Clicked without hitting the right element?
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.style.maxHeight && content.style.maxHeight !== '0px') {
|
||||
// Currently showing, so let's hide it, but only if this nav item
|
||||
// is already selected. This prevents newly selected items from
|
||||
// collapsing in an annoying fashion.
|
||||
if (force || window.location.hash && endsWith(event.target.href, window.location.hash)) {
|
||||
content.style.maxHeight = '0px';
|
||||
}
|
||||
} else {
|
||||
// Currently hidden, so let's show it
|
||||
content.style.maxHeight = inner.offsetHeight + 12 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Refresh the page after a live update from the server. This only
|
||||
works in live preview mode (using the `--server` parameter).
|
||||
*/
|
||||
function refresh(body) {
|
||||
document.querySelector('body').className = 'preload';
|
||||
document.body.innerHTML = body;
|
||||
|
||||
// Re-initialize the page
|
||||
init();
|
||||
autoCollapse();
|
||||
|
||||
document.querySelector('body').className = '';
|
||||
}
|
||||
|
||||
/*
|
||||
Determine which navigation items should be auto-collapsed to show as many
|
||||
as possible on the screen, based on the current window height. This also
|
||||
collapses them.
|
||||
*/
|
||||
function autoCollapse() {
|
||||
var windowHeight = getWindowDimensions()[1];
|
||||
var itemsHeight = 64; /* Account for some padding */
|
||||
var itemsArray = Array.prototype.slice.call(
|
||||
document.querySelectorAll('nav .resource-group .heading'));
|
||||
|
||||
// Get the total height of the navigation items
|
||||
itemsArray.forEach(function (item) {
|
||||
itemsHeight += item.parentNode.offsetHeight;
|
||||
});
|
||||
|
||||
// Should we auto-collapse any nav items? Try to find the smallest item
|
||||
// that can be collapsed to show all items on the screen. If not possible,
|
||||
// then collapse the largest item and do it again. First, sort the items
|
||||
// by height from smallest to largest.
|
||||
var sortedItems = itemsArray.sort(function (a, b) {
|
||||
return a.parentNode.offsetHeight - b.parentNode.offsetHeight;
|
||||
});
|
||||
|
||||
while (sortedItems.length && itemsHeight > windowHeight) {
|
||||
for (var i = 0; i < sortedItems.length; i++) {
|
||||
// Will collapsing this item help?
|
||||
var itemHeight = sortedItems[i].nextSibling.offsetHeight;
|
||||
if ((itemsHeight - itemHeight <= windowHeight) || i === sortedItems.length - 1) {
|
||||
// It will, so let's collapse it, remove its content height from
|
||||
// our total and then remove it from our list of candidates
|
||||
// that can be collapsed.
|
||||
itemsHeight -= itemHeight;
|
||||
toggleCollapseNav({target: sortedItems[i].children[0]}, true);
|
||||
sortedItems.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the interactive functionality of the page.
|
||||
*/
|
||||
function init() {
|
||||
var i, j;
|
||||
|
||||
// Make collapse buttons clickable
|
||||
var buttons = document.querySelectorAll('.collapse-button');
|
||||
for (i = 0; i < buttons.length; i++) {
|
||||
buttons[i].onclick = toggleCollapseButton;
|
||||
|
||||
// Show by default? Then toggle now.
|
||||
if (buttons[i].className.indexOf('show') !== -1) {
|
||||
toggleCollapseButton({target: buttons[i].children[0]});
|
||||
}
|
||||
}
|
||||
|
||||
var responseCodes = document.querySelectorAll('.example-names');
|
||||
for (i = 0; i < responseCodes.length; i++) {
|
||||
var tabButtons = childrenByClass(responseCodes[i], 'tab-button');
|
||||
for (j = 0; j < tabButtons.length; j++) {
|
||||
tabButtons[j].onclick = toggleTabButton;
|
||||
|
||||
// Show by default?
|
||||
if (j === 0) {
|
||||
toggleTabButton({target: tabButtons[j]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make nav items clickable to collapse/expand their content.
|
||||
var navItems = document.querySelectorAll('nav .resource-group .heading');
|
||||
for (i = 0; i < navItems.length; i++) {
|
||||
navItems[i].onclick = toggleCollapseNav;
|
||||
|
||||
// Show all by default
|
||||
toggleCollapseNav({target: navItems[i].children[0]});
|
||||
}
|
||||
}
|
||||
|
||||
// Initial call to set up buttons
|
||||
init();
|
||||
|
||||
window.onload = function () {
|
||||
autoCollapse();
|
||||
// Remove the `preload` class to enable animations
|
||||
document.querySelector('body').className = '';
|
||||
};
|
||||
@@ -1,150 +1,758 @@
|
||||
# Blockstack Specifications
|
||||
# Group Authorization
|
||||
|
||||
## Dashboard Endpoints
|
||||
## Auth Request View [GET /auth?authRequest={authRequestToken}]
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Dashboard Home | GET / | identity | Serves the identity management panel |
|
||||
| Auth Request View | GET /auth?authRequest={authRequestToken} | identity | Serves the auth request view |
|
||||
When the user clicks “login” in an application, the app should
|
||||
redirect the user to this endpoint. If the user already has an
|
||||
account, they will be redirected along with requested data. If the
|
||||
user doesn’t have an account, the user will be presented with each of
|
||||
the app’s requested permissions, then will satisfy or deny them. The
|
||||
dashboard will then redirect the user back with a JWT. The response
|
||||
JWT contains a signature and an API token that the app can use for
|
||||
future authorization of endpoints.
|
||||
|
||||
#### Explanation of the auth request view:
|
||||
Each application specifies in advance which family of API calls it
|
||||
will need to make to function properly. This list is passed along to
|
||||
the dashboard endpoint when creating an application account. The
|
||||
account-creation page shows this list of API endpoints and what they
|
||||
do, and allows the user to line-item approve or deny them. The list
|
||||
is stored by the API server in the local account structure, and the
|
||||
list is given to the application as part of the session JWT. The API
|
||||
server will NACK requests to endpoints in API families absent from the
|
||||
session JWT.
|
||||
|
||||
When the user clicks “login” in an application, the app should redirect the user to this endpoint. If the user already has an account, they will be redirected along with requested data. If the user doesn’t have an account, the user will be presented with each of the app’s requested permissions, then will satisfy or deny them. The dashboard will then redirect the user back with a JWT. The response JWT contains a signature and an API token that the app can use for future authorization of endpoints.
|
||||
+ Requires root authorization
|
||||
+ Parameters
|
||||
+ authRequestToken: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJhcHBfZG9tYWluIjoiaGVsbG9ibG9ja3N0YWNrLmNvbSIsIm1ldGhvZHMiOltdLCJhcHBfcHVibGljX2tleSI6IjAyYjk0ZjY4NDgzOGFkMjdmZTE0Nzk1MGMyNjQ1ZjRhYzhjYmU1OTJlYjYzYmQwYTQ5MWQ2YzBlYWZjNjE0YzVjMCJ9.0lLrxt8uGtB2rCKB9sb0jK1DdrrWuuuWM-nsyjvFnmjNx0XfG14Npl72w6hp9W2OHoXdPe7VuXkfvKmVNlQdeA (jwt token) - app token before signing
|
||||
+ Response 200
|
||||
+ Body
|
||||
|
||||
Each application specifies in advance which family of API calls it will need to make to function properly. This list is passed along to the dashboard endpoint when creating an application account. The account-creation page shows this list of API endpoints and what they do, and allows the user to line-item approve or deny them. The list is stored by the API server in the local account structure, and the list is given to the application as part of the session JWT. The API server will NACK requests to endpoints in API families absent from the session JWT.
|
||||
{"token":
|
||||
"eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJhcHBfZG9tYWluIjoiaGVsbG9ibG9ja3N0YWNrLmNvbSIsIm1ldGhvZHMiOltdLCJ0aW1lc3RhbXAiOjE0OTkzNDc4OTUsImV4cGlyZXMiOjE0OTk5NTI2OTUsImFwcF91c2VyX2lkIjoiMUVITmE2UTRKejJ1dk5FeEw0OTdtRTQzaWtYaHdGNmtabSIsImRldmljZV9pZCI6IjAiLCJibG9ja2NoYWluX2lkIjpudWxsLCJzdG9yYWdlIjp7ImNsYXNzZXMiOnsid3JpdGVfcHJpdmF0ZSI6WyJkaXNrIiwiczMiLCJibG9ja3N0YWNrX3NlcnZlciIsImRodCJdLCJyZWFkX2xvY2FsIjpbImRpc2siXSwicmVhZF9wdWJsaWMiOlsiczMiLCJibG9ja3N0YWNrX3Jlc29sdmVyIiwiYmxvY2tzdGFja19zZXJ2ZXIiLCJodHRwIiwiZGh0Il0sIndyaXRlX2xvY2FsIjpbImRpc2siXSwid3JpdGVfcHVibGljIjpbXSwicmVhZF9wcml2YXRlIjpbImRpc2siXX0sInByZWZlcmVuY2VzIjp7fX0sImFwaV9lbmRwb2ludCI6ImxvY2FsaG9zdDo2MjcwIiwiYXBwX3B1YmxpY19rZXlzIjpbXSwidmVyc2lvbiI6MX0.Bhne8wQpPVfkV-VLf2mrsoMmNiE2e04crgLN7OUFKEh_YWeGmqjoZU7JVSzXA5r7LCpZ9Eki5uAWlJSHk-JuCA"
|
||||
}
|
||||
|
||||
## Administrative API
|
||||
# Group Core Node Administration
|
||||
## Ping the node [GET /v1/node/ping]
|
||||
Ping the blockstack node to see if it's alive.
|
||||
+ Public Endpoint
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"status": "alive",
|
||||
"version": "0.14.2"
|
||||
}
|
||||
|
||||
### Node
|
||||
## Get the node's config [GET /v1/node/config]
|
||||
Returns the current configuation settings of the blockstack node.
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Ping the node | GET /v1/node/ping | - | Requires pre-shared secret in the `Authorization:` header |
|
||||
| - | - | - | - |
|
||||
| Get the node's config | GET /v1/node/config | - | Requires pre-shared secret in the `Authorization:` header. Returns a dict with the config file |
|
||||
| Set one or more config fields in a config section | POST /v1/node/config/{section}?{key}={value} | - | Requires pre-shared secret in the `Authorization:` header. |
|
||||
| Delete a config field | DELETE /v1/node/config/{section}/{key} | - | Requires pre-shared secret in the `Authorization:` header. |
|
||||
| Delete a config section | DELETE /v1/node/config/{section} | - | Requires pre-shared secret in the `Authorization:` header. |
|
||||
| - | - | - | - |
|
||||
| Get registrar state | GET /v1/node/registrar/state | - | Requires pre-shared secret in the `Authorization:` header. |
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
### Wallet
|
||||
{
|
||||
"bitcoind": {
|
||||
"passwd": "blockstacksystem",
|
||||
"port": "18332",
|
||||
"regtest": "True",
|
||||
"server": "localhost",
|
||||
"spv_path": "/tmp/.../spv_headers.dat",
|
||||
"use_https": "False",
|
||||
"user": "blockstack"
|
||||
},
|
||||
"blockchain-reader": {
|
||||
"port": "18332",
|
||||
"rpc_password": "blockstacksystem",
|
||||
"rpc_username": "blockstack",
|
||||
"server": "localhost",
|
||||
"use_https": "False",
|
||||
"utxo_provider": "bitcoind_utxo",
|
||||
"version_byte": "0"
|
||||
},
|
||||
"blockchain-writer": {
|
||||
"port": "18332",
|
||||
"rpc_password": "blockstacksystem",
|
||||
"rpc_username": "blockstack",
|
||||
"server": "localhost",
|
||||
"use_https": "False",
|
||||
"utxo_provider": "bitcoind_utxo",
|
||||
"version_byte": "0"
|
||||
},
|
||||
"blockstack-client": {
|
||||
"accounts": "/tmp/.../client/app_accounts",
|
||||
"advanced_mode": "true",
|
||||
"anonymous_statistics": false,
|
||||
"api_endpoint_port": "16268",
|
||||
"api_password": "blockstack_integration_test_api_password",
|
||||
"blockchain_reader": "bitcoind_utxo",
|
||||
"blockchain_writer": "bitcoind_utxo",
|
||||
"client_version": "0.14.3.0",
|
||||
"datastores": "/tmp/.../client/datastores",
|
||||
"email": "",
|
||||
"metadata": "/tmp/.../client/metadata",
|
||||
"poll_interval": "1",
|
||||
"port": "16264",
|
||||
"queue_path": "/tmp/.../client/queues.db",
|
||||
"rpc_detach": "True",
|
||||
"server": "localhost",
|
||||
"storage_drivers": "disk",
|
||||
"storage_drivers_required_write": "disk",
|
||||
"users": "/tmp/.../client/users"
|
||||
}
|
||||
}
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Get wallet payment address | GET /v1/wallet/payment_address | wallet_read | - |
|
||||
| Get wallet owner address | GET /v1/wallet/owner_address | wallet_read | - |
|
||||
| Get wallet data public key | GET /v1/wallet/data_pubkey | wallet_read | - |
|
||||
| - | - | - | - |
|
||||
| Set the wallet | PUT /v1/wallet/keys | - | Requires a pre-shared secret in the `Authorization:` header |
|
||||
| Get the wallet | GET /v1/wallet/keys | - | Requires a pre-shared secret in the `Authorization:` header |
|
||||
| - | - | - | - |
|
||||
| Get the wallet balance | GET /v1/wallet/balance | wallet_read | - |
|
||||
| Get the wallet balance, specifying the minconfs for txns included | GET /v1/wallet/balance/{minconfs} | wallet_read | - |
|
||||
| Withdraw funds from the wallet | POST /v1/wallet/balance | wallet_write | Payload: `{'address': str, 'amount': int, 'min_confs': int, 'tx_only': bool} |
|
||||
| - | - | - | - |
|
||||
| Change wallet password | PUT /v1/wallet/password | wallet_write | Payload: `{'password': ..., 'new_password': ...}`|
|
||||
## Set config field [POST /v1/node/config/{section}?{key}={value}]
|
||||
Set one or more config fields in a config section.
|
||||
|
||||
### Authorization
|
||||
+ Parameters
|
||||
+ section: blockstack-client (string) - configuration section
|
||||
+ key: server (string) - configuration variable to set
|
||||
+ value: node.blockstack.org (string) - value to set
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Create an authorization token | GET /v1/auth?authRequest={authRequestToken} | - | Requires a pre-shared secret in the `Authorization:` header. |
|
||||
+ Response 200 (application/json)
|
||||
|
||||
TODO: authRequestToken format
|
||||
{ 'status' : true }
|
||||
|
||||
## Naming API
|
||||
## Delete a config field [DELETE /v1/node/config/{section}/{key}]
|
||||
Delete a single field from the configuration.
|
||||
+ Parameters
|
||||
+ section: blockstack-client (string) - configuration section
|
||||
+ key: advanced_mode (string) - configuration variable to set
|
||||
|
||||
### Names
|
||||
+ Response 200 (application/json)
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Get all names | GET /v1/names | names | - |
|
||||
| Register name | POST /v1/names | register | Payload: {"name": NAME, "zonefile" : ZONEFILE, "owner_address" : OWNER, "min_confs" : MIN_PAYMENT_CONFS, "unsafe" : REGISTRATION_SAFETY} Required: "name" |
|
||||
| Get name info | GET /v1/names/{name} | names | - |
|
||||
| Get name history | GET /v1/names/{name}/history | names | - |
|
||||
| Get historical zone file | GET /names/{name}/zonefile/{zoneFileHash} | zonefiles | - |
|
||||
| Revoke name | DELETE /v1/names/{name} | revoke | - |
|
||||
| Transfer name | PUT /v1/names/{name}/owner | transfer | Payload: {"owner": OWNER } |
|
||||
| Set zone file | PUT /v1/names/{name}/zonefile | update | Payload: {"zonefile": ZONE_FILE } |
|
||||
| Set zone file hash | PUT /v1/names/{name}/zonefile | update | Payload: {"zonefile_hash": ZONE_FILE_HASH } |
|
||||
{ 'status' : true }
|
||||
|
||||
### Addresses
|
||||
## Delete a config section [DELETE /v1/node/config/{section}]
|
||||
Deletes a whole section from the node's configuration.
|
||||
+ Parameters
|
||||
+ section: blockstack-client (string) - configuration section
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Get names owned by address | GET /v1/addresses/{address} | names | - |
|
||||
+ Response 200 (application/json)
|
||||
|
||||
### Namespaces
|
||||
{ 'status' : true }
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Get all namespaces | GET /v1/namespaces | namespaces | - |
|
||||
| Create namespace | POST /v1/namespaces | namespace_registration | NOT IMPLEMENTED |
|
||||
| Launch namespace | PUT /v1/namespaces/{tld} | namespace_registration | NOT IMPLEMENTED |
|
||||
| Get namespace names | GET /v1/namespaces/{tld}/names | namespaces | - |
|
||||
| Pre-register a name | POST /v1/namespaces/{tld}/names | namespace_registration | NOT IMPLEMENTED |
|
||||
| Update pre-registered name | PUT /v1/namespaces/{tld}/names/{name} | namespace_registration | NOT IMPLEMENTED |
|
||||
## Get registrar state [GET /v1/node/registrar/state]
|
||||
Gets the current state of the registrar. That is, the blockstack operations
|
||||
that have been submitted that are still waiting on confirmations.
|
||||
|
||||
### Prices
|
||||
+ Requires root authorization
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Get namespace price | GET /v1/prices/namespaces/{tld} | prices | May return a warning if the wallet does not have enough funds |
|
||||
| Get name price | GET /v1/prices/names/{name} | prices | May return a warning if the wallet does not have enough funds |
|
||||
[
|
||||
{
|
||||
"block_height": 666,
|
||||
"fqu": "bar.test",
|
||||
"owner_address": "myaPViveUWiiZQQTb51KXCDde4iLC3Rf3K",
|
||||
"payment_address": "mv1uqYWZpnap4VBSKTHfKW6noTZcNtxtCW",
|
||||
"profile": {
|
||||
"@type": "Person",
|
||||
"accounts": []
|
||||
},
|
||||
"transfer_address": null,
|
||||
"tx_hash": "b0fa7d4d79bb69cb3eccf40978514dec1620d05fe7822c550c2764c670efcd29",
|
||||
"type": "preorder",
|
||||
"zonefile": "$ORIGIN bar.test\n$TTL 3600\npubkey TXT \"pubkey:data:03ea5d8c2a3ba84eb17625162320bb53440557c71f7977a57d61405e86be7bdcda\"\n_file URI 10 1 \"file:///home/bar/.blockstack/storage-disk/mutable/bar.test\"\n",
|
||||
"zonefile_hash": "cbe11bbbfffe415b915a7f9566748f72a0d8b2bd"
|
||||
}
|
||||
]
|
||||
|
||||
# Group Core Wallet Management
|
||||
The blockstack core node manages its own wallet -- this has three keys
|
||||
for payment, name ownership, and signing data (e.g., user profiles). This
|
||||
wallet can be managed through these endpoints.
|
||||
|
||||
## Get wallet payment address [GET /v1/wallet/payment_address]
|
||||
Returns core node's payment address.
|
||||
+ Authorization: `wallet_read`
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"address": "mv1uqYWZpnap4VBSKTHfKW6noTZcNtxtCW"
|
||||
}
|
||||
|
||||
## Set a specific wallet key [PUT /v1/wallet/keys/{keyname}]
|
||||
This call instructs the blockstack core node to use a particular key
|
||||
instead of the core node's configured wallet key. The setting of this
|
||||
key is *temporary*. It is not written to `~/.blockstack/wallet.json`,
|
||||
and on a subsequent restart, the key will return to the original key.
|
||||
Therefore, particular care should be taken when registering with such
|
||||
a key, as the registrar may be unable to issue a `REGISTER` or
|
||||
`UPDATE` if the node restarts in the middle of the registration process.
|
||||
|
||||
+ Requires root authorization
|
||||
+ Parameters
|
||||
+ keyname: owner (string) - which key to set (one of 'owner', 'data', 'payment')
|
||||
|
||||
+ Request (application/json)
|
||||
+ Schema
|
||||
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"pattern": "^([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)$",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"pattern": "^([0-9a-fA-F]+)$",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"address": {
|
||||
"pattern": "^([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)$",
|
||||
"type": "string"
|
||||
},
|
||||
"private_keys": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"pattern": "^([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)$",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"pattern": "^([0-9a-fA-F]+)$",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"redeem_script": {
|
||||
"pattern": "^([0-9a-fA-F]+)$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"address",
|
||||
"redeem_script",
|
||||
"private_keys"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
## Get payment wallet balance [GET /v1/wallet/balance/{minconfs}]
|
||||
|
||||
Fetches wallet balance, including UTXOs from transactions with at
|
||||
least a specified number of confirmations.
|
||||
|
||||
+ Authorization: `wallet_read`
|
||||
+ Parameters
|
||||
+ minconfs: 0 (number, optional) - the minimum confs of transactions to include in balance
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"balance": {
|
||||
"bitcoin": 49.931727,
|
||||
"satoshis": 4993172700
|
||||
}
|
||||
}
|
||||
|
||||
## Withdraw payment wallet funds [POST /v1/wallet/balance]
|
||||
Withdraw an amount (given in satoshis) from the core payment
|
||||
wallet, to a particular address.
|
||||
+ Authorization: `wallet_write`
|
||||
+ Request (application/json)
|
||||
+ Body
|
||||
|
||||
{'address' : 'mibZW6EBpXSTWQNQ9E4fi9hhGKYSMkjyg9',
|
||||
'amount' : 100,
|
||||
'min_confs' : 6,
|
||||
'tx_only' : false}
|
||||
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"status": true,
|
||||
"transaction_hash": "c4ee8d1993794487e6b5aca802a1793530bdff35c763ca051fbaa4b998780822",
|
||||
"success": true
|
||||
}
|
||||
|
||||
## Get wallet owner address [GET /v1/wallet/owner_address]
|
||||
Returns core node's owner address.
|
||||
+ Authorization: `wallet_read`
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"address": "myaPViveUWiiZQQTb51KXCDde4iLC3Rf3K"
|
||||
}
|
||||
|
||||
## Get wallet data public key [GET /v1/wallet/data_pubkey]
|
||||
Returns the public key the core node uses for signing user data
|
||||
+ Authorization: `wallet_read`
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"public_key": "03ea5d8c2a3ba84eb17625162320bb53440557c71f7977a57d61405e86be7bdcda"
|
||||
}
|
||||
|
||||
## Change wallet password [PUT /v1/wallet/password]
|
||||
This will change the password for core's wallet. Currently not working endpoint.
|
||||
+ Authorization: `wallet_write`
|
||||
+ Request (application/json)
|
||||
+ Body
|
||||
|
||||
{'password' : '"0123456789abcdef"',
|
||||
'new_password' : "abcdef0123456789"'}
|
||||
|
||||
## Set all wallet keys [PUT /v1/wallet/keys]
|
||||
+ Requires root authorization
|
||||
## Get all wallet keys [GET /v1/wallet/keys]
|
||||
+ Requires root authorization
|
||||
|
||||
# Group Managing Names
|
||||
|
||||
## Register a name [POST /v1/names]
|
||||
Registers a name. If no `owner_address` is supplied in the POSTed JSON
|
||||
object, core will register a name for the current owner address in core's
|
||||
wallet. If an `owner_address` is supplied, a `TRANSFER` operation will be
|
||||
called to send the name to appropriate owner.
|
||||
|
||||
The `min_confs` keyword controls the minimum number of confirmations for
|
||||
UTXOs used as payments for name registration.
|
||||
|
||||
The `unsafe` keyword instructs core's registrar to ignore certain
|
||||
safety checks while registering the name (in particular, the registrar
|
||||
will not verify that the user own's the name before issuing a
|
||||
`REGISTER` and `UPDATE`). This allows the registrar to submit
|
||||
operations before they have been confirmed on remote resolvers or
|
||||
indexers, in this mode, the registrar will wait for 4 confirmations on
|
||||
a `PREORDER`, 1 confirmation on a `REGISTER` and 1 confirmation on an
|
||||
`UPDATE`. `node.blockstack.org` will correctly detect the registration
|
||||
after the `UPDATE` has 6 confirmations.
|
||||
|
||||
+ Authorization: `register`
|
||||
+ Request (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
'name' : 'bar.test'
|
||||
}
|
||||
|
||||
+ Request (application/json)
|
||||
+ Schema
|
||||
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
"name": {
|
||||
'type': 'string',
|
||||
'pattern': OP_NAME_PATTERN
|
||||
},
|
||||
"zonefile": {
|
||||
'type': 'string',
|
||||
'maxLength': RPC_MAX_ZONEFILE_LEN,
|
||||
},
|
||||
"owner_address": {
|
||||
'type': 'string',
|
||||
'pattern': OP_BASE58CHECK_PATTERN,
|
||||
},
|
||||
'min_confs': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
},
|
||||
'tx_fee': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': TX_MAX_FEE,
|
||||
},
|
||||
'cost_satoshis': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
},
|
||||
'unsafe': {
|
||||
'type': 'boolean'
|
||||
}
|
||||
},
|
||||
'required': [
|
||||
'name'
|
||||
],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"message": "Name queued for registration. The process takes several hours. You can check the status with `blockstack info`.",
|
||||
"success": true,
|
||||
"transaction_hash": "6cdb9722f72875b30e1ab3de463e3960aced951f674be942b302581a9a9469a5"
|
||||
}
|
||||
|
||||
## Revoke name [DELETE /v1/names/{name}]
|
||||
Revokes the name from blockstack.
|
||||
+ Parameters
|
||||
+ name: bar.test (string) - fully-qualified name
|
||||
+ Authorization: `revoke`
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"message": "Name queued for revocation. The process takes ~1 hour. You can check the status with `blockstack info`.",
|
||||
"success": true,
|
||||
"transaction_hash": "b2745b706d7a14ce652265de103d7eaefb44a75eb658d7bb1db8868da08768b2"
|
||||
}
|
||||
|
||||
## Transfer name [PUT /v1/names/{name}/owner]
|
||||
Transfers a name to a different owner.
|
||||
+ Authorization: `transfer`
|
||||
+ Parameters
|
||||
+ name: bar.test (string) - name to transfer
|
||||
+ Request (application/json)
|
||||
+ Body
|
||||
|
||||
{ "owner" : "mjZicz7GSJBZuGeCMEgpzr8U9w6d41DfXm" }
|
||||
+ Response 202 (application/json)
|
||||
|
||||
{
|
||||
"message": "Name queued for transfer. The process takes ~1 hour. You can check the status with `blockstack info`.",
|
||||
"success": true,
|
||||
"transaction_hash": "c0d677f9ee681abbed8ca6d231bc4ece517c8c6695ce883e5e196b5395402779"
|
||||
}
|
||||
|
||||
## Set zone file [PUT /v1/names/{name}/zonefile]
|
||||
Sets the user's zonefile hash, and, if supplied, propagates the
|
||||
zonefile. If you supply the zonefile, the hash will be calculated from
|
||||
that. Ultimately, your requests should only supply one of `zonefile`,
|
||||
`zonefile_b64`, or `zonefile_hash`.
|
||||
|
||||
+ Authorization: `update`
|
||||
+ Request (application/json)
|
||||
+ Schema
|
||||
|
||||
request_schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
"zonefile": {
|
||||
'type': 'string',
|
||||
'maxLength': RPC_MAX_ZONEFILE_LEN,
|
||||
},
|
||||
'zonefile_b64': {
|
||||
'type': 'string',
|
||||
'maxLength': (RPC_MAX_ZONEFILE_LEN * 4) / 3 + 1,
|
||||
},
|
||||
'zonefile_hash': {
|
||||
'type': 'string',
|
||||
'pattern': OP_ZONEFILE_HASH_PATTERN,
|
||||
},
|
||||
'tx_fee': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': TX_MAX_FEE
|
||||
},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
+ Response 202 (application/json)
|
||||
+ Body
|
||||
|
||||
{'transaction_hash' : '...'}
|
||||
|
||||
# Group Name Querying
|
||||
This family of API endpoints deals with querying name information.
|
||||
|
||||
## Get all names [GET /v1/names?page={page}]
|
||||
Fetch a list of all names known to the node.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ page: 23 (number) - names are returned in pages of size 100,
|
||||
so specify the page number.
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
[ "aldenquimby.id", "aldeoryn.id",
|
||||
"alderete.id", "aldert.id",
|
||||
"aldi.id", "aldighieri.id", ... ]
|
||||
|
||||
## Get name info [GET /v1/names/{name}]
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ name: muneeb.id (string) - fully-qualified name
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"address": "1QJQxDas5JhdiXhEbNS14iNjr8auFT96GP",
|
||||
"blockchain": "bitcoin",
|
||||
"expire_block": 489247,
|
||||
"last_txid": "1edfa419f7b83f33e00830bc9409210da6c6d1db60f99eda10c835aa339cad6b",
|
||||
"status": "registered",
|
||||
"zonefile": "$ORIGIN muneeb.id\n$TTL 3600\n_http._tcp IN URI 10 1 \"https://blockstack.s3.amazonaws.com/muneeb.id\"\n",
|
||||
"zonefile_hash": "b100a68235244b012854a95f9114695679002af9"
|
||||
}
|
||||
|
||||
## Name history [GET /v1/names/{name}/history]
|
||||
Get a history of all blockchain records of a registered name.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ name: muneeb.id (string) - name to query
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"373821": [
|
||||
{
|
||||
"address": "1QJQxDas5JhdiXhEbNS14iNjr8auFT96GP",
|
||||
"block_number": 373821,
|
||||
"consensus_hash": null,
|
||||
"first_registered": 373821,
|
||||
"importer": "76a9143e2b5fdd12db7580fb4d3434b31d4fe9124bd9f088ac",
|
||||
"importer_address": "16firc3qZU97D1pWkyL6ZYwPX5UVnWc82V",
|
||||
"last_creation_op": ";",
|
||||
"last_renewed": 373821,
|
||||
"name": "muneeb.id",
|
||||
"name_hash128": "deb7fe99776122b77925cbf0a24ab6f8",
|
||||
"namespace_block_number": 373601,
|
||||
"namespace_id": "id",
|
||||
"op": ";",
|
||||
"op_fee": 100000.0,
|
||||
"opcode": "NAME_IMPORT",
|
||||
"preorder_block_number": 373821,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
## Get historical zone file [GET /v1/names/{name}/zonefile/{zoneFileHash}]
|
||||
Fetches the historical zonefile specified by the username and zone hash.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ name: muneeb.id (string) username to fetch
|
||||
+ zoneFileHash: b100a68235244b012854a95f9114695679002af9
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"zonefile":
|
||||
"$ORIGIN muneeb.id\n$TTL 3600\n_http._tcp IN URI 10 1 \"https://blockstack.s3.amazonaws.com/muneeb.id\"\n"
|
||||
}
|
||||
|
||||
## Get names owned by address [GET /v1/addresses/{blockchain}/{address}]
|
||||
Retrieves a list of names owned by the address provided.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ blockchain: bitcoin (string) - the layer-1 blockchain for the address
|
||||
+ address: 1QJQxDas5JhdiXhEbNS14iNjr8auFT96GP (string) - the address to lookup
|
||||
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"names": ["muneeb.id"]
|
||||
}
|
||||
|
||||
|
||||
### Blockchains
|
||||
# Group Price Checks
|
||||
## Get namespace price [GET /v1/prices/namespaces/{tld}]
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ tld: id (string) - namespace to query price for
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Get block operations | GET /v1/blockchains/{blockchainName}/block/{blockHeight} | blockchain | - |
|
||||
| Get raw name history | GET /v1/blockchains/{blockchainName}/names/{nameID}/history | blockchain | - |
|
||||
| Get consensus hash | GET /v1/blockchains/{blockchainName}/consensusHash | blockchain | - |
|
||||
| Get pending transactions | GET /v1/blockchains/{blockchainName}/pending | blockchain | - |
|
||||
{
|
||||
"satoshis": 4000000000
|
||||
}
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------ | -------- | ---------- | ----- |
|
||||
| Get unspent outputs | GET /v1/blockchains/{blockchainName}/{address}/unspent | blockchain | Returns `{"transaction_hash": str, "output_index": int, "value": int (satoshis), "script_hex": str, "confirmations": int}` |
|
||||
| Broadcast transaction | POST /v1/blockchains/{blockchainName}/txs | blockchain | Takes `{"tx": str}` as its payload |
|
||||
## Get name price [GET /v1/prices/names/{name}]
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ name: muneeb.id (string) - name to query price information for
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
## Identity API
|
||||
{
|
||||
"name_price": {
|
||||
"satoshis": 100000,
|
||||
"btc": 0.001
|
||||
},
|
||||
"total_tx_fees": 519209,
|
||||
"register_tx_fee": {
|
||||
"satoshis": 159110,
|
||||
"btc": 0.0015911
|
||||
},
|
||||
"preorder_tx_fee": {
|
||||
"satoshis": 163703,
|
||||
"btc": 0.00163703
|
||||
},
|
||||
"warnings": [
|
||||
"Insufficient funds; fees are rough estimates."
|
||||
],
|
||||
"total_estimated_cost": {
|
||||
"satoshis": 619209,
|
||||
"btc": 0.00619209
|
||||
},
|
||||
"update_tx_fee": {
|
||||
"satoshis": 196396,
|
||||
"btc": 0.00196396
|
||||
}
|
||||
}
|
||||
|
||||
### Profiles
|
||||
# Group Blockchain Operations
|
||||
## Get consensus hash [GET /v1/blockchains/{blockchainName}/consensus]
|
||||
Get the current Blockstack consensus hash on a blockchain.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ blockchainName : bitcoin (string) - the given blockchain
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
TODO: this is not decided
|
||||
{
|
||||
"consensus_hash": "2fcbdf66c350894fe03b42c6a2e8a6ac"
|
||||
}
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Create profile | POST /v1/profiles | profile_write | Payload: `{"name": NAME, "profile": PROFILE}`. Wallet must own the name. |
|
||||
| Get profile | GET /v1/profiles/{name} | profile_read | - |
|
||||
| Delete profile | DELETE /v1/profiles/{name} | profile_write | Wallet must own {name} |
|
||||
| Update profile | PATCH /v1/profiles/{name} | profile_write | Payload: `{"blockchain_id": NAME, "profile": PROFILE }`. Wallet must own the name |
|
||||
## Get pending transactions [GET /v1/blockchains/{blockchainName}/pending]
|
||||
Get the current transactions that the node has issued and are still pending.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ blockchainName : bitcoin (string) - the given blockchain
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
### Datastores
|
||||
{
|
||||
"queues": {}
|
||||
}
|
||||
## Get unspent outputs [GET /v1/blockchains/{blockchainName}/{address}/unspent]
|
||||
+ Authorization: `blockchain`
|
||||
+ Parameters
|
||||
+ blockchainName : bitcoin (string) - the given blockchain
|
||||
+ address : 1GuKR3nJi2VH3E1ZSPvuX8nAu3jNnr7xzq (string) - the address to get unspents for
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Create store for this session | POST /v1/stores | store_write | Creates a datastore for the application indicated by the session |
|
||||
| Get store metadata | GET /v1/stores/{storeID} | store_admin | - |
|
||||
| Delete store | DELETE /v1/stores/{storeID} | store_write | Deletes all files and directories in the store as well |
|
||||
| - | - | - | - |
|
||||
| Get inode info (stat) | GET /v1/stores/{storeID}/inodes?path={path} | store_read | - |
|
||||
| - | - | - | - |
|
||||
| Get directory files (ls) | GET /v1/stores/{storeID}/directories?path={path} | store_read | Returns structured inode data |
|
||||
| Create directory (mkdir) | POST /v1/stores/{storeID}/directories?path={path} | store_write | Only works on the datastore for the application indicated by the session |
|
||||
| Delete directory (rmdir) | DELETE /v1/stores/{storeID}/directories?path={path} | store_write | Only works on the datastore for the application indicated by the session |
|
||||
| - | - | - | - |
|
||||
| Get file data (cat) | GET /v1/stores/{storeID}/files?path={path} | store_read | Returns `application/octet-stream` data |
|
||||
| Create file | POST /v1/stores/{storeID}/files?path={path} | store_write | Uploads `application/octet-stream` raw file data. Only works on the datastore for the application indicated by the session. |
|
||||
| Update file | PUT /v1/stores/{storeID}/files?path={path} | store_write | Uploads `application/octet-stream` raw file data. Only works on the datastore for the application indicated by the session. |
|
||||
| Delete file (rm) | DELETE /v1/stores/{storeID}/files?path={path} | store_write | Only works on the datastore for the application indicated by the session |
|
||||
[
|
||||
{
|
||||
"confirmations": 18,
|
||||
"out_script": "76a914ae6ee3760fccb8225541ca89f08c927930adf97b88ac",
|
||||
"outpoint": {
|
||||
"hash": "977d3a025790e2cbdb50f63761872f36e78fbb9c53d515cb4c53155a1964932d",
|
||||
"index": 1
|
||||
},
|
||||
"transaction_hash": "977d3a025790e2cbdb50f63761872f36e78fbb9c53d515cb4c53155a1964932d",
|
||||
"value": 76779
|
||||
}
|
||||
]
|
||||
|
||||
### Collections
|
||||
## Broadcast transaction [POST /v1/blockchains/{blockchainName}/txs]
|
||||
+ Authorization: `blockchain`
|
||||
+ Parameters
|
||||
+ blockchainName : bitcoin (string) - the blockchain to broadcast on
|
||||
+ Request (application/json)
|
||||
+ Schema
|
||||
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'tx': {
|
||||
'type': 'string',
|
||||
'pattern': OP_HEX_PATTERN,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
'required': [
|
||||
'tx'
|
||||
],
|
||||
}
|
||||
|
||||
| Method | API Call | API family | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| Create collection | POST /v1/collections | collection_admin | NOT IMPLEMENTED |
|
||||
| Get all collection items | GET /v1/collections/{collectionID} | collection_read | NOT IMPLEMENTED |
|
||||
| Create collection item | POST /v1/collections/{collectionID} | collection_write | NOT IMPLEMENTED |
|
||||
| Get collection item | GET /v1/{collectionID}/{itemID} | collection_read | NOT IMPLEMENTED |
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{ 'status' : True, 'tx_hash' : '...' }
|
||||
|
||||
## Get raw name history [GET /v1/blockchains/{blockchainName}/names/{nameID}/history]
|
||||
Not implemented
|
||||
|
||||
+ Response 405 (application/json)
|
||||
+ Body
|
||||
|
||||
{ 'error' : 'Unimplemented' }
|
||||
|
||||
# Group Gaia Endpoints
|
||||
The Gaia endpoints interface with `blockstack-storage.js` to provide
|
||||
storage to blockstack applications.
|
||||
|
||||
## Create "store" for this session [POST /v1/stores]
|
||||
## Get "store" metadata [GET /v1/stores/{storeID}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
## Delete "store" [DELETE /v1/stores/{storeID}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
## Get inode info [GET /v1/stores/{storeID}/inodes?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Get directory files [GET /v1/stores/{storeID}/directories?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Create directory [POST /v1/stores/{storeID}/directories?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Delete directory [DELETE /v1/stores/{storeID}/directories?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Get file data [GET /v1/stores/{storeID}/files?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Create file [POST /v1/stores/{storeID}/files?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Update file [PUT /v1/stores/{storeID}/files?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
## Delete file [DELETE /v1/stores/{storeID}/files?path={path}]
|
||||
+ Parameters
|
||||
+ storeID : (string)
|
||||
+ path : (string) - path of inode
|
||||
|
||||
# Group Namespace Operations
|
||||
## Get all namespaces [GET /v1/namespaces]
|
||||
+ Public Endpoint
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
{
|
||||
"namespaces": [
|
||||
".id"
|
||||
]
|
||||
}
|
||||
|
||||
## Get namespace names [GET /v1/namespaces/{tld}/names?page={page}]
|
||||
Fetch a list of names from the namespace.
|
||||
+ Public Endpoint
|
||||
+ Parameters
|
||||
+ tld: id (string) - the namespace to fetch names from
|
||||
+ page: 23 (number) - names are returned in pages of size 100,
|
||||
so specify the page number.
|
||||
+ Response 200 (application/json)
|
||||
+ Body
|
||||
|
||||
[ "aldenquimby.id", "aldeoryn.id",
|
||||
"alderete.id", "aldert.id",
|
||||
"aldi.id", "aldighieri.id", ... ]
|
||||
|
||||
## Create namespace [POST /v1/namespaces]
|
||||
Not implemented.
|
||||
## Pre-register a name [POST /v1/namespaces/{tld}/names]
|
||||
Not implemented.
|
||||
## Update pre-registered name [POST /v1/namespaces/{tld}/names/{name}]
|
||||
Not implemented.
|
||||
## Launch namespace [PUT /v1/namespaces/{tld}]
|
||||
Not implemented.
|
||||
|
||||
@@ -22,6 +22,13 @@ more conventional types of applications.
|
||||
refactored to run many I/O operations in parallel. This leads to faster data
|
||||
interaction times, even for indexed storage systems like Dropbox.
|
||||
|
||||
* **Support for Fast Registrations.** This release adds an `unsafe` attribute to
|
||||
registration requests which, when enabled, directs core's registrar to issue
|
||||
the blockstack transactions _preorder_, _register_, and _update_, with only 4,
|
||||
1, and 1 confirmations respectively. To support this, core must ignore some
|
||||
of the safety checks, because our resolvers will not have processed the name
|
||||
before the _update_ is issued.
|
||||
|
||||
* **Initial subdomain support.** This release allows users to register
|
||||
subdomains of existing blockchain IDs, such that _subdomains are independently
|
||||
owned_. A user Bob can register `bob.alice.id`, where `alice.id` is owned by
|
||||
|
||||
Reference in New Issue
Block a user