Getting Started

Agence is a layer 1 blockchain platform with a goal to onboard the mass market through first class NFTs, delegated transactions fees, and EVM Compatibility. This document provides details on how to interact with the Agence Network specifically for developers and node operators.

Installation

The easy way to install is to run the following command.


curl --proto '=https' --tlsv1.2 -sSf https://storage.googleapis.com/aetheras-public/get_agence/install.sh | bash

Takecopter Testnet

Takecopter is Agence's development testnet. Like many other blockchain networks, it is designed for developers of the whole ecosystem to be able to test everything, including node updates and DApp development.

These are the important links for the Testnet:

Evm Compatibility

Agence supports EVM and Ethereum Transactions, which means the network can handle smart contracts written in Solidity and Viper. The takecopter testnet's public RPC url is https://takecopter.cloud.agence.network. This URL can be entered into any ethereum compatible wallet, such as Metamask. There is a shortcut button at the ethereum block explorer

image

To add the Takecopter Testnet manually, use the following settings:

image

Interacting with Chain through Agence Command (acmd)

We have provided the acmd command line tool to help users interact with the network. Make you have already executed the installation script.

cd ~/.agence/bin

# ws://localhost:9944 will be used if the node rpc parameter is ommitted 
~/.agence/bin/acmd wss://ws.takecopter.cloud.agence.network
Connected to: wss://ws.takecopter.cloud.agence.network
 chain: Takecopter Testnet
 client: Agence Node 0.10.0-a46d67a-x86_64-linux-gnu
 token: [HME]
 token decimals: [18]
 ss58 prefix: 887
agence>

From now on, you can use acmd to query data using an interactive javascript terminal.

You can press tab to show all the possible matches to an api.query:

agence> await api.query. #TAB
api.query.__defineGetter__          api.query.__defineSetter__          api.query.__lookupGetter__          api.query.__lookupSetter__          api.query.__proto__
api.query.constructor               api.query.hasOwnProperty            api.query.isPrototypeOf             api.query.propertyIsEnumerable      api.query.toLocaleString
api.query.toString                  api.query.valueOf

api.query.assets                    api.query.auctions                  api.query.authorship                api.query.babe                      api.query.balances
api.query.council                   api.query.elections                 api.query.forges                    api.query.grandpa                   api.query.imOnline
api.query.indices                   api.query.offences                  api.query.randomnessCollectiveFlip  api.query.recovery                  api.query.session
api.query.staking                   api.query.substrate                 api.query.sudo                      api.query.system                    api.query.timestamp
api.query.transactionPayment        api.query.treasury

Creating an Account

If you want to sign and submit extrinsic to change the state of the chain, you need to create your own account first.

Agence account consists of a public and private key pair, as well as an address which are generated from a group of mnemonic seed phrase.

To get a new phrase, run the commands below in other terminal window:

cd ~/.agence/bin

# ${WORD_COUNT} is 12 by default
./acu key generate ${WORD_COUNT}

It will show following output, please backup and save your secret phrase:

Secret phrase `electric kit dream idea comic trigger simple vanish seek group blade angle` is account:
  Secret seed:      0x7d5a460e2dc8c7cc4921f5f68dd3c7c0ca774c81084c31389cdb3ffbfcaf8238
  Public key (hex): 0x3e681d4d0c49cd928226d95c6f0422ebbb0273fe69cb3a95fc2c50f1b4977117
  Account ID:       0x3e681d4d0c49cd928226d95c6f0422ebbb0273fe69cb3a95fc2c50f1b4977117
  SS58 Address:     SUA2p2buoGnsRe2HVoF9EyP39zwL9Bfacq3nKanYVtdEo5Xb

To insert your newly created keypair into acmd, run the following command, where secret_phrase is the phrase you haved created on last step:

agence> const keypair = this.keyring.addFromUri(secret_phrase, {})

To show your account address in SS58 format, just run:

