Explorar o código

Add solana test case

Signed-off-by: Sean Young <sean@mess.org>
Sean Young %!s(int64=5) %!d(string=hai) anos
pai
achega
ba431af1f0
Modificáronse 36 ficheiros con 1353 adicións e 4 borrados
  1. 35 0
      .github/workflows/test.yml
  2. 0 4
      .gitignore
  3. 13 0
      integration/solana/.babelrc
  4. 1 0
      integration/solana/.eslintignore
  5. 52 0
      integration/solana/.eslintrc.js
  6. 22 0
      integration/solana/.flowconfig
  7. 8 0
      integration/solana/.gitignore
  8. 7 0
      integration/solana/.prettierrc.yaml
  9. 4 0
      integration/solana/.theia/settings.json
  10. 20 0
      integration/solana/LICENSE
  11. 49 0
      integration/solana/README-gitpod.md
  12. 70 0
      integration/solana/README-installation-notes.md
  13. 270 0
      integration/solana/README.md
  14. 2 0
      integration/solana/cluster-devnet.env
  15. 2 0
      integration/solana/cluster-mainnet-beta.env
  16. 2 0
      integration/solana/cluster-testnet.env
  17. 4 0
      integration/solana/flow-typed/bn.js.js
  18. 6 0
      integration/solana/flow-typed/bs58.js
  19. 4 0
      integration/solana/flow-typed/buffer-layout.js
  20. 6 0
      integration/solana/flow-typed/cbor.js
  21. 122 0
      integration/solana/flow-typed/event-emitter.js
  22. 38 0
      integration/solana/flow-typed/json-to-pretty-yaml.js
  23. 32 0
      integration/solana/flow-typed/mkdirp-promise_vx.x.x.js
  24. 73 0
      integration/solana/flow-typed/npm/mz_vx.x.x.js
  25. 15 0
      integration/solana/flow-typed/readline-promise.js
  26. 3 0
      integration/solana/flow-typed/semver.js
  27. 9 0
      integration/solana/hello_world.sol
  28. 86 0
      integration/solana/package.json
  29. 205 0
      integration/solana/src/client/hello_world.js
  30. 40 0
      integration/solana/src/client/main.js
  31. 26 0
      integration/solana/src/client/util/new-account-with-lamports.js
  32. 17 0
      integration/solana/src/client/util/new-system-account-with-airdrop.js
  33. 47 0
      integration/solana/src/client/util/send-and-confirm-transaction.js
  34. 6 0
      integration/solana/src/client/util/sleep.js
  35. 26 0
      integration/solana/src/client/util/store.js
  36. 31 0
      integration/solana/url.js

+ 35 - 0
.github/workflows/test.yml

@@ -60,3 +60,38 @@ jobs:
       uses: actions/checkout@v2
     - name: Build dockerfile
       run: docker build .
+
+  solana:
+    name: Solana Integration test
+    runs-on: ubuntu-18.04
+    container: hyperledgerlabs/solang:ci
+    services:
+      solana:
+        image: solanalabs/solana:v1.4.2
+        ports:
+          - 8899
+          - 8900
+    steps:
+    - name: Checkout sources
+      uses: actions/checkout@v2
+    - uses: actions/setup-node@v1
+      with:
+        node-version: '12'
+    - name: Rust stable
+      run: rustup default stable
+    - name: Install npm packages
+      run: npm install
+      working-directory: ./integration/solana
+    - name: Compile stdlib
+      run: make
+      working-directory:  ./stdlib
+    - name: Build
+      run: cargo build --verbose
+    - name: Build Solang contract
+      run: npm run build:bpf-solang
+      working-directory: ./integration/solana
+    - name: Set github env
+      run: echo "RPC_URL=http://solana:8899/" >> $GITHUB_ENV
+    - name: Deploy and test contract
+      run: npm run start
+      working-directory: ./integration/solana

+ 0 - 4
.gitignore

