AvalancheJS - The Avalanche Platform JavaScript Library
Deprecation of avalancheJS
The npm package https://www.npmjs.com/package/avalanche
is being deprecated. For the latest version please use @avalabs/avalanchejs. We will no longer support the avalanche npm package.
Overview
AvalancheJS is a JavaScript Library for interfacing with the Avalanche Platform. It is built using TypeScript and intended to support both browser and Node.js. The AvalancheJS library allows you to issue commands to the Avalanche node APIs.
The APIs currently supported by default are:
- Admin API
- Auth API
- AVM API (X-Chain)
- EVM API (C-Chain)
- Health API
- Index API
- Info API
- Keystore API
- Metrics API
- PlatformVM API (P-Chain)
- Socket
We built AvalancheJS with ease of use in mind. With this library, any Javascript developer is able to interact with a node on the Avalanche Platform who has enabled their API endpoints for the developer's consumption. We keep the library up-to-date with the latest changes in the Avalanche Platform Specification.
Using AvalancheJS, developers can:
- Locally manage private keys
- Retrieve balances on addresses
- Get UTXOs for addresses
- Build and sign transactions
- Issue signed transactions to the X-Chain, P-Chain, and C-Chain
- Perform cross-chain swaps between the X-Chain<->P-Chain and between the X-Chain<->C-Chain
- Add Validators and Delegators to the Primary Subnetwork by staking AVAX
- Create a Subnetwork
- Administer a local node
- Retrieve Avalanche network information from a node
Requirements
AvalancheJS requires Node.js LTS version 14.16.0 or higher to compile.
Installation
Avalanche is available for install via yarn
:
yarn add avalanche
You can also pull the repo down directly and build it from scratch:
yarn build
This will generate a pure Javascript library and place it in a folder named "web" in the project root. The "avalanche.js" file can then be dropped into any project as a pure javascript implementation of Avalanche.
The AvalancheJS library can be imported into your existing Node.js project as follows:
const avalanche = require("avalanche")
Or into your TypeScript project like this:
import { Avalanche } from "avalanche"
Importing essentials
import { Avalanche, BinTools, BN, Buffer } from "avalanche"
const bintools = BinTools.getInstance()
The above lines import the libraries used in the tutorials. The libraries include:
- Avalanche: Our javascript module.
- BinTools: A singleton built into AvalancheJS that is used for dealing with binary data.
- BN: A bignumber module use by AvalancheJS.
- Buffer: A Buffer library.
Example 1 — Managing X-Chain Keys
AvalancheJS comes with its own AVM Keychain. This KeyChain is used in the functions of the API, enabling them to sign using keys it's registered. The first step in this process is to create an instance of AvalancheJS connected to our Avalanche Platform endpoint of choice.
import { Avalanche, BinTools, Buffer, BN } from "avalanche"
const bintools = BinTools.getInstance()
const myNetworkID = 12345
const avalanche = new Avalanche("localhost", 9650, "http", myNetworkID)
const xchain = avalanche.XChain()
Accessing the KeyChain
The KeyChain is accessed through the X-Chain and can be referenced directly or through a reference variable.
const myKeychain = xchain.keyChain()
This exposes the instance of the class AVMKeyChain which is created when the X-Chain API is created. At present, this supports secp256k1 curve for ECDSA key pairs.
Creating X-Chain key pairs
The KeyChain has the ability to create new KeyPairs for you and return the address associated with the key pair.
const newAddress1 = myKeychain.makeKey()
You may also import your existing private key into the KeyChain using either a Buffer...
const mypk = bintools.cb58Decode(
"JaCCSxdoWfo3ao5KwenXrJjJR7cBTQ287G1C5qpv2hr2tCCdb"
)
const newAddress2 = myKeychain.importKey(mypk)
... or an CB58 string works, too:
const mypk = "PrivateKey-JaCCSxdoWfo3ao5KwenXrJjJR7cBTQ287G1C5qpv2hr2tCCdb"
const newAddress2 = myKeychain.importKey(mypk)
Working with KeyChains
The X-Chains's KeyChain has standardized key management capabilities. The following functions are available on any KeyChain that implements this interface.
const addresses = myKeychain.getAddresses()
const addressStrings = myKeychain.getAddressStrings()
const exists = myKeychain.hasKey(addresses[0])
const keypair = myKeychain.getKey(addresses[0])
Working with KeyPairs
The X-Chain's KeyPair has standardized KeyPair functionality. The following operations are available on any KeyPair that implements this interface.
const address = keypair.getAddress()
const addressString = keypair.getAddressString()
const pubk = keypair.getPublicKey()
const pubkstr = keypair.getPublicKeyString()
const privk = keypair.getPrivateKey()
const privkstr = keypair.getPrivateKeyString()
keypair.generateKey()
const mypk = bintools.cb58Decode(
"24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5"
)
const successful = keypair.importKey(mypk)
const message = Buffer.from("Through consensus to the stars")
const signature = keypair.sign(message)
const signerPubk = keypair.recover(message, signature)
const isValid = keypair.verify(message, signature)
Example 2 — Creating An Asset
This example creates an asset in the X-Chain and publishes it to the Avalanche Platform. The first step in this process is to create an instance of AvalancheJS connected to our Avalanche Platform endpoint of choice.
import { Avalanche, BinTools, Buffer, BN } from "avalanche"
import { InitialStates, SECPTransferOutput } from "avalanche/dist/apis/avm"
const myNetworkID = 12345
const avalanche = new Avalanche("localhost", 9650, "http", myNetworkID)
const xchain = avalanche.XChain()
Describe the new asset
The first steps in creating a new asset using AvalancheJS is to determine the qualities of the asset. We will give the asset a name, a ticker symbol, as well as a denomination.
const name = "TeamRocket"
const symbol = "ROKT"
const denomination = 9
Creating the initial state
We want to mint an asset with 400 coins to all of our managed keys, 500 to the second address we know of, and 600 to the second and third address. This sets up the state that will result from the Create Asset transaction.
Note: This example assumes we have the keys already managed in our X-Chain's Keychain.
const addresses = xchain.keyChain().getAddresses()
const secpOutput1 = new SECPTransferOutput(
new BN(400),
new BN(400),
1,
addresses
)
const secpOutput2 = new SECPTransferOutput(new BN(500), new BN(400), 1, [
addresses[1],
])
const secpOutput3 = new SECPTransferOutput(new BN(600), new BN(400), 1, [
addresses[1],
addresses[2],
])
const initialState = new InitialStates()
initialState.addOutput(secpOutput1)
initialState.addOutput(secpOutput2)
initialState.addOutput(secpOutput3)
Creating the signed transaction
Now that we know what we want an asset to look like, we create an output to send to the network. There is an AVM helper function buildCreateAssetTx()
which does just that.
const utxos = await xchain.getUTXOs(addresses)
const unsigned = await xchain.buildCreateAssetTx(
utxos,
addresses,
addresses,
initialState,
name,
symbol,
denomination
)
const signed = unsigned.sign(xchain)
Issue the signed transaction
Now that we have a signed transaction ready to send to the network, let's issue it!
Using the AvalancheJS X-Chain API, we going to call the issueTx
function. This function can take either the Tx class returned in the previous step, a CB58 representation of the transaction, or a raw Buffer class with the data for the transaction. Examples of each are below:
const txid = await xchain.issueTx(signed)
const txid = await xchain.issueTx(signed.toString())
const txid = await xchain.issueTx(signed.toBuffer())
We assume ONE of those methods are used to issue the transaction.
Get the status of the transaction
Now that we sent the transaction to the network, it takes a few seconds to determine if the transaction has gone through. We can get an updated status on the transaction using the TxID through the AVM API.
const status = await xchain.getTxStatus(txid)
The statuses can be one of "Accepted", "Processing", "Unknown", and "Rejected":
- "Accepted" indicates that the transaction has been accepted as valid by the network and executed
- "Processing" indicates that the transaction is being voted on.
- "Unknown" indicates that node knows nothing about the transaction, indicating the node doesn't have it
- "Rejected" indicates the node knows about the transaction, but it conflicted with an accepted transaction
Identifying the newly created asset
The X-Chain uses the TxID of the transaction which created the asset as the unique identifier for the asset. This unique identifier is henceforth known as the "AssetID" of the asset. When assets are traded around the X-Chain, they always reference the AssetID that they represent.
Example 3 — Sending An Asset
This example sends an asset in the X-Chain to a single recipient. The first step in this process is to create an instance of Avalanche connected to our Avalanche Platform endpoint of choice.
import { Avalanche, BinTools, Buffer, BN } from "avalanche"
const myNetworkID = 12345
const avalanche = new avalanche.Avalanche(
"localhost",
9650,
"http",
myNetworkID
)
const xchain = avalanche.XChain()
We're also assuming that the keystore contains a list of addresses used in this transaction.
Getting the UTXO Set
The X-Chain stores all available balances in a datastore called Unspent Transaction Outputs (UTXOs). A UTXO Set is the unique list of outputs produced by transactions, addresses that can spend those outputs, and other variables such as lockout times (a timestamp after which the output can be spent) and thresholds (how many signers are required to spend the output).
For the case of this example, we're going to create a simple transaction that spends an amount of available coins and sends it to a single address without any restrictions. The management of the UTXOs will mostly be abstracted away.
However, we do need to get the UTXO Set for the addresses we're managing.
const myAddresses = xchain.keyChain().getAddresses()
const addressStrings = xchain.keyChain().getAddressStrings()
const u = await xchain.getUTXOs(myAddresses)
const utxos = u.utxos
Spending the UTXOs
The buildBaseTx()
helper function sends a single asset type. We have a particular assetID whose coins we want to send to a recipient address. This is an imaginary asset for this example which we believe to have 400 coins. Let's verify that we have the funds available for the transaction.
const assetID = "8pfG5CTyL5KBVaKrEnCvNJR95dUWAKc1hrffcVxfgi8qGhqjm"
const mybalance = utxos.getBalance(myAddresses, assetID)
We have 400 coins! We're going to now send 100 of those coins to our friend's address.
const sendAmount = new BN(100)
const friendsAddress = "X-avax1k26jvfdzyukms95puxcceyzsa3lzwf5ftt0fjk"
const unsignedTx = await xchain.buildBaseTx(
utxos,
sendAmount,
[friendsAddress],
addressStrings,
addressStrings,
assetID
)
const signedTx = xchain.signTx(unsignedTx)
const txid = await xchain.issueTx(signedTx)
And the transaction is sent!
Get the status of the transaction
Now that we sent the transaction to the network, it takes a few seconds to determine if the transaction has gone through. We can get an updated status on the transaction using the TxID through the X-Chain.
const status = await xchain.getTxStatus(txid)
The statuses can be one of "Accepted", "Processing", "Unknown", and "Rejected":
- "Accepted" indicates that the transaction has been accepted as valid by the network and executed
- "Processing" indicates that the transaction is being voted on.
- "Unknown" indicates that node knows nothing about the transaction, indicating the node doesn't have it
- "Rejected" indicates the node knows about the transaction, but it conflicted with an accepted transaction
Check the results
The transaction finally came back as "Accepted", now let's update the UTXOSet and verify that the transaction balance is as we expected.
Note: In a real network the balance isn't guaranteed to match this scenario. Transaction fees or additional spends may vary the balance. For the purpose of this example, we assume neither of those cases.
const updatedU = await xchain.getUTXOs()
const updatedUTXOs = updatedU.utxos
const newBalance = updatedUTXOs.getBalance(myAddresses, assetID)
if (newBalance.toNumber() != mybalance.sub(sendAmount).toNumber()) {
throw Error("heyyy these should equal!")
}
Repo Dependency Updates
Dependabot will make pull requests against the development branch. If all tests pass, it is safe to merge into development, but for redundancy you want to try to build it locally.
git fetch origin
git checkout -b <branchName>
git merge development
yarn build && yarn test
If the E2E check does not pass, go into the 'checks' section of the PR.
https://github.com/ava-labs/avalanchejs/pull/<PR number>/checks
- Click on the
> E2E
tab on the left - Click 'Re-run jobs' on the right