agence> keypair.address

HUME

The native token on Agence is HUME(HME). Accounts can request HME on the Testnet Faucet.

Account Balance

To check the HME balance of specific agence account, you can execute following command in acmd:

(await api.query.system.account(${ADDRESS})).toHuman()

It will return following results that contains your account details:

{
  nonce: '3',
  consumers: '2',
  providers: '1',
  sufficients: '0',
  data: {
    free: '789,961,019,998,007,013,458',
    reserved: '0',
    miscFrozen: '500,000,000,000,000,000,000',
    feeFrozen: '500,000,000,000,000,000,000'
  }
}

Transfer HME

If you want to transfer your HME to another account, try the command below:

await api.tx.balances.transfer(${DESTINATION_ADDRESS}, ${AMOUNT}).signAndSend((keypair, result) => {console.log(JSON.stringify(result))})

Forges

Overview of Forges

A Forge is an entity for minting Assets. An account can possess multiple forges, and as many as they deem necessary.

Accounts may want multiple forges as a way to segregate which sets of assets are supported by which games.

Creating a Forge

To create a forge, users can run the command:

await api.tx.forges.createForge().signAndSend(keypair, result => {console.log(JSON.stringify(result))})

This will create a new forge, and for the current testnet, take_copter, this will cost 100 HME to execute. The HME will be divided amongst the validators and the community pool.

The corresponding logs will also be returned.

{"events":[],"status":{"Ready":null}}
{"events":[],"status":{"Broadcast":["12D3KooWN9LGk4nEAZCMA67ioxF4yCe7au7tMq7R6uJDPVxYwBDm"]}}
{
  "dispatchInfo": {
    "weight": 510394471,
    "class": "Normal",
    "paysFee": "Yes"
  },
  "events": [
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x0003",
        "data": [
          "SVDhDxQbbgqUyP7ZdUunYN9KPKEUbxHFrf5UsaF1MwhR9wpQ"
        ]
      },
      "topics": [
        
      ]
    },
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x0400",
        "data": [
          "SVDhDxQbbgqUyP7ZdUunYN9KPKEUbxHFrf5UsaF1MwhR9wpQ",
          10000000000
        ]
      },
      "topics": [
        
      ]
    },
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x1600",
        "data": [
          46,
          "SY3GFws1bxSiXmS6Zn5HgiPiguBhN8r4fKxvgdMux84K1EZm"
        ]
      },
      "topics": [
        
      ]
    },
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x0906",
        "data": [
          185600000
        ]
      },
      "topics": [
        
      ]
    },
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x0000",
        "data": [
          {
            "weight": 510394471,
            "class": "Normal",
            "paysFee": "Yes"
          }
        ]
      },
      "topics": [
        
      ]
    }
  ],
  "status": {
    "InBlock": "0x3f15607eb788372c74a37f5a4670de906017dbbb2c6ebde171359e2849cdbf74"
  }
}

Assuming that you submitted using the wallet SY3GFws1bxSiXmS6Zn5HgiPiguBhN8r4fKxvgdMux84K1EZm, you can find the id of newly created forge in the logs by searching for it. In the example above, 46 is the id of the forge.

"event": 
{
  "index": "0x1600",
  "data": [
    46,
    "SY3GFws1bxSiXmS6Zn5HgiPiguBhN8r4fKxvgdMux84K1EZm"
  ]
}

Creating AssetKinds

Before starting to create new assets, we need to create new assetKind as the template for them.

await api.tx.forges.createAssetKind(forgeId, assetKindId, ipfsHash, alternatveDataUri).signAndSend(keypair , result => {console.log(JSON.stringify(result))})

Creating Assets

Once the Forge and AssetKinds are created, assets can be created with the following command:

await api.tx.forges.createAsset(destination, forgeId, assetKindId, metadata, result => {console.log(JSON.stringify(result))})

Royalties