@@ -2,7 +2,3 @@
 Cargo.lock
 /target
 **/*.rs.bk
-
-*.wasm
-*.json
-*.abi

+ 13 - 0
integration/solana/.babelrc

@@ -0,0 +1,13 @@
+{
+  "presets": [
+    "env",
+    "flow",
+    "react",
+    "stage-2",
+  ],
+  "plugins": [
+    "transform-class-properties",
+    "transform-function-bind",
+    "transform-runtime",
+  ]
+}

+ 1 - 0
integration/solana/.eslintignore

@@ -0,0 +1 @@
+dist/

+ 52 - 0
integration/solana/.eslintrc.js

@@ -0,0 +1,52 @@
+module.exports = {
+  // eslint-disable-line import/no-commonjs
+  env: {
+    browser: true,
+    es6: true,
+    node: true,
+  },
+  plugins: ['react'],
+  extends: [
+    'eslint:recommended',
+    'plugin:import/errors',
+    'plugin:import/warnings',
+    'plugin:react/recommended',
+  ],
+  parser: 'babel-eslint',
+  parserOptions: {
+    sourceType: 'module',
+    ecmaVersion: 8,
+  },
+  rules: {
+    'no-trailing-spaces': ['error'],
+    'import/first': ['error'],
+    'import/no-commonjs': ['error'],
+    'import/order': [
+      'error',
+      {
+        groups: [
+          ['internal', 'external', 'builtin'],
+          ['index', 'sibling', 'parent'],
+        ],
+        'newlines-between': 'always',
+      },
+    ],
+    indent: [
+      'error',
+      2,
+      {
+        MemberExpression: 1,
+        SwitchCase: 1,
+      },
+    ],
+    'linebreak-style': ['error', 'unix'],
+    'no-console': [0],
+    quotes: [
+      'error',
+      'single',
+      {avoidEscape: true, allowTemplateLiterals: true},
+    ],
+    'require-await': ['error'],
+    semi: ['error', 'always'],
+  },
+};

+ 22 - 0
integration/solana/.flowconfig

@@ -0,0 +1,22 @@
+[ignore]
+<PROJECT_ROOT>/node_modules/*
+
+[include]
+
+[libs]
+node_modules/@solana/web3.js/module.flow.js
+flow-typed/
+
+[options]
+
+emoji=true
+esproposal.class_instance_fields=enable
+esproposal.class_static_fields=enable
+esproposal.decorators=ignore
+esproposal.export_star_as=enable
+module.system.node.resolve_dirname=./src
+module.use_strict=true
+experimental.const_params=true
+include_warnings=true
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue

+ 8 - 0
integration/solana/.gitignore

@@ -0,0 +1,8 @@
+/node_modules
+*.sw[po]
+/store
+/.cargo
+/dist
+.env
+*.so
+*.abi

+ 7 - 0
integration/solana/.prettierrc.yaml

@@ -0,0 +1,7 @@
+arrowParens: "avoid"
+bracketSpacing: false
+jsxBracketSameLine: false
+semi: true
+singleQuote: true
+tabWidth: 2
+trailingComma: "all"

+ 4 - 0
integration/solana/.theia/settings.json

@@ -0,0 +1,4 @@
+{
+    "workbench.iconTheme": "theia-file-icons",
+    "preview.openByDefault": true
+}

+ 20 - 0
integration/solana/LICENSE

@@ -0,0 +1,20 @@
+Copyright (c) 2018 Solana Labs, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 49 - 0
integration/solana/README-gitpod.md

@@ -0,0 +1,49 @@
+[![Build status][travis-image]][travis-url]
+[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/solana-labs/example-helloworld) 
+
+[travis-image]: https://travis-ci.org/solana-labs/example-helloworld.svg?branch=master
+[travis-url]: https://travis-ci.org/solana-labs/example-helloworld
+
+# Hello world on Solana (Gitpod version)
+
+This project demonstrates how to use the [Solana Javascript API](https://github.com/solana-labs/solana-web3.js)
+to build, deploy, and interact with programs on the Solana blockchain.
+
+The project comprises of:
+
+* An on-chain hello world program
+* A client that can send a "hello" to an account and get back the number of times "hello" has been sent
+
+## Table of Contents
+- [Hello world on Solana (Gitpod version)](#hello-world-on-solana-gitpod-version)
+  - [Table of Contents](#table-of-contents)
+  - [Quick Start](#quick-start)
+    - [Expected output](#expected-output)
+    - [Customizing the Program](#customizing-the-program)
+  - [Learn about Solana](#learn-about-solana)
+  - [Learn about the client](#learn-about-the-client)
+  - [Learn about the on-chain program](#learn-about-the-on-chain-program)
+  - [Expand your skills with advanced examples](#expand-your-skills-with-advanced-examples)
+
+## Quick Start
+
+Using this example in Gitpod connects to the public Solana `devnet` cluster.  Gitpod does not support Docker so running a local cluster is not supported.  Use the environment variable `CLUSTER` to choose a different Solana cluster.
+
+Run the client to load and interact with the on-chain program:
+```bash
+$ npm run start
+```
+
+The remaining sections of this document point back to the non-Gitpod version of the README for more information.  Just remember, using Docker or running a local cluster is not supported if you are using Gitpod.
+
+### [Expected output](README.md#expected-output)
+
+### [Customizing the Program](README.md#Customizing-the-Program)
+
+## [Learn about Solana](README.md#learn-about-solana)
+
+## [Learn about the client](README.md#learn-about-the-client)
+
+## [Learn about the on-chain program](README.md#learn-about-the-on-chain-program)
+
+## [Expand your skills with advanced examples](README.md#expand-your-skills-with-advanced-examples)

+ 70 - 0
integration/solana/README-installation-notes.md

@@ -0,0 +1,70 @@
+# Installation Notes
+if you are a first-time user of Docker or Rust, the notes below may help you to install some of the dependencies on a Mac or Linux workstation.
+
+### Rust
+We suggest that you install Rust using the 'rustup' tool. Rustup will install
+the latest version of Rust, Cargo, and the other binaries used in Solana.
+
+Follow the instructions at [Installing Rust](https://www.rust-lang.org/tools/install).
+
+For Mac users, Homebrew is also an option.  The Mac Homebrew command is `brew install rustup` and then
+`rustup-init`. See [Mac Setup](https://sourabhbajaj.com/mac-setup/Rust/) &
+[Installing Rust](https://www.rust-lang.org/tools/install) for more details.
+
+After installation, you should have `rustc`, `cargo`, & `rustup`. You should
+also have `~/.cargo/bin` in your PATH environment variable.
+
+### NodeJS/NPM
+Fetch the `npm` dependencies, including `@solana/web3.js`, by running:
+```bash
+$ npm install
+```
+
+### Docker
+Docker runs as a service and it needs to be running before you can start the
+Solana cluster. The exact start method depends on your system and how you
+installed docker.
+
+#### Install and Start Docker On Linux
+The instructions to install Docker have changed over time. If you have
+previously installed Docker, this will be a good time to update your system.
+See [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/) for a step-by-step walk-through. When complete, `sudo docker run hello-world` should confirm that everything works correctly.
+
+To run Docker without typing `sudo` every time, take a look at Step 2 of [How To Install and Use Docker on Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04)
+
+#### Install and Start Docker On A Mac
+Docker provides a desktop application for Mac at [Docker Desktop for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac/) with additional instructions here [Install Docker Desktop on Mac](https://docs.docker.com/docker-for-mac/install/). If you install the Docker Desktop app, you can skip the HomeBrew instructions below. If `docker run hello-world` works, you are ready to Start the local Solana cluster.
+
+If you are using HomeBrew on a Mac, the commands are:
+
+```bash
+$ brew install docker
+$ brew install docker-machine
+# The next two commands to install virtualbox & create a machine may need a
+# password. You may also need to address a System Preference setting and
+# re-try the installation.
+$ brew cask install virtualbox
+$ docker-machine create --driver virtualbox default
+# To see config info:
+$ docker-machine env default
+# Port forwarding of 8899 from your OS to the Docker machine:
+$ docker-machine ssh default -f -N -L 8899:localhost:8899
+# To configure your shell to use the docker-machine
+$ eval "$(docker-machine env default)"
+```
+
+NOTE: Later, you can run `docker-machine stop default` to stop the docker machine.
+
+Resources for Mac HomeBrew users:
+- https://medium.com/@yutafujii_59175/a-complete-one-by-one-guide-to-install-docker-on-your-mac-os-using-homebrew-e818eb4cfc3
+- https://stackoverflow.com/questions/32174560/port-forwarding-in-docker-machine
+
+### Git Repository
+Clone the 'example-helloworld' repository into your development machine:
+```bash
+$ cd /path/to/your/work/folder/
+$ git clone https://github.com/solana-labs/example-helloworld.git
+$ cd example-helloworld
+```
+(If you plan to submit changes in a pull request, be sure to create a fork
+first and then clone your fork.)

+ 270 - 0
integration/solana/README.md

@@ -0,0 +1,270 @@
+<p align="center">
+  <a href="https://solana.com">
+    <img alt="Solana" src="https://i.imgur.com/OMnvVEz.png" width="250" />
+  </a>
+</p>
+
+[![Build status][travis-image]][travis-url]
+[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/solana-labs/example-helloworld)
+
+[travis-image]: https://travis-ci.org/solana-labs/example-helloworld.svg?branch=master
+[travis-url]: https://travis-ci.org/solana-labs/example-helloworld
+
+# Hello world on Solana
+
+This project demonstrates how to use the [Solana Javascript API](https://github.com/solana-labs/solana-web3.js)
+to build, deploy, and interact with programs on the Solana blockchain.
+
+The project comprises of:
+
+* An on-chain hello world program
+* A client that can send a "hello" to an account and get back the number of times "hello" has been sent
+
+## Table of Contents
+- [Hello world on Solana](#hello-world-on-solana)
+  - [Table of Contents](#table-of-contents)
+  - [Quick Start](#quick-start)
+    - [Start local Solana cluster](#start-local-solana-cluster)
+    - [Build the on-chain program](#build-the-on-chain-program)
+    - [Run the client](#run-the-client)
+    - [Expected output](#expected-output)
+      - [Not seeing the expected output?](#not-seeing-the-expected-output)
+    - [Customizing the Program](#customizing-the-program)
+  - [Learn about Solana](#learn-about-solana)
+  - [Learn about the client](#learn-about-the-client)
+    - [Entrypoint](#entrypoint)
+    - [Establish a connection to the cluster](#establish-a-connection-to-the-cluster)
+    - [Load the helloworld on-chain program if not already loaded](#load-the-helloworld-on-chain-program-if-not-already-loaded)
+    - [Send a "Hello" transaction to the on-chain program](#send-a-%22hello%22-transaction-to-the-on-chain-program)
+    - [Query the Solana account used in the "Hello" transaction](#query-the-solana-account-used-in-the-%22hello%22-transaction)
+  - [Learn about the on-chain program](#learn-about-the-on-chain-program)
+    - [Entrypoint](#entrypoint-1)
+    - [Processing an instruction](#processing-an-instruction)
+    - [Rust limitations](#rust-limitations)
+  - [Pointing to a public Solana cluster](#pointing-to-a-public-solana-cluster)
+  - [Expand your skills with advanced examples](#expand-your-skills-with-advanced-examples)
+
+## Quick Start
+
+[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/solana-labs/example-helloworld)
+
+If you decide to open in Gitpod then refer to [README-gitpod.md](README-gitpod.md), otherwise continue reading.
+
+The following dependencies are required to build and run this example,
+depending on your OS, they may already be installed:
+
+```bash
+$ node --version
+$ npm --version
+$ docker -v
+$ wget --version
+$ rustup --version
+$ rustc --version
+$ cargo --version
+```
+
+If this is your first time using Docker or Rust, these [Installation Notes](README-installation-notes.md) might be helpful.
+
+### Start local Solana cluster
+
+This example connects to a local Solana cluster by default.
+
+Enable on-chain program logs:
+```bash
+$ export RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=info,solana_bpf_loader=debug,solana_rbpf=debug
+```
+
+Start a local Solana cluster:
+```bash
+$ npm run localnet:update
+$ npm run localnet:up
+```
+
+View the cluster logs:
+```bash
+$ npm run localnet:logs
+```
+
+Note: To stop the local Solana cluster later:
+```bash
+$ npm run localnet:down
+```
+
+### Build the on-chain program
+
+There is both a Rust and C version of the on-chain program, whichever is built last will be the one used when running the example.
+
+```bash
+$ npm run build:program-rust
+```
+
+```bash
+$ npm run build:program-c
+```
+
+### Run the client
+
+```bash
+$ npm run start
+```
+
+### Expected output
+
+Public key values will differ:
+
+```bash
+Lets say hello to a Solana account...
+Connection to cluster established: http://localhost:8899 { solana-core: 1.1.2 }
+Loading hello world program...
+Program loaded to account 47bZX1D1tdmw3KWTo5MfBrAwwHBJQQzQL4VnNGT7HtyQ
+Creating account Eys1jdLHdZ2AE56QAKpfadbjziMZ6NAvpL7qsdtM6sbk to say hello to
+Saying hello to Eys1jdLHdZ2AE56QAKpfadbjziMZ6NAvpL7qsdtM6sbk
+Eys1jdLHdZ2AE56QAKpfadbjziMZ6NAvpL7qsdtM6sbk has been greeted 1 times
+Success
+```
+
+#### Not seeing the expected output?
+
+- Ensure you've [started the local cluster](#start-local-solana-cluster) and [built the on-chain program](#build-the-on-chain-program).
+- Ensure Docker is running.  You might try bumping up its resource settings, 8 GB of memory and 3 GB of swap should help.
+- Inspect the Solana cluster logs looking for any failed transactions or failed on-chain programs
+  - Expand the log filter and restart the cluster to see more detail
+    - ```bash
+      $ npm run localnet:down
+      $ export RUST_LOG=solana_runtime::native_loader=trace,solana_runtime::system_instruction_processor=trace,solana_runtime::bank=debug,solana_bpf_loader=debug,solana_rbpf=debug
+      $ npm run localnet:up
+
+### Customizing the Program
+
+To customize the example, make changes to the files under `/src`.  If you change any files under `/src/program-rust` or `/src/program-c` you will need to [rebuild the on-chain program](#build-the-on-chain-program)
+
+Now when you rerun `npm run start`, you should see the results of your changes.
+
+## Learn about Solana
+
+More information about how Solana works is available in the [Solana documentation](https://docs.solana.com/) and all the source code is available on [github](https://github.com/solana-labs/solana)
+
+Futher questions?  Visit us on [Discord](https://discordapp.com/invite/pquxPsq)
+
+## Learn about the client
+
+The client in this example is written in JavaScript using:
+- [Solana web3.js SDK](https://github.com/solana-labs/solana-web3.js)
+- [Solana web3 API](https://solana-labs.github.io/solana-web3.js)
+
+### Entrypoint
+
+The [client's entrypoint](https://github.com/solana-labs/example-helloworld/blob/e936ab42e168f1939df0164d5996adf9ca635bd0/src/client/main.js#L14) does four things
+
+### Establish a connection to the cluster
+
+The client establishes a connection with the client by calling [`establishConnection`](https://github.com/solana-labs/example-helloworld/blob/e936ab42e168f1939df0164d5996adf9ca635bd0/src/client/hello_world.js#L45).
+
+### Load the helloworld on-chain program if not already loaded
+
+The process of loading a program on the cluster includes storing the shared object's bytes in a Solana account's data vector and marking the account executable.
+
+The client loads the program by calling [`loadProgram`](https://github.com/solana-labs/example-helloworld/blob/e936ab42e168f1939df0164d5996adf9ca635bd0/src/client/hello_world.js#L54).  The first time `loadProgram` is called the client:
+
+- Read the shared object from the file system
+- Calculates the fees associated with loading the program
+- Airdrops lamports to a payer account to pay for the load
+- Loads the program via the Solana web3.js function ['BPFLoader.load']([TODO](https://github.com/solana-labs/solana-web3.js/blob/37d57926b9dba05d1ad505d4fd39d061030e2e87/src/bpf-loader.js#L36))
+- Creates a new "greeter" account that will be used in the "Hello" transaction
+- Records the [public key](https://github.com/solana-labs/solana-web3.js/blob/37d57926b9dba05d1ad505d4fd39d061030e2e87/src/publickey.js#L10) of both the loaded helloworld program and the "greeter" account in a config file.  Repeated calls to the client will refer to the same loaded program and "greeter" account.  (To force the reload of the program issue `npm clean:store`)
+
+### Send a "Hello" transaction to the on-chain program
+
+The client then constructs and sends a "Hello" transaction to the program by calling [`sayHello`](https://github.com/solana-labs/example-helloworld/blob/e936ab42e168f1939df0164d5996adf9ca635bd0/src/client/hello_world.js#L121).  The transaction contains a single very simple instruction that primarily caries the public key of the helloworld program account to call and the "greeter" account to which the client wishes to say "Hello" to.
+
+### Query the Solana account used in the "Hello" transaction
+
+Each time the client says "Hello" to an account, the program increments a numerical count in the "greeter" account's data.  The client queries the "greeter" account's data to discover the current number of times the account has been greeted by calling [`reportHellos`](https://github.com/solana-labs/example-helloworld/blob/e936ab42e168f1939df0164d5996adf9ca635bd0/src/client/hello_world.js#L138.)
+
+## Learn about the on-chain program
+
+The [on-chain helloworld program](src/program/Cargo.toml) is a Rust program compiled to [Berkley Packet Format (BPF)](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter) and stored as an [Executable and Linkable Format (ELF) shared object](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format).
+
+The program is written using:
+- [Solana Rust SDK](https://github.com/solana-labs/solana/tree/master/sdk)
+
+### Entrypoint
+
+The program's [entrypoint](https://github.com/solana-labs/example-helloworld/blob/6508bdb54c4d7f60747263b4274283fbddfabffe/src/program/src/lib.rs#L12) takes three parameters:
+
+```rust
+fn process_instruction<'a>(
+    program_id: &Pubkey, // Public key of the account the hello world program was loaded into
+    accounts: &'a [AccountInfo<'a>], // The account to say hello to
+    _instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
+) -> ProgramResult {
+```
+
+- `program_id` is the public key of the currently executing program.  The same program can be uploaded to the cluster under different accounts, and a program can use `program_id` to determine which instance of the program is currently executing.
+- `accounts` is a slice of [`Account Info's](https://github.com/solana-labs/solana/blob/b4e00275b2da6028cc839a79cdc4453d4c9aca13/sdk/src/account_info.rs#L10) representing each account included in the instruction being processed.
+- `_instruction_data` is a data vector containing the [data passed as part of the instruction](https://github.com/solana-labs/solana-web3.js/blob/37d57926b9dba05d1ad505d4fd39d061030e2e87/src/transaction.js#L46).  In the case of helloworld no instruction data is passed and thus ignored (all instructions are treated as a "Hello" instruction).  Typically the instruction data would contain information about what kind of command the program should process and details about that particular command.
+
+### Processing an instruction
+
+Given the inputs to the entrypoint, the result of the instruction are updates to account's lamports and data vectors.  In the case of helloworld, the "greeted" account's data holds a 32-bit Little-endian encoded unsigned integer, which gets incremented.
+
+The program does a series of checks to ensure that the instruction is well-formed (the "greeted" account is owned by the program and has sufficient data to hold a 32-bit unsigned integer).
+
+The accounts slice may contain the same account in multiple positions, so a Rust ` std protects any writable data::cell::RefCell`
+
+The program prints a diagnostic message to the validators' logs by calling [`info!`](https://github.com/solana-labs/solana/blob/b4e00275b2da6028cc839a79cdc4453d4c9aca13/sdk/src/log.rs#L12).  On a local cluster you can view the logs by including `solana_bpf_loader_program=info` in `RUST_LOG`.
+
+If the program fails, it returns a `ProgramError`; otherwise, it returns `Ok(())` to indicate to the runtime that any updates to the accounts may be recorded on the chain.
+
+### Rust limitations
+
+On-chain Rust programs support most of Rust's libstd, libcore, and liballoc, as well as many 3rd party crates.
+
+There are some limitations since these programs run in a resource-constrained, single-threaded environment, and must be deterministic:
+
+- No access to
+  - `rand` or any crates that depend on it
+  - `std::fs`
+  - `std::net`
+  - `std::os`
+  - `std::future`
+  - `std::net`
+  - `std::process`
+  - `std::sync`
+  - `std::task`
+  - `std::thread`
+  - `std::time`
+- Limited access to:
+  - `std::hash`
+  - `std::os`
+- Bincode is extreamly computationally expensive in both cycles and call depth and should be avoided
+- String formating should be avoided since it is also computationaly expensive
+- No support for `println!`, `print!`, the Solana SDK helpers in `src/log.rs` should be used instead
+- The runtime enforces a limit on the number of instructions a program can execute during the processing of one instruction
+
+## Pointing to a public Solana cluster
+
+Solana maintains three public clusters:
+- `devnet` - Development cluster with airdrops enabled
+- `testnet` - Tour De Sol test cluster without airdrops enabled
+- `mainnet-beta` -  Main cluster
+  
+Use npm scripts to configure which cluster.
+
+To point to `devnet`:
+```bash
+$ npm run cluster:devnet
+```
+
+To point back to the local cluster:
+```bash
+$ npm run cluster:localnet
+```
+
+## Expand your skills with advanced examples
+
+There is lots more to learn; The following examples demonstrate more advanced features like custom errors, advanced account handling, suggestions for data serialization, benchmarking, etc..
+
+- [ERC-20-like Token](https://github.com/solana-labs/example-token)
+- [TicTacToe](https://github.com/solana-labs/example-tictactoe)
+- [MessageFeed](https://github.com/solana-labs/example-messagefeed)

+ 2 - 0
integration/solana/cluster-devnet.env

@@ -0,0 +1,2 @@
+LIVE=1
+CLUSTER=devnet

+ 2 - 0
integration/solana/cluster-mainnet-beta.env

@@ -0,0 +1,2 @@
+LIVE=1
+CLUSTER=mainnet-beta

+ 2 - 0
integration/solana/cluster-testnet.env

@@ -0,0 +1,2 @@
+LIVE=1
+CLUSTER=testnet

+ 4 - 0
integration/solana/flow-typed/bn.js.js

@@ -0,0 +1,4 @@
+declare module 'bn.js' {
+  // TODO: Fill in types
+  declare module.exports: any;
+}

+ 6 - 0
integration/solana/flow-typed/bs58.js

@@ -0,0 +1,6 @@
+declare module 'bs58' {
+  declare module.exports: {
+    encode(input: Buffer): string;
+    decode(input: string): Buffer;
+  };
+}

+ 4 - 0
integration/solana/flow-typed/buffer-layout.js

@@ -0,0 +1,4 @@
+declare module 'buffer-layout' {
+  // TODO: Fill in types
+  declare module.exports: any;
+}

+ 6 - 0
integration/solana/flow-typed/cbor.js

@@ -0,0 +1,6 @@
+declare module 'cbor' {
+  declare module.exports: {
+    decode(input: Buffer): Object;
+    encode(input: any): Buffer;
+  };
+}

+ 122 - 0
integration/solana/flow-typed/event-emitter.js

@@ -0,0 +1,122 @@
+// flow-typed signature: 4f92d81ee3831cb415b4b216cc0679d9
+// flow-typed version: <<STUB>>/event-emitter_v0.3.5/flow_v0.84.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ *   'event-emitter'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'event-emitter' {
+  declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'event-emitter/all-off' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/benchmark/many-on' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/benchmark/single-on' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/emit-error' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/has-listeners' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/pipe' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/test/all-off' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/test/emit-error' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/test/has-listeners' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/test/index' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/test/pipe' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/test/unify' {
+  declare module.exports: any;
+}
+
+declare module 'event-emitter/unify' {
+  declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'event-emitter/all-off.js' {
+  declare module.exports: $Exports<'event-emitter/all-off'>;
+}
+declare module 'event-emitter/benchmark/many-on.js' {
+  declare module.exports: $Exports<'event-emitter/benchmark/many-on'>;
+}
+declare module 'event-emitter/benchmark/single-on.js' {
+  declare module.exports: $Exports<'event-emitter/benchmark/single-on'>;
+}
+declare module 'event-emitter/emit-error.js' {
+  declare module.exports: $Exports<'event-emitter/emit-error'>;
+}
+declare module 'event-emitter/has-listeners.js' {
+  declare module.exports: $Exports<'event-emitter/has-listeners'>;
+}
+declare module 'event-emitter/index' {
+  declare module.exports: $Exports<'event-emitter'>;
+}
+declare module 'event-emitter/index.js' {
+  declare module.exports: $Exports<'event-emitter'>;
+}
+declare module 'event-emitter/pipe.js' {
+  declare module.exports: $Exports<'event-emitter/pipe'>;
+}
+declare module 'event-emitter/test/all-off.js' {
+  declare module.exports: $Exports<'event-emitter/test/all-off'>;
+}
+declare module 'event-emitter/test/emit-error.js' {
+  declare module.exports: $Exports<'event-emitter/test/emit-error'>;
+}
+declare module 'event-emitter/test/has-listeners.js' {
+  declare module.exports: $Exports<'event-emitter/test/has-listeners'>;
+}
+declare module 'event-emitter/test/index.js' {
+  declare module.exports: $Exports<'event-emitter/test/index'>;
+}
+declare module 'event-emitter/test/pipe.js' {
+  declare module.exports: $Exports<'event-emitter/test/pipe'>;
+}
+declare module 'event-emitter/test/unify.js' {
+  declare module.exports: $Exports<'event-emitter/test/unify'>;
+}
+declare module 'event-emitter/unify.js' {
+  declare module.exports: $Exports<'event-emitter/unify'>;
+}

+ 38 - 0
integration/solana/flow-typed/json-to-pretty-yaml.js

@@ -0,0 +1,38 @@
+// flow-typed signature: a65f8ee05f35bc382c3b0f8740bc609d
+// flow-typed version: <<STUB>>/json-to-pretty-yaml_v1.2.2/flow_v0.84.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ *   'json-to-pretty-yaml'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'json-to-pretty-yaml' {
+  declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'json-to-pretty-yaml/index.test' {
+  declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'json-to-pretty-yaml/index' {
+  declare module.exports: $Exports<'json-to-pretty-yaml'>;
+}
+declare module 'json-to-pretty-yaml/index.js' {
+  declare module.exports: $Exports<'json-to-pretty-yaml'>;
+}
+declare module 'json-to-pretty-yaml/index.test.js' {
+  declare module.exports: $Exports<'json-to-pretty-yaml/index.test'>;
+}

+ 32 - 0
integration/solana/flow-typed/mkdirp-promise_vx.x.x.js

@@ -0,0 +1,32 @@
+// flow-typed signature: 65e18196703cbb222ea294226e99826d
+// flow-typed version: <<STUB>>/mkdirp-promise_v5.0.1/flow_v0.84.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ *   'mkdirp-promise'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'mkdirp-promise' {
+  declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'mkdirp-promise/lib/index' {
+  declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'mkdirp-promise/lib/index.js' {
+  declare module.exports: $Exports<'mkdirp-promise/lib/index'>;
+}

+ 73 - 0
integration/solana/flow-typed/npm/mz_vx.x.x.js

@@ -0,0 +1,73 @@
+// flow-typed signature: ed29f42bf4f4916e4f3ba1f5e7343c9d
+// flow-typed version: <<STUB>>/mz_v2.7.0/flow_v0.81.0
+
+/**
+ * This is an autogenerated libdef stub for:
+ *
+ *   'mz'
+ *
+ * Fill this stub out by replacing all the `any` types.
+ *
+ * Once filled out, we encourage you to share your work with the
+ * community by sending a pull request to:
+ * https://github.com/flowtype/flow-typed
+ */
+
+declare module 'mz' {
+  declare module.exports: any;
+}
+
+/**
+ * We include stubs for each file inside this npm package in case you need to
+ * require those files directly. Feel free to delete any files that aren't
+ * needed.
+ */
+declare module 'mz/child_process' {
+  declare module.exports: any;
+}
+
+declare module 'mz/crypto' {
+  declare module.exports: any;
+}
+
+declare module 'mz/dns' {
+  declare module.exports: any;
+}
+
+declare module 'mz/fs' {
+  declare module.exports: any;
+}
+
+declare module 'mz/readline' {
+  declare module.exports: any;
+}
+
+declare module 'mz/zlib' {
+  declare module.exports: any;
+}
+
+// Filename aliases
+declare module 'mz/child_process.js' {
+  declare module.exports: $Exports<'mz/child_process'>;
+}
+declare module 'mz/crypto.js' {
+  declare module.exports: $Exports<'mz/crypto'>;
+}
+declare module 'mz/dns.js' {
+  declare module.exports: $Exports<'mz/dns'>;
+}
+declare module 'mz/fs.js' {
+  declare module.exports: $Exports<'mz/fs'>;
+}
+declare module 'mz/index' {
+  declare module.exports: $Exports<'mz'>;
+}
+declare module 'mz/index.js' {
+  declare module.exports: $Exports<'mz'>;
+}
+declare module 'mz/readline.js' {
+  declare module.exports: $Exports<'mz/readline'>;
+}
+declare module 'mz/zlib.js' {
+  declare module.exports: $Exports<'mz/zlib'>;
+}

