Near Protocol Chainlink External Adapter
This service is used by a Chainlink node as an external adapter for writing to NEAR Blockchain.
The external adapter allows you to configure an endpoint, account and private key to sign and send transactions.
Prerequisite
Yarn v1.22+: You will need to have Yarn v1.22+ installed locally.
or..
Node v12+: n is a great interactive manager for your Node.js versions.
Install
If using Yarn v1.22+ (default), or when switching between versions (from v1.22+ -> v2, and back), please run:
yarn install
Set up NEAR account
Prerequisite
NEAR account: Please create one with NEAR Wallet.
NEAR CLI: Please install it following the NEAR docs instructions.
NEAR login
In the project root, login with near-cli
by following the instructions after this command:
near login
This step is required to get to the NEAR account private key which can be found at ~/.near-credentials/default
/${ACCOUNT_ID}.json
:
{ “account_id”: “${ACCOUNT_ID}”, “private_key”: “ed25519:…” }
Start
Supported environment variables:
ACCOUNT_ID: NEAR account that this service will use
PRIVATE_KEY: NEAR account private key
PORT: (optional) defaults to 3000
NODE_ENV: (optional) one of [production|mainnet|development|testnet|devnet|betanet|local]
, defaults to development
NETWORK_ID: (optional) custom network id, NODE_URL
must also be set
NODE_URL: (optional) custom node url, NETWORK_ID
must also be set
Set the required environment, and run from the project root:
yarn start
Alternatively you can set the environment inline:
env \
ACCOUNT_ID=dummy.testnet \ PRIVATE_KEY=ed25519:3Zo9bWRC7vUoDHMaXdMd6osajUktbgGWxL3P89QxR8VguVPnFa7BXd5brw6tBa6RASn8YCVjPgkhpujnorCF7FR2 \ NODE_ENV=testnet \
yarn start
Or for a custom connection:
env \ ACCOUNT_ID=dummy.acmenet \ PRIVATE_KEY=ed25519:3Zo9bWRC7vUoDHMaXdMd6osajUktbgGWxL3P89QxR8VguVPnFa7BXd5brw6tBa6RASn8YCVjPgkhpujnorCF7FR2 \ NETWORK_ID=acmenet \
NODE_URL=https://rpc.acmenet.acme.org \
yarn start
This service can be started as a standalone service or as a Chainlink node adapter.
API
The standalone service exposes all endpoints discussed next, while the service started as a Chainlink node adapter exposes just one endpoint POST /call
, but it’s mapped as root endpoint POST /
.
HTTP GET /
endpoint
Read NEAR network connection status.
Output:
{ “chain_id”: “testnet”, “latest_protocol_version”: 22, “protocol_version”: 22, “rpc_addr”: “0.0.0.0:3030”, “sync_info”: { “latest_block_hash”: “7nWiri3qx9G2h2qG2KCX9N2ChDPDgWjgzo5V31HTMxxf”, “latest_block_height”: 7626572, “latest_block_time”: “2020-06-25T10:36:40.696884241Z”, “latest_state_root”: “FMwXSxgkgvtxdMZaHMrsLsZVKcxDJi2mBk9nbrz4CxWN”, “syncing”: false }, “validators”: [ { “account_id”: “bisontrails.stakingpool”, “is_slashed”: false }, … // more validators ], “version”: { “build”: “ebe21b33”, “version”: “1.0.0” } }
HTTP GET /account
endpoint
Read configured NEAR account.
Output:
{ “accessKey”: “ed25519:6J9NoFmr4mzBLch3tzKDfetY4YVNGQG74urLHeUDfCcw”, “accountId”: “krebernisak_1.testnet” }
HTTP GET /view
endpoint
Read contract state from NEAR network.
Input:contractId
: account where the contract is deployedmethodName
: function name to call on the contractargs
: (optional) function arguments
{ “contractId”: “counter.testnet”, “methodName”: “getCounter” }
Output:
{ “data”: …, // View function output data “statusCode”: 200 }
HTTP POST /call
endpoint
Available as POST /
when started as Chainlink node adapter
Send transaction and write state to the NEAR network.
Input:contractId
: account where the contract is deployedmethodName
: function name to call on the contractargs
: (optional) function argumentsgas
: (optional) gas amount allocated for transaction executionamount
: (optional) amount of NEAR sent with the transaction
{ “contractId”: “counter.testnet”, “methodName”: “decrementCounter”, “args”: { “value”: 13 }, “gas”: 5000000000000, “amount”: 50 }
Output:
{ “data”: { … // Transaction result data }, “statusCode”: 200 }
Errors
API will return a HTTP 400
for client errors:
{ “message”: “Missing required fields: contractId or methodName”, “status”: “BadRequest”, “statusCode”: 400, “type”: “UntypedError” }
Or HTTP 500
for server errors:
{ “message”: “Exceeded the prepaid gas”, “status”: “Error”, “statusCode”: 500, “type”: “GasExceeded” }
Test
Unit tests
Run from the project root:
yarn test
Integration tests
Step 1: Deploy contract to NEAR network
For this test we are going to use NEAR counter example deployed to @counter.testnet
.
Please follow their instructions to:
Set up a new NEAR account where the contract will be deployed
Login to NEAR CLI with your new account
Deploy the contract
Step 2: Interact with contracts
To interact with the contracts, we are going to use HTTPie, a user-friendly command-line HTTP client, as an alternative to CURL.
Make sure the counter contract is deployed and the adapter service is started.
Read contract state
Use the /view
endpoint to read contract state:
echo ‘{“contractId”: “counter.testnet”, “methodName”: “getCounter”}’ | http GET :3000/view
Example output:
{ “data”: 0, “statusCode”: 200 }
Write contract state
Use the /call
endpoint to write contract state:
echo ‘{“contractId”: “counter.testnet”, “methodName”: “incrementCounter”, “args”: {“value”: 4}}’ | http POST :3000/call
The /call
endpoint also lets you control the gas
and amount
sent to the contract:
echo ‘{“contractId”: “counter.testnet”, “methodName”: “decrementCounter”, “args”: {“value”: 13}, “gas”: 5000000000000, “amount”: 50}’ | http POST :3000/call
Example output:
{ “data”: { “receipts_outcome”: [ { “block_hash”: “9fdLpAdoPKoxwETaycVvwT5Lm2zy28fLGPKj2f4pkAVP”, “id”: “DeJ1GKwsRTfW63DBJBnAZPC4T8mnVgkkpvconBtfsWzM”, “outcome”: { “gas_burnt”: 2757914274712, “logs”: [“Counter is now: -9”], “receipt_ids”: [“FeGwwskgZ95NQ6Pg9z745uq8sRJyyw83xYEXfJP4qoCe”], “status”: { “SuccessValue”: “” } }, “proof”: [] }, { “block_hash”: “HEs6d5HkTCojADaw2DHJgXMEBGNkn3GuLfNDVjWqV3Qx”, “id”: “FeGwwskgZ95NQ6Pg9z745uq8sRJyyw83xYEXfJP4qoCe”, “outcome”: { “gas_burnt”: 0, “logs”: [], “receipt_ids”: [], “status”: { “SuccessValue”: “” } }, “proof”: [] } ], “status”: { “SuccessValue”: “” }, “transaction”: { “actions”: [ { “FunctionCall”: { “args”: “eyJ2YWx1ZSI6MTN9”, “deposit”: “50”, “gas”: 5000000000000, “method_name”: “decrementCounter” } } ], “hash”: “EGMf4L6YvinLpjeCbfVVVXEPcfRMZ2HrvcHbDfiNiRMN”, “nonce”: 37, “public_key”: “ed25519:6J9NoFmr4mzBLch3tzKDfetY4YVNGQG74urLHeUDfCcw”, “receiver_id”: “counter.testnet”, “signature”: “ed25519:21zN5PZKVueb79oUwKMkhshrwqw7uGzh7vgGCqkr8K1CF5XL6G6NtciYyU4xochHFXm4WY3pcHACPfnKUXSh7EvU”, “signer_id”: “krebernisak_1.testnet” },
“transaction_outcome”: { “block_hash”: “GDZGu1P2Ff983WgiVuDkMVULZs5ED85L99TJaQE62pyL”,
“id”: “EGMf4L6YvinLpjeCbfVVVXEPcfRMZ2HrvcHbDfiNiRMN”, “outcome”: { “gas_burnt”: 2427983606152, “logs”: [], “receipt_ids”: [“DeJ1GKwsRTfW63DBJBnAZPC4T8mnVgkkpvconBtfsWzM”], “status”: { “SuccessReceiptId”: “DeJ1GKwsRTfW63DBJBnAZPC4T8mnVgkkpvconBtfsWzM” } }, “proof”: [] } }, “statusCode”: 200 }
Chainlink Node Integration
The Chainlink node expects a specific adapter API – we only expose the call function (send tx) on /
endpoint.
Chainlink adapter requires and supports additional environment variables:
CONTRACT_ID: NEAR account where the contract is deployed
METHOD_NAME: Method name that will be called by the adapter
GAS: (optional) Gas sent with the transaction, defaults to 300000000000000
AMOUNT: (optional) Amount sent with the transaction, defaults to 0
To start the service as Chainlink node adapter:
env \
NODE_ENV=testnet \
PORT=3000 \
ACCOUNT_ID=dummy.testnet \ PRIVATE_KEY=ed25519:3Zo9bWRC7vUoDHMaXdMd6osajUktbgGWxL3P89QxR8VguVPnFa7BXd5brw6tBa6RASn8YCVjPgkhpujnorCF7FR2 \ CONTRACT_ID=oracle.oracle.testnet \ METHOD_NAME=fulfill_request \
yarn start:adapter
In this configuration the /
endpoint connects directly to NEAR oracle contract, and expects a slightly different input.
We need to include the job spec id:
{ “id”: 1, // job spec id “data”: {…} // input to our call function (specified in the env) }
Example for adapter set up to call the fulfill_request
function:
echo ‘{“id”: 1, “data”: {“nonce”:”1″, “account”: “client.oracle.testnet”, “value”: 17}}’ | http POST :3000/
Output:
{ “data”: { “result”: “6yTpkCW3UqVtpGj766Wvr73zEambzL2n44CDYiB4TpVP” // tx hash }, “jobRunID”: 1, “result”: “6yTpkCW3UqVtpGj766Wvr73zEambzL2n44CDYiB4TpVP”, // tx hash “statusCode”: 200 }
Deployment
To deploy a container, build the Docker image:
docker build -t near-protocol-adapter .
Run the Docker container:
docker run -d \
–name near-protocol-adapter \
-p 3000:3000 \ -e NODE_ENV=testnet \
-e PORT=3000 \
-e ACCOUNT_ID=dummy.testnet \
-e PRIVATE_KEY=ed25519:3Zo9bWRC7vUoDHMaXdMd6osajUktbgGWxL3P89QxR8VguVPnFa7BXd5brw6tBa6RASn8YCVjPgkhpujnorCF7FR2 \
-e CONTRACT_ID=oracle.oracle.testnet \
-e METHOD_NAME=fulfill_request \
near-protocol-adapter