As a reward mechanism to forge owner, whenevenr any assets created by the forge are transferred through auction market, the forge owner will receive some of the auction price as royalty.

//#TODO: We can show the royalty section in MyForge page in future.

Assets

In Agence, every account can own assets, which can translate to in-game items, etc. The format of information that is stored in the assets.

The asset ID is currently defined as <ForgeID>x<AssetKindID>x<AssetId>, so 3x1111x2 would represent the 2nd created asset from Forge 3 with AssetKind 1111.

View owned Assets

We can check account's owned assets on AgenceExplorer's AccountDetail page:

https://explorer.takecopter.agence.network/<account_address>

image

Currently, the recommended way to store asset data is to store the bulk of the data on IPFS. In the picture above, we are only storing the IPFS hash of the asset on the chain, while the rest of the data is on IPFS.

There, you will find a directory with an attrs.json file to serve as its index file. As we progress, we intend to have standard formats set and iterated upon.

Query AssetKind

Using forgeId + assetKindId, we can query assetKind as follows:

JSON.stringify(await api.query.assets.assetKind([forgeId, assetKindId]))

example output:

'{
    "data_hash": "0x516d616832684b744a597774616d344533346354395447363764614c7574644d7342734878364c79696e4a76424c",
    "data_uri": null,
    "volume": 103
}'

Query Asset

Using forgeId + assetKindId + assetId, we can query asset as follows:

JSON.stringify(await api.query.assets.asset([forgeId, assetKindId, assetId]))

Example output:

'{
    "owner": "SWvNjWfEoTavvuMtM4DLyYKd2Myn6duBeG51vSmbqpcyGuT5","metadata":"0x6d657461"
}'

Transferring Assets

In order to send an asset from one account to another, the following command can be executed:

await api.tx.assets.transfer(destination, [forgeId, assetKindId, assetId]).signAndSend(keypair , result => {console.log(JSON.stringify(result))})

Where destination is the recipient's address.

Auctions

In Agence supports a built in Auction module. We can create our own auction to sell assets or buy new assets by bidding on existing auctions.

Create Auction

Agence support three types of auctions:

  1. BASIC: which is the most common type that the bidder with highest price wins at the end of the auction duration

    Creation commands in acmd:

    const auctionKind = api.createType("AuctionKind", 0)
    
    await api.tx.auctions.createAuction(auctionKind, [forgeId, assetKindId, assetId], startPrice, duration_millisec).signAndSend(keypair, result => {console.log(JSON.stringify(result))})
    
  2. WITH_BUY_NOW: which is the type that has both features of BASIC and BUY_ONLY type:

    Creation commands in acmd:

    const auctionKind = api.createType("AuctionKind", buyNowPrice, 1)
    
    await api.tx.auctions.createAuction(auctionKind, [forgeId, assetKindId, assetId], startPrice, duration_millisec).signAndSend(keypair, result => {console.log(JSON.stringify(result))})
    
  3. BUY_ONLY: which buyer can directly buy the asset for buyNowPrice

    Creation commands in acmd:

    const auctionKind = api.createType("AuctionKind", 2)
    
    await api.tx.auctions.createAuction(auctionKind, [forgeId, assetKindId, assetId], buyNowPrice, duration_millisec).signAndSend(keypair, result => {console.log(JSON.stringify(result))})
    

After executing the creation commands, we will get logs like:

{
  "dispatchInfo": {
    "weight": 1867546902,
    "class": "Normal",
    "paysFee": "Yes"
  },
  "events": [
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x1700",
        "data": [
          206,
          4,
          42
        ]
      },
      "topics": [
        
      ]
    },
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x0906",
        "data": [
          220800000
        ]
      },
      "topics": [
        
      ]
    },
    {
      "phase": {
        "ApplyExtrinsic": 1
      },
      "event": {
        "index": "0x0000",
        "data": [
          {
            "weight": 1867546902,
            "class": "Normal",
            "paysFee": "Yes"
          }
        ]
      },
      "topics": [
        
      ]
    }
  ],
  "status": {
    "InBlock": "0x0eb7d6540e3030a0662b988dc2fde2e38f1bdc11c94f27d1334301db67d25e2b"
  }
}