+ 15 - 0
integration/solana/flow-typed/readline-promise.js

@@ -0,0 +1,15 @@
+declare module 'readline-promise' {
+
+  declare class ReadLine {
+    questionAsync(prompt: string): Promise<string>;
+    write(text: string): void;
+  }
+
+  declare module.exports: {
+    createInterface({
+      input: Object,
+      output: Object,
+      terminal: boolean
+    }): ReadLine;
+  }
+}

+ 3 - 0
integration/solana/flow-typed/semver.js

@@ -0,0 +1,3 @@
+declare module 'semver' {
+  declare module.exports: any;
+}

+ 9 - 0
integration/solana/hello_world.sol

@@ -0,0 +1,9 @@
+contract hello_world {
+	constructor() {
+		print("Hello from the constructor");
+	}
+
+	function test() public pure {
+		print("Hello from the test function");
+	}
+}

+ 86 - 0
integration/solana/package.json

@@ -0,0 +1,86 @@
+{
+  "name": "helloworld",
+  "version": "0.0.1",
+  "description": "",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/solana-labs/example-helloworld"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "MIT",
+  "testnetDefaultChannel": "v1.3.17",
+  "scripts": {
+    "start": "babel-node src/client/main.js",
+    "lint": "npm run pretty && eslint .",
+    "lint:fix": "npm run lint -- --fix",
+    "lint:watch": "watch 'npm run lint:fix' . --wait=1",
+    "flow": "flow",
+    "flow:watch": "watch 'flow' . --wait=1 --ignoreDirectoryPattern=/doc/",
+    "bpf-sdk:update": "solana-bpf-sdk-install node_modules/@solana/web3.js && npm run clean",
+    "clean:store": "rm -rf store",
+    "build:bpf-solang": "cargo run -- --target solana -v hello_world.sol",
+    "cluster:localnet": "npm run clean:store && rm -f .env",
+    "cluster:devnet": "npm run clean:store && cp cluster-devnet.env .env",
+    "cluster:testnet": "npm run clean:store && cp cluster-testnet.env .env",
+    "cluster:mainnet-beta": "npm run clean:store && cp cluster-mainnet-beta.env .env",
+    "localnet:update": "solana-localnet update",
+    "localnet:up": "set -x; solana-localnet down; set -e; solana-localnet up",
+    "localnet:down": "solana-localnet down",
+    "localnet:logs": "solana-localnet logs -f",
+    "pretty": "prettier --write '{,src/**/}*.js'",
+    "postinstall": "npm run build:bpf-solang"
+  },
+  "devDependencies": {
+    "prettier": "^2.0.2"
+  },
+  "dependencies": {
+    "@solana/web3.js": "^0.80.2",
+    "web3-eth-abi": "1.3.0",
+    "babel-cli": "^6.26.0",
+    "babel-core": "^6.26.3",
+    "babel-eslint": "^10.0.1",
+    "babel-loader": "^7.1.5",
+    "babel-plugin-transform-class-properties": "^6.24.1",
+    "babel-plugin-transform-function-bind": "^6.22.0",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "babel-preset-env": "^1.7.0",
+    "babel-preset-flow": "^6.23.0",
+    "babel-preset-react": "^6.24.1",
+    "babel-preset-stage-2": "^6.24.1",
+    "babel-runtime": "^6.26.0",
+    "bn.js": "^5.0.0",
+    "body-parser": "^1.18.3",
+    "buffer-layout": "^1.2.0",
+    "css-loader": "^3.1.0",
+    "dotenv": "8.2.0",
+    "eslint": "^6.1.0",
+    "eslint-loader": "^4.0.0",
+    "eslint-plugin-import": "^2.13.0",
+    "eslint-plugin-react": "^7.11.1",
+    "event-emitter": "^0.3.5",
+    "express": "^4.16.4",
+    "flow-bin": "0.122.0",
+    "flow-typed": "^3.0.0",
+    "http-server": "^0.12.3",
+    "jayson": "^3.0.1",
+    "json-to-pretty-yaml": "^1.2.2",
+    "mkdirp-promise": "^5.0.1",
+    "moment": "^2.22.2",
+    "mz": "^2.7.0",
+    "node-fetch": "^2.2.0",
+    "react": "^16.5.2",
+    "react-bootstrap": "^1.0.0",
+    "react-dom": "^16.5.2",
+    "readline-promise": "^1.0.3",
+    "semver": "^7.0.0",
+    "superstruct": "^0.8.0",
+    "watch": "^1.0.2",
+    "webpack": "^4.20.2",
+    "webpack-cli": "^3.1.1",
+    "webpack-dev-server": "^3.11.0"
+  },
+  "engines": {
+    "node": "11.x"
+  }
+}

