mchardysam / binance-chain-signing-service

Signing Service for Binance Chain
MIT License
8 stars 5 forks source link

=============================================== Welcome to Binance Chain Signing Service v0.0.4

Features

Read the Changelog <changelog.rst>_

Test Service

View the OpenAPI docs here binance-signing-service.xyz <https://binance-signing-service.xyz/docs>_

Import the OpenAPI schema to Postman from here binance-signing-service.xyz <https://binance-signing-service.xyz/api/openapi.json>_

Use https://binance-signing-service.xyz as the endpoint to communicate if you're using the python-binance-chain <https://github.com/sammchardy/python-binance-chain/>_ python client library.

Login credentials are

Configuration

Modify the config/config.yml configuration file to include the wallets and users you need.

The configuration has 3 sections

General

.. code:: yaml

# amount of minutes access tokens are valid, set to a large number if a long expiry is needed
access_token_expiry_minutes: 10080
# secret key to encode tokens, generate with a bcrypt tool
secret_key: <bcrypt_hash>

Wallets

.. code:: yaml

wallets:
  # the private key for this wallet
  - private_key: <private_key>
    # name of the wallet, this is used in requests to the signing service
    name: wallet_1
    # specify the environment to use for this wallet
    env_name: TESTNET  # or PROD
    ip_whitelist:  # optional section
      - 127.0.0.1
      - 10.0.0.1
    permissions:  # limit of permissions that users may be able to perform on this
      - trade
      - transfer

  # initialise wallet with mnemonic
  - mnemonic: '<mnemonic word string>'
    env_name: TESTNET
    name: wallet_2
    permissions:
      - transfer
      - freeze

# add other wallets as needed

Users

.. code:: yaml

users:
  - username: sam
    password_hash: <bcrypt_password_hash> # generate with online tool or command line

    # list of wallet permissions this user has
    wallet_permissions:
      # the wallet name from the wallets list above
      - wallet_name: wallet_1
        # perissions here are a subset of the wallet permissions
        permissions:
          - trade
          - transfer
      - wallet_name: wallet_2
        permissions:
          - transfer

If the user has trade permission but the wallet doesn't, then the wallet permission denies trade access.

Permissions

trade - allow order create and canceld transfer - allow the transfer of funds from one account to another freeze - allow freezing and unfreezing tokens resync - allow resynchronising sequence info for the wallet

Wallets can have any combination of permissions to restrict access per wallet and per user.

Combined with multiple users you have the most flexibility in how accounts are accessed and used.

Bcrypt Generation

Some parts of the config require password hashes or just random strings to keep things secure.

Try Bcrypt-Generator.com <https://bcrypt-generator.com/>_ or the command line if you're more advance.

Running the server locally

This requires python 3.6+ and this setup

.. code:: bash

# create an environment to use
python3 -v venv .venv
source .venv/bin/activate

# install the requirements
pip install -r app/requirements.txt

Run the server

.. code:: bash

cd app

uvicorn main:app --reload

If having issues with secp256k1 check the Installation instructions for the sec256k1-py library <https://github.com/ludbb/secp256k1-py#installation>_

Running the server with Docker

There is a sample Dockerfile available based on the tiangolo/uvicorn-gunicorn-fastapi <https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker> container. See the container docs <https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker> for more configuration options.

The /app and /config directories are copied into the container.

To run it in Docker, build and run the container. Feel free to change bdex-sign and bdex-sign-c to your own image and container names.

.. code:: bash

docker build -t bdex-sign ./
docker run -d --name bdex-sign-c -p 8001:80 bdex-sign

To check the log output

.. code:: bash

docker logs bdex-sign-c

To stop the container

.. code:: bash

docker stop bdex-sign-c

Finally to remove the container

.. code:: bash

docker rm bdex-sign-c

Running the server more securely with Docker

I would recommend using the container with Traefik <https://github.com/tiangolo/medium-posts/tree/master/docker-swarm-mode-and-traefik-for-a-https-cluster>_ to include Let's Encrypt support to serve content over HTTPS.

By running in an environment like AWS using ECS, one could point API Gateway to the instance and define IP whitelisting in this way.

Terraform

A Terraform config for running the container in AWS Fargate with an ALB can be found in the terraform directory.

After pushing your build docker container to ECR you are nearly ready to go.

To do that

.. code:: bash

docker tag bdex-sign <account_id>.dkr.ecr.us-east-1.amazonaws.com/bdex-sign

.. code:: bash

docker push <account_id>.dkr.ecr.us-east-1.amazonaws.com/bdex-sign

