├── .dockerignore ├── .env_template ├── .gitignore ├── LICENSES.md ├── Makefile ├── README.md ├── apps_config.py ├── client ├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── app_index_template.html ├── browserlist ├── demo │ ├── index.html │ ├── start.html │ ├── styles.css │ └── windowing.js ├── interledger │ ├── js │ │ ├── app.js │ │ └── components │ │ │ ├── account_detail.js │ │ │ ├── asset_row.js │ │ │ ├── assets.js │ │ │ └── interledger.js │ └── scss │ │ └── custom_style.scss ├── lib │ ├── css │ │ └── scss │ │ │ ├── account.scss │ │ │ ├── asset.scss │ │ │ ├── card.scss │ │ │ ├── custom_bootstrap.scss │ │ │ ├── main.scss │ │ │ ├── normalize.css │ │ │ ├── search.scss │ │ │ ├── sidebar.scss │ │ │ ├── spinner.scss │ │ │ ├── style.scss │ │ │ └── variables.scss │ └── js │ │ ├── constants │ │ ├── api_urls.js │ │ └── application_constants.js │ │ ├── plugins │ │ └── ledger_utils.js │ │ ├── react │ │ ├── actions │ │ │ ├── account_actions.js │ │ │ └── asset_actions.js │ │ ├── alt.js │ │ ├── components │ │ │ ├── account_detail.js │ │ │ ├── account_list.js │ │ │ ├── asset_action_panel.js │ │ │ ├── asset_detail.js │ │ │ ├── bigchaindb_connection.js │ │ │ ├── search.js │ │ │ └── spinner.js │ │ ├── sources │ │ │ ├── account_source.js │ │ │ └── asset_source.js │ │ └── stores │ │ │ ├── account_store.js │ │ │ └── asset_store.js │ │ └── utils │ │ ├── bigchaindb │ │ └── in_backlog.js │ │ ├── cryptoconditions │ │ ├── filter_by_type.js │ │ ├── parse_escrow_data.js │ │ └── type_ids.js │ │ └── request.js ├── on_the_record │ ├── js │ │ ├── app.js │ │ └── components │ │ │ ├── asset_history.js │ │ │ ├── assets.js │ │ │ └── on_the_record.js │ └── scss │ │ └── custom_style.scss ├── package.json ├── server.demo.js ├── share_trader │ ├── js │ │ ├── app.js │ │ └── components │ │ │ ├── asset_matrix.js │ │ │ ├── asset_row.js │ │ │ ├── assets.js │ │ │ └── share_trader.js │ └── scss │ │ └── custom_style.scss └── webpack.config.js ├── commands ├── __init__.py └── bigchaindb_examples.py ├── compose ├── frontend │ └── Dockerfile └── server │ └── Dockerfile ├── docs.yml ├── docs ├── Makefile ├── img │ ├── on_the_record_v0.0.1.png │ └── share_trader_v0.0.1.png ├── on_the_record.md ├── share_trade.md └── source │ ├── _static │ ├── ontherecord.png │ └── sharetrader.png │ ├── ack.rst │ ├── conf.py │ ├── docs.rst │ ├── index.rst │ ├── install.rst │ ├── interledger.rst │ ├── ontherecord.rst │ ├── run.rst │ ├── sharetrader.rst │ ├── structure.rst │ └── troubleshooting.rst ├── init_accounts.py ├── init_assets.py ├── ledgers.yml ├── server ├── __init__.py ├── app.py ├── config_bigchaindb.py ├── lib │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ └── views.py │ └── models │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── assets.py │ │ └── connector.py └── tornado_app.py └── setup.py /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.env_template: -------------------------------------------------------------------------------- 1 | # App settings 2 | CLIENT_HOST=localhost 3 | CLIENT_PORT=3000 4 | FLASK_HOST=localhost 5 | FLASK_PORT=8000 6 | TORNADO_HOST=localhost 7 | TORNADO_PORT=8888 8 | 9 | # Docker settings 10 | DOCKER_MACHINE_IP= 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | venv/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | node_modules 20 | rethinkdb_data 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | bundles/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | .env 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Sphinx documentation 61 | docs/build/ 62 | 63 | # PyBuilder 64 | target/ 65 | 66 | # Ipython Notebook 67 | .ipynb_checkpoints 68 | 69 | # pyenv 70 | .python-version 71 | 72 | # Private key files from AWS 73 | *.pem 74 | 75 | # Some files created when deploying a cluster on AWS 76 | deploy-cluster-aws/conf/rethinkdb.conf 77 | deploy-cluster-aws/hostlist.py 78 | 79 | # bigchaindb-examples 80 | .bigchaindb* 81 | .bigchaindb_examples 82 | server/static/ 83 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build init start 2 | 3 | build: 4 | docker-compose -f ledgers.yml build 5 | 6 | init: reinit_db config accounts assets 7 | 8 | start: 9 | docker-compose -f ledgers.yml up 10 | 11 | restart: init start 12 | 13 | config: 14 | rm -rf .bigchaindb_examples_docker .bigchaindb_examples_docker_connector 15 | touch .bigchaindb_examples_docker .bigchaindb_examples_docker_connector 16 | docker-compose -f ledgers.yml run --rm bdb-0 bigchaindb -yc .bigchaindb_examples configure 17 | docker-compose -f ledgers.yml run --rm bdb-0 bigchaindb -c .bigchaindb_examples init 18 | docker-compose -f ledgers.yml run --rm bdb-1 bigchaindb -yc .bigchaindb_examples configure 19 | docker-compose -f ledgers.yml run --rm bdb-1 bigchaindb -c .bigchaindb_examples init 20 | docker-compose -f ledgers.yml run --rm connector bigchaindb -yc .bigchaindb_examples configure 21 | 22 | accounts: 23 | docker-compose -f ledgers.yml run --rm bdb-0 python init_accounts.py 24 | 25 | assets: 26 | docker-compose -f ledgers.yml run --rm bdb-0 python init_assets.py 27 | 28 | 29 | drop_db: 30 | docker-compose -f ledgers.yml stop rdb 31 | docker-compose -f ledgers.yml rm -f rdb 32 | 33 | start_db: 34 | docker-compose -f ledgers.yml up -d rdb 35 | 36 | reinit_db: drop_db start_db 37 | sleep 10 38 | 39 | stop: 40 | docker-compose -f ledgers.yml down 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BigchainDB Examples 2 | 3 |

🔥 This project won't be updated anymore and has been succeeded by Kyber, a full suite of BigchainDB components with examples, tutorials & burning experiments. Head on over for more up-to-date examples, more fun and way more midi-chlorians.