+ 205 - 0
integration/solana/src/client/hello_world.js

@@ -0,0 +1,205 @@
+// @flow
+
+import {
+  Account,
+  Connection,
+  BpfLoader,
+  BPF_LOADER_PROGRAM_ID,
+  PublicKey,
+  LAMPORTS_PER_SOL,
+  SystemProgram,
+  TransactionInstruction,
+  Transaction,
+} from '@solana/web3.js';
+import fs from 'mz/fs';
+import * as BufferLayout from 'buffer-layout';
+
+import { url, urlTls } from '../../url';
+import { Store } from './util/store';
+import { newAccountWithLamports } from './util/new-account-with-lamports';
+import { sendAndConfirmTransaction } from './util/send-and-confirm-transaction';
+import AbiCoder from 'web3-eth-abi';
+
+/**
+ * Connection to the network
+ */
+let connection: Connection;
+
+/**
+ * Connection to the network
+ */
+let payerAccount: Account;
+
+/**
+ * Hello world's program id
+ */
+let programId: PublicKey;
+
+/**
+ * The public key of the account we are saying hello to
+ */
+let greetedPubkey: PublicKey;
+
+const pathToProgram = 'hello_world.so';
+
+/**
+ * Layout of the greeted account data
+ */
+const greetedAccountDataLayout = BufferLayout.struct([
+  BufferLayout.u32('numGreets'),
+]);
+
+/**
+ * Establish a connection to the cluster
+ */
+export async function establishConnection(): Promise<void> {
+  connection = new Connection(url, 'recent');
+  const version = await connection.getVersion();
+  console.log('Connection to cluster established:', url, version);
+}
+
+/**
+ * Establish an account to pay for everything
+ */
+export async function establishPayer(): Promise<void> {
+  if (!payerAccount) {
+    let fees = 0;
+    const { feeCalculator } = await connection.getRecentBlockhash();
+
+    // Calculate the cost to load the program
+    const data = await fs.readFile(pathToProgram);
+    const NUM_RETRIES = 500; // allow some number of retries
+    fees +=
+      feeCalculator.lamportsPerSignature *
+      (BpfLoader.getMinNumSignatures(data.length) + NUM_RETRIES) +
+      (await connection.getMinimumBalanceForRentExemption(data.length));
+
+    // Calculate the cost to fund the greeter account
+    fees += await await connection.getMinimumBalanceForRentExemption(
+      greetedAccountDataLayout.span,
+    );
+
+    // Calculate the cost of sending the transactions
+    fees += feeCalculator.lamportsPerSignature * 100; // wag
+
+    // Fund a new payer via airdrop
+    payerAccount = await newAccountWithLamports(connection, fees);
+  }
+
+  const lamports = await connection.getBalance(payerAccount.publicKey);
+  console.log(
+    'Using account',
+    payerAccount.publicKey.toBase58(),
+    'containing',
+    lamports / LAMPORTS_PER_SOL,
+    'Sol to pay for fees',
+  );
+}
+
+/**
+ * Load the hello world BPF program if not already loaded
+ */
+export async function loadProgram(): Promise<void> {
+  const store = new Store();
+
+  // Check if the program has already been loaded
+  try {
+    let config = await store.load('config.json');
+    programId = new PublicKey(config.programId);
+    greetedPubkey = new PublicKey(config.greetedPubkey);
+    await connection.getAccountInfo(programId);
+    console.log('Program already loaded to account', programId.toBase58());
+    return;
+  } catch (err) {
+    // try to load the program
+  }
+
+  // Load the program
+  console.log('Loading hello world program...');
+  const data = await fs.readFile(pathToProgram);
+  const programAccount = new Account();
+  await BpfLoader.load(
+    connection,
+    payerAccount,
+    programAccount,
+    data,
+    BPF_LOADER_PROGRAM_ID,
+  );
+  programId = programAccount.publicKey;
+  console.log('Program loaded to account', programId.toBase58());
+
+  // Create the greeted account
+  const greetedAccount = new Account();
+  greetedPubkey = greetedAccount.publicKey;
+  console.log('Creating account', greetedPubkey.toBase58(), 'to say hello to');
+  const space = greetedAccountDataLayout.span;
+  const lamports = await connection.getMinimumBalanceForRentExemption(
+    greetedAccountDataLayout.span,
+  );
+  const transaction = new Transaction().add(
+    SystemProgram.createAccount({
+      fromPubkey: payerAccount.publicKey,
+      newAccountPubkey: greetedPubkey,
+      lamports,
+      space,
+      programId,
+    }),
+  );
+  await sendAndConfirmTransaction(
+    'createAccount',
+    connection,
+    transaction,
+    payerAccount,
+    greetedAccount,
+  );
+
+  // Save this info for next time
+  await store.save('config.json', {
+    url: urlTls,
+    programId: programId.toBase58(),
+    greetedPubkey: greetedPubkey.toBase58(),
+  });
+}
+
+/**
+ * Call constructor
+ */
+export async function callConstructor(): Promise<void> {
+  console.log('Calling constructor', greetedPubkey.toBase58());
+
+  const constructor_input = AbiCoder.encodeParameters([], []);
+
+  const instruction = new TransactionInstruction({
+    keys: [{ pubkey: greetedPubkey, isSigner: false, isWritable: true }],
+    programId,
+    data: Buffer.from(constructor_input, 'hex'),
+  });
+  await sendAndConfirmTransaction(
+    'callConstructor',
+    connection,
+    new Transaction().add(instruction),
+    payerAccount,
+  );
+}
+
+/**
+ * Call test function
+ */
+export async function callTest(): Promise<void> {
+  console.log('Calling function test', greetedPubkey.toBase58());
+
+  const hello_world_abi = JSON.parse(await fs.readFile('hello_world.abi'));
+  const constructor_input = AbiCoder.encodeFunctionCall(hello_world_abi.find(e => e.name == 'test'), []);
+
+  const instruction = new TransactionInstruction({
+    keys: [{ pubkey: greetedPubkey, isSigner: false, isWritable: true }],
+    programId,
+    data: Buffer.from(constructor_input, 'hex'),
+  });
+  await sendAndConfirmTransaction(
+    'callTestFunction',
+    connection,
+    new Transaction().add(instruction),
+    payerAccount,
+  );
+}