Update terraform/variables.tf and fill in your aws_profile, container location and ecs_task_execution_role (it will look something like arn:aws:iam:::role/ecsTaskExecutionRole.

Now initialise terraform

.. code:: bash

terraform init terraform/

Then apply the terraform plan

.. code:: bash

terraform apply terraform/

This will output the URL to access your signing service.

To delete at any time

.. code:: bash

terraform destroy terraform/

Authentication

POST /api/auth/login

Pass username and password payload to the endpoint to generate a JWT token to use for subsequent requests.

By default tokens expire after 7 days, this can be changed in the config.yml.

Request

.. code:: json

{
    "username": "sambot",
    "password": "don'tforgetthis"
}

Response

.. code:: json

{
    "access_token": "eyJ0eXAiOiJKV1Qi....",
    "token_type": "bearer"
}

Message Interaction

All other endpoints require JWT token for authentication. Add this as a request header.

.. code:: yaml

Authorization: Bearer <access_token>

POST /api/order/sign

Sign a new order message object and return the hash

Requires permission - trade

Request

.. code:: json

{
    "msg": {
        "order_type": "LIMIT",
        "price": 0.000396,
        "quantity": 10,
        "side": "buy",
        "symbol": "ANN-457_BNB",
        "time_in_force": "GTE"
    },
    "wallet_name": "wallet_1"
}

Response

.. code:: json

{
    "signed_msg": "de01f0625dee0a6..."
}

POST /api/order/broadcast

Sign a new order message object and return the exchanges response

Requires permission - trade

Request

Same as /api/order/sign

Response

Is the response from the Binance Chain exchange

POST /api/cancel_order/sign

Sign a cancel order message object and return the hash

Requires permission - trade

Request

.. code:: json

{
    "msg": {
        "order_id": "<order_id>",
        "symbol": "ANN-457_BNB"
    },
    "wallet_name": "wallet_1"
}

Response

.. code:: json

{
    "signed_msg": "de01f0625dee0a6..."
}

POST /api/order/broadcast

Requires permission - trade

Sign a cancel order message object and return the exchanges response

Request

Same as /api/cancel_order/sign

Response

Is the response from the Binance Chain exchange

POST /api/transfer/sign

Requires permission - transfer

Sign a transfer message object and return the hash

Request

.. code:: json

{
    "msg": {
        "symbol": "BNB",
        "amount": 1,
        "to_address": "<to address>"
    },
    "wallet_name": "wallet_1"
}

Response

.. code:: json

{
    "signed_msg": "de01f0625dee0a6..."
}

POST /api/transfer/broadcast

Requires permission - transfer Sign a transfer message object and return the exchanges response

Request

Same as /api/transfer/sign

Response

Is the response from the Binance Chain exchange

POST /api/freeze/sign

Requires permission - freeze

Sign a freeze message object and return the hash

Request

.. code:: json

{
    "msg": {
        "symbol": "BNB",
        "amount": 1,
    },
    "wallet_name": "wallet_1"
}

Response

.. code:: json

{
    "signed_msg": "de01f0625dee0a6..."
}

POST /api/freeze/broadcast

Sign a transfer message object and return the exchanges response

Requires permission - freeze

Request

Same as /api/freeze/sign

Response

Is the response from the Binance Chain exchange

POST /api/unfreeze/sign

Sign an unfreeze message object and return the hash

Requires permission - freeze

Request

.. code:: json

{
    "msg": {
        "symbol": "BNB",
        "amount": 1,
    },
    "wallet_name": "wallet_1"
}

Response

.. code:: json

{
    "signed_msg": "de01f0625dee0a6..."
}

POST /api/unfreeze/broadcast

Sign an unfreeze message object and return the exchanges response

Requires permission - freeze

Request

Same as /api/unfreeze/sign

Response

Is the response from the Binance Chain exchange

Wallet Interaction

POST /api/wallet/resync

Resynchronise the wallet on the signing service. This can happen if the sequence gets out of order.

Requires permission - resync

Request

.. code:: json

{
    "wallet_name": "wallet_1"
}

Response

.. code:: json

{}

GET /api/wallet

Fetch all wallet info the currently authorised user has access to

Requires permission - none

Response

.. code:: json

[
    {
        "name": "wallet_1",
            "permissions": [
            "transfer",
            "trade"
        ],
        "env": "TESTNET",
        "address": "tbnb10a6kkxlf823w9lwr6l9hzw4uyphcw7qzrud5rr",
        "public_key": "02cce2ee4e37dc8c65d6445c966faf31ebfe578a90695138947ee7cab8ae9a2c08"
    },
    {
        "name": "wallet_2",
        "permissions": [
            "transfer"
        ],
        "env": "TESTNET",
        "address": "tbnb10a6kkxlf823w9lwr6l9hzw4uyphcw7qzrud5rr",
        "public_key": "02cce2ee4e37dc8c65d6445c966faf31ebfe578a90695138947ee7cab8ae9a2c08"
    }
]

GET /api/wallet/{wallet_name}

Fetch wallet info for the named wallet and the currently authorised user

Requires permission - none

Response

.. code:: json

{
    "name": "wallet_1",
        "permissions": [
        "transfer",
        "trade"
    ],
    "env": "TESTNET",
    "address": "tbnb10a6kkxlf823w9lwr6l9hzw4uyphcw7qzrud5rr",
    "public_key": "02cce2ee4e37dc8c65d6445c966faf31ebfe578a90695138947ee7cab8ae9a2c08"
}

Docs & OpenAPI

/docs

View the OpenAPI docs for this service and interact with it.

/redoc

View the docs in Redoc format

/api/openapi.json

Retrieve the OpenAPI JSON Schema for this service.

You can also import this directly into Postman <https://www.getpostman.com/>_

Using python-binance-chain

python-binance-chain <https://github.com/sammchardy/python-binance-chain/>_ has been updated to include this signing service interface as an option to process messages

Initialise the client to interact with your signing service

.. code:: python

from binance_chain.signing.http import HttpSigningClient
from binance_chain.messages import NewOrderMsg

signing_client = HttpSigningClient(url="http://localhost:8000", username="username", password="password")

# create the message object
new_order_msg = NewOrderMsg(
    symbol='ANN-457_BNB',
    order_type=OrderType.LIMIT,
    side=OrderSide.BUY,
    price=0.000396000,
    quantity=10,
    time_in_force=TimeInForce.GOOD_TILL_EXPIRE
)

# get hex data for a message
new_order_hex = signing_client.sign_order(new_order_msg, wallet_name='wallet_1')

# broadcast a message directly
new_order_res = signing_client.broadcast_order(new_order_msg, wallet_name='wallet_1')