4 | 5 | --- 6 | 7 | [![Status](https://img.shields.io/badge/status-in%20flux-yellow.svg)]() 8 | [![Documentation Status](http://readthedocs.org/projects/bigchaindb-examples/badge/?version=latest)](http://bigchaindb-examples.readthedocs.io/en/latest/?badge=latest) 9 | [![Join the chat at https://gitter.im/bigchaindb/bigchaindb](https://badges.gitter.im/bigchaindb/bigchaindb.svg)](https://gitter.im/bigchaindb/bigchaindb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 10 | 11 | This repo contains examples and tutorials for BigchainDB. 12 | 13 | __Warning__: These examples are for demonstration purposes and should not be used as-is for production 14 | 15 | See the [documentation](http://bigchaindb-examples.readthedocs.io/en/latest/index.html): 16 | * [Installing](http://bigchaindb-examples.readthedocs.io/en/latest/install.html) 17 | * [Running](http://bigchaindb-examples.readthedocs.io/en/latest/run.html) 18 | * [Troubleshooting](http://bigchaindb-examples.readthedocs.io/en/latest/troubleshooting.html) 19 | 20 | Examples: 21 | * [On the Record](#example-on-the-record) 22 | * [Share Trader](#example-share-trader) 23 | * [Interledger](#example-interledger) 24 | 25 | ### Dependencies 26 | 27 | The examples can be [run via Docker](http://bigchaindb-examples.readthedocs.io/en/latest/install.html#the-docker-way) 28 | (**recommended**), but, if you'd like, you can also [run them locally](http://bigchaindb-examples.readthedocs.io/en/latest/install.html#install-from-source) 29 | with the following system dependencies: 30 | 31 | - OS dependencies: see [setup BigchainDB & RethinkDB](https://bigchaindb.readthedocs.io/en/latest/installing-server.html#install-and-run-rethinkdb-server) 32 | - python>=3.4 33 | - node>=5.3 using [nvm](https://github.com/creationix/nvm#installation) (**recommended**), or [manually](https://nodejs.org/en/download/) 34 | - [npm>=3.3](https://docs.npmjs.com/getting-started/installing-node) (should be installed with node) 35 | 36 | ## Quick start 37 | 38 | 39 | ### Docker 40 | 41 | To run via Docker, set up your docker environment as necessary and: 42 | 43 | ```bash 44 | $ make 45 | ``` 46 | 47 | **Note**: If using docker-machine, you'll have to run `make` with your docker-machine ip: 48 | 49 | ```bash 50 | $ DOCKER_MACHINE_IP=$(docker-machine ip) make 51 | ``` 52 | 53 | The app will be available at (replace ``localhost`` with your 54 | docker-machine ip as necessary). 55 | 56 | ### Locally 57 | 58 | If you'd like to run these examples locally (preferably in a virtualenv), you can do so using 59 | the handy CLI: 60 | 61 | ```bash 62 | $ bigchaindb-examples --help 63 | 64 | # Start everything 65 | $ bigchaindb-examples start --init --all 66 | 67 | # Reset everything 68 | $ bigchaindb-examples reset-all 69 | ``` 70 | 71 | The app will be available at . 72 | 73 | ## Example: "On the Record" 74 | 75 | "On the Record" is a simple logging app, wrapped as a messaging board. 76 | 77 |

78 | 79 |

80 | 81 | ### Use cases 82 | 83 | - Immutable logging of data 84 | - Notarization of data, text, emails 85 | 86 | ### Functionality 87 | 88 | #### Create assets 89 | - with arbitrary payload 90 | - and an unlimited amount 91 | 92 | #### Retrieve assets 93 | - that you currently own (like UTXO's) 94 | - by searching the asset data/payload 95 | - state indicator (in backlog vs. on bigchain) 96 | 97 | #### What this app doesn't provide 98 | 99 | - Proper user and key management 100 | - Transfer of assets 101 | 102 | ## Example: Share Trader 103 | 104 | Share Trader is a simple share allocation and trade app. Each square represents an asset that can be traded amongst accounts. 105 | 106 |

107 | 108 |

109 | 110 | ### Use cases 111 | 112 | - Reservation of tickets, seats in a concert/transport, ... 113 | - Trade of limited issued assets 114 | 115 | ### Functionality 116 | 117 | #### Create assets 118 | - assets are created following a structured payload 119 | - the amount is limited 120 | 121 | #### Transfer assets 122 | - easy transfer of assets between accounts by: 123 | - clicking on an account first. This will give the assets for that account 124 | - clicking on an asset of that account. Transfer actions will appear on the right side. 125 | 126 | #### Retrieve assets 127 | - that you currently own (like UTXO's) 128 | - all assets on bigchain 129 | - state indicator (blinks if asset has various owners) 130 | 131 | #### What this app doesn't provide 132 | 133 | - Proper user and key management 134 | - Proper signing of transfers 135 | - Proper search by payload 136 | 137 | ## Example: Interledger 138 | 139 | TODO 140 | 141 | ## Acknowledgements: 142 | 143 | Special thanks to the BigchainDB/ascribe.io team for their insights and code contributions: 144 | 145 | @r-marques, @vrde, @ttmc, @rhsimplex, @SohKai, @sbellem, @TimDaub 146 | -------------------------------------------------------------------------------- /apps_config.py: -------------------------------------------------------------------------------- 1 | APPS = [ 2 | { 3 | 'name': 'ontherecord', 4 | 'num_accounts': 3, 5 | 'num_assets': 0, 6 | 'ledger': 0, 7 | 'payload_func': ( 8 | lambda x: { 9 | 'app': 'ontherecord', 10 | 'content': x 11 | } 12 | ) 13 | }, 14 | { 15 | 'name': 'sharetrader', 16 | 'num_accounts': 5, 17 | 'num_assets': 64, 18 | 'ledger': 0, 19 | 'payload_func': ( 20 | lambda i: { 21 | 'app': 'sharetrader', 22 | 'content': { 23 | 'x': int(i / 8), 24 | 'y': int(i % 8) 25 | } 26 | } 27 | ) 28 | }, 29 | { 30 | 'name': 'interledger', 31 | 'accounts': [ 32 | { 33 | 'name': 'alice', 34 | 'ledgers': [ 35 | { 36 | 'id': 0, 37 | 'num_assets': 3 38 | } 39 | ] 40 | }, 41 | { 42 | 'name': 'bob', 43 | 'ledgers': [ 44 | { 45 | 'id': 1, 46 | 'num_assets': 3 47 | } 48 | ] 49 | }, 50 | { 51 | 'name': 'chloe', 52 | 'ledgers': [ 53 | { 54 | 'id': 0, 55 | 'num_assets': 3 56 | }, 57 | { 58 | 'id': 1, 59 | 'num_assets': 3 60 | } 61 | ] 62 | } 63 | ], 64 | 'payload_func': ( 65 | lambda x: { 66 | 'app': 'interledger', 67 | 'content': x 68 | } 69 | ) 70 | } 71 | ] 72 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets': ['react', 'es2015'], 3 | 'plugins': [ 4 | 'transform-object-assign', 5 | 'transform-react-display-name', 6 | [ 'transform-runtime', { 7 | 'polyfill': false, 8 | 'regenerator': true 9 | } ] 10 | ], 11 | 'sourceMaps': true, 12 | 13 | 'env': { 14 | 'demo': { 15 | 'plugins': [ 16 | [ 'react-transform', { 17 | 'transforms': [{ 18 | 'transform': 'react-transform-hmr', 19 | 'imports': ['react'], 20 | 'locals': ['module'] 21 | }] 22 | } ] 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | build/* 2 | dist/* 3 | node_modules/* 4 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "ascribe-react" 3 | } 4 | -------------------------------------------------------------------------------- /client/app_index_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title || 'BigchainDB Examples' %> 9 | 10 | 11 | <% if (!htmlWebpackPlugin.options.PRODUCTION) { %> 12 | 19 | <% } %> 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /client/browserlist: -------------------------------------------------------------------------------- 1 | # Supported browsers by this project. 2 | # For format, see https://github.com/ai/browserslist 3 | Chrome >= 30 4 | Safari >= 6.1 5 | Firefox >= 35 6 | Opera >= 32 7 | Explorer >= 10 8 | iOS >= 8 9 | Android >= 2.3 10 | Last 2 versions 11 | -------------------------------------------------------------------------------- /client/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BigchainDB Examples 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/demo/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | BigchainDB Examples 9 | 10 | 11 | 18 | 19 | 20 | 21 |

BigchainDB Examples

22 |

You can check these out!

23 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /client/demo/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | margin: 0; 4 | overflow: hidden; 5 | } 6 | 7 | .button--add-frame { 8 | bottom: 15px; 9 | height: 45px; 10 | position: absolute; 11 | right: 15px; 12 | z-index: 100; 13 | } 14 | 15 | .button--back-frame { 16 | height: 25px; 17 | position: absolute; 18 | left: 10px; 19 | top: 10px; 20 | } 21 | 22 | .button--close-frame { 23 | height: 25px; 24 | position: absolute; 25 | right: 10px; 26 | top: 10px; 27 | } 28 | 29 | .frame-container { 30 | /* Floating frames containers left will allow any frames to be fluidly added or removed */ 31 | float: left; 32 | 33 | position: relative; 34 | } 35 | -------------------------------------------------------------------------------- /client/demo/windowing.js: -------------------------------------------------------------------------------- 1 | // ESLint doesn't provide a good way of turning of the ES6 features... so we'll have to do it 2 | // manually. 3 | /* eslint-disable no-var, prefer-arrow-callback, prefer-template, strict */ 4 | /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * Windowing script to dynamically create, resize, and destroy iframe instances of the BigchainDB 10 | * examples React app. Up to four windows can be open at the same time with one window shown 11 | * initially. 12 | * 13 | * Targets an element with the id `add-frame-handler` to become the button for adding new frames. 14 | * All new frames will be appended into the body. 15 | */ 16 | (function windowing() { 17 | var APP_SOURCE = './start.html'; 18 | var ADD_FRAME_HANDLER_QUERY = '#add-frame-handler'; 19 | var IFRAME_QUERY = 'iframe[src="' + APP_SOURCE + '"]'; 20 | var INITIAL_WINDOWS = 1; 21 | var MAX_FRAMES = 4; 22 | var ORIG_DOCUMENT_TITLE = document.title; 23 | 24 | var addFrameHandler = document.querySelector(ADD_FRAME_HANDLER_QUERY); 25 | var frames = []; 26 | var nextFrameNum = 0; 27 | 28 | /** Functions **/ 29 | function addFrame() { 30 | // Create a new iframe, decorate it some handlers for closing, etc, and wrap it in a 31 | // container 32 | var newFrameContainer = decorateFrameWithCapabilities(createFrame()); 33 | 34 | frames.push(newFrameContainer); 35 | adjustFrameSizing(frames); 36 | 37 | if (frames.length === MAX_FRAMES) { 38 | addFrameHandler.disabled = true; 39 | } 40 | 41 | // Finally, push new window into DOM body 42 | document.body.appendChild(newFrameContainer, addFrameHandler); 43 | } 44 | 45 | // Adjust sizing of each frame based on the total number of frames 46 | function adjustFrameSizing(totalFrames) { 47 | // Size windows into a 2-column grid 48 | var numGridCells = totalFrames.length % 2 ? (totalFrames.length + 1) : totalFrames.length; 49 | var baseFrameHeight = 100 / (numGridCells / 2); 50 | var baseFrameWidth = 50; 51 | var baseFrameHeightPercentage = baseFrameHeight + '%'; 52 | var baseFrameWidthPercentage = baseFrameWidth + '%'; 53 | 54 | totalFrames.forEach(function resizeFrame(frame, ii) { 55 | var overflowWidthPercentage; 56 | 57 | if (ii === totalFrames.length - 1 && totalFrames.length % 2) { 58 | // When there are an odd number of frames, make the last frame overflow to cover 59 | // the leftover bottom area of the screen 60 | overflowWidthPercentage = (2 * baseFrameWidth) + '%'; 61 | 62 | frame.style.height = baseFrameHeightPercentage; 63 | frame.style.width = overflowWidthPercentage; 64 | } else { 65 | frame.style.height = baseFrameHeightPercentage; 66 | frame.style.width = baseFrameWidthPercentage; 67 | } 68 | }); 69 | 70 | // Remove iframe borders if only one frame is visible 71 | if (totalFrames.length === 1) { 72 | totalFrames[0].querySelector(IFRAME_QUERY).style.borderWidth = 0; 73 | } else { 74 | // Reset first frame's borders in case they were removed 75 | totalFrames[0].querySelector(IFRAME_QUERY).style.borderWidth = ''; 76 | } 77 | } 78 | 79 | // Creates a new iframe 80 | function createFrame() { 81 | var frame = document.createElement('iframe'); 82 | frame.id = getNextFrameId(); 83 | frame.name = frame.id; 84 | frame.src = APP_SOURCE; 85 | 86 | // Frames are always 100% of their containers 87 | frame.height = '100%'; 88 | frame.width = '100%'; 89 | 90 | return frame; 91 | } 92 | 93 | // Wrap the iframe with a container, add back and close functionality, and attach event listeners 94 | function decorateFrameWithCapabilities(frame) { 95 | var container = document.createElement('div'); 96 | var backButton = document.createElement('button'); 97 | var closeButton = document.createElement('button'); 98 | 99 | // Set up container 100 | container.className = 'frame-container'; 101 | 102 | // Set up backButton 103 | backButton.className = 'button--back-frame'; 104 | backButton.innerHTML = 'Back'; 105 | backButton.onclick = function goBackInFrame() { 106 | // Only allow the back button to function if we're not on the start page 107 | if (frame.contentWindow.location.pathname !== APP_SOURCE.substring(1)) { 108 | frame.contentWindow.history.back(); 109 | } 110 | }; 111 | 112 | // Set up close button 113 | closeButton.className = 'button--close-frame'; 114 | closeButton.innerHTML = 'Close window ' + frame.name.replace(/example-frame-/, ''); 115 | closeButton.onclick = function closeFrame() { 116 | var removeIndex = frames.indexOf(container); 117 | var nextDevtoolFrame; 118 | 119 | if (removeIndex > -1) { 120 | frames.splice(removeIndex, 1); 121 | } 122 | 123 | // __REACT_DEVTOOLS_HOLDER__ holds the window of the iframe that is attached to the 124 | // devtools during development mode. If we are closing that window, attach the devtools 125 | // to the next iframe. 126 | // eslint-disable-next-line no-underscore-dangle 127 | if (window.__REACT_DEVTOOLS_HOLDER__ === frame.contentWindow) { 128 | nextDevtoolFrame = frames[0] && frames[0].querySelector(IFRAME_QUERY); 129 | 130 | if (nextDevtoolFrame) { 131 | attachFrameWithReactDevtools(nextDevtoolFrame); 132 | } else { 133 | detachCurrentFrameFromReactDevtools(); 134 | } 135 | } 136 | 137 | // Remove the frame from the DOM, adjust remaining frames' sizes, and allow more windows 138 | // to be created 139 | container.parentNode.removeChild(container); 140 | 141 | adjustFrameSizing(frames); 142 | addFrameHandler.disabled = false; 143 | }; 144 | 145 | // Set up frame listeners 146 | frame.onfocus = function frameOnFocus() { 147 | document.title = frame.contentDocument.title; 148 | }; 149 | frame.onblur = function frameOnBlur() { 150 | document.title = ORIG_DOCUMENT_TITLE; 151 | }; 152 | 153 | container.appendChild(backButton); 154 | container.appendChild(closeButton); 155 | container.appendChild(frame); 156 | 157 | return container; 158 | } 159 | 160 | // Gets next frame id 161 | function getNextFrameId() { 162 | return 'example-frame-' + (++nextFrameNum); 163 | } 164 | 165 | /** 166 | * Devtool utils 167 | * 168 | * Use __REACT_DEVTOOLS_HOLDER__ to determine which frame the devtool is attached to 169 | */ 170 | // Deregister the current frame from React devtools 171 | function detachCurrentFrameFromReactDevtools() { 172 | /* eslint-disable no-underscore-dangle */ 173 | if (window.__REACT_DEVTOOLS_HOLDER__) { 174 | window.__REACT_DEVTOOLS_HOLDER__.__REACT_DEVTOOLS_GLOBAL_HOOK__ = null; 175 | } 176 | 177 | window.__REACT_DEVTOOLS_HOLDER__ = null; 178 | /* eslint-enable no-underscore-dangle */ 179 | } 180 | 181 | // Register frame with React devtools 182 | function attachFrameWithReactDevtools(frame) { 183 | /* eslint-disable no-underscore-dangle */ 184 | // If the frame's hasn't loaded far enough yet to have a window, then we'll give up 185 | if (frame.contentWindow) { 186 | frame.contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__ = __REACT_DEVTOOLS_GLOBAL_HOOK__; 187 | 188 | window.__REACT_DEVTOOLS_HOLDER__ = frame.contentWindow; 189 | } else { 190 | console.error('Tried to attach React devtools to window: ' + frame.name + ' but ' + 191 | 'frame was not loaded yet. React devtools will not be available.'); 192 | } 193 | /* eslint-enable no-underscore-dangle */ 194 | } 195 | 196 | /** Initialization **/ 197 | // Initialize initial iframe windows 198 | (function initializeWindows(numWindows) { 199 | var ii; 200 | 201 | for (ii = 0; ii < numWindows; ++ii) { 202 | addFrame(); 203 | } 204 | }(INITIAL_WINDOWS)); 205 | 206 | // Attach action listener to addFrameHandler 207 | addFrameHandler.onclick = addFrame; 208 | }()); 209 | -------------------------------------------------------------------------------- /client/interledger/js/app.js: -------------------------------------------------------------------------------- 1 | // Install necessary polyfills (see supported browsers) into global 2 | import 'core-js/es6'; 3 | import 'core-js/stage/4'; 4 | import 'isomorphic-fetch'; 5 | 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | 9 | import Interledger from './components/interledger'; 10 | 11 | import '../../lib/css/scss/main.scss'; 12 | 13 | 14 | const App = () => ( 15 |
16 | 17 |
18 | ); 19 | 20 | ReactDOM.render(, document.getElementById('bigchaindb-example-app')); 21 | -------------------------------------------------------------------------------- /client/interledger/js/components/account_detail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classnames from 'classnames'; 4 | import { Col } from 'react-bootstrap/lib'; 5 | 6 | import Assets from './assets'; 7 | 8 | 9 | const AccountDetail = React.createClass({ 10 | propTypes: { 11 | account: React.PropTypes.object, 12 | accountList: React.PropTypes.array, 13 | activeAccount: React.PropTypes.object, 14 | activeAsset: React.PropTypes.object, 15 | assetList: React.PropTypes.object, 16 | handleAssetClick: React.PropTypes.func, 17 | handleClick: React.PropTypes.func 18 | }, 19 | 20 | render() { 21 | const { 22 | account, 23 | accountList, 24 | activeAccount, 25 | activeAsset, 26 | assetList, 27 | handleAssetClick, 28 | handleClick 29 | } = this.props; 30 | 31 | if (account && assetList && Array.isArray(assetList[account.vk])) { 32 | const assetListForAccount = assetList[account.vk]; 33 | return ( 34 | 37 |
38 |
39 | {account.name} 40 |
41 |
42 | {account.vk} 43 |
44 |
45 | {account.api} 46 |
47 | 54 |
55 | 56 | ); 57 | } 58 | return null; 59 | } 60 | }); 61 | 62 | export default AccountDetail; 63 | -------------------------------------------------------------------------------- /client/interledger/js/components/asset_row.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import classnames from 'classnames'; 4 | import { safeInvoke } from 'js-utility-belt/es6'; 5 | 6 | import AssetActionPanel from '../../../lib/js/react/components/asset_action_panel'; 7 | import AssetDetail from '../../../lib/js/react/components/asset_detail'; 8 | 9 | import inBacklog from '../../../lib/js/utils/bigchaindb/in_backlog'; 10 | 11 | 12 | const AssetRow = React.createClass({ 13 | propTypes: { 14 | account: React.PropTypes.object.isRequired, 15 | asset: React.PropTypes.object.isRequired, 16 | accountList: React.PropTypes.array, 17 | actionMap: React.PropTypes.object, 18 | handleAccountClick: React.PropTypes.func, 19 | handleAssetClick: React.PropTypes.func, 20 | isActive: React.PropTypes.bool 21 | }, 22 | 23 | getDefaultProps() { 24 | return { 25 | actionMap: { 26 | 'single-owner-transfer': { 27 | actionName: 'ESCROW', 28 | actionMessage: 'Escrow asset with:', 29 | selectAccounts: true 30 | }, 31 | 'multi-owner-execute': { 32 | actionName: 'EXECUTE', 33 | actionMessage: 'Execute escrow of asset:', 34 | selectAccounts: false 35 | }, 36 | 'multi-owner-abort': { 37 | actionName: 'ABORT', 38 | actionMessage: 'Abort escrow of asset:', 39 | selectAccounts: false 40 | } 41 | } 42 | }; 43 | }, 44 | 45 | getInitialState() { 46 | return { 47 | connectors: null, 48 | expiresIn: null 49 | }; 50 | }, 51 | 52 | componentDidMount() { 53 | if (this.getOperation() !== 'transfer') { 54 | this.intervalId = window.setInterval(this.setExpiryTime, 1000); 55 | } 56 | }, 57 | 58 | componentWillUnmount() { 59 | window.clearInterval(this.intervalId); 60 | }, 61 | 62 | setExpiryTime() { 63 | const { 64 | asset 65 | } = this.props; 66 | const expires = moment.unix(parseFloat(asset.expiryTime)); 67 | const expiresIn = moment.utc(expires.diff(moment.utc())); 68 | this.setState({ 69 | expiresIn: expiresIn > 0 ? expiresIn : -1 70 | }); 71 | }, 72 | 73 | handleAssetClick() { 74 | const { 75 | account, 76 | handleAccountClick, 77 | asset, 78 | handleAssetClick 79 | } = this.props; 80 | 81 | safeInvoke(handleAssetClick, asset); 82 | safeInvoke(handleAccountClick, account); 83 | }, 84 | 85 | handleDestinationAccountSelection(destinationAccount) { 86 | const { 87 | account, 88 | asset 89 | } = this.props; 90 | 91 | account.ledger.getConnectors().then((res) => { 92 | this.setState({ 93 | connectors: res.connectors 94 | }); 95 | }); 96 | 97 | // quoting should happen here 98 | // const quotes = connectors.map((connector) => connector.getQuote(asset, destinationAccount)); 99 | }, 100 | 101 | handleActionClick(selectedAccount) { 102 | const { 103 | connectors 104 | } = this.state; 105 | 106 | const { 107 | account, 108 | asset 109 | } = this.props; 110 | 111 | const idToTransfer = { 112 | txid: asset.id, 113 | cid: 0 114 | }; 115 | 116 | if (asset.type === 'single-owner') { 117 | const transfer = { 118 | account: selectedAccount.ledger.id === account.ledger.id ? 119 | selectedAccount : connectors[0], 120 | asset: idToTransfer, 121 | destinationAccount: selectedAccount, 122 | executionCondition: 'cc:0:3:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:0', 123 | expiresAt: moment().unix() + 100 124 | }; 125 | 126 | account.ledger.send(transfer); 127 | } else if (asset.type === 'multi-owner') { 128 | const transfer = { 129 | account, 130 | asset: idToTransfer 131 | }; 132 | if (this.getOperation() === 'execute') { 133 | const conditionFulfillment = 'cf:0:'; 134 | account.ledger.fulfillCondition(transfer, conditionFulfillment); 135 | } else { 136 | account.ledger.fulfillCondition(transfer); 137 | } 138 | } 139 | }, 140 | 141 | getOperation() { 142 | const { 143 | account, 144 | asset 145 | } = this.props; 146 | 147 | let operation = 'transfer'; 148 | if (asset.hasOwnProperty('executeCondition') && 149 | account.vk === asset.executeCondition.public_key) { 150 | operation = 'execute'; 151 | } else if (asset.hasOwnProperty('abortCondition') && 152 | account.vk === asset.abortCondition.public_key) { 153 | operation = 'abort'; 154 | } 155 | return operation; 156 | }, 157 | 158 | render() { 159 | const { 160 | account, 161 | accountList, 162 | actionMap, 163 | asset, 164 | isActive 165 | } = this.props; 166 | 167 | const { 168 | expiresIn 169 | } = this.state; 170 | 171 | const assetInBacklog = inBacklog(asset); 172 | const operation = this.getOperation(); 173 | 174 | let actionsPanel = null; 175 | if (isActive && accountList && !assetInBacklog) { 176 | const actionType = actionMap[`${asset.type}-${operation}`]; 177 | actionsPanel = ( 178 | 186 | ); 187 | } 188 | 189 | 190 | let escrowDetails = null; 191 | if (expiresIn) { 192 | const isExpired = expiresIn === -1; 193 | escrowDetails = ( 194 |
195 | {isExpired ? 'EXPIRED' : `Expires in ${expiresIn.format('HH:mm:ss')}`} 196 |
197 | ); 198 | } 199 | 200 | 201 | return ( 202 |
206 | 210 | {escrowDetails} 211 | {actionsPanel} 212 | 213 |
214 | ); 215 | } 216 | }); 217 | 218 | export default AssetRow; 219 | -------------------------------------------------------------------------------- /client/interledger/js/components/assets.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AssetRow from './asset_row'; 4 | import Spinner from '../../../lib/js/react/components/spinner'; 5 | 6 | 7 | const Assets = ({ 8 | account, 9 | accountList, 10 | assetList, 11 | activeAsset, 12 | handleAccountClick, 13 | handleAssetClick 14 | }) => { 15 | if (assetList && assetList.length) { 16 | return ( 17 |
18 | {assetList.map((asset) => { 19 | const isActive = !!activeAsset && activeAsset.id === asset.id; 20 | 21 | return ( 22 | 30 | ); 31 | })} 32 |
33 | ); 34 | } else { 35 | return ( 36 |
37 | 38 |
39 | ); 40 | } 41 | }; 42 | 43 | Assets.propTypes = { 44 | account: React.PropTypes.object, 45 | accountList: React.PropTypes.array, 46 | activeAsset: React.PropTypes.object, 47 | assetClasses: React.PropTypes.object, 48 | assetList: React.PropTypes.array, 49 | handleAccountClick: React.PropTypes.func, 50 | handleAssetClick: React.PropTypes.func 51 | }; 52 | 53 | export default Assets; 54 | -------------------------------------------------------------------------------- /client/interledger/js/components/interledger.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Navbar } from 'react-bootstrap/lib'; 4 | 5 | import AccountList from '../../../lib/js/react/components/account_list'; 6 | import AccountDetail from './account_detail'; 7 | 8 | import AssetActions from '../../../lib/js/react/actions/asset_actions'; 9 | 10 | import BigchainDBConnection from '../../../lib/js/react/components/bigchaindb_connection'; 11 | 12 | 13 | const Interledger = React.createClass({ 14 | propTypes: { 15 | // Injected through BigchainDBConnection 16 | accountList: React.PropTypes.array, 17 | activeAccount: React.PropTypes.object, 18 | activeAsset: React.PropTypes.object, 19 | assetList: React.PropTypes.object, 20 | handleAccountChange: React.PropTypes.func, 21 | handleAssetChange: React.PropTypes.func 22 | }, 23 | 24 | fetchAssetList({ account }) { 25 | if (account) { 26 | AssetActions.fetchAssetList({ 27 | account 28 | }); 29 | } 30 | }, 31 | 32 | render() { 33 | const { 34 | accountList, 35 | activeAccount, 36 | activeAsset, 37 | assetList, 38 | handleAccountChange, 39 | handleAssetChange 40 | } = this.props; 41 | 42 | return ( 43 |
44 | 45 |

Interledger

46 |
47 |
48 |
49 |
50 | 55 | 60 | 61 |
62 |
63 |
64 |
65 | ); 66 | } 67 | }); 68 | 69 | export default BigchainDBConnection(Interledger); 70 | -------------------------------------------------------------------------------- /client/interledger/scss/custom_style.scss: -------------------------------------------------------------------------------- 1 | @import "../../lib/css/scss/variables"; 2 | 3 | .interledger { 4 | 5 | .asset-container { 6 | border: 1px solid lighten($fg-color, 70%); 7 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2); 8 | margin-top: 1em; 9 | 10 | &:hover { 11 | border: 1px solid $ascribe-pink; 12 | cursor: pointer; 13 | } 14 | 15 | &.inBacklog { 16 | background: rgba(black, .05); 17 | &:hover { 18 | border: 1px solid lighten($ascribe-pink, 50%); 19 | cursor: default; 20 | } 21 | } 22 | } 23 | 24 | .asset-container-actions { 25 | margin-top: 1.7em 26 | } 27 | 28 | .asset-container-id { 29 | color: lighten($fg-color, 50%); 30 | } 31 | 32 | .asset-escrow-details { 33 | color: rgba(black, 0.8); 34 | font-size: 0.8em; 35 | font-style: italic; 36 | margin-bottom: -1em; 37 | margin-top: 1em; 38 | text-align: right; 39 | 40 | &.isExpired { 41 | color: rgba(red, 0.8); 42 | } 43 | } 44 | 45 | .card { 46 | overflow: visible; 47 | } 48 | 49 | .ledger-0 { 50 | color: darken($ascribe-blue, 10%); 51 | } 52 | .ledger-1 { 53 | color: $ascribe-pink; 54 | } 55 | .ledger-2 { 56 | color: $ascribe-dark-blue; 57 | } 58 | .ledger-3 { 59 | color: $ascribe-black; 60 | } 61 | 62 | #wrapper { 63 | margin-top: 80px; 64 | padding-left: 0 !important; 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /client/lib/css/scss/account.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .list-row, 4 | .list-item { 5 | font-size: .9em; 6 | text-transform: uppercase; 7 | padding: 1em; 8 | background-color: $bg-color; 9 | border-bottom: 1px solid lighten($fg-color, 80%); 10 | color: lighten($fg-color, 30%); 11 | } 12 | 13 | .list-row { 14 | &:hover { 15 | color: $bg-color !important; 16 | .list-row-detail { 17 | color: $bg-color !important; 18 | } 19 | background-color: lighten($bg-color--hover, 30%); 20 | cursor: pointer; 21 | } 22 | } 23 | 24 | .list-row.active { 25 | color: $bg-color !important; 26 | .list-row-detail { 27 | color: $bg-color !important; 28 | } 29 | background-color: $bg-color--hover; 30 | } 31 | 32 | .list-row-name { 33 | font-weight: bold; 34 | } 35 | 36 | .list-row-detail { 37 | font-size: 0.8em; 38 | font-style: italic; 39 | color: lighten($fg-color, 50%); 40 | } 41 | 42 | .list-row-name, 43 | .list-row-detail { 44 | text-overflow: ellipsis; 45 | overflow: hidden; 46 | } -------------------------------------------------------------------------------- /client/lib/css/scss/asset.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | 4 | .asset-container { 5 | border-bottom: 1px solid lighten($fg-color, 50%); 6 | border-left: 5px solid transparent; 7 | padding: 1em; 8 | } 9 | 10 | .asset-container-actions { 11 | margin-top: 1em 12 | } 13 | 14 | .asset-container-id, 15 | .asset-container-timestamp { 16 | font-size: 0.8em; 17 | text-transform: uppercase; 18 | } 19 | 20 | .asset-container-id { 21 | margin-bottom: 1em; 22 | overflow: hidden; 23 | font-size: 0.75em; 24 | font-style: italic; 25 | text-overflow: ellipsis; 26 | } 27 | 28 | .asset-container-timestamp { 29 | color: lighten($fg-color, 50%); 30 | margin-bottom: -.6em; 31 | text-align: right; 32 | } -------------------------------------------------------------------------------- /client/lib/css/scss/card.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .card { 4 | max-width: $row--wide; 5 | background: $bg-color; 6 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4); 7 | padding: 20px 40px; 8 | margin: 0 auto 40px; 9 | overflow: hidden; 10 | 11 | .desc { 12 | font-size: 35px; 13 | line-height: 150%; 14 | margin: 0 0 25px 0; 15 | 16 | @media (max-width: 600px) { 17 | font-size: 20px; 18 | } 19 | 20 | strong { 21 | background-color: black; 22 | color: $fg-color; 23 | padding: 4px 20px; 24 | } 25 | } 26 | } 27 | 28 | .card--summary { 29 | .desc { 30 | float: left; 31 | width: 60%; 32 | @media (max-width: 600px) { 33 | float: none; 34 | width: 100%; 35 | display: block; 36 | margin-left: auto; 37 | margin-right: auto; 38 | } 39 | } 40 | 41 | .preview { 42 | margin-top: -10px; 43 | float: right; 44 | width: 30%; 45 | max-width: 400px; 46 | @media (max-width: 600px) { 47 | float: none; 48 | width: 100%; 49 | margin-bottom: 30px; 50 | } 51 | } 52 | } 53 | 54 | .card { 55 | .disclaimer { 56 | padding-top: 30px; 57 | text-align: right; 58 | font-size: 12px; 59 | } 60 | } 61 | 62 | .card--claim { 63 | box-shadow: none; 64 | background-color: transparent; 65 | .desc { 66 | font-size: 20px; 67 | text-align: center; 68 | a { 69 | color: inherit; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /client/lib/css/scss/custom_bootstrap.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .pagination { 4 | margin-right: 2em; 5 | } 6 | .pagination > li > a, 7 | .pagination > li > span { 8 | color: lighten($fg-color, 60%); 9 | } 10 | 11 | .pagination > li > a:hover, 12 | .pagination > li > span:hover { 13 | color: lighten($fg-color, 30%); 14 | border-color: lighten($fg-color, 40%); 15 | } 16 | 17 | .pagination > .active > a, 18 | .pagination > .active > a:hover, 19 | .pagination > .active > a:focus, 20 | .pagination > .active > span, 21 | .pagination > .active > span:hover, 22 | .pagination > .active > span:focus { 23 | background-color: $bg-color; 24 | border-color: lighten($fg-color, 30%); 25 | color: $fg-color; 26 | } 27 | 28 | .btn-default { 29 | border-radius: 0; 30 | border: 1px solid lighten($fg-color, 20%); 31 | margin: .2em; 32 | background: $bg-color; 33 | color: lighten($fg-color, 20%); 34 | } 35 | 36 | .btn-default:focus, 37 | .btn-default.focus, 38 | .btn-default:active, 39 | .btn-default.active, 40 | .btn-default.active:focus, 41 | .btn-default.active:hover, 42 | .btn-default:hover { 43 | background: lighten($fg-color, 20%); 44 | color: $bg-color; 45 | border: 1px solid $bg-color; 46 | } 47 | 48 | .btn-secondary{ 49 | border-radius: 0; 50 | border: 1px solid $bg-color; 51 | margin: .2em; 52 | background: lighten($fg-color, 20%); 53 | color: $bg-color; 54 | } 55 | 56 | .label-primary { 57 | background-color: lighten($fg-color, 40%); 58 | color: $bg-color !important; 59 | border: 1px solid lighten($fg-color, 10%); 60 | margin-right: .3em; 61 | max-width: 100%; 62 | text-overflow: ellipsis; 63 | white-space: nowrap; 64 | overflow: hidden; 65 | display: inline-block; 66 | padding: .5em 0.8em .4em 0.5em 67 | } 68 | 69 | .dropdown-menu { 70 | > li > a { 71 | color: $fg-color; 72 | } 73 | background-color: $bg-color; 74 | } 75 | 76 | .dropdown-menu > li > a:hover { 77 | color: $bg-color; 78 | background-color: $bg-color--hover; 79 | cursor: pointer; 80 | } 81 | 82 | .btn-group.open .dropdown-toggle { 83 | box-shadow: none; 84 | } 85 | 86 | .open > .btn-default.dropdown-toggle:hover, 87 | .open > .btn-default.dropdown-toggle:focus, 88 | .open > .btn-default.dropdown-toggle.focus{ 89 | color: $fg-color; 90 | background-color: lighten($bg-color, 7%); 91 | border: 1px solid $fg-color; 92 | } -------------------------------------------------------------------------------- /client/lib/css/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato:300); 2 | // TODO: this should be removed 3 | @import url(https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css); 4 | 5 | @import './style'; 6 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; 7 | @import "~bootstrap-sass/assets/stylesheets/bootstrap"; 8 | @import 'custom_bootstrap'; 9 | @import 'normalize.css'; 10 | @import 'account'; 11 | @import 'asset'; 12 | @import 'card'; 13 | @import 'search'; 14 | @import 'sidebar'; 15 | @import 'spinner'; 16 | @import 'style'; 17 | @import '../../../on_the_record/scss/custom_style'; 18 | @import '../../../share_trader/scss/custom_style'; 19 | @import '../../../interledger/scss/custom_style'; 20 | -------------------------------------------------------------------------------- /client/lib/css/scss/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability of focused elements when they are also in an 95 | * active/hover state. 96 | */ 97 | 98 | a:active, 99 | a:hover { 100 | outline: 0; 101 | } 102 | 103 | /* Text-level semantics 104 | ========================================================================== */ 105 | 106 | /** 107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 108 | */ 109 | 110 | abbr[title] { 111 | border-bottom: 1px dotted; 112 | } 113 | 114 | /** 115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bold; 121 | } 122 | 123 | /** 124 | * Address styling not present in Safari and Chrome. 125 | */ 126 | 127 | dfn { 128 | font-style: italic; 129 | } 130 | 131 | /** 132 | * Address variable `h1` font-size and margin within `section` and `article` 133 | * contexts in Firefox 4+, Safari, and Chrome. 134 | */ 135 | 136 | h1 { 137 | font-size: 2em; 138 | margin: 0.67em 0; 139 | } 140 | 141 | /** 142 | * Address styling not present in IE 8/9. 143 | */ 144 | 145 | mark { 146 | background: #ff0; 147 | color: #000; 148 | } 149 | 150 | /** 151 | * Address inconsistent and variable font size in all browsers. 152 | */ 153 | 154 | small { 155 | font-size: 80%; 156 | } 157 | 158 | /** 159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 160 | */ 161 | 162 | sub, 163 | sup { 164 | font-size: 75%; 165 | line-height: 0; 166 | position: relative; 167 | vertical-align: baseline; 168 | } 169 | 170 | sup { 171 | top: -0.5em; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | /* Embedded content 179 | ========================================================================== */ 180 | 181 | /** 182 | * Remove border when inside `a` element in IE 8/9/10. 183 | */ 184 | 185 | img { 186 | border: 0; 187 | } 188 | 189 | /** 190 | * Correct overflow not hidden in IE 9/10/11. 191 | */ 192 | 193 | svg:not(:root) { 194 | overflow: hidden; 195 | } 196 | 197 | /* Grouping content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Address margin not present in IE 8/9 and Safari. 202 | */ 203 | 204 | figure { 205 | margin: 1em 40px; 206 | } 207 | 208 | /** 209 | * Address differences between Firefox and other browsers. 210 | */ 211 | 212 | hr { 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 354 | */ 355 | 356 | input[type="search"] { 357 | -webkit-appearance: textfield; /* 1 */ 358 | box-sizing: content-box; /* 2 */ 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 363 | * Safari (but not Chrome) clips the cancel button when the search input has 364 | * padding (and `textfield` appearance). 365 | */ 366 | 367 | input[type="search"]::-webkit-search-cancel-button, 368 | input[type="search"]::-webkit-search-decoration { 369 | -webkit-appearance: none; 370 | } 371 | 372 | /** 373 | * Define consistent border, margin, and padding. 374 | */ 375 | 376 | fieldset { 377 | border: 1px solid #c0c0c0; 378 | margin: 0 2px; 379 | padding: 0.35em 0.625em 0.75em; 380 | } 381 | 382 | /** 383 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 385 | */ 386 | 387 | legend { 388 | border: 0; /* 1 */ 389 | padding: 0; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove default vertical scrollbar in IE 8/9/10/11. 394 | */ 395 | 396 | textarea { 397 | overflow: auto; 398 | } 399 | 400 | /** 401 | * Don't inherit the `font-weight` (applied by a rule above). 402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 403 | */ 404 | 405 | optgroup { 406 | font-weight: bold; 407 | } 408 | 409 | /* Tables 410 | ========================================================================== */ 411 | 412 | /** 413 | * Remove most spacing between table cells. 414 | */ 415 | 416 | table { 417 | border-collapse: collapse; 418 | border-spacing: 0; 419 | } 420 | 421 | td, 422 | th { 423 | padding: 0; 424 | } 425 | -------------------------------------------------------------------------------- /client/lib/css/scss/search.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .searchbar { 4 | margin: 0 auto; 5 | } 6 | 7 | .searchbar input[type="url"] { 8 | width: 80%; 9 | display: inline-block; 10 | @media (max-width: 480px) { 11 | width: 70%; 12 | } 13 | } 14 | 15 | .searchbar input[type="submit"] { 16 | width: calc(20% - 15px); 17 | padding-left: 0; 18 | padding-right: 0; 19 | margin-left: 15px; 20 | margin-right: 0; 21 | display: inline-block; 22 | @media (max-width: 480px) { 23 | width: 30%; 24 | margin-left: 0px; 25 | } 26 | } -------------------------------------------------------------------------------- /client/lib/css/scss/sidebar.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | 4 | #wrapper { 5 | padding-left: $sidebar--wide; 6 | transition: all 0.4s ease 0s; 7 | margin-top: $navbar-height; 8 | } 9 | 10 | #sidebar-wrapper { 11 | margin-left: -$sidebar--wide; 12 | left: $sidebar--wide; 13 | width: $sidebar--wide; 14 | height: 100%; 15 | overflow-y: auto; 16 | z-index: 1200; 17 | transition: all 0.4s ease 0s; 18 | } 19 | 20 | .sidebar-nav { 21 | position: fixed; 22 | top: $navbar-height; 23 | width: $sidebar--wide; 24 | margin: 0; 25 | padding: 2em 0; 26 | z-index: 1100; 27 | height: 100vh; 28 | border-right: 1px solid #888888; 29 | ul { 30 | list-style: none; 31 | padding: 0; 32 | } 33 | .dropdown-menu { 34 | overflow-y: scroll; 35 | max-height: 50vh; 36 | } 37 | 38 | } 39 | 40 | @media (max-width: 930px) { 41 | 42 | #wrapper { 43 | padding-left: 0; 44 | } 45 | 46 | #sidebar-wrapper { 47 | left: 0; 48 | } 49 | 50 | #wrapper.active { 51 | position: relative; 52 | left: $sidebar--wide; 53 | } 54 | 55 | #wrapper.active #sidebar-wrapper { 56 | left: $sidebar--wide; 57 | width: $sidebar--wide; 58 | transition: all 0.4s ease 0s; 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /client/lib/css/scss/spinner.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | 4 | [class^="spinner-wrapper-"]{ 5 | margin: auto; 6 | } 7 | 8 | .spinner-wrapper-blue { 9 | color: $ascribe-blue; 10 | .spinner-circle { 11 | border-color: $ascribe-blue; 12 | } 13 | } 14 | 15 | .spinner-wrapper-dark-blue { 16 | color: $ascribe-dark-blue; 17 | .spinner-circle { 18 | border-color: $ascribe-dark-blue; 19 | } 20 | } 21 | 22 | .spinner-wrapper-pink { 23 | color: $ascribe-pink; 24 | .spinner-circle { 25 | border-color: $ascribe-pink; 26 | } 27 | } 28 | 29 | .spinner-wrapper-loop { 30 | -webkit-animation: spinner-color-loop 20s infinite; 31 | -moz-animation: spinner-color-loop 20s infinite; 32 | -o-animation: spinner-color-loop 20s infinite; 33 | -ms-animation: spinner-color-loop 20s infinite; 34 | animation: spinner-color-loop 20s; 35 | 36 | } 37 | 38 | .spinner-wrapper-black { 39 | color: $ascribe-black; 40 | .spinner-circle { 41 | border-color: $ascribe-black; 42 | } 43 | } 44 | 45 | .spinner-wrapper-white { 46 | color: $ascribe-white; 47 | .spinner-circle { 48 | border-color: $ascribe-white; 49 | } 50 | } 51 | 52 | .spinner-wrapper-lg { 53 | width: $ascribe--spinner-size-lg; 54 | height: $ascribe--spinner-size-lg; 55 | } 56 | 57 | .spinner-wrapper-md { 58 | width: $ascribe--spinner-size-md; 59 | height: $ascribe--spinner-size-md; 60 | } 61 | 62 | .spinner-wrapper-sm { 63 | width: $ascribe--spinner-size-sm; 64 | height: $ascribe--spinner-size-sm; 65 | } 66 | 67 | .spinner-circle { 68 | border-radius: 50%; 69 | border-style: solid; 70 | 71 | -webkit-animation: spin 1s infinite linear; 72 | -moz-animation: spin 1s infinite linear; 73 | -o-animation: spin 1s infinite linear; 74 | -ms-animation: spin 1s infinite linear; 75 | animation: spin 1s infinite linear; 76 | } 77 | .spinner-wrapper-lg .spinner-inner, 78 | .spinner-wrapper-lg .spinner-circle { 79 | width: $ascribe--spinner-size-lg; 80 | height: $ascribe--spinner-size-lg; 81 | } 82 | 83 | .spinner-wrapper-md .spinner-inner, 84 | .spinner-wrapper-md .spinner-circle { 85 | width: $ascribe--spinner-size-md; 86 | height: $ascribe--spinner-size-md; 87 | } 88 | 89 | .spinner-wrapper-sm .spinner-inner, 90 | .spinner-wrapper-sm .spinner-circle { 91 | width: $ascribe--spinner-size-sm; 92 | height: $ascribe--spinner-size-sm; 93 | } 94 | 95 | .spinner-wrapper-lg .spinner-circle, 96 | .spinner-wrapper-sm .spinner-circle, 97 | .spinner-wrapper-md .spinner-circle { 98 | border-width: 1px 1px 1px 0; 99 | } 100 | 101 | //.spinner-inner { 102 | // position: relative; 103 | // text-align: center; 104 | //} 105 | 106 | .spinner-inner { 107 | display: none; 108 | } 109 | 110 | .spinner-wrapper-lg .spinner-inner { 111 | font-size: $ascribe--spinner-size-lg; 112 | line-height: $ascribe--spinner-size-lg; 113 | top: -52px; 114 | } 115 | 116 | .spinner-wrapper-md .spinner-inner { 117 | font-size: $ascribe--spinner-size-md; 118 | line-height: $ascribe--spinner-size-md; 119 | top: -34px; 120 | } 121 | 122 | .spinner-wrapper-sm .spinner-inner { 123 | font-size: $ascribe--spinner-size-sm; 124 | line-height: $ascribe--spinner-size-sm; 125 | top: -15px; 126 | } 127 | 128 | @-webkit-keyframes spin { 129 | from {-webkit-transform: rotate(0deg);} 130 | to {-webkit-transform: rotate(359deg);} 131 | } 132 | 133 | @-moz-keyframes spin { 134 | from {-moz-transform: rotate(0deg);} 135 | to {-moz-transform: rotate(359deg);} 136 | } 137 | 138 | @-o-keyframes spin { 139 | from {-o-transform: rotate(0deg);} 140 | to {-o-transform: rotate(359deg);} 141 | } 142 | 143 | @keyframes spin{ 144 | from {transform: rotate(0deg);} 145 | to {transform: rotate(359deg);} 146 | } -------------------------------------------------------------------------------- /client/lib/css/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | /** 4 | * Colors and backgrounds 5 | */ 6 | 7 | $row: 100%; 8 | $row--wide: 900px; 9 | $gutter: 20px; 10 | $small: 12px; 11 | 12 | $color--brand: #c90000; 13 | $color--background-secondary: #EEE; 14 | 15 | html { 16 | font-family: 'Lato', sans-serif; 17 | } 18 | 19 | body { 20 | background-color: $bg-color; 21 | } 22 | 23 | .app { 24 | margin: auto; 25 | //max-width: $content--max-width + $sidebar--wide; 26 | height: 100%; 27 | } 28 | 29 | a { 30 | color: $fg-color; 31 | } 32 | 33 | #page-content-wrapper { 34 | width: 100%; 35 | } 36 | 37 | .page-content { 38 | padding: 0 1em; 39 | } 40 | 41 | .content-text { 42 | margin-top: 2em; 43 | text-align: center; 44 | } 45 | 46 | .hero, 47 | .page--result { 48 | background-color: $color--background-secondary; 49 | } 50 | 51 | .logo-brand { 52 | > span:first-of-type { 53 | font-weight: 700; 54 | } 55 | } 56 | 57 | .navbar-fixed-bottom { 58 | margin-left: $sidebar--wide; 59 | } 60 | 61 | @media (max-width: 930px) { 62 | .navbar-fixed-bottom { 63 | margin-left: 0; 64 | } 65 | } 66 | 67 | /** 68 | * Layout 69 | */ 70 | 71 | .row { 72 | max-width: $row; 73 | margin-left: auto; 74 | margin-right: auto; 75 | } 76 | 77 | .vertical-align-outer { 78 | position: absolute; 79 | display: table; 80 | width: 100%; 81 | height: 100%; 82 | } 83 | 84 | .vertical-align-inner { 85 | display: table-cell; 86 | vertical-align: middle; 87 | } 88 | 89 | input { 90 | width: 100%; 91 | font-size: 20px; 92 | line-height: 130%; 93 | border: 1px solid #BBB; 94 | padding: 1em; 95 | } 96 | 97 | #app-container { 98 | margin-top: 50vh; 99 | text-align: center; 100 | background: $fg-color; 101 | color: black; 102 | } 103 | 104 | #container { 105 | background-color: $color--background; 106 | } 107 | 108 | .content-header-wrapper { 109 | background-color: $bg-color; 110 | width: 100%; 111 | } 112 | 113 | .content-header { 114 | width: 100%; 115 | max-width: $content--max-width; 116 | position: fixed; 117 | background-color: $bg-color; 118 | height: 70px; 119 | box-shadow: 0 2px 2px -2px rgba(0, 0, 0, .5); 120 | z-index: 1200; 121 | } 122 | -------------------------------------------------------------------------------- /client/lib/css/scss/variables.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Colors and backgrounds 4 | */ 5 | 6 | /*79589F*/ 7 | $ascribe-black: #222; 8 | $ascribe-dark-blue: #003C69; 9 | $ascribe-blue: #65CFE9; 10 | $ascribe-light-blue: #D3DEE4; 11 | $ascribe-pink: #D10074; 12 | $ascribe-white: white; 13 | 14 | $color--brand: #c90000; 15 | $color--background: #FAFAFA; 16 | $color--background-secondary: #FAFAFA; 17 | 18 | $bg-color: #FAFAFA; 19 | $fg-color: black; 20 | $bg-color--hover: lighten($fg-color, 20%); 21 | 22 | /** 23 | * Sizes 24 | */ 25 | 26 | $ascribe--spinner-color: $ascribe-blue; 27 | 28 | $ascribe--spinner-size-lg: 50px; 29 | $ascribe--spinner-size-md: 30px; 30 | $ascribe--spinner-size-sm: 15px; 31 | 32 | $navbar-height: 70px; 33 | $header-height: 70px; 34 | 35 | $row: 700px; 36 | $row--wide: 900px; 37 | $gutter: 20px; 38 | $small: 12px; 39 | 40 | $content--max-width: 1100px; 41 | $sidebar--wide: 250px; 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /client/lib/js/constants/api_urls.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-template */ 2 | import { API_PATH } from './application_constants'; 3 | 4 | 5 | const ApiUrls = { 6 | 'accounts': API_PATH + 'accounts/', 7 | 'accounts_detail': API_PATH + 'accounts/%(accountId)s/', 8 | 'assets_for_account': API_PATH + 'accounts/%(accountId)s/assets/', 9 | 'assets': API_PATH + 'assets/', 10 | 'assets_detail': API_PATH + 'assets/%(assetId)s/', 11 | 'assets_transfer': API_PATH + 'assets/%(assetId)s/%(cid)s/transfer/', 12 | 'assets_escrow': API_PATH + 'assets/%(assetId)s/%(cid)s/escrow/', 13 | 'assets_escrow_fulfill': API_PATH + 'assets/%(assetId)s/%(cid)s/escrow/fulfill/' 14 | }; 15 | 16 | 17 | export default ApiUrls; 18 | -------------------------------------------------------------------------------- /client/lib/js/constants/application_constants.js: -------------------------------------------------------------------------------- 1 | export const FLASK_BASE_URL = process.env.FLASK_BASE_URL; 2 | export const API_PATH = `${FLASK_BASE_URL}/api/`; 3 | 4 | export default { 5 | API_PATH, 6 | FLASK_BASE_URL, 7 | }; 8 | -------------------------------------------------------------------------------- /client/lib/js/plugins/ledger_utils.js: -------------------------------------------------------------------------------- 1 | import BigchainDBLedgerPlugin from 'ilp-plugin-bigchaindb'; 2 | 3 | const connectToBigchainDBLedger = (account) => { 4 | const ledgerPlugin = new BigchainDBLedgerPlugin({ 5 | auth: { 6 | account: { 7 | id: account.vk, 8 | key: account.sk, 9 | uri: { 10 | api: `http://${account.ledger.api}`, 11 | ws: `ws://${account.ledger.ws}/users/${account.vk}` 12 | } 13 | } 14 | }, 15 | ledgerId: account.ledger.id 16 | }); 17 | 18 | ledgerPlugin.connect().catch(console.error); 19 | return ledgerPlugin; 20 | }; 21 | 22 | 23 | export default connectToBigchainDBLedger; 24 | -------------------------------------------------------------------------------- /client/lib/js/react/actions/account_actions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt'; 2 | 3 | 4 | class AccountActions { 5 | constructor() { 6 | this.generateActions( 7 | 'flushAccount', 8 | 'fetchAccount', 9 | 'successFetchAccount', 10 | 'flushAccountList', 11 | 'fetchAccountList', 12 | 'successFetchAccountList', 13 | 'errorAccount', 14 | 'postAccount', 15 | 'successPostAccount' 16 | ); 17 | } 18 | } 19 | 20 | export default alt.createActions(AccountActions); 21 | -------------------------------------------------------------------------------- /client/lib/js/react/actions/asset_actions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt'; 2 | 3 | 4 | class AssetActions { 5 | constructor() { 6 | this.generateActions( 7 | 'flushAsset', 8 | 'fetchAsset', 9 | 'successFetchAsset', 10 | 'transferAsset', 11 | 'escrowAsset', 12 | 'fulfillEscrowAsset', 13 | 'flushAssetList', 14 | 'fetchAssetList', 15 | 'successFetchAssetList', 16 | 'errorAsset', 17 | 'postAsset', 18 | 'successPostAsset' 19 | ); 20 | } 21 | } 22 | 23 | export default alt.createActions(AssetActions); 24 | -------------------------------------------------------------------------------- /client/lib/js/react/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from 'alt'; 2 | 3 | 4 | const alt = new Alt(); 5 | 6 | export default alt; 7 | -------------------------------------------------------------------------------- /client/lib/js/react/components/account_detail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classnames from 'classnames'; 4 | import { Row } from 'react-bootstrap/lib'; 5 | 6 | 7 | const AccountDetail = ({ 8 | account, 9 | isActive, 10 | handleClick 11 | }) => { 12 | return ( 13 | 16 |
17 | {account.name} 18 |
19 |
20 | {account.vk} 21 |
22 |
23 | ); 24 | }; 25 | 26 | AccountDetail.propTypes = { 27 | account: React.PropTypes.object, 28 | handleClick: React.PropTypes.func, 29 | isActive: React.PropTypes.bool 30 | }; 31 | 32 | export default AccountDetail; 33 | -------------------------------------------------------------------------------- /client/lib/js/react/components/account_list.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { safeInvoke } from 'js-utility-belt/es6'; 3 | import classnames from 'classnames'; 4 | 5 | import AccountActions from '../actions/account_actions'; 6 | import AccountStore from '../stores/account_store'; 7 | 8 | import Spinner from './spinner'; 9 | 10 | const AccountList = React.createClass({ 11 | propTypes: { 12 | activeAccount: React.PropTypes.object, 13 | appName: React.PropTypes.string, 14 | children: React.PropTypes.node, 15 | className: React.PropTypes.string, 16 | handleAccountClick: React.PropTypes.func 17 | }, 18 | 19 | getInitialState() { 20 | return AccountStore.getState(); 21 | }, 22 | 23 | componentDidMount() { 24 | AccountStore.listen(this.onChange); 25 | this.fetchAccountList(); 26 | }, 27 | 28 | componentWillUnmount() { 29 | AccountStore.unlisten(this.onChange); 30 | }, 31 | 32 | onChange(state) { 33 | this.setState(state); 34 | }, 35 | 36 | fetchAccountList() { 37 | const { appName } = this.props; 38 | AccountActions.flushAccountList(); 39 | AccountActions.fetchAccountList({ app: appName }); 40 | }, 41 | 42 | render() { 43 | const { 44 | activeAccount, 45 | children, 46 | className, 47 | handleAccountClick 48 | } = this.props; 49 | 50 | const { accountList } = this.state; 51 | 52 | if (accountList && accountList.length > 0) { 53 | return ( 54 |
55 | {accountList 56 | .sort((a, b) => { 57 | if (a.name < b.name) return -1; 58 | if (a.name > b.name) return 1; 59 | return 0; 60 | }) 61 | .map(account => ( 62 | 67 | {children} 68 | 69 | ))} 70 |
71 | ); 72 | } else { 73 | return ( 74 |
75 | 76 |
77 | ); 78 | } 79 | } 80 | }); 81 | 82 | const AccountWrapper = React.createClass({ 83 | propTypes: { 84 | account: React.PropTypes.object, 85 | children: React.PropTypes.node, 86 | handleClick: React.PropTypes.func, 87 | isActive: React.PropTypes.bool 88 | }, 89 | 90 | handleClick() { 91 | const { account, handleClick } = this.props; 92 | safeInvoke(handleClick, account); 93 | }, 94 | 95 | render() { 96 | const { 97 | account, 98 | isActive, 99 | children 100 | } = this.props; 101 | 102 | return ( 103 |
104 | { 105 | React.Children.map(children, (child) => 106 | React.cloneElement(child, { 107 | account, 108 | isActive, 109 | handleClick: this.handleClick 110 | }) 111 | ) 112 | } 113 |
114 | ); 115 | } 116 | }); 117 | 118 | export default AccountList; 119 | -------------------------------------------------------------------------------- /client/lib/js/react/components/asset_action_panel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Button, DropdownButton, MenuItem } from 'react-bootstrap/lib'; 4 | import { safeInvoke } from 'js-utility-belt/es6'; 5 | 6 | 7 | const AssetActionPanel = React.createClass({ 8 | propTypes: { 9 | accountList: React.PropTypes.array.isRequired, 10 | activeAccount: React.PropTypes.object.isRequired, 11 | handleActionClick: React.PropTypes.func.isRequired, 12 | actionMessage: React.PropTypes.string, 13 | actionName: React.PropTypes.string, 14 | handleAccountSelection: React.PropTypes.func, 15 | selectAccounts: React.PropTypes.bool 16 | }, 17 | 18 | getDefaultProps() { 19 | return { 20 | actionMessage: 'Transfer asset to:', 21 | actionName: 'TRANSFER', 22 | selectAccounts: true 23 | }; 24 | }, 25 | 26 | getInitialState() { 27 | return { 28 | selectedAccount: null 29 | }; 30 | }, 31 | 32 | setSelectedAccount(account) { 33 | this.setState({ 34 | selectedAccount: account 35 | }); 36 | 37 | safeInvoke(this.props.handleAccountSelection, account); 38 | }, 39 | 40 | render() { 41 | const { 42 | accountList, 43 | actionMessage, 44 | actionName, 45 | activeAccount, 46 | handleActionClick, 47 | selectAccounts 48 | } = this.props; 49 | 50 | const { 51 | selectedAccount 52 | } = this.state; 53 | 54 | const transferButton = (!selectAccounts || selectedAccount) ? 55 | : null; 60 | 61 | const accountDropdown = selectAccounts ? 62 | 68 | { 69 | accountList 70 | .filter((account) => account !== activeAccount) 71 | .map((account) => ( 72 | this.setSelectedAccount(account)}> 75 | {account.name} 76 | 77 | )) 78 | } 79 | : null; 80 | 81 | return ( 82 |
83 |
{actionMessage}
84 | {accountDropdown} 85 | {transferButton} 86 |
87 | ); 88 | } 89 | }); 90 | 91 | 92 | export default AssetActionPanel; 93 | -------------------------------------------------------------------------------- /client/lib/js/react/components/asset_detail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Row, Glyphicon } from 'react-bootstrap/lib'; 4 | import classnames from 'classnames'; 5 | import moment from 'moment'; 6 | 7 | 8 | const AssetDetail = React.createClass({ 9 | propTypes: { 10 | asset: React.PropTypes.object.isRequired, 11 | assetContent: React.PropTypes.string, 12 | children: React.PropTypes.node, 13 | className: React.PropTypes.string, 14 | inBacklog: React.PropTypes.bool, 15 | onClick: React.PropTypes.func 16 | }, 17 | 18 | getAssetContent() { 19 | const { 20 | asset, 21 | assetContent 22 | } = this.props; 23 | 24 | if (assetContent) { 25 | return assetContent; 26 | } 27 | // TODO: Validate 28 | const { data: { payload: { content } } = {} } = asset.transaction; 29 | return content || '-'; 30 | }, 31 | 32 | render() { 33 | const { 34 | asset, 35 | children, 36 | className, 37 | inBacklog, 38 | onClick 39 | } = this.props; 40 | 41 | const assetContent = this.getAssetContent(); 42 | const validGlyph = inBacklog ? : ; 43 | const timestamp = 44 | moment(parseInt(asset.transaction.timestamp, 10) * 1000).toDate().toGMTString(); 45 | 46 | return ( 47 | 48 |
49 |
50 | {asset.id} 51 |
52 |
53 | {assetContent} 54 |
55 |
56 | {`${timestamp} `}{validGlyph} 57 |
58 | {children} 59 |
60 |
61 | ); 62 | } 63 | }); 64 | 65 | export default AssetDetail; 66 | -------------------------------------------------------------------------------- /client/lib/js/react/components/bigchaindb_connection.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { safeInvoke, safeMerge } from 'js-utility-belt/es6'; 4 | 5 | import AssetStore from '../stores/asset_store'; 6 | import AccountStore from '../stores/account_store'; 7 | 8 | 9 | export default function BigchainDBConnection(Component) { 10 | return React.createClass({ 11 | displayName: `BigchainDBConnection(${Component.displayName || Component})`, 12 | 13 | getInitialState() { 14 | const accountStore = AccountStore.getState(); 15 | const assetStore = AssetStore.getState(); 16 | 17 | return safeMerge( 18 | { 19 | activeAccount: null, 20 | activeAsset: null 21 | }, 22 | accountStore, 23 | assetStore 24 | ); 25 | }, 26 | 27 | componentDidMount() { 28 | AccountStore.listen(this.onAccountStoreChange); 29 | AssetStore.listen(this.onChange); 30 | }, 31 | 32 | componentWillUnmount() { 33 | AccountStore.unlisten(this.onAccountStoreChange); 34 | AssetStore.unlisten(this.onChange); 35 | }, 36 | 37 | onChange(state) { 38 | this.setState(state); 39 | }, 40 | 41 | onAccountStoreChange(state) { 42 | const { oldAccountList } = this.state; 43 | state.accountList.forEach((account) => { 44 | if (account.ledger && 45 | (!oldAccountList || 46 | (oldAccountList && oldAccountList.indexOf(account) === -1))) { 47 | account.ledger.on('incoming', this.handleLedgerChanges); 48 | } 49 | }); 50 | 51 | this.setState(state); 52 | }, 53 | 54 | handleAccountChange(activeAccount) { 55 | this.setState({ 56 | activeAccount 57 | }); 58 | }, 59 | 60 | handleLedgerChanges(changes) { 61 | console.log('incoming: ', changes); 62 | 63 | if (changes && changes.client && this.refs.component) { 64 | const { 65 | accountList 66 | } = this.state; 67 | 68 | const account = accountList.filter((account) => account.vk === changes.client)[0]; 69 | safeInvoke(this.refs.component.fetchAssetList, { 70 | account 71 | }); 72 | } 73 | }, 74 | 75 | handleAssetChange(asset) { 76 | this.setState({ 77 | activeAsset: asset 78 | }); 79 | }, 80 | 81 | resetActiveAccount() { 82 | this.handleAccountChange(null); 83 | }, 84 | 85 | render() { 86 | return ( 87 | 93 | ); 94 | } 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /client/lib/js/react/components/search.js: -------------------------------------------------------------------------------- 1 | import React from 'react/'; 2 | 3 | import { FormGroup, InputGroup, FormControl, Glyphicon } from 'react-bootstrap/lib/'; 4 | 5 | 6 | const Search = React.createClass({ 7 | propTypes: { 8 | handleSearch: React.PropTypes.func, 9 | initialQuery: React.PropTypes.string 10 | }, 11 | 12 | getInitialState() { 13 | return { 14 | timer: null, 15 | searchQuery: this.props.initialQuery, 16 | loading: false 17 | }; 18 | }, 19 | 20 | startTimer(searchQuery) { 21 | const { timer } = this.state; 22 | // The timer waits for the specified threshold time in milliseconds 23 | // and then calls `evaluateTimer`. 24 | // If another letter has been called in the mean time (timespan < `threshold`), 25 | // the present timer gets cleared and a new one is added to `this.state`. 26 | // This means that `evaluateTimer`, will only be called when the threshold has actually 27 | // passed, (tdaub) 28 | clearTimeout(timer); // apparently `clearTimeout` can be called with null, without throwing errors 29 | const newTimer = setTimeout(this.evaluateTimer(searchQuery), 500); 30 | 31 | this.setState({ timer: newTimer }); 32 | }, 33 | 34 | evaluateTimer(searchQuery) { 35 | return () => { 36 | this.setState({ timer: null, loading: true }, () => { 37 | this.handleSearch(searchQuery); 38 | }); 39 | }; 40 | }, 41 | 42 | handleSearchThrottled(event) { 43 | // On each letter entry we're updating the state of the component 44 | // and start a timer, which we're also pushing to the state 45 | // of the component 46 | const value = event.target.value; 47 | this.startTimer(value); 48 | this.setState({ searchQuery: value }); 49 | }, 50 | 51 | handleSearch(searchQuery) { 52 | const { handleSearch } = this.props; 53 | handleSearch(searchQuery); 54 | }, 55 | 56 | render() { 57 | const { searchQuery } = this.state; 58 | return ( 59 |
60 | 61 | 62 | 63 | 69 | 70 | 71 |
72 | ); 73 | } 74 | }); 75 | 76 | export default Search; 77 | -------------------------------------------------------------------------------- /client/lib/js/react/components/spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | 5 | const Spinner = React.createClass({ 6 | propTypes: { 7 | classNames: React.PropTypes.string, 8 | color: React.PropTypes.oneOf( 9 | ['black', 'blue', 'dark-blue', 'light-blue', 'pink', 'white', 'loop'] 10 | ), 11 | size: React.PropTypes.oneOf( 12 | ['sm', 'md', 'lg'] 13 | ) 14 | }, 15 | 16 | getDefaultProps() { 17 | return { 18 | inline: false, 19 | size: 'md', 20 | color: 'black' 21 | }; 22 | }, 23 | 24 | render() { 25 | const { 26 | classNames: classes, 27 | color, 28 | size 29 | } = this.props; 30 | 31 | return ( 32 |
36 |
37 |
A
38 |
39 | ); 40 | } 41 | }); 42 | 43 | export default Spinner; 44 | -------------------------------------------------------------------------------- /client/lib/js/react/sources/account_source.js: -------------------------------------------------------------------------------- 1 | import AccountActions from '../actions/account_actions'; 2 | 3 | import request from '../../utils/request'; 4 | 5 | 6 | const AccountSource = { 7 | lookupAccount: { 8 | remote(state) { 9 | return request('accounts_detail', { 10 | urlTemplateSpec: { 11 | accountId: state.accountMeta.idToFetch 12 | } 13 | }); 14 | }, 15 | 16 | success: AccountActions.successFetchAccount, 17 | error: AccountActions.errorAccount 18 | }, 19 | 20 | lookupAccountList: { 21 | remote(state) { 22 | return request('accounts', { 23 | query: { 24 | app: state.accountMeta.app 25 | } 26 | }); 27 | }, 28 | 29 | success: AccountActions.successFetchAccountList, 30 | error: AccountActions.errorAccount 31 | }, 32 | 33 | postAccount: { 34 | remote(state) { 35 | return request('accounts', { 36 | method: 'POST', 37 | jsonBody: state.accountMeta.payloadToPost 38 | }); 39 | }, 40 | 41 | success: AccountActions.successPostAccount, 42 | error: AccountActions.errorAccount 43 | } 44 | }; 45 | 46 | export default AccountSource; 47 | -------------------------------------------------------------------------------- /client/lib/js/react/sources/asset_source.js: -------------------------------------------------------------------------------- 1 | import request from '../../utils/request'; 2 | 3 | import AssetActions from '../actions/asset_actions'; 4 | 5 | 6 | const AssetSource = { 7 | lookupAssetList: { 8 | remote(state) { 9 | const { account, search } = state.assetMeta; 10 | // fetch assets for account 11 | const url = `${account.api}/accounts/${account.vk}/assets/`; 12 | return request(url, { 13 | query: { search } 14 | }); 15 | }, 16 | 17 | success: AssetActions.successFetchAssetList, 18 | error: AssetActions.errorAsset 19 | }, 20 | 21 | postAsset: { 22 | remote(state) { 23 | const { account, payloadToPost } = state.assetMeta; 24 | const url = `${account.api}/assets/`; 25 | return request(url, { 26 | method: 'POST', 27 | jsonBody: payloadToPost 28 | }); 29 | }, 30 | 31 | success: AssetActions.successPostAsset, 32 | error: AssetActions.errorAsset 33 | }, 34 | 35 | transferAsset: { 36 | remote(state) { 37 | const { account, idToTransfer: { cid, txid: assetId }, payloadToPost } = state.assetMeta; 38 | const url = `${account.api}/assets/${assetId}/${cid}/transfer/`; 39 | return request(url, { 40 | method: 'POST', 41 | jsonBody: payloadToPost, 42 | urlTemplateSpec: { assetId, cid } 43 | }); 44 | }, 45 | 46 | success: AssetActions.successPostAsset, 47 | error: AssetActions.errorAsset 48 | }, 49 | 50 | escrowAsset: { 51 | remote(state) { 52 | const { idToTransfer: { cid, txid: assetId }, payloadToPost } = state.assetMeta; 53 | 54 | return request('assets_escrow', { 55 | method: 'POST', 56 | jsonBody: payloadToPost, 57 | urlTemplateSpec: { assetId, cid } 58 | }); 59 | }, 60 | 61 | success: AssetActions.successPostAsset, 62 | error: AssetActions.errorAsset 63 | }, 64 | 65 | fulfillEscrowAsset: { 66 | remote(state) { 67 | const { idToTransfer: { cid, txid: assetId }, payloadToPost } = state.assetMeta; 68 | 69 | return request('assets_escrow_fulfill', { 70 | method: 'POST', 71 | jsonBody: payloadToPost, 72 | urlTemplateSpec: { assetId, cid } 73 | }); 74 | }, 75 | 76 | success: AssetActions.successPostAsset, 77 | error: AssetActions.errorAsset 78 | } 79 | }; 80 | 81 | export default AssetSource; 82 | -------------------------------------------------------------------------------- /client/lib/js/react/stores/account_store.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt'; 2 | 3 | import AccountActions from '../actions/account_actions'; 4 | import AccountSource from '../sources/account_source'; 5 | 6 | import AssetActions from '../actions/asset_actions'; 7 | 8 | import connectToBigchainDBLedger from '../../plugins/ledger_utils'; 9 | 10 | 11 | class AccountStore { 12 | constructor() { 13 | this.account = null; 14 | this.accountList = null; 15 | this.accountMeta = { 16 | err: null, 17 | payloadToPost: null, 18 | idToFetch: null, 19 | app: null 20 | }; 21 | this.bindActions(AccountActions); 22 | this.registerAsync(AccountSource); 23 | } 24 | 25 | onFetchAccount(idToFetch) { 26 | this.accountMeta.idToFetch = idToFetch; 27 | this.getInstance().lookupAccount(); 28 | } 29 | 30 | onSuccessFetchAccount(account) { 31 | if (account) { 32 | this.account = this.postProcessAccount(account); 33 | this.accountMeta.err = null; 34 | this.accountMeta.idToFetch = null; 35 | this.accountMeta.app = null; 36 | } else { 37 | this.accountMeta.err = new Error('Problem fetching the account'); 38 | } 39 | } 40 | 41 | onFetchAccountList({ app }) { 42 | this.accountMeta.app = app; 43 | this.getInstance().lookupAccountList(); 44 | } 45 | 46 | onSuccessFetchAccountList(accountList) { 47 | if (accountList) { 48 | this.accountList = accountList.accounts.map((account) => this.postProcessAccount(account)); 49 | this.accountMeta.err = null; 50 | this.accountMeta.app = null; 51 | } else { 52 | this.accountMeta.err = new Error('Problem fetching the account list'); 53 | } 54 | } 55 | 56 | postProcessAccount(account) { 57 | const processedAccount = Object.assign({}, account); 58 | 59 | // ledger bindings 60 | processedAccount.ledger = connectToBigchainDBLedger(account); 61 | processedAccount.api = `http://${account.ledger.api}/api`; 62 | 63 | // connectors 64 | processedAccount.ledger.getConnectors() 65 | .then((res) => { 66 | processedAccount.connectors = res.connectors; 67 | processedAccount.isConnector = 68 | res.connectors.filter((connector) => connector.vk === account.vk).length > 0; 69 | }); 70 | 71 | // assets 72 | AssetActions.fetchAssetList.defer({ 73 | account: processedAccount 74 | }); 75 | 76 | return processedAccount; 77 | } 78 | 79 | onPostAccount(payloadToPost) { 80 | this.accountMeta.payloadToPost = payloadToPost; 81 | this.getInstance().postAccount(); 82 | } 83 | 84 | onSuccessPostAccount(account) { 85 | if (account) { 86 | this.account = account; 87 | this.accountMeta.err = null; 88 | this.accountMeta.payloadToPost = null; 89 | this.accountMeta.app = null; 90 | } else { 91 | this.accountMeta.err = new Error('Problem posting to the account'); 92 | } 93 | } 94 | 95 | onFlushAccount() { 96 | this.account = null; 97 | this.accountMeta.err = null; 98 | this.accountMeta.payloadToPost = null; 99 | this.accountMeta.idToFetch = null; 100 | this.accountMeta.app = null; 101 | } 102 | 103 | onFlushAccountList() { 104 | this.accountList = null; 105 | this.accountMeta.err = null; 106 | this.accountMeta.app = null; 107 | } 108 | 109 | onErrorAccount(err) { 110 | this.accountMeta.err = err; 111 | } 112 | } 113 | 114 | export default alt.createStore(AccountStore, 'AccountStore'); 115 | -------------------------------------------------------------------------------- /client/lib/js/react/stores/asset_store.js: -------------------------------------------------------------------------------- 1 | import { safeMerge } from 'js-utility-belt/es6'; 2 | import alt from '../alt'; 3 | 4 | import parseEscrowData from '../../utils/cryptoconditions/parse_escrow_data'; 5 | 6 | import AssetActions from '../actions/asset_actions'; 7 | import AssetSource from '../sources/asset_source'; 8 | 9 | class AssetStore { 10 | constructor() { 11 | this.asset = null; 12 | this.assetList = {}; 13 | this.assetMeta = { 14 | err: null, 15 | isFetchingList: false, 16 | payloadToPost: null, 17 | idToFetch: null, 18 | idToTransfer: null, 19 | account_: null, 20 | search: null 21 | }; 22 | this.bindActions(AssetActions); 23 | this.registerAsync(AssetSource); 24 | } 25 | 26 | onFetchAssetList({ account, search, blockWhenFetching }) { 27 | if (!blockWhenFetching || 28 | (blockWhenFetching && !this.assetMeta.isFetchingList)) { 29 | this.assetMeta.account = account; 30 | this.assetMeta.search = search; 31 | this.assetMeta.isFetchingList = true; 32 | this.getInstance().lookupAssetList(); 33 | } 34 | } 35 | 36 | onSuccessFetchAssetList(assetList) { 37 | if (assetList) { 38 | const { assets, account } = assetList; 39 | if (account && assets) { 40 | if (assets.hasOwnProperty('bigchain')) { 41 | this.assetList[account] = 42 | assets.bigchain 43 | .concat(assets.backlog) 44 | .map(this.postProcessAsset) 45 | .sort((a, b) => a.transaction.timestamp - b.transaction.timestamp); 46 | } 47 | } 48 | this.assetMeta.err = null; 49 | this.assetMeta.account = null; 50 | } else { 51 | this.assetMeta.err = new Error('Problem fetching the asset list'); 52 | } 53 | this.assetMeta.isFetchingList = false; 54 | } 55 | 56 | postProcessAsset(asset) { 57 | const condition = asset.transaction.conditions[0].condition; 58 | 59 | if (Array.isArray(condition.details.subfulfillments)) { 60 | asset.type = 'multi-owner'; 61 | return safeMerge( 62 | asset, 63 | parseEscrowData(condition.details) 64 | ); 65 | } else { 66 | asset.type = 'single-owner'; 67 | } 68 | 69 | return asset; 70 | } 71 | 72 | onFlushAssetList(account) { 73 | this.assetList[account] = []; 74 | this.assetMeta.account = null; 75 | this.assetMeta.search = null; 76 | this.assetMeta.isFetchingList = false; 77 | } 78 | 79 | onPostAsset({ account, payloadToPost }) { 80 | this.assetMeta.account = account; 81 | this.assetMeta.payloadToPost = payloadToPost; 82 | this.getInstance().postAsset(); 83 | } 84 | 85 | onTransferAsset({ account, idToTransfer, payloadToPost }) { 86 | this.assetMeta.account = account; 87 | this.assetMeta.idToTransfer = idToTransfer; 88 | this.assetMeta.payloadToPost = payloadToPost; 89 | this.getInstance().transferAsset(); 90 | } 91 | 92 | onEscrowAsset({ idToTransfer, payloadToPost }) { 93 | this.assetMeta.idToTransfer = idToTransfer; 94 | this.assetMeta.payloadToPost = payloadToPost; 95 | this.getInstance().escrowAsset(); 96 | } 97 | 98 | onFulfillEscrowAsset({ idToTransfer, payloadToPost }) { 99 | this.assetMeta.idToTransfer = idToTransfer; 100 | this.assetMeta.payloadToPost = payloadToPost; 101 | this.getInstance().fulfillEscrowAsset(); 102 | } 103 | 104 | onSuccessPostAsset(asset) { 105 | if (asset) { 106 | this.asset = asset; 107 | this.assetMeta.err = null; 108 | this.assetMeta.idToTransfer = null; 109 | this.assetMeta.payloadToPost = null; 110 | } else { 111 | this.assetMeta.err = new Error('Problem posting to the asset'); 112 | } 113 | } 114 | 115 | onFlushAsset() { 116 | this.asset = null; 117 | this.assetMeta.err = null; 118 | this.assetMeta.payloadToPost = null; 119 | this.assetMeta.idToFetch = null; 120 | this.assetMeta.search = null; 121 | this.assetMeta.isFetchingList = false; 122 | } 123 | 124 | onErrorAsset(err) { 125 | this.assetMeta.err = err; 126 | this.assetMeta.isFetchingList = false; 127 | } 128 | } 129 | 130 | export default alt.createStore(AssetStore, 'AssetStore'); 131 | -------------------------------------------------------------------------------- /client/lib/js/utils/bigchaindb/in_backlog.js: -------------------------------------------------------------------------------- 1 | // TODO: improve backlog identifier: asset.hasOwnProperty('assignee') 2 | 3 | const inBacklog = (asset) => { 4 | return asset.hasOwnProperty('assignee'); 5 | }; 6 | 7 | export default inBacklog; 8 | -------------------------------------------------------------------------------- /client/lib/js/utils/cryptoconditions/filter_by_type.js: -------------------------------------------------------------------------------- 1 | 2 | const filterByType = ({ condition, typeId, maxDepth }) => { 3 | let res = []; 4 | if (condition.hasOwnProperty('type_id') && condition.type_id === typeId) { 5 | res.push(condition); 6 | } 7 | 8 | if (condition.hasOwnProperty('subfulfillments') && (maxDepth || maxDepth == null)) { 9 | res = res.concat(...Object.values(condition.subfulfillments).map((subcondition) => { 10 | return filterByType({ 11 | typeId, 12 | condition: subcondition, 13 | maxDepth: maxDepth && maxDepth - 1 14 | }); 15 | })); 16 | } 17 | 18 | return res; 19 | }; 20 | 21 | 22 | export default filterByType; 23 | -------------------------------------------------------------------------------- /client/lib/js/utils/cryptoconditions/parse_escrow_data.js: -------------------------------------------------------------------------------- 1 | import filterByType from './filter_by_type'; 2 | import TypeIds from './type_ids'; 3 | 4 | 5 | const filterChildrenByTypes = ({ condition, types }) => { 6 | if (condition.hasOwnProperty('subfulfillments')) { 7 | const children = Object.values(types) 8 | .map((type) => { 9 | return filterByType({ 10 | condition, 11 | typeId: type, 12 | maxDepth: 1 13 | }); 14 | }) 15 | .reduce((a, b) => a.concat(b)); 16 | if (children.length >= types.length) { 17 | return children; 18 | } 19 | } 20 | return null; 21 | }; 22 | 23 | 24 | export default function parseEscrowData(condition) { 25 | const expiryCondition = filterByType({ 26 | condition, 27 | typeId: TypeIds.timeout 28 | }); 29 | 30 | const thresholdConditions = filterByType({ 31 | condition, 32 | typeId: TypeIds.threshold 33 | }); 34 | 35 | let executeCondition = null; 36 | let abortCondition = null; 37 | 38 | Object.values(thresholdConditions).forEach((thresholdCondition) => { 39 | if (!executeCondition) { 40 | const filteredExecuteCondition = filterChildrenByTypes({ 41 | condition: thresholdCondition, 42 | types: [TypeIds.ed25519, TypeIds.timeout] 43 | }); 44 | executeCondition = filteredExecuteCondition ? 45 | filterByType({ 46 | condition: thresholdCondition, 47 | typeId: TypeIds.ed25519 48 | })[0] : null; 49 | } 50 | if (!abortCondition) { 51 | const filteredAbortCondition = filterChildrenByTypes({ 52 | condition: thresholdCondition, 53 | types: [TypeIds.ed25519, TypeIds.inverter] 54 | }); 55 | abortCondition = filteredAbortCondition ? 56 | filterByType({ 57 | condition: thresholdCondition, 58 | typeId: TypeIds.ed25519 59 | })[0] : null; 60 | } 61 | }); 62 | 63 | return { 64 | expiryTime: (expiryCondition.length > 0) ? expiryCondition[0].expire_time : null, 65 | executeCondition, 66 | abortCondition 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /client/lib/js/utils/cryptoconditions/type_ids.js: -------------------------------------------------------------------------------- 1 | 2 | const TypeIds = { 3 | 'preimage': 0, 4 | 'prefix': 1, 5 | 'threshold': 2, 6 | 'rsa': 3, 7 | 'ed25519': 4, 8 | 'inverter': 98, 9 | 'timeout': 99 10 | }; 11 | 12 | 13 | export default TypeIds; 14 | -------------------------------------------------------------------------------- /client/lib/js/utils/request.js: -------------------------------------------------------------------------------- 1 | import { request as baseRequest, sanitize } from 'js-utility-belt/es6'; 2 | 3 | import ApiUrls from '../constants/api_urls'; 4 | 5 | 6 | const DEFAULT_REQUEST_CONFIG = { 7 | credentials: 'include', 8 | headers: { 9 | 'Accept': 'application/json', 10 | 'Content-Type': 'application/json' 11 | } 12 | }; 13 | 14 | /** 15 | * Small wrapper around js-utility-belt's request that provides url resolving, default settings, and 16 | * response handling. 17 | */ 18 | export default function request(url, config = {}) { 19 | // Load default fetch configuration and remove any falsy query parameters 20 | const requestConfig = Object.assign({}, DEFAULT_REQUEST_CONFIG, config, { 21 | query: config.query && sanitize(config.query) 22 | }); 23 | let apiUrl = url; 24 | 25 | if (!url) { 26 | return Promise.reject(new Error('Request was not given a url.')); 27 | } else if (!url.match(/^http/)) { 28 | apiUrl = ApiUrls[url]; 29 | if (!apiUrl) { 30 | return Promise.reject(new Error(`Request could not find a url mapping for "${url}"`)); 31 | } 32 | } 33 | 34 | return baseRequest(apiUrl, requestConfig) 35 | .then((res) => res.json()) 36 | .catch((err) => { 37 | console.error(err); 38 | throw err; 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /client/on_the_record/js/app.js: -------------------------------------------------------------------------------- 1 | // Install necessary polyfills (see supported browsers) into global 2 | import 'core-js/es6'; 3 | import 'core-js/stage/4'; 4 | import 'isomorphic-fetch'; 5 | 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | 9 | import OnTheRecord from './components/on_the_record'; 10 | 11 | import '../../lib/css/scss/main.scss'; 12 | 13 | 14 | const App = () => ( 15 |
16 | 17 |
18 | ); 19 | 20 | ReactDOM.render(, document.getElementById('bigchaindb-example-app')); 21 | -------------------------------------------------------------------------------- /client/on_the_record/js/components/asset_history.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AssetDetail from '../../../lib/js/react/components/asset_detail'; 4 | import inBacklog from '../../../lib/js/utils/bigchaindb/in_backlog'; 5 | 6 | 7 | const AssetHistory = ({ 8 | assetList 9 | }) => { 10 | if (assetList.length === 0) { 11 | return ( 12 |
13 | No messages found on BigchainDB. Start typing... 14 |
15 | ); 16 | } 17 | 18 | return ( 19 |
20 | {assetList 21 | .map(asset => ( 22 | 27 | ))} 28 |
29 | ); 30 | }; 31 | 32 | AssetHistory.propTypes = { 33 | assetList: React.PropTypes.array.isRequired 34 | }; 35 | 36 | export default AssetHistory; 37 | -------------------------------------------------------------------------------- /client/on_the_record/js/components/assets.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Scroll from 'react-scroll'; 4 | 5 | import AssetActions from '../../../lib/js/react/actions/asset_actions'; 6 | 7 | import AssetHistory from './asset_history'; 8 | 9 | 10 | const Assets = React.createClass({ 11 | 12 | propTypes: { 13 | activeAccount: React.PropTypes.object, 14 | assetList: React.PropTypes.array 15 | }, 16 | 17 | getInitialState() { 18 | return { value: "" }; 19 | }, 20 | 21 | handleInputSubmit(event) { 22 | event.preventDefault(); 23 | const { activeAccount } = this.props; 24 | const { value } = this.state; 25 | 26 | const payloadToPost = { 27 | to: activeAccount.vk, 28 | content: value 29 | }; 30 | AssetActions.postAsset({ 31 | payloadToPost, 32 | account: activeAccount 33 | }); 34 | 35 | this.setState({ value: "" }); 36 | 37 | Scroll.animateScroll.scrollToBottom(); 38 | }, 39 | 40 | handleInputChange(event) { 41 | this.setState({ value: event.target.value }); 42 | }, 43 | 44 | render() { 45 | const { 46 | activeAccount, 47 | assetList 48 | } = this.props; 49 | 50 | const { value } = this.state; 51 | 52 | if (!assetList || !activeAccount) { 53 | return ( 54 |
55 | Select account from the list... 56 |
57 | ); 58 | } 59 | 60 | return ( 61 |
62 | 64 |
65 | 71 |
72 |
73 | ); 74 | } 75 | }); 76 | 77 | export default Assets; 78 | -------------------------------------------------------------------------------- /client/on_the_record/js/components/on_the_record.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Navbar } from 'react-bootstrap/lib'; 4 | 5 | import Scroll from 'react-scroll'; 6 | 7 | import AccountList from '../../../lib/js/react/components/account_list'; 8 | import AccountDetail from '../../../lib/js/react/components/account_detail'; 9 | 10 | import Assets from './assets'; 11 | import Search from '../../../lib/js/react/components/search'; 12 | 13 | import AssetActions from '../../../lib/js/react/actions/asset_actions'; 14 | 15 | import BigchainDBConnection from '../../../lib/js/react/components/bigchaindb_connection'; 16 | 17 | 18 | const OnTheRecord = React.createClass({ 19 | propTypes: { 20 | // Injected through BigchainDBConnection 21 | activeAccount: React.PropTypes.object, 22 | assetList: React.PropTypes.object, 23 | assetMeta: React.PropTypes.object, 24 | handleAccountChange: React.PropTypes.func 25 | }, 26 | 27 | getInitialState() { 28 | return { 29 | search: null 30 | }; 31 | }, 32 | 33 | fetchAssetList({ account, search }) { 34 | if (account) { 35 | AssetActions.fetchAssetList({ 36 | account, 37 | search, 38 | blockWhenFetching: true 39 | }); 40 | Scroll.animateScroll.scrollToBottom(); 41 | } 42 | }, 43 | 44 | handleAccountChangeAndScroll(account) { 45 | this.props.handleAccountChange(account); 46 | Scroll.animateScroll.scrollToBottom(); 47 | }, 48 | 49 | handleSearch(query) { 50 | const { activeAccount } = this.props; 51 | 52 | this.setState({ 53 | search: query 54 | }); 55 | 56 | this.fetchAssetList({ 57 | account: activeAccount, 58 | search: query 59 | }); 60 | }, 61 | 62 | render() { 63 | const { 64 | activeAccount, 65 | assetList, 66 | assetMeta 67 | } = this.props; 68 | 69 | const assetListForAccount = ( 70 | assetList && activeAccount && Array.isArray(assetList[activeAccount.vk])) ? 71 | assetList[activeAccount.vk] : null; 72 | 73 | return ( 74 |
75 | 76 |

"On the Record"

77 |
78 |
79 | 92 |
93 |
94 | 97 |
98 |
99 |
100 |
101 | ); 102 | } 103 | }); 104 | 105 | 106 | export default BigchainDBConnection(OnTheRecord); 107 | -------------------------------------------------------------------------------- /client/on_the_record/scss/custom_style.scss: -------------------------------------------------------------------------------- 1 | 2 | .on-the-record { 3 | 4 | .asset-container { 5 | background: lighten($fg-color, 95%); 6 | border: 1px solid lighten($fg-color, 50%); 7 | border-radius: .7em; 8 | margin-bottom: .5em; 9 | } 10 | 11 | .asset-container-id { 12 | color: lighten($fg-color, 50%); 13 | } 14 | 15 | .page-content { 16 | padding-bottom: 5em; 17 | padding-top: 1em; 18 | } 19 | } -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bigchaindb-examples", 3 | "version": "0.0.3", 4 | "description": "BigchainDB Examples", 5 | "homepage": "https://www.bigchain.io/", 6 | "bugs": "https://github.com/bigchain/bigchaindb-examples/issues", 7 | "license": "Apache-2.0", 8 | "author": "Dimitri De Jonghe", 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:bigchaindb/bigchaindb-examples.git" 12 | }, 13 | "keywords": [ 14 | "bigchaindb", 15 | "blockchain", 16 | "examples", 17 | "react" 18 | ], 19 | "scripts": { 20 | "lint": "eslint ./", 21 | "build": "rimraf ./build && cross-env NODE_ENV=extract webpack", 22 | "build:dist": "rimraf ./dist && cross-env NODE_ENV=production webpack -p", 23 | "clean": "rimraf ./build ./dist", 24 | "start": "cross-env NODE_ENV=demo node server.demo.js", 25 | "test": "echo \"Error: no test specified\" && exit 1", 26 | "postinstall": "npm run build" 27 | }, 28 | "dependencies": { 29 | "alt": "^0.18.4", 30 | "bootstrap-sass": "^3.3.6", 31 | "classnames": "^2.2.5", 32 | "core-js": "^2.4.0", 33 | "ilp-plugin-bigchaindb": "^0.0.7", 34 | "js-utility-belt": "^1.5.0", 35 | "moment": "^2.14.1", 36 | "react": "^15.2.1", 37 | "react-bootstrap": "^0.30.7", 38 | "react-dom": "^15.2.1", 39 | "react-matrix": "0.0.6", 40 | "react-scroll": "^1.0.24" 41 | }, 42 | "devDependencies": { 43 | "autoprefixer": "^6.3.7", 44 | "babel-cli": "^6.10.1", 45 | "babel-eslint": "^6.1.2", 46 | "babel-loader": "^6.2.4", 47 | "babel-plugin-react-transform": "^2.0.2", 48 | "babel-plugin-transform-object-assign": "^6.8.0", 49 | "babel-plugin-transform-react-display-name": "^6.8.0", 50 | "babel-plugin-transform-runtime": "^6.9.0", 51 | "babel-preset-es2015": "^6.9.0", 52 | "babel-preset-react": "^6.11.1", 53 | "babel-runtime": "^6.9.2", 54 | "cross-env": "^2.0.0", 55 | "css-loader": "^0.23.1", 56 | "dotenv": "^2.0.0", 57 | "eslint": "^2.10.2", 58 | "eslint-config-ascribe-react": "^1.0.1", 59 | "eslint-plugin-import": "^1.10.3", 60 | "eslint-plugin-jsx-a11y": "^2.0.1", 61 | "eslint-plugin-react": "^5.2.2", 62 | "extract-text-webpack-plugin": "=2.0.0-beta.1", 63 | "file-loader": "^0.9.0", 64 | "html-webpack-plugin": "^2.22.0", 65 | "node-sass": "^3.8.0", 66 | "postcss-loader": "^0.9.1", 67 | "react-transform-hmr": "^1.0.4", 68 | "rimraf": "^2.5.3", 69 | "sass-loader": "^4.0.0", 70 | "style-loader": "^0.13.1", 71 | "url-loader": "^0.5.7", 72 | "webpack": "=2.1.0-beta.17", 73 | "webpack-combine-loaders": "^2.0.0", 74 | "webpack-dev-server": "=2.1.0-beta.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/server.demo.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict, no-console */ 2 | /* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */ 3 | 'use strict'; 4 | 5 | const WebpackDevServer = require('webpack-dev-server'); 6 | const webpack = require('webpack'); 7 | const config = require('./webpack.config.js'); 8 | 9 | require('dotenv').load({ silent: true }); 10 | 11 | const HOST_NAME = process.env.CLIENT_HOST || 'localhost'; 12 | const PORT = process.env.CLIENT_PORT || 3000; 13 | 14 | // Enable hot reloading if on demo mode 15 | if (process.env.NODE_ENV === 'demo') { 16 | // Each entry must have the hot dev server included 17 | Object.keys(config.entry).forEach((entryName) => { 18 | config.entry[entryName] = [ 19 | config.entry[entryName], 20 | 'webpack/hot/dev-server', 21 | `webpack-dev-server/client?http://${HOST_NAME}:${PORT}/` 22 | ]; 23 | }); 24 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 25 | // React hot reloading is enabled through .babelrc and babel-react-transform 26 | } 27 | 28 | // Specify output location for bundled files 29 | config.output.publicPath = '/'; 30 | 31 | // Configure server 32 | const compiler = webpack(config); 33 | 34 | const server = new WebpackDevServer(compiler, { 35 | publicPath: config.output.publicPath, 36 | contentBase: './demo', 37 | hot: true, 38 | noInfo: true, 39 | stats: { colors: true } 40 | }); 41 | 42 | // Start server 43 | server.listen(PORT, HOST_NAME, (err) => { 44 | if (err) { 45 | console.error(`Demo server ran into ${err} while starting on ${HOST_NAME}:${PORT}.` + 46 | 'Shutting down...'); 47 | server.close(); 48 | } 49 | console.log(`Demo server running on ${HOST_NAME}:${PORT}`); 50 | }); 51 | -------------------------------------------------------------------------------- /client/share_trader/js/app.js: -------------------------------------------------------------------------------- 1 | // Install necessary polyfills (see supported browsers) into global 2 | import 'core-js/es6'; 3 | import 'core-js/stage/4'; 4 | import 'isomorphic-fetch'; 5 | 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | 9 | import ShareTrader from './components/share_trader'; 10 | 11 | import '../../lib/css/scss/main.scss'; 12 | 13 | 14 | const App = () => ( 15 |
16 | 17 |
18 | ); 19 | 20 | ReactDOM.render(, document.getElementById('bigchaindb-example-app')); 21 | -------------------------------------------------------------------------------- /client/share_trader/js/components/asset_matrix.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Matrix from 'react-matrix'; 3 | import { safeInvoke } from 'js-utility-belt/es6'; 4 | 5 | const AssetMatrix = React.createClass({ 6 | 7 | propTypes: { 8 | assetList: React.PropTypes.array, 9 | cols: React.PropTypes.number, 10 | handleAssetClick: React.PropTypes.func, 11 | rows: React.PropTypes.number, 12 | squareSize: React.PropTypes.number, 13 | states: React.PropTypes.object 14 | }, 15 | 16 | getDefaultProps() { 17 | return { 18 | rows: 8, 19 | cols: 8, 20 | squareSize: 50, 21 | states: { 22 | '0': 'available', 23 | '1': 'state1', 24 | '2': 'state2', 25 | '3': 'state3', 26 | '4': 'state4', 27 | '5': 'state5', 28 | '6': 'state6', 29 | '7': 'state7', 30 | '8': 'state8' 31 | } 32 | }; 33 | }, 34 | 35 | initializeMatrix(rows, cols) { 36 | const matrix = new Array(cols); 37 | 38 | for (let i = 0; i < rows; i++) { 39 | matrix[i] = new Array(cols); 40 | 41 | for (let j = 0; j < cols; j++) { 42 | matrix[i][j] = 'default'; 43 | } 44 | } 45 | return matrix; 46 | }, 47 | 48 | mapAssetsOnMatrix() { 49 | const { rows, cols } = this.props; 50 | const matrix = this.initializeMatrix(cols, rows); 51 | 52 | for (const content of this.getAssetListContent()) { 53 | matrix[content.y][content.x] = content.vk; 54 | } 55 | 56 | return matrix; 57 | }, 58 | 59 | getAssetListContent() { 60 | const { assetList } = this.props; 61 | 62 | if (assetList) { 63 | return assetList.map((asset) => ({ 64 | vk: asset.transaction.conditions[0].new_owners[0], 65 | x: asset.transaction.data.payload.content.x, 66 | y: asset.transaction.data.payload.content.y 67 | })); 68 | } 69 | return []; 70 | }, 71 | 72 | getAssetForCell(x, y) { 73 | const { assetList } = this.props; 74 | 75 | if (assetList) { 76 | for (const asset of assetList) { 77 | const content = asset.transaction.data.payload.content; 78 | 79 | if (content.x === x && content.y === y) { 80 | return asset; 81 | } 82 | } 83 | } 84 | 85 | return null; 86 | }, 87 | 88 | handleCellClick(cellState) { 89 | const { handleAssetClick } = this.props; 90 | 91 | const x = parseInt(cellState.x, 10); 92 | const y = parseInt(cellState.y, 10); 93 | 94 | const activeAsset = this.getAssetForCell(x, y); 95 | safeInvoke(handleAssetClick, activeAsset); 96 | }, 97 | 98 | render() { 99 | const { 100 | squareSize, 101 | states 102 | } = this.props; 103 | 104 | return ( 105 |
106 | 111 |
112 | ); 113 | } 114 | }); 115 | 116 | 117 | export default AssetMatrix; 118 | -------------------------------------------------------------------------------- /client/share_trader/js/components/asset_row.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import { safeInvoke } from 'js-utility-belt/es6'; 4 | 5 | import AssetActions from '../../../lib/js/react/actions/asset_actions'; 6 | 7 | import AssetActionPanel from '../../../lib/js/react/components/asset_action_panel'; 8 | import AssetDetail from '../../../lib/js/react/components/asset_detail'; 9 | 10 | 11 | const AssetRow = React.createClass({ 12 | propTypes: { 13 | accountList: React.PropTypes.array, 14 | activeAccount: React.PropTypes.object, 15 | asset: React.PropTypes.object, 16 | assetClass: React.PropTypes.string, 17 | handleAssetClick: React.PropTypes.func, 18 | isActive: React.PropTypes.bool 19 | }, 20 | 21 | getInitialState() { 22 | return { 23 | inTransfer: false 24 | }; 25 | }, 26 | 27 | handleAssetClick() { 28 | const { 29 | asset, 30 | handleAssetClick 31 | } = this.props; 32 | safeInvoke(handleAssetClick, asset); 33 | }, 34 | 35 | handleTransferClick(selectedAccount) { 36 | const { 37 | asset, 38 | activeAccount 39 | } = this.props; 40 | 41 | const idToTransfer = { 42 | txid: asset.id, 43 | cid: 0 44 | }; 45 | 46 | const payloadToPost = { 47 | source: activeAccount, 48 | to: selectedAccount 49 | }; 50 | 51 | AssetActions.transferAsset({ 52 | idToTransfer, 53 | payloadToPost, 54 | account: activeAccount 55 | }); 56 | 57 | this.setState({ inTransfer: true }); 58 | }, 59 | 60 | render() { 61 | const { 62 | asset, 63 | activeAccount, 64 | accountList, 65 | assetClass, 66 | isActive 67 | } = this.props; 68 | 69 | const { 70 | inTransfer 71 | } = this.state; 72 | 73 | const { data: { payload: { content } } = {} } = asset.transaction; 74 | 75 | let actionsPanel = null; 76 | if (isActive && activeAccount && accountList && !inTransfer) { 77 | actionsPanel = ( 78 | 82 | ); 83 | } 84 | 85 | return ( 86 | 92 | {actionsPanel} 93 | 94 | ); 95 | } 96 | }); 97 | 98 | export default AssetRow; 99 | -------------------------------------------------------------------------------- /client/share_trader/js/components/assets.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AssetRow from './asset_row'; 4 | import Spinner from '../../../lib/js/react/components/spinner'; 5 | 6 | 7 | const Assets = ({ 8 | accountList, 9 | activeAccount, 10 | activeAsset, 11 | assetClasses, 12 | assetList, 13 | handleAssetClick 14 | }) => { 15 | if (assetList && assetList.length) { 16 | // re-sorting assetList because assets of multiple accounts possible 17 | return ( 18 |
19 | {assetList.sort((a, b) => { 20 | if (a.transaction.timestamp === b.transaction.timestamp) { 21 | if (a.id < b.id) { 22 | return -1; 23 | } else { 24 | return a.id > b.id ? 1 : 0; 25 | } 26 | } 27 | return a.transaction.timestamp - b.transaction.timestamp; 28 | }) 29 | .map((asset) => { 30 | const isActive = !!activeAsset && activeAsset.id === asset.id; 31 | const assetClass = assetClasses[asset.transaction.conditions[0].new_owners[0]]; 32 | 33 | return ( 34 | 42 | ); 43 | })} 44 |
45 | ); 46 | } else { 47 | return ( 48 |
49 | 50 |
51 | ); 52 | } 53 | }; 54 | 55 | Assets.propTypes = { 56 | accountList: React.PropTypes.array, 57 | activeAccount: React.PropTypes.object, 58 | activeAsset: React.PropTypes.object, 59 | assetClasses: React.PropTypes.object, 60 | assetList: React.PropTypes.array, 61 | handleAssetClick: React.PropTypes.func 62 | }; 63 | 64 | export default Assets; 65 | -------------------------------------------------------------------------------- /client/share_trader/js/components/share_trader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navbar, Row, Col, Button } from 'react-bootstrap/lib'; 3 | 4 | import AccountList from '../../../lib/js/react/components/account_list'; 5 | import AccountDetail from '../../../lib/js/react/components/account_detail'; 6 | 7 | import Assets from './assets'; 8 | import AssetMatrix from './asset_matrix'; 9 | 10 | import AssetActions from '../../../lib/js/react/actions/asset_actions'; 11 | 12 | import BigchainDBConnection from '../../../lib/js/react/components/bigchaindb_connection'; 13 | 14 | 15 | const ShareTrader = React.createClass({ 16 | propTypes: { 17 | // Injected through BigchainDBConnection 18 | accountList: React.PropTypes.array, 19 | activeAccount: React.PropTypes.object, 20 | activeAsset: React.PropTypes.object, 21 | assetList: React.PropTypes.object, 22 | handleAccountChange: React.PropTypes.func, 23 | handleAssetChange: React.PropTypes.func, 24 | resetActiveAccount: React.PropTypes.func 25 | }, 26 | 27 | fetchAssetList({ account }) { 28 | AssetActions.fetchAssetList({ 29 | account, 30 | blockWhenFetching: false 31 | }); 32 | }, 33 | 34 | mapAccountsOnStates(accountList) { 35 | const states = { 36 | 'default': 'available' 37 | }; 38 | 39 | if (!accountList) { 40 | return states; 41 | } 42 | 43 | for (let i = 0; i < accountList.length; i++) { 44 | states[accountList[i].vk] = `state${i}`; 45 | } 46 | 47 | return states; 48 | }, 49 | 50 | flattenAssetList(assetList) { 51 | return [].concat(...Object.values(assetList)); 52 | }, 53 | 54 | render() { 55 | const { 56 | activeAccount, 57 | accountList, 58 | activeAsset, 59 | assetList, 60 | handleAccountChange, 61 | handleAssetChange, 62 | resetActiveAccount 63 | } = this.props; 64 | 65 | const states = this.mapAccountsOnStates(accountList); 66 | const assetListForAccount = 67 | activeAccount && assetList.hasOwnProperty(activeAccount.vk) ? 68 | assetList[activeAccount.vk] : this.flattenAssetList(assetList); 69 | 70 | return ( 71 |
72 | 73 |

Share Trader

74 |
75 |
76 | 93 |
94 |
95 | 96 | 97 |
98 |
99 | 105 |
106 |
107 | 108 | 109 | 116 | 117 |
118 |
119 |
120 |
121 |
122 | ); 123 | } 124 | }); 125 | 126 | export default BigchainDBConnection(ShareTrader); 127 | -------------------------------------------------------------------------------- /client/share_trader/scss/custom_style.scss: -------------------------------------------------------------------------------- 1 | .share-trader { 2 | $colorAvailable: #ECF0F1; 3 | $colorGrid: #BDC3C7; 4 | $colorState0: #3498DB; 5 | $colorState1: #39418b; 6 | $colorState2: #E74C3C; 7 | $colorState3: #34495E; 8 | $colorState4: #1ABC9C; 9 | $colorState5: #ff8600; 10 | $colorState6: #437c2b; 11 | $colorState7: #adaf35; 12 | $colorState8: #5bbc50; 13 | 14 | $colorList: ( 15 | ("available", $colorAvailable), 16 | ("state0", $colorState0), 17 | ("state1", $colorState1), 18 | ("state2", $colorState2), 19 | ("state3", $colorState3), 20 | ("state4", $colorState4), 21 | ("state5", $colorState5), 22 | ("state6", $colorState6), 23 | ("state7", $colorState7), 24 | ("state8", $colorState8) 25 | ); 26 | 27 | .Matrix { 28 | border: 1px solid black; 29 | 30 | .Grid { 31 | stroke: $colorGrid; 32 | } 33 | 34 | .Cell { 35 | @each $colorMap in $colorList { 36 | $state: nth($colorMap, 1); 37 | $color: nth($colorMap, 2); 38 | 39 | &.#{$state} { 40 | fill: $color; 41 | } 42 | } 43 | } 44 | .available { 45 | stroke: black; 46 | stroke-width: 0.1; 47 | } 48 | } 49 | 50 | @each $colorMap in $colorList { 51 | $state: nth($colorMap, 1); 52 | $color: nth($colorMap, 2); 53 | 54 | .asset-container.#{$state} { 55 | .asset-container-id, 56 | .asset-container-timestamp .glyphicon { 57 | color: $color 58 | } 59 | &.active { 60 | background: rgba($color, 0.1); 61 | border-left-color: $color; 62 | } 63 | &:hover { 64 | background: rgba($color, 0.05); 65 | border-left-color: rgba($color, 0.6); 66 | cursor: pointer; 67 | } 68 | &.inTransfer { 69 | background: rgba($color, 0.05); 70 | border-left-color: rgba($color, 0.4); 71 | cursor: wait; 72 | } 73 | } 74 | } 75 | 76 | .asset-container { 77 | width: 100% 78 | } 79 | 80 | .asset-history { 81 | height: calc(100vh - 70px); 82 | overflow-y: scroll; 83 | padding: 0; 84 | } 85 | 86 | .asset-matrix { 87 | border-right: 1px solid #888888; 88 | height: 100%; 89 | } 90 | 91 | .page-content { 92 | height: calc(100vh - 70px); 93 | padding-right: 0; 94 | 95 | .row { 96 | height: 100%; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict, no-console, object-shorthand */ 2 | /* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */ 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | const webpack = require('webpack'); 8 | const autoPrefixer = require('autoprefixer'); 9 | const combineLoaders = require('webpack-combine-loaders'); 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 11 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 12 | 13 | require('dotenv').load({ path: '../.env', silent: true }); 14 | 15 | const PRODUCTION = process.env.NODE_ENV === 'production'; 16 | const EXTRACT = process.env.NODE_ENV === 'extract'; 17 | 18 | const PATHS = { 19 | ON_THE_RECORD: path.resolve(__dirname, 'on_the_record/js/app.js'), 20 | SHARE_TRADER: path.resolve(__dirname, 'share_trader/js/app.js'), 21 | INTERLEDGER: path.resolve(__dirname, 'interledger/js/app.js'), 22 | 23 | BUILD: path.resolve(__dirname, 'build'), 24 | DIST: path.resolve(__dirname, 'dist'), 25 | NODE_MODULES: path.resolve(__dirname, 'node_modules'), 26 | }; 27 | 28 | 29 | /** ENTRY POINTS **/ 30 | const ENTRY = { 31 | // Use one entry per app 32 | ontherecord: PATHS.ON_THE_RECORD, 33 | sharetrader: PATHS.SHARE_TRADER, 34 | interledger: PATHS.INTERLEDGER, 35 | }; 36 | 37 | const ENTRY_NAMES = { 38 | ontherecord: 'On the Record', 39 | sharetrader: 'Share Trader', 40 | interledger: 'Interledger', 41 | }; 42 | 43 | 44 | /** EXTERNAL DEFINITIONS INJECTED INTO APP **/ 45 | const DEFINITIONS = { 46 | 'process.env': { 47 | NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'), 48 | FLASK_BASE_URL: JSON.stringify(`http://${process.env.FLASK_HOST || 'localhost'}:` + 49 | `${process.env.FLASK_PORT || '8000'}`), 50 | }, 51 | }; 52 | 53 | 54 | /** PLUGINS **/ 55 | const PLUGINS = [ 56 | new webpack.DefinePlugin(DEFINITIONS), 57 | new webpack.NoErrorsPlugin(), 58 | ]; 59 | 60 | const PROD_PLUGINS = [ 61 | new webpack.optimize.UglifyJsPlugin({ 62 | compress: { 63 | warnings: false 64 | }, 65 | output: { 66 | comments: false 67 | }, 68 | sourceMap: true, 69 | }), 70 | new webpack.LoaderOptionsPlugin({ 71 | debug: false, 72 | minimize: true 73 | }), 74 | ]; 75 | 76 | const EXTRACT_CSS_PLUGIN = new ExtractTextPlugin( 77 | PRODUCTION ? '[name]/styles.min.css' : '[name]/styles.css', { 78 | allChunks: true 79 | } 80 | ); 81 | 82 | // Generate html files for each of the example apps specified in ENTRY 83 | const HTML_PLUGINS = Object.keys(ENTRY).map((entryName) => ( 84 | new HtmlWebpackPlugin({ 85 | filename: `${entryName}/index.html`, 86 | title: `${ENTRY_NAMES[entryName]} - powered by BigchainDB`, 87 | chunks: [entryName], 88 | minify: PRODUCTION ? { 89 | collapseWhitespace: true, 90 | minifyJS: true, 91 | removeComments: true, 92 | removeRedundantAttributes: true 93 | } : false, 94 | template: path.resolve(__dirname, 'app_index_template.html'), 95 | 96 | // Our own options 97 | PRODUCTION: PRODUCTION 98 | }) 99 | )); 100 | 101 | PLUGINS.push(...HTML_PLUGINS); 102 | 103 | if (EXTRACT || PRODUCTION) { 104 | PLUGINS.push(EXTRACT_CSS_PLUGIN); 105 | } 106 | 107 | if (PRODUCTION) { 108 | PLUGINS.push(...PROD_PLUGINS); 109 | } 110 | 111 | 112 | /** LOADERS **/ 113 | const JS_LOADER = combineLoaders([ 114 | { 115 | loader: 'babel', 116 | query: { 117 | cacheDirectory: true, 118 | }, 119 | }, 120 | ]); 121 | 122 | const CSS_LOADER = combineLoaders([ 123 | { 124 | loader: 'css', 125 | query: { 126 | sourceMap: true 127 | } 128 | }, 129 | { loader: 'postcss' }, 130 | { 131 | loader: 'sass', 132 | query: { 133 | precision: '8', // See https://github.com/twbs/bootstrap-sass#sass-number-precision 134 | outputStyle: 'expanded', 135 | sourceMap: true 136 | } 137 | }, 138 | ]); 139 | 140 | const LOADERS = [ 141 | { 142 | test: /\.jsx?$/, 143 | exclude: [PATHS.NODE_MODULES], 144 | loader: JS_LOADER, 145 | }, 146 | { 147 | test: /\.json$/, 148 | loader: 'json' 149 | }, 150 | { 151 | test: /\.s[ac]ss$/, 152 | exclude: [PATHS.NODE_MODULES], 153 | loader: PRODUCTION || EXTRACT ? ExtractTextPlugin.extract('style', CSS_LOADER) 154 | : `style!${CSS_LOADER}`, 155 | }, 156 | { 157 | test: /.(png|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/, 158 | loader: 'url-loader?limit=100000' 159 | }, 160 | ]; 161 | 162 | 163 | /** EXPORTED WEBPACK CONFIG **/ 164 | module.exports = { 165 | entry: ENTRY, 166 | 167 | output: { 168 | filename: PRODUCTION ? '[name]/bundle.min.js' : '[name]/bundle.js', 169 | path: PRODUCTION ? PATHS.DIST : PATHS.BUILD, 170 | }, 171 | 172 | debug: !PRODUCTION, 173 | 174 | devtool: PRODUCTION ? '#source-map' : '#inline-source-map', 175 | 176 | resolve: { 177 | alias: { 178 | 'babel-runtime': path.resolve(PATHS.NODE_MODULES, 'babel-runtime'), 179 | 'core-js': path.resolve(PATHS.NODE_MODULES, 'core-js'), 180 | }, 181 | extensions: ['', '.js', '.jsx'], 182 | modules: ['node_modules'], // Don't use absolute path here to allow recursive matching 183 | }, 184 | 185 | plugins: PLUGINS, 186 | 187 | module: { 188 | loaders: LOADERS, 189 | }, 190 | 191 | postcss: [autoPrefixer()], 192 | }; 193 | -------------------------------------------------------------------------------- /commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/commands/__init__.py -------------------------------------------------------------------------------- /commands/bigchaindb_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import argparse 4 | import logging 5 | import subprocess 6 | 7 | import rethinkdb as r 8 | from bigchaindb import Bigchain 9 | 10 | from init_accounts import main as init_accounts_main 11 | from init_assets import main as init_assets_main 12 | from apps_config import APPS 13 | 14 | logging.basicConfig(level=logging.INFO) 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | def start(parser, scope): 19 | """Utility function to execute a subcommand. 20 | The function will look up in the ``scope`` 21 | if there is a function called ``run_`` 22 | and will run it using ``parser.args`` as first positional argument. 23 | Args: 24 | parser: an ArgumentParser instance. 25 | scope (dict): map containing (eventually) the functions to be called. 26 | Raises: 27 | NotImplementedError: if ``scope`` doesn't contain a function called 28 | ``run_``. 29 | """ 30 | args = parser.parse_args() 31 | 32 | if not args.command: 33 | parser.print_help() 34 | return 35 | 36 | # look up in the current scope for a function called 'run_' 37 | # replacing all the dashes '-' with the lowercase character '_' 38 | func = scope.get('run_' + args.command.replace('-', '_')) 39 | 40 | # if no command has been found, raise a `NotImplementedError` 41 | if not func: 42 | raise NotImplementedError('Command `{}` not yet implemented'. 43 | format(args.command)) 44 | 45 | func(args) 46 | 47 | 48 | def delete_databases(dbnames=[]): 49 | b = Bigchain() 50 | 51 | for dbname in dbnames: 52 | logger.info('Dropping database: {}'.format(dbname)) 53 | try: 54 | r.db_drop(dbname).run(b.conn) 55 | except r.ReqlOpFailedError as e: 56 | logger.info(e.message) 57 | 58 | 59 | def init_ledgers(ledger_ids=[]): 60 | for ledger_id in ledger_ids: 61 | my_env = os.environ.copy() 62 | bigchaindb_db_name = 'bigchaindb_examples_{}'.format(ledger_id) 63 | logger.info('Initializing ledger {}'.format(bigchaindb_db_name)) 64 | my_env['BIGCHAINDB_DATABASE_NAME'] = bigchaindb_db_name 65 | subprocess.Popen(['bigchaindb', '-yc', '.bigchaindb_examples', 'configure'], env=my_env).wait() 66 | subprocess.Popen(['bigchaindb', '-c', '.bigchaindb_examples', 'init'], env=my_env).wait() 67 | 68 | 69 | def start_services(ledger_num): 70 | procs = [] 71 | 72 | # setup env 73 | frontend_port = 3000 + ledger_num 74 | flask_port = 8000 + ledger_num 75 | tornado_port = 8888 + ledger_num 76 | bigchaindb_db_name = 'bigchaindb_examples_{}'.format(ledger_num) 77 | bigchaindb_server_bind = 'localhost:{}'.format(9984 + ledger_num) 78 | 79 | my_env = os.environ.copy() 80 | my_env['CLIENT_PORT'] = str(frontend_port) 81 | my_env['FLASK_PORT'] = str(flask_port) 82 | my_env['TORNADO_PORT'] = str(tornado_port) 83 | my_env['BIGCHAINDB_DATABASE_NAME'] = bigchaindb_db_name 84 | my_env['BIGCHAINDB_SERVER_BIND'] = bigchaindb_server_bind 85 | my_env['BIGCHAINDB_LEDGER_NUMBER'] = str(ledger_num) 86 | 87 | # start flask 88 | p_flask = subprocess.Popen(['python', '-m', 'server.app'], env=my_env) 89 | procs.append(p_flask) 90 | 91 | # start tornado 92 | p_tornado = subprocess.Popen(['python', '-m', 'server.tornado_app'], env=my_env) 93 | procs.append(p_tornado) 94 | 95 | # start bigchaindb 96 | p_bigchaindb = subprocess.Popen(['bigchaindb', '-c', '.bigchaindb_examples', 'start'], env=my_env) 97 | procs.append(p_bigchaindb) 98 | 99 | return procs 100 | 101 | 102 | def start_connectors(): 103 | # start connectors 104 | return [subprocess.Popen(['python', '-m', 'server.lib.models.connector'])] 105 | 106 | 107 | def get_ledger_ids_from_config(config): 108 | # read the config file and return all ledger ids 109 | ledger_ids = [] 110 | for app in config: 111 | if app['name'] != 'interledger': 112 | ledger_ids.append(app['ledger']) 113 | else: 114 | for account in app['accounts']: 115 | for ledger in account['ledgers']: 116 | ledger_ids.append(ledger['id']) 117 | 118 | return list(set(ledger_ids)) 119 | 120 | 121 | def run_init_bigchaindb(args): 122 | # initialize the databases for ledger args.ledger 123 | ledger_ids = [] 124 | if args.ledger: 125 | ledger_ids = [args.ledger] 126 | elif args.all: 127 | ledger_ids = get_ledger_ids_from_config(APPS) 128 | 129 | init_ledgers(ledger_ids) 130 | 131 | 132 | def run_reset_bigchaindb(args): 133 | # delete databases for ledger args.ledger or all 134 | b = Bigchain() 135 | 136 | # dbs do delete 137 | dbnames = [] 138 | if args.ledger: 139 | dbnames = ['bigchaindb_examples_{}'.format(args.ledger)] 140 | elif args.all: 141 | regex_db = re.compile(r'^(bigchaindb_examples_\d*$)') 142 | for dbname in r.db_list().run(b.conn): 143 | if regex_db.match(dbname): 144 | dbnames.append(dbname) 145 | 146 | delete_databases(dbnames) 147 | 148 | 149 | def run_init_accounts(args): 150 | init_accounts_main() 151 | 152 | 153 | def run_reset_accounts(args): 154 | delete_databases(['interledger', 'ontherecord', 'sharetrader']) 155 | 156 | 157 | def run_init_assets(args): 158 | init_assets_main() 159 | 160 | 161 | def run_start(args): 162 | # check if we need to initialize 163 | if args.init: 164 | init_args = argparse.Namespace() 165 | run_init_all(init_args) 166 | 167 | ledger_ids = [] 168 | if args.ledger: 169 | ledger_ids = [args.ledger] 170 | elif args.all: 171 | ledger_ids = get_ledger_ids_from_config(APPS) 172 | 173 | procs = [] 174 | for ledger in ledger_ids: 175 | procs += start_services(ledger) 176 | 177 | # start npm 178 | p_npm = subprocess.Popen(['npm', 'start'], cwd='./client/') 179 | procs.append(p_npm) 180 | 181 | procs += start_connectors() 182 | 183 | # wait for processes to finish 184 | for proc in procs: 185 | proc.wait() 186 | 187 | 188 | def run_reset_all(args): 189 | # reset bigchaindb 190 | args = argparse.Namespace(all=True, command='reset-bigchaindb', ledger=None) 191 | run_reset_bigchaindb(args) 192 | 193 | # reset accounts 194 | args = argparse.Namespace() 195 | run_reset_accounts(args) 196 | 197 | 198 | def run_init_all(args): 199 | # init bigchaindb 200 | args = argparse.Namespace(all=True, command='init-bigchaindb', ledger=None) 201 | run_init_bigchaindb(args) 202 | 203 | # init accounts 204 | args = argparse.Namespace() 205 | run_init_accounts(args) 206 | 207 | # init assets 208 | args = argparse.Namespace() 209 | run_init_assets(args) 210 | 211 | 212 | def main(): 213 | parser = argparse.ArgumentParser(prog='bigchaindb-examples', 214 | description='Run bigchaindb examples') 215 | 216 | subparser = parser.add_subparsers(title='Commands', 217 | dest='command') 218 | 219 | # Start services 220 | start_parser = subparser.add_parser('start', 221 | help='start a new ledger') 222 | start_parser.add_argument('-l', '--ledger', 223 | type=int, 224 | help='Start the services for the provided ledger') 225 | start_parser.add_argument('-a', '--all', 226 | default=False, 227 | action='store_true', 228 | help='Start the services for all ledgers') 229 | start_parser.add_argument('-i', '--init', 230 | default=False, 231 | action='store_true', 232 | help='First initialize and then start the services for all ledgers') 233 | 234 | # Initialize bigchaindb 235 | init_bigchaindb_parser = subparser.add_parser('init-bigchaindb', 236 | help='Initialize a new bigchaindb ledger') 237 | init_bigchaindb_parser.add_argument('-l', '--ledger', 238 | type=int, 239 | help='Initialize the databases for a ledger') 240 | init_bigchaindb_parser.add_argument('-a', '--all', 241 | default=False, 242 | action='store_true', 243 | help='Initialize all databases for the ledgers') 244 | 245 | # Reset bigchaindb 246 | reset_bigchaindb_parser = subparser.add_parser('reset-bigchaindb', 247 | help='Delete the bigchaindb ledger') 248 | reset_bigchaindb_parser.add_argument('-l', '--ledger', 249 | type=int, 250 | help='Delete the bigchaindb ledger with the number provided') 251 | reset_bigchaindb_parser.add_argument('-a', '--all', 252 | default=False, 253 | action='store_true', 254 | help='Delete all the bigchaindb ledgers') 255 | 256 | # Initialize accounts 257 | subparser.add_parser('init-accounts', 258 | help='Initialize accounts for all the apps') 259 | 260 | # Reset accounts 261 | subparser.add_parser('reset-accounts', 262 | help='Delete the accounts databases') 263 | 264 | # Initialize assets 265 | subparser.add_parser('init-assets', 266 | help='Initialize assets for all the apps in all the ledgers') 267 | 268 | # Initialize everything 269 | subparser.add_parser('init-all', 270 | help='Initializes all the databases for apps, ledgers and assets') 271 | 272 | # Reset everything 273 | subparser.add_parser('reset-all', 274 | help='Deletes all databases created by apps and all the ledgers') 275 | 276 | start(parser, globals()) 277 | 278 | 279 | if __name__ == '__main__': 280 | main() 281 | -------------------------------------------------------------------------------- /compose/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6 2 | 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app/client 5 | 6 | COPY client /usr/src/app/client 7 | 8 | # Adds fs-extra to npm and replaces the fs.rename method with the fs.extra 9 | # move method that now automatic chooses what to do (rename/move). 10 | # See https://github.com/npm/npm/issues/9863. 11 | RUN cd $(npm root -g)/npm \ 12 | && npm install fs-extra \ 13 | && sed -i -e s/graceful-fs/fs-extra/ -e s/fs\.rename/fs\.move/ ./lib/utils/rename.js 14 | 15 | # On some platforms, the .dockerignore file is being ignored in some versions of docker-compose 16 | # See https://github.com/docker/compose/issues/1607. 17 | RUN rm -rf node_modules 18 | 19 | RUN npm install 20 | -------------------------------------------------------------------------------- /compose/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | COPY setup.py /usr/src/app/ 7 | COPY server /usr/src/app/server 8 | 9 | RUN pip install --upgrade pip 10 | RUN pip install --no-cache-dir -e .[dev] 11 | -------------------------------------------------------------------------------- /docs.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | bdocs: 6 | build: 7 | context: . 8 | dockerfile: Dockerfile-bdb 9 | volumes: 10 | - .:/usr/src/app/ 11 | working_dir: /usr/src/app/docs 12 | command: make html 13 | 14 | vdocs: 15 | image: nginx 16 | ports: 17 | - '41234:80' 18 | volumes: 19 | - ./docs/build/html:/usr/share/nginx/html 20 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BigchainDBExamples.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BigchainDBExamples.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/BigchainDBExamples" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BigchainDBExamples" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /docs/img/on_the_record_v0.0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/img/on_the_record_v0.0.1.png -------------------------------------------------------------------------------- /docs/img/share_trader_v0.0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/img/share_trader_v0.0.1.png -------------------------------------------------------------------------------- /docs/on_the_record.md: -------------------------------------------------------------------------------- 1 | # On The Record -------------------------------------------------------------------------------- /docs/share_trade.md: -------------------------------------------------------------------------------- 1 | # Share Trade -------------------------------------------------------------------------------- /docs/source/_static/ontherecord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/source/_static/ontherecord.png -------------------------------------------------------------------------------- /docs/source/_static/sharetrader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/source/_static/sharetrader.png -------------------------------------------------------------------------------- /docs/source/ack.rst: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ================ 3 | Special thanks to the BigchainDB/ascribe.io team for their insights and code 4 | contributions: 5 | 6 | `@r-marques`_, `@vrde`_, `@ttmc`_, `@rhsimplex`_, `@SohKai`_, `@sbellem`_, `@TimDaub`_, `@diminator`_ 7 | 8 | 9 | .. _@r-marques: https://github.com/r-marques 10 | .. _@vrde: https://github.com/vrde 11 | .. _@ttmc: https://github.com/ttmc 12 | .. _@rhsimplex: https://github.com/rhsimplex 13 | .. _@SohKai: https://github.com/SohKai 14 | .. _@sbellem: https://github.com/sbellem 15 | .. _@TimDaub: https://github.com/TimDaub 16 | .. _@diminator: https://github.com/diminator 17 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sphinx_rtd_theme 5 | 6 | 7 | extensions = [ 8 | 'sphinx.ext.autodoc', 9 | 'sphinx.ext.doctest', 10 | 'sphinx.ext.intersphinx', 11 | 'sphinx.ext.todo', 12 | 'sphinx.ext.coverage', 13 | 'sphinx.ext.viewcode', 14 | 'sphinx.ext.napoleon', 15 | ] 16 | 17 | templates_path = ['_templates'] 18 | source_suffix = '.rst' 19 | master_doc = 'index' 20 | project = 'BigchainDB Examples' 21 | copyright = '2016, BigchainDB Contributors' 22 | author = 'BigchainDB Contributors' 23 | version = '0.0.2' 24 | release = '0.0.2' 25 | language = None 26 | exclude_patterns = [] 27 | pygments_style = 'sphinx' 28 | todo_include_todos = True 29 | html_theme = 'sphinx_rtd_theme' 30 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 31 | html_static_path = ['_static'] 32 | htmlhelp_basename = 'BigchainDBExamplesdoc' 33 | 34 | latex_elements = {} 35 | latex_documents = [ 36 | (master_doc, 'BigchainDBExamples.tex', 'BigchainDB Examples Documentation', 37 | 'BigchainDB Contributors', 'manual'), 38 | ] 39 | 40 | man_pages = [ 41 | (master_doc, 'bigchaindbexamples', 'BigchainDB Examples Documentation', 42 | [author], 1) 43 | ] 44 | 45 | texinfo_documents = [ 46 | (master_doc, 'BigchainDBExamples', 'BigchainDB Examples Documentation', 47 | author, 'BigchainDBExamples', 'One line description of project.', 48 | 'Miscellaneous'), 49 | ] 50 | 51 | intersphinx_mapping = {'https://docs.python.org/': None} 52 | -------------------------------------------------------------------------------- /docs/source/docs.rst: -------------------------------------------------------------------------------- 1 | About this Documentation 2 | ======================== 3 | 4 | This section contains instructions to build and view the documentation locally, 5 | using the ``docs.yml`` file of the `bigchaindb-examples`_ repository. 6 | 7 | If you do not have a clone of the repo, you need to get one. 8 | 9 | 10 | Building the documentation 11 | -------------------------- 12 | To build the docs, simply run 13 | 14 | .. code-block:: bash 15 | 16 | $ docker-compose -f docs.yml up bdocs 17 | 18 | Or if you prefer, start a ``bash`` session, 19 | 20 | .. code-block:: bash 21 | 22 | $ docker-compose -f docs.yml run --rm bdocs bash 23 | 24 | and build the docs: 25 | 26 | .. code-block:: bash 27 | 28 | root@a651959a1f2d:/usr/src/app/docs# make html 29 | 30 | 31 | Viewing the documentation 32 | ------------------------- 33 | You can start a little web server to view the docs at http://localhost:41234/ 34 | 35 | .. code-block:: bash 36 | 37 | $ docker-compose -f docs.yml up -d vdocs 38 | 39 | .. note:: If you are using ``docker-machine`` you need to replace ``localhost`` 40 | with the ``ip`` of the machine (e.g.: ``docker-machine ip tm`` if your 41 | machine is named ``tm``). 42 | 43 | 44 | Making changes 45 | -------------- 46 | The necessary source code is mounted, which allows you to make modifications, 47 | and view the changes by simply re-building the docs, and refreshing the 48 | browser. 49 | 50 | 51 | .. _bigchaindb-examples: https://github.com/bigchaindb/bigchaindb-examples 52 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ******************* 2 | BigchainDB Examples 3 | ******************* 4 | 5 | Documentation for the BigchainDB examples and tutorials found under 6 | https://github.com/bigchaindb/bigchaindb-examples 7 | 8 | .. warning:: These examples are for demonstration purpose and should not be 9 | used for production 10 | 11 | 12 | 13 | Contents 14 | ======== 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | 19 | structure 20 | install 21 | run 22 | ontherecord 23 | sharetrader 24 | interledger 25 | troubleshooting 26 | docs 27 | ack 28 | 29 | 30 | Indices and tables 31 | ================== 32 | 33 | * :ref:`genindex` 34 | * :ref:`modindex` 35 | * :ref:`search` 36 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Clone the repository: 5 | 6 | .. code-block:: bash 7 | 8 | $ git clone git@github.com:bigchaindb/bigchaindb-examples.git 9 | 10 | Go into it! 11 | 12 | .. code-block:: bash 13 | 14 | $ cd bigchaindb-examples 15 | 16 | We now document three options: 17 | 18 | * Installing via Docker (**recommended**); supports OSX 19 | * Installing from source via the CLI 20 | * Installing from source Manually 21 | 22 | 23 | The Docker Way 24 | -------------- 25 | Just make sure you have recent versions of `docker engine`_ and 26 | `docker-compose`_, e.g.: 27 | 28 | .. code-block:: bash 29 | 30 | $ docker --version 31 | Docker version 1.11.1, build 5604cbe 32 | 33 | $ docker-compose --version 34 | docker-compose version 1.7.0, build 0d7bf73 35 | 36 | 37 | We've provided a `Makefile` to make starting the examples through Docker easy, so once you've set 38 | up your docker environment (e.g. starting docker-machine if necessary), simply: 39 | 40 | .. code-block:: bash 41 | 42 | # Make all the things! Build, inits, configures, and runs everything. 43 | $ make 44 | 45 | 46 | If you're using docker-machine instances (ie. on OSX / Windows), you should run `make` with your 47 | docker-machine ip: 48 | 49 | .. code-block:: bash 50 | 51 | $ DOCKER_MACHINE_IP=$(docker-machine ip) make 52 | 53 | 54 | The `Makefile` will automatically start the examples so just sit back and wait :) 55 | 56 | Note: we've renamed the `docker-compose.yml` to `ledgers.yml`, so if you want to do Docker builds manually, use `-f ledgers.yml`. 57 | 58 | Install from Source 59 | ------------------- 60 | 61 | .. _dependencies: 62 | 63 | Dependencies 64 | ^^^^^^^^^^^^ 65 | 66 | * OS dependencies: see `setup BigchainDB & RethinkDB `_ 67 | * ``python>=3.4`` 68 | * node>=5.3 using `nvm `_ (**recommended**), or 69 | `manually `_ 70 | * `npm>=3.3 `_ (should be installed with node) 71 | 72 | 73 | Using the CLI 74 | ^^^^^^^^^^^^^ 75 | 76 | This examples project includes a CLI tool to configure and start the project. If you'll be running 77 | things locally, it's **recommended** to use the CLI. 78 | 79 | .. code-block:: bash 80 | 81 | # (optional) Run a virtualenv (make sure you have a recent version) 82 | $ virtualenv venv -p python3 83 | $ source venv/bin/activate 84 | 85 | # Install server 86 | $ pip install -e .[dev] 87 | 88 | # (optional) Check out the CLI 89 | $ bigchaindb-examples --help 90 | 91 | # Initialize BigchainDB and load initial data 92 | $ bigchaindb-examples init --all 93 | 94 | # Install client dependencies 95 | $ cd client && npm install && cd - 96 | 97 | 98 | The CLI will handle any initialization that's necessary for the client and servers so you can skip 99 | to :ref:`run` to begin running the examples. 100 | 101 | .. _manual-setup: 102 | 103 | Manual Setup 104 | ^^^^^^^^^^^^ 105 | 106 | Make sure you have all the :ref:`dependencies`. 107 | 108 | .. code-block:: bash 109 | 110 | # (optional) Run a virtualenv (make sure you have a recent version) 111 | $ virtualenv venv -p python3 112 | $ source venv/bin/activate 113 | 114 | # Install server 115 | $ pip install -e .[dev] 116 | 117 | # Make sure RethinkDB is running! 118 | # Configure and initialize BigchainDB with a different BIGCHAINDB_DATABASE_NAME for each ledger 119 | $ BIGCHAINDB_DATABASE_NAME=bigchaindb_examples_0 \ 120 | bigchaindb -yc .bigchaindb_examples configure 121 | $ bigchaindb -c .bigchaindb_examples init 122 | 123 | $ BIGCHAINDB_DATABASE_NAME=bigchaindb_examples_1 \ 124 | bigchaindb -yc .bigchaindb_examples configure 125 | $ bigchaindb -c .bigchaindb_examples init 126 | 127 | # Load initial data 128 | $ python3 init_accounts.py 129 | $ python3 init_assets.py 130 | 131 | # Install client dependencies 132 | $ cd client && npm install && cd - 133 | 134 | 135 | You should now be ready to run the examples. See :ref:`run` for instructions. 136 | 137 | 138 | 139 | .. _docker engine: https://www.docker.com/products/docker-engine 140 | .. _docker-compose: https://www.docker.com/products/docker-compose 141 | -------------------------------------------------------------------------------- /docs/source/interledger.rst: -------------------------------------------------------------------------------- 1 | Interledger Lab 2 | =============== 3 | 4 | .. note:: **Work in progress**. 5 | 6 | 7 | Quickstart with Docker 8 | ---------------------- 9 | 10 | .. code-block:: bash 11 | 12 | $ make 13 | 14 | If you are using ``docker-machine``, then: 15 | 16 | 17 | .. code-block:: bash 18 | 19 | $ DOCKER_MACHINE_IP=$(docker-machine ip ) make 20 | 21 | Where ```` is the name of the machine you created, e.g.: 22 | 23 | .. code-block:: bash 24 | 25 | $ docker-machine create --driver virtualbox 26 | 27 | 28 | Step by step with Docker 29 | ------------------------ 30 | 31 | Build the services: 32 | 33 | .. code-block:: bash 34 | 35 | $ docker-compose -f ledgers.yml build 36 | 37 | Run RethinkDB in the background: 38 | 39 | .. code-block:: bash 40 | 41 | $ docker-compose -f ledgers.yml up -d rdb 42 | 43 | 44 | Configure each ledger: 45 | 46 | .. code-block:: bash 47 | 48 | $ docker-compose -f ledgers.yml run bdb-0 bigchaindb -y configure 49 | $ docker-compose -f ledgers.yml run bdb-1 bigchaindb -y configure 50 | 51 | Initialize each ledger: 52 | 53 | .. code-block:: bash 54 | 55 | $ docker-compose -f ledgers.yml run bdb-0 bigchaindb init 56 | $ docker-compose -f ledgers.yml run bdb-1 bigchaindb init 57 | 58 | Initialize the accounts and assets: 59 | 60 | .. code-block:: bash 61 | 62 | $ docker-compose -f ledgers.yml run bdb-0 python init_accounts.py 63 | $ docker-compose -f ledgers.yml run bdb-0 python init_assets.py 64 | 65 | .. note:: Since each ledger/service (``bdb-0``, ``bdb-1``) is connected to the 66 | same RethinkDB instance, the initialization commands can be run with either 67 | service (``bdb-0``, or ``bdb-1``). 68 | 69 | Start everything: 70 | 71 | .. code-block:: bash 72 | 73 | $ docker-compose -f ledgers.yml up 74 | 75 | 76 | To view each ledger in browser, visit: 77 | 78 | * ``bdb-0``: http://localhost:32800 79 | * ``bdb-1``: http://localhost:32810 80 | 81 | .. note:: Replace ``localhost`` with your docker-machine ip as necessary. 82 | -------------------------------------------------------------------------------- /docs/source/ontherecord.rst: -------------------------------------------------------------------------------- 1 | .. _ontherecord: 2 | 3 | On the Record 4 | ============= 5 | 6 | "On the Record" is a simple logging app, wrapped as a messaging board. 7 | 8 | .. image:: /_static/ontherecord.png 9 | 10 | 11 | Use cases 12 | --------- 13 | 14 | - Immutable logging of data 15 | - Notarization of data, text, emails 16 | 17 | Functionality 18 | ------------- 19 | 20 | Create assets 21 | ^^^^^^^^^^^^^ 22 | 23 | - With arbitrary payload 24 | - And an unlimited amount 25 | 26 | Retrieve assets 27 | *************** 28 | 29 | - That you currently own (like UTXO's) 30 | - By searching the asset data/payload 31 | - State indicator (in backlog vs. on bigchain) 32 | 33 | What this app doesn't provide 34 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 35 | 36 | - Proper user and key management 37 | - Transfer of assets 38 | -------------------------------------------------------------------------------- /docs/source/run.rst: -------------------------------------------------------------------------------- 1 | .. _run: 2 | 3 | Running the Examples 4 | ==================== 5 | Details about each app is documented under: 6 | 7 | * :ref:`ontherecord` 8 | * :ref:`sharetrader` 9 | * :ref:`interledger` 10 | 11 | 12 | Docker 13 | ------ 14 | 15 | Use the provided `Makefile` to configure, initialize, and start running on Docker all in one go: 16 | 17 | .. code-block:: bash 18 | 19 | $ make 20 | 21 | Or, if you're using docker-machine instances (ie. on OSX / Windows), 22 | 23 | .. code-block:: bash 24 | 25 | $ DOCKER_MACHINE_IP=$(docker-machine ip) make 26 | 27 | You should be able to view the app at ``_ (replace ``localhost`` with your 28 | docker-machine ip as necessary). 29 | 30 | 31 | Locally 32 | ------- 33 | 34 | Using the CLI 35 | ^^^^^^^^^^^^^ 36 | 37 | .. code-block:: bash 38 | 39 | $ bigchaindb-examples start --init --all 40 | 41 | Starting Everything Manually 42 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 43 | 44 | Not for the faint of heart; use the CLI instead! 45 | 46 | You'll need to run at least two instances of BigchainDB along with a Flask and a Tornado server for 47 | each instance (Flask should be run under ports 8000 and 8001; Tornado should be run under 8888 and 48 | 8889). 49 | 50 | Running the javascript client 51 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | In one terminal, run ``npm start`` in ``client/`` to serve the client apps 53 | 54 | .. code-block:: bash 55 | 56 | $ cd client 57 | $ npm start 58 | 59 | 60 | Running BigchainDB 61 | ^^^^^^^^^^^^^^^^^^ 62 | Launch ``BigchainDB`` with ``RethinkDB`` in a separate terminal 63 | 64 | .. code-block:: bash 65 | 66 | $ rethinkdb & # skip this if RethinkDB is already running 67 | $ bigchaindb -c .bigchaindb_examples start 68 | 69 | 70 | Running the App servers 71 | ^^^^^^^^^^^^^^^^^^^^^^^ 72 | In another terminal, launch the ``flask`` server 73 | 74 | .. code-block:: bash 75 | 76 | $ python3 -m server.app 77 | 78 | In (yet) another terminal, launch the ``tornado`` server 79 | 80 | .. code-block:: bash 81 | 82 | $ python3 -m server.tornado_app 83 | 84 | You should be able to view the app at ``_. 85 | -------------------------------------------------------------------------------- /docs/source/sharetrader.rst: -------------------------------------------------------------------------------- 1 | .. _sharetrader: 2 | 3 | Share Trader 4 | ============ 5 | 6 | Share Trader is a simple share allocation and trade app. Each square represents 7 | an asset that can be traded amongst accounts. 8 | 9 | .. image:: /_static/sharetrader.png 10 | 11 | 12 | Use cases 13 | --------- 14 | 15 | - Reservation of tickets, seats in a concert/transport, ... 16 | - Trade of limited issued assets 17 | 18 | Functionality 19 | ------------- 20 | 21 | Create assets 22 | ^^^^^^^^^^^^^ 23 | 24 | - Assets are created following a structured payload 25 | - The amount is limited 26 | 27 | Transfer assets 28 | ^^^^^^^^^^^^^^^ 29 | 30 | - Easy transfer of assets between accounts by: 31 | - Clicking on an account first. This will give the assets for that account 32 | - Clicking on an asset of that account. Transfer actions will appear on the 33 | right side. 34 | 35 | Retrieve assets 36 | ^^^^^^^^^^^^^^^ 37 | 38 | - That you currently own (like UTXO's) 39 | - All assets on bigchain 40 | - State indicator (blinks if asset has various owners) 41 | 42 | What this app doesn't provide 43 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 44 | - Proper user and key management 45 | - Proper signing of transfers 46 | - Proper search by payload 47 | -------------------------------------------------------------------------------- /docs/source/structure.rst: -------------------------------------------------------------------------------- 1 | Structure 2 | ========= 3 | 4 | The apps are structured as follows: 5 | 6 | * Client: ReactJS 7 | * Server: Python Flask REST API server 8 | * DB: BigchainDB 9 | 10 | All messages are JSON based. 11 | -------------------------------------------------------------------------------- /docs/source/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | Oops ¯\\\_(ツ)\_/¯ 5 | ------------------ 6 | 7 | My installation fails with: 8 | 9 | .. code-block:: bash 10 | 11 | error: Setup script exited with error in BigchainDB setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers 12 | 13 | * **Solution**: update the ``setuptools``, see `PR fix `_ 14 | 15 | 16 | OMG: I've messed up my database 17 | ------------------------------- 18 | 19 | * **Solution**: reset your bigchaindb_examples database 20 | * **Warning**: the following resets your bigchaindb database to its default initialized state! 21 | 22 | Via Docker 23 | ^^^^^^^^^^ 24 | 25 | .. code-block:: bash 26 | 27 | $ make init 28 | 29 | Or, to reinitialize and restart: 30 | 31 | .. code-block:: bash 32 | 33 | $ make restart 34 | 35 | 36 | Via the CLI 37 | ^^^^^^^^^^^ 38 | 39 | .. code-block:: bash 40 | 41 | $ bigchaindb-examples init --all 42 | 43 | Or, to reinitialize and restart: 44 | 45 | .. code-block:: bash 46 | 47 | $ bigchaindb-examples start --init --all 48 | 49 | 50 | Manually 51 | ^^^^^^^^ 52 | 53 | Restart your RethinkDB instance and follow the initialization steps in 54 | :ref:`manual-setup`. 55 | -------------------------------------------------------------------------------- /init_accounts.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os.path 3 | 4 | import bigchaindb.config_utils 5 | 6 | import apps_config 7 | from server.config_bigchaindb import get_bigchain 8 | from server.lib.models.accounts import Account 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | logger = logging.getLogger(__name__) 12 | 13 | APPS = apps_config.APPS 14 | 15 | LEDGER_API_BASE_HOST = os.environ.get('DOCKER_MACHINE_IP') or 'localhost' 16 | LEDGER_API_BASE_PORT = int(os.environ.get('LEDGER_API_BASE_PORT', '8000')) 17 | LEDGER_WS_BASE_HOST = os.environ.get('DOCKER_MACHINE_IP') or 'localhost' 18 | LEDGER_WS_BASE_PORT = int(os.environ.get('LEDGER_WS_BASE_PORT', '8888')) 19 | 20 | bigchain = get_bigchain() 21 | logging.info('INIT: bigchain initialized with database: {}'.format(bigchaindb.config['database']['name'])) 22 | 23 | 24 | def creat_uri(host, port, offset): 25 | return '{}:{}'.format(host, port+offset) 26 | 27 | 28 | def main(): 29 | 30 | for app in APPS: 31 | accounts = [] 32 | app_name = '{}'.format(app['name']) 33 | if 'num_accounts' in app: 34 | for i in range(app['num_accounts']): 35 | account = Account(bigchain=bigchain, 36 | name='account_{}'.format(i), 37 | ledger={ 38 | 'id': app['ledger'], 39 | 'api': creat_uri(LEDGER_API_BASE_HOST, 40 | LEDGER_API_BASE_PORT, 41 | app['ledger']), 42 | 'ws': creat_uri(LEDGER_WS_BASE_HOST, 43 | LEDGER_WS_BASE_PORT, 44 | app['ledger']) 45 | }, 46 | db=app_name) 47 | accounts.append(account) 48 | elif 'accounts' in app: 49 | for account_config in app['accounts']: 50 | for ledger in account_config['ledgers']: 51 | account = Account(bigchain=bigchain, 52 | name=account_config['name'], 53 | ledger={ 54 | 'id': ledger['id'], 55 | 'api': creat_uri( 56 | LEDGER_API_BASE_HOST, 57 | LEDGER_API_BASE_PORT, 58 | ledger['id'] 59 | ), 60 | 'ws': creat_uri( 61 | LEDGER_WS_BASE_HOST, 62 | LEDGER_WS_BASE_PORT, 63 | ledger['id'] 64 | ) 65 | }, 66 | db=app_name) 67 | accounts.append(account) 68 | logging.info('INIT: {} accounts initialized for app: {}'.format(len(accounts), app_name)) 69 | 70 | 71 | if __name__ == '__main__': 72 | main() 73 | -------------------------------------------------------------------------------- /init_assets.py: -------------------------------------------------------------------------------- 1 | import random 2 | import logging 3 | 4 | import bigchaindb 5 | import bigchaindb.config_utils 6 | 7 | import apps_config 8 | from server.lib.models.accounts import retrieve_accounts 9 | from server.lib.models.assets import create_asset 10 | from server.config_bigchaindb import get_bigchain 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | logger = logging.getLogger(__name__) 14 | 15 | APPS = apps_config.APPS 16 | 17 | 18 | def get_accounts_by_name(accounts): 19 | # returns a dict with key = 'name-' value = account 20 | return {'{}-{}'.format(account['name'], account['ledger']['id']): account for account in accounts} 21 | 22 | 23 | def main(): 24 | for app in APPS: 25 | app_name = '{}'.format(app['name']) 26 | if 'num_accounts' in app: 27 | ledger_name = 'bigchaindb_examples_{}'.format(app['ledger']) 28 | bigchain = get_bigchain(ledger_id=app['ledger']) 29 | accounts = retrieve_accounts(bigchain, app_name) 30 | assets = [] 31 | for i in range(app['num_assets']): 32 | asset = create_asset(bigchain=bigchain, 33 | to=accounts[random.randint(0, app['num_accounts'] - 1)]['vk'], 34 | payload=app['payload_func'](i)) 35 | assets.append(asset) 36 | logging.info('{} assets initialized for app {} on ledger {}'.format(len(assets), 37 | app_name, 38 | ledger_name)) 39 | elif 'accounts' in app: 40 | bigchain = bigchaindb.Bigchain() 41 | accounts_by_name = get_accounts_by_name(retrieve_accounts(bigchain, app['name'])) 42 | for account in app['accounts']: 43 | for ledger in account['ledgers']: 44 | ledger_name = 'bigchaindb_examples_{}'.format(ledger['id']) 45 | account_name = '{}-{}'.format(account['name'], ledger['id']) 46 | bigchain = bigchaindb.Bigchain(dbname=ledger_name) 47 | assets = [] 48 | for i in range(ledger['num_assets']): 49 | asset = create_asset(bigchain=bigchain, 50 | to=accounts_by_name[account_name]['vk'], 51 | payload=app['payload_func'](i)) 52 | assets.append(asset) 53 | logging.info('{} assets initialized for account {} in app {} on ledger {}' 54 | .format(len(assets), account['name'], app_name, ledger_name)) 55 | 56 | 57 | if __name__ == '__main__': 58 | main() 59 | -------------------------------------------------------------------------------- /ledgers.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | 4 | services: 5 | 6 | rdb: 7 | image: rethinkdb 8 | ports: 9 | - "58087:8080" 10 | - "28015" 11 | 12 | frontend: 13 | build: 14 | context: . 15 | dockerfile: ./compose/frontend/Dockerfile 16 | volumes: 17 | - ./client/demo:/usr/src/app/client/demo 18 | - ./client/lib:/usr/src/app/client/lib 19 | - ./client/on_the_record:/usr/src/app/client/on_the_record 20 | - ./client/share_trader:/usr/src/app/client/share_trader 21 | - ./client/app_index_template.html:/usr/src/app/client/app_index_template.html 22 | - ./client/browserlist:/usr/src/app/client/browserlist 23 | - ./client/package.json:/usr/src/app/client/package.json 24 | - ./client/server.demo.js/:/usr/src/app/client/server.demo.js 25 | - ./client/webpack.config.js:/usr/src/app/client/webpack.config.js 26 | environment: 27 | CLIENT_HOST: 0.0.0.0 28 | FLASK_HOST: "${DOCKER_MACHINE_IP}" 29 | FLASK_PORT: 48000 30 | ports: 31 | - "33000:3000" 32 | command: node server.demo.js 33 | 34 | connector: 35 | build: 36 | context: . 37 | dockerfile: ./compose/server/Dockerfile 38 | volumes: 39 | - ./server:/usr/src/app/server 40 | - ./.bigchaindb_examples_docker_connector:/usr/src/app/.bigchaindb_examples 41 | environment: 42 | BIGCHAINDB_CONFIG: .bigchaindb_examples 43 | BIGCHAINDB_DATABASE_HOST: rdb 44 | command: python -m server.lib.models.connector 45 | 46 | bdb-0: 47 | build: 48 | context: . 49 | dockerfile: ./compose/server/Dockerfile 50 | volumes: 51 | - ./commands:/usr/src/app/commands 52 | - ./setup.py:/usr/src/app/setup.py 53 | - ./docs:/usr/src/app/docs 54 | - ./init_accounts.py:/usr/src/app/init_accounts.py 55 | - ./init_assets.py:/usr/src/app/init_assets.py 56 | - ./apps_config.py:/usr/src/app/apps_config.py 57 | - ./.bigchaindb_examples_docker:/usr/src/app/.bigchaindb_examples 58 | environment: 59 | BIGCHAINDB_CONFIG: .bigchaindb_examples 60 | BIGCHAINDB_DATABASE_HOST: rdb 61 | BIGCHAINDB_DATABASE_NAME: bigchaindb_examples_0 62 | LEDGER_API_BASE_PORT: 48000 63 | LEDGER_WS_BASE_PORT: 48888 64 | DOCKER_MACHINE_IP: "${DOCKER_MACHINE_IP}" 65 | command: bigchaindb -c .bigchaindb_examples start 66 | 67 | app-0: 68 | build: 69 | context: . 70 | dockerfile: ./compose/server/Dockerfile 71 | volumes: 72 | - ./server:/usr/src/app/server 73 | volumes_from: 74 | - bdb-0 75 | environment: 76 | BIGCHAINDB_CONFIG: .bigchaindb_examples 77 | BIGCHAINDB_DATABASE_HOST: rdb 78 | BIGCHAINDB_LEDGER_NUMBER: 0 79 | FLASK_HOST: 0.0.0.0 80 | DOCKER_MACHINE_IP: "${DOCKER_MACHINE_IP}" 81 | ports: 82 | - "48000:8000" 83 | command: python -m server.app 84 | 85 | ws-0: 86 | build: 87 | context: . 88 | dockerfile: ./compose/server/Dockerfile 89 | volumes_from: 90 | - app-0 91 | environment: 92 | BIGCHAINDB_CONFIG: .bigchaindb_examples 93 | BIGCHAINDB_DATABASE_HOST: rdb 94 | BIGCHAINDB_LEDGER_NUMBER: 0 95 | TORNADO_HOST: 0.0.0.0 96 | ports: 97 | - "48888:8888" 98 | command: python -m server.tornado_app 99 | 100 | bdb-1: 101 | build: 102 | context: . 103 | dockerfile: ./compose/server/Dockerfile 104 | volumes: 105 | - ./setup.py:/usr/src/app/setup.py 106 | - ./docs:/usr/src/app/docs 107 | - ./init_accounts.py:/usr/src/app/init_accounts.py 108 | - ./init_assets.py:/usr/src/app/init_assets.py 109 | - ./apps_config.py:/usr/src/app/apps_config.py 110 | - ./.bigchaindb_examples_docker:/usr/src/app/.bigchaindb_examples 111 | environment: 112 | BIGCHAINDB_CONFIG: .bigchaindb_examples 113 | BIGCHAINDB_DATABASE_HOST: rdb 114 | BIGCHAINDB_DATABASE_NAME: bigchaindb_examples_1 115 | command: bigchaindb -c .bigchaindb_examples start 116 | 117 | app-1: 118 | build: 119 | context: . 120 | dockerfile: ./compose/server/Dockerfile 121 | volumes: 122 | - ./server:/usr/src/app/server 123 | volumes_from: 124 | - bdb-1 125 | environment: 126 | BIGCHAINDB_CONFIG: .bigchaindb_examples 127 | BIGCHAINDB_DATABASE_HOST: rdb 128 | BIGCHAINDB_LEDGER_NUMBER: 1 129 | FLASK_HOST: 0.0.0.0 130 | DOCKER_MACHINE_IP: "${DOCKER_MACHINE_IP}" 131 | ports: 132 | - "48001:8000" 133 | command: python -m server.app 134 | 135 | ws-1: 136 | build: 137 | context: . 138 | dockerfile: ./compose/server/Dockerfile 139 | volumes_from: 140 | - app-1 141 | environment: 142 | BIGCHAINDB_CONFIG: .bigchaindb_examples 143 | BIGCHAINDB_DATABASE_HOST: rdb 144 | BIGCHAINDB_LEDGER_NUMBER: 1 145 | TORNADO_HOST: 0.0.0.0 146 | ports: 147 | - "48889:8888" 148 | command: python -m server.tornado_app 149 | -------------------------------------------------------------------------------- /server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/__init__.py -------------------------------------------------------------------------------- /server/app.py: -------------------------------------------------------------------------------- 1 | """This module contains basic functions to instantiate the BigchainDB API. 2 | 3 | The application is implemented in Flask and runs using Gunicorn. 4 | """ 5 | import os 6 | 7 | from flask import Flask 8 | from flask.ext.cors import CORS 9 | 10 | from server.lib.api.views import api_views 11 | 12 | 13 | def create_app(debug): 14 | """Return an instance of the Flask application. 15 | 16 | Args: 17 | debug (bool): a flag to activate the debug mode for the app 18 | (default: False). 19 | """ 20 | 21 | app = Flask(__name__) 22 | hostname = os.environ.get('DOCKER_MACHINE_IP', 'localhost') 23 | if not hostname: 24 | hostname = 'localhost' 25 | origins = ('^(https?://)?(www\.)?({}|0|0.0.0.0|dimi-bat.local|' 26 | 'localhost|127.0.0.1)(\.com)?:\d{{1,5}}$').format(hostname), 27 | CORS(app, 28 | origins=origins, 29 | headers=( 30 | 'x-requested-with', 31 | 'content-type', 32 | 'accept', 33 | 'origin', 34 | 'authorization', 35 | 'x-csrftoken', 36 | 'withcredentials', 37 | 'cache-control', 38 | 'cookie', 39 | 'session-id', 40 | ), 41 | supports_credentials=True, 42 | ) 43 | 44 | app.debug = debug 45 | 46 | app.register_blueprint(api_views, url_prefix='/api') 47 | return app 48 | 49 | 50 | def run_flask_server(): 51 | app = create_app(debug=True) 52 | app.run(host=os.environ.get('FLASK_HOST', '127.0.0.1'), port=int(os.environ.get('FLASK_PORT', 8000))) 53 | app.run(use_reloader=False) 54 | 55 | if __name__ == '__main__': 56 | run_flask_server() 57 | -------------------------------------------------------------------------------- /server/config_bigchaindb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | 4 | import bigchaindb 5 | import bigchaindb.config_utils 6 | 7 | try: 8 | CONFIG_FILE = os.environ['BIGCHAINDB_CONFIG'] 9 | except KeyError: 10 | CONFIG_FILE = '.bigchaindb_examples' 11 | 12 | 13 | def get_bigchain(conf=CONFIG_FILE, ledger_id=None): 14 | if os.path.isfile(conf): 15 | bigchaindb.config_utils.autoconfigure(filename=conf, force=True) 16 | 17 | if ledger_id is not None: 18 | return bigchaindb.Bigchain(dbname='bigchaindb_examples_{}'.format(ledger_id)) 19 | else: 20 | return bigchaindb.Bigchain() 21 | -------------------------------------------------------------------------------- /server/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/lib/__init__.py -------------------------------------------------------------------------------- /server/lib/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/lib/api/__init__.py -------------------------------------------------------------------------------- /server/lib/api/views.py: -------------------------------------------------------------------------------- 1 | """This module provides the blueprint for some basic API endpoints. 2 | 3 | For more information please refer to the documentation in Apiary: 4 | - http://docs.bigchaindb.apiary.io/ 5 | """ 6 | import os 7 | 8 | import flask 9 | from flask import request, Blueprint 10 | 11 | from server.config_bigchaindb import get_bigchain 12 | from server.lib.models import accounts 13 | from server.lib.models import assets 14 | 15 | api_views = Blueprint('api_views', __name__) 16 | 17 | bigchain = get_bigchain(ledger_id=os.environ.get('BIGCHAINDB_LEDGER_NUMBER')) 18 | 19 | 20 | @api_views.route('/accounts/') 21 | def get_accounts(): 22 | app = '{}'.format(request.args.get('app')) 23 | result = accounts.retrieve_accounts(bigchain, app) 24 | return flask.jsonify({'accounts': result}) 25 | 26 | 27 | @api_views.route('/accounts/', methods=['POST']) 28 | def post_account(): 29 | json_payload = request.get_json(force=True) 30 | tx = assets.create_asset(bigchain=bigchain, 31 | to=json_payload['to'], 32 | payload={'content': json_payload['content']}) 33 | return flask.jsonify(**tx) 34 | 35 | 36 | @api_views.route('/accounts//assets/') 37 | def get_assets_for_account(account_vk): 38 | query = request.args.get('search') 39 | 40 | result = { 41 | 'bigchain': assets.get_owned_assets(bigchain, vk=account_vk, query=query), 42 | 'backlog': assets.get_owned_assets(bigchain, vk=account_vk, query=query, table='backlog') 43 | } 44 | return flask.jsonify({'assets': result, 'account': account_vk}) 45 | 46 | 47 | @api_views.route('/ledgers//connectors/') 48 | def get_connectors_for_account(ledger_id): 49 | app = '{}'.format(request.args.get('app')) 50 | result = accounts.get_connectors(bigchain, ledger_id, app) 51 | return flask.jsonify({'connectors': result}) 52 | 53 | 54 | @api_views.route('/assets/') 55 | def get_assets(): 56 | search = request.args.get('search') 57 | result = assets.get_assets(bigchain, search) 58 | return flask.jsonify({'assets': result}) 59 | 60 | 61 | @api_views.route('/assets/', methods=['POST']) 62 | def post_asset(): 63 | json_payload = request.get_json(force=True) 64 | to = json_payload.pop('to') 65 | tx = assets.create_asset(bigchain=bigchain, 66 | to=to, 67 | payload=json_payload) 68 | 69 | return flask.jsonify(**tx) 70 | 71 | 72 | @api_views.route('/assets///transfer/', methods=['POST']) 73 | def transfer_asset(asset_id, cid): 74 | json_payload = request.get_json(force=True) 75 | source = json_payload.pop('source') 76 | to = json_payload.pop('to') 77 | 78 | tx = assets.transfer_asset(bigchain=bigchain, 79 | source=source['vk'], 80 | to=to['vk'], 81 | asset_id={ 82 | 'txid': asset_id, 83 | 'cid': int(cid) 84 | }, 85 | sk=source['sk']) 86 | 87 | return flask.jsonify(**tx) 88 | 89 | 90 | @api_views.route('/assets///escrow/', methods=['POST']) 91 | def escrow_asset(asset_id, cid): 92 | json_payload = request.get_json(force=True) 93 | source = json_payload.pop('source') 94 | expires_at = json_payload.pop('expiresAt') 95 | ilp_header = json_payload.pop('ilpHeader', None) 96 | execution_condition = json_payload.pop('executionCondition') 97 | to = json_payload.pop('to') 98 | 99 | tx = assets.escrow_asset(bigchain=bigchain, 100 | source=source['vk'], 101 | to=to['vk'], 102 | asset_id={ 103 | 'txid': asset_id, 104 | 'cid': int(cid) 105 | }, 106 | sk=source['sk'], 107 | expires_at=expires_at, 108 | ilp_header=ilp_header, 109 | execution_condition=execution_condition) 110 | 111 | return flask.jsonify(**tx) 112 | 113 | 114 | @api_views.route('/assets///escrow/fulfill/', methods=['POST']) 115 | def fulfill_escrow_asset(asset_id, cid): 116 | json_payload = request.get_json(force=True) 117 | source = json_payload.pop('source') 118 | to = json_payload.pop('to') 119 | 120 | execution_fulfillment = json_payload.pop('conditionFulfillment', None) 121 | 122 | tx = assets.fulfill_escrow_asset(bigchain=bigchain, 123 | source=source['vk'], 124 | to=to['vk'], 125 | asset_id={ 126 | 'txid': asset_id, 127 | 'cid': int(cid) 128 | }, 129 | sk=source['sk'], 130 | execution_fulfillment=execution_fulfillment) 131 | 132 | return flask.jsonify(**tx) 133 | -------------------------------------------------------------------------------- /server/lib/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/lib/models/__init__.py -------------------------------------------------------------------------------- /server/lib/models/accounts.py: -------------------------------------------------------------------------------- 1 | import rethinkdb as r 2 | 3 | import bigchaindb.crypto 4 | from .assets import transfer_asset 5 | 6 | 7 | class Account: 8 | def __init__(self, bigchain, name, ledger, db): 9 | self.bigchain = bigchain 10 | self.db = db 11 | self.name = name 12 | self.sk, self.vk = bigchaindb.crypto.generate_key_pair() 13 | self.ledger = ledger 14 | self.save() 15 | 16 | @property 17 | def assets(self): 18 | return self.bigchain.get_owned_ids(self.vk) 19 | 20 | def transfer(self, to, asset_id): 21 | return transfer_asset(bigchain=self.bigchain, 22 | source=self.vk, 23 | to=to, 24 | asset_id=asset_id, 25 | sk=self.sk) 26 | 27 | def save(self): 28 | try: 29 | r.db_create(self.db).run(self.bigchain.conn) 30 | except r.ReqlOpFailedError: 31 | pass 32 | 33 | try: 34 | r.db(self.db).table_create('accounts').run(self.bigchain.conn) 35 | except r.ReqlOpFailedError: 36 | pass 37 | 38 | user_exists = list(r.db(self.db) 39 | .table('accounts') 40 | .filter(lambda user: (user['name'] == self.name) 41 | & (user['ledger']['id'] == self.ledger['id'])) 42 | .run(self.bigchain.conn)) 43 | if not len(user_exists): 44 | r.db(self.db)\ 45 | .table('accounts')\ 46 | .insert(self.as_dict(), durability='hard')\ 47 | .run(self.bigchain.conn) 48 | else: 49 | user_persistent = user_exists[0] 50 | self.vk = user_persistent['vk'] 51 | self.sk = user_persistent['sk'] 52 | 53 | def as_dict(self): 54 | return { 55 | 'name': self.name, 56 | 'sk': self.sk, 57 | 'vk': self.vk, 58 | 'ledger': self.ledger 59 | } 60 | 61 | 62 | def retrieve_accounts(bigchain, db): 63 | return list(r.db(db) 64 | .table('accounts') 65 | .run(bigchain.conn)) 66 | 67 | 68 | def get_connectors(bigchain, ledger_id, db): 69 | account_on_ledgers = \ 70 | list(r.db(db) 71 | .table('accounts') 72 | .filter(lambda user: user['ledger']['id'] == int(ledger_id)) 73 | .run(bigchain.conn)) 74 | result = [] 75 | for account_on_ledger in account_on_ledgers: 76 | account_on_multiple_ledgers = \ 77 | list(r.db(db) 78 | .table('accounts') 79 | .filter(lambda user: user['name'] == account_on_ledger['name']) 80 | .run(bigchain.conn)) 81 | if len(account_on_multiple_ledgers) > 1: 82 | result += [account for account in account_on_multiple_ledgers if account['ledger']['id'] == int(ledger_id)] 83 | return result 84 | -------------------------------------------------------------------------------- /server/lib/models/connector.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import multiprocessing 3 | from itertools import groupby 4 | 5 | import rethinkdb as r 6 | 7 | from bigchaindb import Bigchain 8 | import cryptoconditions as cc 9 | 10 | from server.lib.models.accounts import retrieve_accounts 11 | from server.lib.models.assets import escrow_asset, get_subcondition_indices_from_type, fulfill_escrow_asset 12 | from server.config_bigchaindb import get_bigchain 13 | 14 | 15 | class Connector(object): 16 | 17 | def __init__(self, account1, account2): 18 | self.accounts = {} 19 | self.add_accounts(account1) 20 | self.add_accounts(account2) 21 | 22 | def add_accounts(self, account): 23 | self.accounts[account['ledger']['id']] = account 24 | 25 | def listen_events(self): 26 | listeners = [] 27 | for ledger_id in self.accounts.keys(): 28 | listen = threading.Thread(target=self._listen_events, args=(ledger_id,)) 29 | listen.start() 30 | listeners.append(listen) 31 | 32 | for listen in listeners: 33 | listen.join() 34 | 35 | def handle_escrow(self, tx, current_ledger_id): 36 | print('called handle_escrow {}'.format(tx['id'])) 37 | 38 | ilp_header = tx['transaction']['data']['payload']['ilp_header'] 39 | if 'hops' not in ilp_header: 40 | ilp_header['hops'] = [] 41 | ilp_header['hops'].append({ 42 | 'ledger': current_ledger_id, 43 | 'txid': tx['id'] 44 | }) 45 | 46 | destination_ledger_id = ilp_header['ledger'] 47 | 48 | ledger = get_bigchain(ledger_id=destination_ledger_id) 49 | source = self.accounts[destination_ledger_id]['vk'] 50 | to = ilp_header['account'] 51 | asset_id = ledger.get_owned_ids(source).pop() 52 | sk = self.accounts[destination_ledger_id]['sk'] 53 | 54 | condition = cc.Fulfillment.from_dict(tx['transaction']['conditions'][0]['condition']['details']) 55 | 56 | timelocks, _ = get_subcondition_indices_from_type(condition, cc.TimeoutFulfillment.TYPE_ID) 57 | expires_at = timelocks[0].expire_time.decode() 58 | 59 | hashlocks, _ = get_subcondition_indices_from_type(condition, cc.PreimageSha256Fulfillment.TYPE_ID) 60 | execution_condition = hashlocks[0].serialize_uri() 61 | 62 | escrow_asset(bigchain=ledger, 63 | source=source, 64 | to=to, 65 | asset_id=asset_id, 66 | sk=sk, 67 | expires_at=expires_at, 68 | ilp_header=ilp_header, 69 | execution_condition=execution_condition) 70 | 71 | def handle_execute(self, tx): 72 | print('called handle_execute {}'.format(tx['id'])) 73 | 74 | ilp_header = tx['transaction']['data']['payload']['ilp_header'] 75 | 76 | hop = ilp_header['hops'][0] 77 | 78 | ledger = get_bigchain(ledger_id=hop['ledger']) 79 | tx_escrow = ledger.get_transaction(hop['txid']) 80 | 81 | source = self.accounts[hop['ledger']]['vk'] 82 | to = source 83 | asset_id = { 84 | 'txid': hop['txid'], 85 | 'cid': 0 86 | } 87 | sk = self.accounts[hop['ledger']]['sk'] 88 | 89 | fulfillment = cc.Fulfillment.from_uri(tx['transaction']['fulfillments'][0]['fulfillment']) 90 | 91 | hashlocks, _ = get_subcondition_indices_from_type(fulfillment, cc.PreimageSha256Fulfillment.TYPE_ID) 92 | execution_fulfillment = hashlocks[0].serialize_uri() 93 | 94 | fulfill_escrow_asset(bigchain=ledger, 95 | source=source, 96 | to=to, 97 | asset_id=asset_id, 98 | sk=sk, 99 | execution_fulfillment=execution_fulfillment) 100 | 101 | def _listen_events(self, ledger_id): 102 | ledger = get_bigchain(ledger_id=ledger_id) 103 | for change in r.table('bigchain').changes().run(ledger.conn): 104 | if change['old_val'] is None: 105 | self._handle_block(change['new_val'], ledger_id) 106 | 107 | def _handle_block(self, block, ledger_id): 108 | """ 109 | 1. Alice ---> [Alice, Chloe] ledger_a 110 | 2. Chloe ---> [Chloe, Bob] ledger_b 111 | 3. [Chloe, Bob] ---> Bob ledger_b 112 | 4. [Alice, Chloe] ---> Chloe ledger_a 113 | 114 | 115 | 1. If chloe not in current owners and if new_owners = [current_owner, chloe] ---> escrow 116 | 2. If current_owners == [chloe] do nothing 117 | 3. If current_owners = [chloe, new_owner] and new_owners = [bob] ---> bob fulfilled hashlock 118 | 4. If new_owner == [chloe] do nothing 119 | """ 120 | vk = self.accounts[ledger_id]['vk'] 121 | 122 | for transaction in block['block']['transactions']: 123 | current_owners = transaction['transaction']['fulfillments'][0]['current_owners'] 124 | new_owners = transaction['transaction']['conditions'][0]['new_owners'] 125 | 126 | # 1. 127 | if vk not in current_owners and sorted(new_owners) == sorted([vk] + current_owners): 128 | print('chloe received escrow {}'.format(transaction['id'])) 129 | self.handle_escrow(transaction, ledger_id) 130 | # 2. 131 | elif current_owners == [vk]: 132 | print('skip {}'.format(transaction['id'])) 133 | # 3. 134 | elif vk in current_owners and vk not in new_owners: 135 | print('hashlock fulfilled {}'.format(transaction['id'])) 136 | self.handle_execute(transaction) 137 | # 4. 138 | elif new_owners == [vk]: 139 | print('skip {}'.format(transaction['id'])) 140 | 141 | 142 | def get_connector_accounts(db='interledger'): 143 | b = get_bigchain() 144 | connector_accounts = [] 145 | accounts_db = retrieve_accounts(b, db) 146 | 147 | for name, accounts in groupby(sorted(accounts_db, key=lambda d: d['name']), key=lambda d: d['name']): 148 | accounts = list(accounts) 149 | if len(accounts) == 2: 150 | connector_accounts.append(tuple(accounts)) 151 | 152 | return connector_accounts 153 | 154 | 155 | def run_connector(account1, account2): 156 | c = Connector(account1=account1, account2=account2) 157 | c.listen_events() 158 | 159 | 160 | if __name__ == '__main__': 161 | connector_accounts = get_connector_accounts() 162 | connector_procs = [] 163 | 164 | for connector_account in connector_accounts: 165 | print('Starting connector: {} <--- {} ---> {}'.format(connector_account[0]['ledger']['id'], 166 | connector_account[0]['name'], 167 | connector_account[1]['ledger']['id'])) 168 | 169 | connector_proc = multiprocessing.Process(target=run_connector, args=connector_account) 170 | connector_proc.start() 171 | connector_procs.append(connector_proc) 172 | 173 | for connector_proc in connector_procs: 174 | connector_proc.join() 175 | 176 | -------------------------------------------------------------------------------- /server/tornado_app.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import os 3 | import logging 4 | 5 | from tornado import websocket, web, ioloop 6 | from tornado.gen import coroutine 7 | 8 | import rethinkdb as r 9 | 10 | from server.config_bigchaindb import get_bigchain 11 | 12 | clients = [] 13 | bigchain = get_bigchain(ledger_id=os.environ.get('BIGCHAINDB_LEDGER_NUMBER')) 14 | 15 | # from http://blog.hiphipjorge.com/django-and-realtime-using-django-with-tornado-and-rethinkdb/ 16 | r.set_loop_type('tornado') 17 | 18 | 19 | logger = logging.getLogger('tornado') 20 | 21 | 22 | @coroutine 23 | def print_changes(db_table): 24 | conn = yield bigchain.conn 25 | feed = yield r.table(db_table).changes().run(conn) 26 | while (yield feed.fetch_next()): 27 | change = yield feed.next() 28 | block = get_block_from_change(change, db_table) 29 | for client in clients: 30 | for tx in block: 31 | # TODO: use REQL for filtering 32 | if tx_contains_vk(tx['transaction'], client.username): 33 | msg = {'change': change, 34 | 'client': client.username} 35 | client.write_message(msg) 36 | break 37 | 38 | 39 | def get_block_from_change(change, db_table): 40 | block = [] 41 | if db_table in ['backlog', 'bigchain'] and (change['old_val'] or change['new_val']): 42 | block_data = change['old_val'] if change['old_val'] else change['new_val'] 43 | if db_table == 'bigchain': 44 | block = block_data['block']['transactions'] 45 | else: 46 | block.append(block_data) 47 | return block 48 | 49 | 50 | def tx_contains_vk(tx, vk): 51 | for condition in tx['conditions']: 52 | if vk in condition['new_owners']: 53 | return True 54 | for fullfillment in tx['fulfillments']: 55 | if vk in fullfillment['current_owners']: 56 | return True 57 | 58 | 59 | class ChangeFeedWebSocket(websocket.WebSocketHandler): 60 | username = None 61 | 62 | def check_origin(self, origin): 63 | return True 64 | 65 | def open(self, username): 66 | if self not in clients: 67 | self.username = username 68 | clients.append(self) 69 | print('ws: open (Pool: {} connections)'.format(len(clients))) 70 | 71 | def on_message(self, message): 72 | pass 73 | 74 | def on_close(self): 75 | for i, client in enumerate(clients): 76 | if client is self: 77 | clients.remove(self) 78 | print('ws: close (Pool: {} connections)'.format(len(clients))) 79 | return 80 | 81 | # TODO: use split changefeed for backlog and bigchain 82 | app = web.Application([ 83 | (r'/users/(.*)/changes', ChangeFeedWebSocket) 84 | ]) 85 | 86 | 87 | def run_tornado_server(): 88 | tornado_port = int(os.environ.get('TORNADO_PORT', 8888)) 89 | tornado_address = os.environ.get('TORNADO_HOST', '127.0.0.1') 90 | app.listen(tornado_port, address=tornado_address) 91 | # TODO: use split changefeed for backlog and bigchain 92 | ioloop.IOLoop.current().add_callback(functools.partial(print_changes, 'backlog')) 93 | ioloop.IOLoop.current().add_callback(functools.partial(print_changes, 'bigchain')) 94 | 95 | logger.info('Running on http://{}:{}'.format(tornado_address, tornado_port)) 96 | ioloop.IOLoop.instance().start() 97 | 98 | if __name__ == '__main__': 99 | run_tornado_server() 100 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | BigchainDB: A Scalable Blockchain Database 3 | 4 | For full docs visit https://bigchaindb.readthedocs.org 5 | 6 | """ 7 | from setuptools import setup, find_packages 8 | 9 | tests_require = [ 10 | 'pytest', 11 | 'coverage', 12 | 'pep8', 13 | 'pyflakes', 14 | 'pylint', 15 | 'pytest', 16 | 'pytest-cov', 17 | 'pytest-xdist', 18 | 'pytest-flask', 19 | ] 20 | 21 | dev_require = [ 22 | 'ipdb', 23 | 'ipython', 24 | ] 25 | 26 | docs_require = [ 27 | 'recommonmark>=0.4.0', 28 | 'Sphinx>=1.3.5', 29 | 'sphinxcontrib-napoleon>=0.4.4', 30 | 'sphinx-rtd-theme>=0.1.9', 31 | ] 32 | 33 | setup( 34 | name='BigchainDB-Examples', 35 | version='0.1.0', 36 | description='Example usages for BigchainDB', 37 | long_description=__doc__, 38 | url='https://github.com/BigchainDB/bigchaindb-examples/', 39 | author='BigchainDB Contributors', 40 | author_email='dev@bigchaindb.com', 41 | license='AGPLv3', 42 | zip_safe=False, 43 | 44 | classifiers=[ 45 | 'Development Status :: 3 - Alpha', 46 | 'Intended Audience :: Developers', 47 | 'Topic :: Database', 48 | 'Topic :: Database :: Database Engines/Servers', 49 | 'Topic :: Software Development', 50 | 'Natural Language :: English', 51 | 'License :: OSI Approved :: GNU Affero General Public License v3', 52 | 'Programming Language :: Python :: 3', 53 | 'Programming Language :: Python :: 3.4', 54 | 'Programming Language :: Python :: 3.5', 55 | 'Operating System :: MacOS :: MacOS X', 56 | 'Operating System :: POSIX :: Linux', 57 | ], 58 | 59 | packages=find_packages(exclude=['tests*']), 60 | 61 | entry_points={ 62 | 'console_scripts': [ 63 | 'bigchaindb-examples=commands.bigchaindb_examples:main' 64 | ] 65 | }, 66 | 67 | install_requires=[ 68 | "rethinkdb==2.3.0", 69 | "BigchainDB==0.5.0", 70 | "decorator==4.0.9", 71 | "flask==0.10.1", 72 | "flask-cors==2.1.2", 73 | "tornado" 74 | ], 75 | 76 | setup_requires=['pytest-runner'], 77 | tests_require=tests_require, 78 | extras_require={ 79 | 'test': tests_require, 80 | 'dev': dev_require + tests_require + docs_require, 81 | 'docs': docs_require, 82 | }, 83 | ) 84 | --------------------------------------------------------------------------------