+ 40 - 0
integration/solana/src/client/main.js

@@ -0,0 +1,40 @@
+/**
+ * Hello world
+ *
+ * @flow
+ */
+
+import {
+  establishConnection,
+  establishPayer,
+  loadProgram,
+  sayHello,
+  reportHellos,
+  callConstructor,
+  callTest,
+} from './hello_world';
+
+async function main() {
+  console.log("Let's say hello to a Solana account...");
+
+  // Establish connection to the cluster
+  await establishConnection();
+
+  // Determine who pays for the fees
+  await establishPayer();
+
+  // Load the program if not already loaded
+  await loadProgram();
+
+  await callConstructor();
+  await callTest();
+
+  console.log('Success');
+}
+
+main()
+  .catch(err => {
+    console.error(err);
+    process.exit(1);
+  })
+  .then(() => process.exit());

+ 26 - 0
integration/solana/src/client/util/new-account-with-lamports.js

@@ -0,0 +1,26 @@
+// @flow
+
+import {Account, Connection} from '@solana/web3.js';
+
+import {sleep} from './sleep';
+
+export async function newAccountWithLamports(
+  connection: Connection,
+  lamports: number = 1000000,
+): Promise<Account> {
+  const account = new Account();
+
+  let retries = 10;
+  await connection.requestAirdrop(account.publicKey, lamports);
+  for (;;) {
+    await sleep(500);
+    if (lamports == (await connection.getBalance(account.publicKey))) {
+      return account;
+    }
+    if (--retries <= 0) {
+      break;
+    }
+    console.log('Airdrop retry ' + retries);
+  }
+  throw new Error(`Airdrop of ${lamports} failed`);
+}