Pay attention on the event whose index is "0x1700". This indicates the auction/Created event that contains auctionId (ex: 206), forgeId(ex: 4), and assetKindId(ex: 42).

"event": {
    "index": "0x1700",
    "data": [
        206,
        4,
        42
    ]
}

As we can see in the event, we just put our asset which assetKind is 4x42 on a new auction 206.

Query Auction

We can query active auction using auctionId in acmd:

JSON.stringify(await api.query.auctions.auction(auctionId))

example output:

'{
  "active_index": 0,
  "auctioneer": "SUqyEgGoeQyCYMJiTGVkqnHHMeH4S6KNjKoV66ku4bLFn1rL",
  "kind": 
  {
    "WithBuyNow": 20000000000
  },
  "asset": [4,42,1],
  "starting_price": 10000000000, 
  "start_at": 1620189576002,
  "duration":60000
}'

Bid on Auction

For Basic or WithBuyNow type auction, we can bid on the auction through acmd by following command:

await api.tx.auctions.bid(auctionId, biddingPrice).signAndSend(keypair, result => {console.log(JSON.stringify(result))})

Directly buy through Auction

For BuyOnly or WithBuyNow type auction, we can directly buy the asset on auction through following command:

await api.tx.auctions.buy(auctionId).signAndSend(keypair, result => {console.log(JSON.stringify(result))})

Run your own Agence Node

Running your own Agence Node allows you to validate the on chain data without trusting a 3rd party node rpc endpoint, which can be modified to return false results. You also retain more fine grained control on the performance of the Node. While public nodes are available, they may be subject to network congestion.

To join the takecopter testnet, run:

~/.agence/bin/agence --chain tak

The chain will take some time to synchronize. 🚀🚀🚀🚀🚀🚀🚀

Running a Validator

Requirements

The easiest way to run a validator is to run one on a Linux machine on a cloud server.

Hardware

The reference hardware for nodes is a e2-medium machine on the Google Cloud Platform (GCP). It is recommended to use hardware that at least matches the specs of the reference hardware to ensure the validator processes all blocks in time. Using subpar hardware can possibly run into performance issues, get less era points, and/or potentially get slashed.

Note that this is not a hard requirement for running validator, but rather a best practice for smoothly running a validator node.

Connect to testnet as validator

Step 1: Sync data

Before preparing a validator, begin syncing your testnet node by following commands:

~/.agence/bin/agence -d ~/agence/ --chain tak --name node_name --validator --no-telemetry --prometheus-external

During the data syncing process, you can open a new terminal and go to next step.

Step 2: Generate secret phrase

Generate secret phrase:

~/.agence/bin/agence keys generate 

It will show following output, please copy your secret phrase for later use:

Secret phrase `author explain property conduct state jacket ankle orphan cancel pear install blouse` is account:
  Secret seed:      0x7ad2ab9e4be6ae53d6a407a3fb557c7b860a25dee529e090cc3aff9453bd8abe
  Public key (hex): 0x3c1631eb6c27301fee59c2ced27c821ee65fd5e38ac2f5a7cc4be9a03e5a6a44
  Account ID:       0x3c1631eb6c27301fee59c2ced27c821ee65fd5e38ac2f5a7cc4be9a03e5a6a44
  SS58 Address:     iBeyeKnGrBHZacyukW4gvyyDnSZrYoAHjJHbHSLGxPefQVHmG

Step 3: Generate keystore files

Prepare validator keystore files through acu:

~/.agence/bin/agence genesis prepare-validator

And key-in the secret phrase we copied from Step 2:

Enter Mnemonic: author explain property conduct state jacket ankle orphan cancel pear install blouse

//The information below are important for later steps!
{
  "stash_id": "5DtFkWnAjtHdpJJL9LeWJkLyJopDMCZfqaywY1dWmYUsmDpW",
  "controller_id": "5DD9kVnegpwJMKZozjyQ7nLKx151V9VGHSQchMMZcQNhYiY2",
  "grandpa_id": "5CVHtaBHvM2GxEMq6ynbBdqWvCub7nFm7532Yd4Wofp1anuf",
  "babe_id": "5GhHQqqfLUR2zFb6Esrdf8Pf1PhfRJDd6Zzb6wXicrV1WgXd",
  "im_online_id": "5GhHQqqfLUR2zFb6Esrdf8Pf1PhfRJDd6Zzb6wXicrV1WgXd",
  "authority_discovery_id": "5GhHQqqfLUR2zFb6Esrdf8Pf1PhfRJDd6Zzb6wXicrV1WgXd",
  "node_key": "12D3KooWFvW3jFTHmkD1VUqdTDJVDawyp8TvTDD9dAn2CK5GaiGr"
}

then copy the generated keystore files to the base path:

cp -r node0/chains/agence/keystore ~/agence/chains/takecopter

restart your node using ctrl + c to shutdown and restart again with the same command:

~/.agence/bin/agence -d ~/agence/ --chain tak --name node_name --validator --no-telemetry --prometheus-external

Step 4: Bond HME

Since we have prepared our keystore files, we still need to setup a pair of accounts(Stash account and Controller account) to act as the validator role in testnet.

Stash account acts as the custodian of validator's staking funds and delegates some functions to controller account.

Controller account is used to control your validator's behavior.

We can inspect our stash account and controller account:

~/.agence/bin/agence key inspect

then use {secret_phrase}/stash or {secret_phrase}/controller to get stash account address or controller account address in ss58 format:

/* Inspect stash account info, please save `Secret Key` and `SS58 Address` for later use */

Enter Suri: author explain property conduct state jacket ankle orphan cancel pear install blouse/stash
Secret Key URI `author explain property conduct state jacket ankle orphan cancel pear install blouse/stash` is account:
  Secret seed:      n/a
  Public key (hex): 0x508021c13d38d92ddc840ee86ab195d656dbd0f69c93cfc7eb9596402619316b
  Account ID:       0x508021c13d38d92ddc840ee86ab195d656dbd0f69c93cfc7eb9596402619316b
  SS58 Address:     iBfSQkaRuzWciA2pxQKK6xDgfsD77uoduYmdYkfKaXNyKAHU5


/* Inspect controller account info, please save `Secret Key` and `SS58 Address` for later use */

Enter Suri: author explain property conduct state jacket ankle orphan cancel pear install blouse/controller
Secret Key URI `author explain property conduct state jacket ankle orphan cancel pear install blouse/controller` is account:
  Secret seed:      n/a
  Public key (hex): 0x32ad5cbf29bf12fe5e334a5b1f30bcf4cf2f7e29b9d4fb65ffd7bcfa98741162
  Account ID:       0x32ad5cbf29bf12fe5e334a5b1f30bcf4cf2f7e29b9d4fb65ffd7bcfa98741162
  SS58 Address:     iBemJkZSPwTGNh46SFidzmFg2WQMv3kZVzd4Dv13dNEs8wrei

We got our stash account: iBfSQkaRuzWciA2pxQKK6xDgfsD77uoduYmdYkfKaXNyKAHU5, and controller account: iBemJkZSPwTGNh46SFidzmFg2WQMv3kZVzd4Dv13dNEs8wrei

Before using the accounts on the testnet, we need to get some HME from testnet faucet.

Key in ss58 address of the stash account to get some HME: image

And run the following commands in acmd to bond somes stake to your controller account:

const stash_suri = 'author explain property conduct state jacket ankle orphan cancel pear install blouse/stash'

//Add stash account keypair into keyring
const stash_account = this.keyring.addFromUri(stash_suri, {})

const controller_suri = 'author explain property conduct state jacket ankle orphan cancel pear install blouse/controller'

//Add controller account keypair into keyring
const controller_account = this.keyring.addFromUri(controller_suri, {})

//Check stash account's balance 
(await api.query.system.account(stash_account.address)).toHuman()

// To make sure controller account have enough balance for signing extrinsics and maintaining existential_deposit,  
// it's better to transfer more than just the existential_deposit */
const transfer_amount = api.consts.balances.existentialDeposit.toBigInt() * 10n

//Transfer some balance from stash account to controller account
api.tx.balances.transfer(controller_account.address, transfer_amount).signAndSend(stash_account)

//Check controller account's balance 
(await api.query.system.account(controller_account.address)).toHuman()

/* The stake fund amount that stash account want to bond to controller account.
The more staking fund your validator have, the higher chance that it will be choosen as active validator */
const stake_amount = api.consts.balances.existentialDeposit.toBigInt() * 100n

//Bond some stake to your controller account
api.tx.staking.bond(controller_account.address, stake_amount, 0).signAndSend(stash_account)

Step 5: Set session keys

To associate the controller account(we made in Step 4) with our validator node, we need to submit an extrinsic in acmd to set session keys.

// The corresponding Ids from Step 3
const grandpaId = api.createType("AccountId", '5CVHtaBHvM2GxEMq6ynbBdqWvCub7nFm7532Yd4Wofp1anuf')
const babeId = api.createType("AccountId", '5GhHQqqfLUR2zFb6Esrdf8Pf1PhfRJDd6Zzb6wXicrV1WgXd')
const imOnlineId = api.createType("AccountId", '5GhHQqqfLUR2zFb6Esrdf8Pf1PhfRJDd6Zzb6wXicrV1WgXd')
const authorityDiscoveryId = api.createType("AccountId", '5GhHQqqfLUR2zFb6Esrdf8Pf1PhfRJDd6Zzb6wXicrV1WgXd')

await api.tx.session.setKeys([grandpaId, babeId, imOnlineId, authorityDiscoveryId], new Uint8Array()).signAndSend(controller_account, (result) => {console.log(JSON.stringify(result))})

Step 6: Validate

Finally, we need to submit a validate extrinsic in acmd to tell testnet that we want to participate as a validator.

await api.tx.staking.validate({"commission": 0}).signAndSend(controller_account, (result) => {console.log(JSON.stringify(result))})

Claiming Rewards

After each era, rewards are paid to validators and nominators, but require executing an extrinsic to receive them.


/* The era number that your validator has earned reward.
You can check rewarded eras in https://explorer.takecopter.agence.network/validators/{stash_account_address} */
const rewardEra = 546

//Claim your reward as validator
await api.tx.staking.payoutStakers(stash_account.address, rewardEra).signAndSend(controller_account, result => {console.log(JSON.stringify(result))})

Stop validating

To stop validating, you must first declare no desire to validate with the chill extrinsic.

await api.tx.staking.chill().signAndSend(controller_account, (result) => {console.log(JSON.stringify(result))})

Your validator will become inactive in next era, then you can gracefully shut down your agence node by ctrl + c.

After it becomes inactive in the next era, if you plan to not only shutdown your node but also unbond your funds or claim rewards, you should do the following actions after submitting chill extrinsic:

//Purge session keys
await api.tx.session.purgeKeys().signAndSend(controller_account, (result) => {console.log(JSON.stringify(result))})

const unbond_amount = (await api.query.system.account(stash_account.address)).data.feeFrozen

//Unbond your funds from controller
await api.tx.staking.unbond(unbond_amount).signAndSend(controller_account, result => {console.log(JSON.stringify(result))})