+ 17 - 0
integration/solana/src/client/util/new-system-account-with-airdrop.js

@@ -0,0 +1,17 @@
+// @flow
+
+import {Account, Connection} from '@solana/web3.js';
+
+/**
+ * Create a new system account and airdrop it some lamports
+ *
+ * @private
+ */
+export async function newSystemAccountWithAirdrop(
+  connection: Connection,
+  lamports: number = 1,
+): Promise<Account> {
+  const account = new Account();
+  await connection.requestAirdrop(account.publicKey, lamports);
+  return account;
+}

+ 47 - 0
integration/solana/src/client/util/send-and-confirm-transaction.js

@@ -0,0 +1,47 @@
+// @flow
+
+import {sendAndConfirmTransaction as realSendAndConfirmTransaction} from '@solana/web3.js';
+import type {Account, Connection, Transaction} from '@solana/web3.js';
+import YAML from 'json-to-pretty-yaml';
+
+type TransactionNotification = (string, string) => void;
+
+let notify: TransactionNotification = () => undefined;
+
+export function onTransaction(callback: TransactionNotification) {
+  notify = callback;
+}
+
+export async function sendAndConfirmTransaction(
+  title: string,
+  connection: Connection,
+  transaction: Transaction,
+  ...signers: Array<Account>
+): Promise<void> {
+  const when = Date.now();
+
+  const signature = await realSendAndConfirmTransaction(
+    connection,
+    transaction,
+    signers,
+    {
+      skipPreflight: true,
+      commitment: 'recent',
+      preflightCommitment: null,
+    },
+  );
+
+  const body = {
+    time: new Date(when).toString(),
+    signature,
+    instructions: transaction.instructions.map(i => {
+      return {
+        keys: i.keys.map(keyObj => keyObj.pubkey.toBase58()),
+        programId: i.programId.toBase58(),
+        data: '0x' + i.data.toString('hex'),
+      };
+    }),
+  };
+
+  notify(title, YAML.stringify(body).replace(/"/g, ''));
+}

+ 6 - 0
integration/solana/src/client/util/sleep.js

@@ -0,0 +1,6 @@
+// @flow
+
+// zzz
+export function sleep(ms: number): Promise<void> {
+  return new Promise(resolve => setTimeout(resolve, ms));
+}

+ 26 - 0
integration/solana/src/client/util/store.js

@@ -0,0 +1,26 @@
+/**
+ * Simple file-based datastore
+ *
+ * @flow
+ */
+
+import path from 'path';
+import fs from 'mz/fs';
+import mkdirp from 'mkdirp-promise';
+
+export class Store {
+  dir = path.join(__dirname, '../../../store');
+
+  async load(uri: string): Promise<Object> {
+    const filename = path.join(this.dir, uri);
+    const data = await fs.readFile(filename, 'utf8');
+    const config = JSON.parse(data);
+    return config;
+  }
+
+  async save(uri: string, config: Object): Promise<void> {
+    await mkdirp(this.dir);
+    const filename = path.join(this.dir, uri);
+    await fs.writeFile(filename, JSON.stringify(config), 'utf8');
+  }
+}

+ 31 - 0
integration/solana/url.js

@@ -0,0 +1,31 @@
+// To connect to a public cluster, set `export LIVE=1` in your
+// environment. By default, `LIVE=1` will connect to the devnet cluster.
+
+import {clusterApiUrl, Cluster} from '@solana/web3.js';
+import dotenv from 'dotenv';
+
+function chooseCluster(): Cluster | undefined {
+  dotenv.config();
+  if (!process.env.LIVE) return;
+  switch (process.env.CLUSTER) {
+    case 'devnet':
+    case 'testnet':
+    case 'mainnet-beta': {
+      return process.env.CLUSTER;
+    }
+  }
+  throw 'Unknown cluster "' + process.env.CLUSTER + '", check the .env file';
+}
+
+export const cluster = chooseCluster();
+
+export const url =
+  process.env.RPC_URL ||
+  (process.env.LIVE ? clusterApiUrl(cluster, false) : 'http://localhost:8899');
+
+export const urlTls =
+  process.env.RPC_URL ||
+  (process.env.LIVE ? clusterApiUrl(cluster, true) : 'http://localhost:8899');
+
+export let walletUrl =
+  process.env.WALLET_URL || 'https://solana-example-webwallet.herokuapp.com/';