Aion University

So you want to be a DApp developer?


Well you've come to the right place. You'll find comprehensive guides and documentation to help you start developing with Aion as quickly as possible, as well as support if you get stuck. Let's jump right in!

Let's DApp

🎓 Create an ATS Token

Guide Level: Intermediate

You are a developer who:

  • knows/understands smart contracts in Solidity
  • is comfortable with deploying contracts onto Aion (if not, start here)
  • wants to build meaningful applications with its own native currency
  • likes to have fun

Overview

Let's learn how to create our own Token on the Aion blockchain! Why would anyone want to do this? Well, you can learn how to create a token for fun, to learn, for a possible ICO, or to drive your animal-frenzied card collectibles DApp. :crocodile+: :cow2+: :camel+:

Below is some information you'll need to understand what an ATS is and how it operates. Or jump down and dive right in!

What IS an ATS?

ATS is an abbreviation for "Aion Token Standard" - and that standard is the protocol that allows tokens to be used natively on the Aion blockchain and along with other connected blockchain networks - given that it's setup to do so.

The purpose of its creation is to allow developers to create a fungible token that intersects functionality requirements of current DApp developers and the innovation to allow cross-chain token movements.

Similarly to how ERC-20 is to Ethereum, ATS is a super-set and improved token for Aion. It derives from the ERC-777 implementation standard.

It's good to note that the standard itself is still a work in progress, but most functionalities are available today. Find more information on Aion Network GitHub.

Difference between ATS and ERC-20

To grasp a better understanding of the ATS, let's dig into the pain points with the ERC-20.

Below are some inherent setbacks of the ERC-20 protocol:

  1. What happens when you send an ERC-20 to an account with no withdrawal method or contracts that do not validate or support them at all? Well, these tokens get stuck or lost permanently and it happens every day.

    ERC-20 has no validation protocol in place - meaning there is no confirmation that the destination where the token is sent to, is compatible with the token or not. In most of these scenarios, these assets cannot be retrieved back, and are lost forever.

  2. In order to send tokens to a smart contract, it requires 2 transactions. What is the downfall of this? Well, we know that more transactions mean higher gas requirements, and higher gas requirements mean higher cost - all this to simply just transfer tokens?

    In more technical details, the transaction is a result from calling upon these two functions:

    • function approve(address _spender, uint _value) returns (bool success);
    • function transferFrom(address _from, address _to, uint _value) returns (bool success);

ATS Functionality:

  1. Validation checks stop tokens attempting to transfer to a smart contract that does not have a withdraw feature (or compatibility with token).

  2. Reduces transactions from 2 to 1 to a smart contract transfer by using the send() function

  3. Send/Receive hooks allow more control to token holders and their tokens. They can also hook a contract to execute.

  4. Approval of operators (which can either be a smart contract or address) to authorize use of funds by transferring or burning on behalf of the token holder. (e.g. automatic payments)

  5. Support cross chain token movement between different blockchain networks and respective bridges (this is a future implementation).

So how does it work?

This diagram provides a high-level overview of the proposed contract architecture related to fungible tokens to enable cross-chain movements and communicate with bridges.

Note: The scope of this AIP (Aion Improvement Proposal) is limited to the ATS contract highlighted. In the diagram below the Aion blockchain is the Home chain.

1. Make your own ATS!

1.1 ATS Template

Below is an template of the implementation of the ATS contract. We'll be modifying this contract so that you can deploy your own Token onto the network! Go ahead and just copy the code, and jump down to the Let's Get Started section. See ya down there :sunglasses+:

pragma solidity 0.4.15;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 * @notice This is a softer (in terms of throws) variant of SafeMath:
 *         https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1121
 */
library SafeMath {

    /**
    * @dev Multiplies two numbers, throws on overflow.
    */
    function mul(uint128 _a, uint128 _b) internal constant returns (uint128 c) {
        // Gas optimization: this is cheaper than asserting 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (_a == 0) {
            return 0;
        }
        c = _a * _b;
        require(c / _a == _b);
        return c;
    }

    /**
    * @dev Integer division of two numbers, truncating the quotient.
    */
    function div(uint128 _a, uint128 _b) internal constant returns (uint128) {
        // Solidity automatically throws when dividing by 0
        // therefore require beforehand avoid throw
        require(_b > 0);
        // uint128 c = _a / _b;
        // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
        return _a / _b;
    }

    /**
    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint128 _a, uint128 _b) internal constant returns (uint128) {
        require(_b <= _a);
        return _a - _b;
    }

    /**
    * @dev Adds two numbers, throws on overflow.
    */
    function add(uint128 _a, uint128 _b) internal constant returns (uint128 c) {
        c = _a + _b;
        require(c >= _a);
        return c;
    }
}

interface ATS {
    
    /// Returns the name of the token
    function name() public constant returns (string);

    /// Returns the symbol of the token
    function symbol() public constant returns (string);
    
    /// Returns the totalSupply of the token, assuming a fixed number of
    /// token circulation, this number should not change.
    function totalSupply() public constant returns (uint128);

    /// Returns the currently liquid supply of the token, assuming a fixed
    /// number of (total) tokens are available, this number should never
    /// exceed the totalSupply() of the token.
    function liquidSupply() public constant returns (uint128);

    function balanceOf(address owner) public constant returns (uint128);

    function granularity() public constant returns (uint128);

    /// Default Operators removed, rationale behind this is that default operators
    /// Rationale behind this is that all operators should be (opt-in), this includes
    // function defaultOperators() public constant returns (address[]);
    
    function isOperatorFor(address operator, address tokenHolder) public constant returns (bool);
    function authorizeOperator(address operator) public;
    function revokeOperator(address operator) public;

    function send(address to, uint128 amount, bytes holderData) public;
    function operatorSend(address from, address to, uint128 amount, bytes holderData, bytes operatorData) public;

    /// Some functionality should still include a burn (for example slashing ERC20 tokens from a validator)
    function burn(uint128 amount, bytes holderData) public;
    function operatorBurn(address from, uint128 amount, bytes holderData, bytes operatorData) public;

    /// @notice Interface for a bridge/relay to execute a `send`
    /// @dev this name was suggested by Michael Kitchen, who suggested
    /// it makes sense to thaw an token from solid to liquid
    ///
    /// @dev function is called by foreign entity to `thaw` tokens
    /// to a particular user.
    function thaw(address localRecipient, uint128 amount, bytes32 bridgeId, bytes bridgeData, bytes32 remoteSender, bytes32 remoteBridgeId, bytes remoteData) public;    
    
    /// @notice Interface for a user to execute a `freeze`, which essentially
    /// is a functionality that locks the token (into the special address)
    /// 
    /// @dev function is called by local user to `freeze` tokens thereby
    /// transferring them to another network.
    function freeze(bytes32 remoteRecipient, uint128 amount, bytes32 bridgeId, bytes localData) public;
    function operatorFreeze(address localSender, bytes32 remoteRecipient, uint128 amount, bytes32 bridgeId, bytes localData) public;

    /// Event to be emit at the time of contract creation. Rationale behind the event is a few things:
    ///
    /// * It allows one to filter for new ATS tokens being created, in the interest of clarity
    ///   this is a big help. We can simply filter for this event.
    ///
    /// * It indicates the `totalSupply` of the network. `totalSupply` is very important in
    ///   our standard, therefore it makes sense to include it as an emission.
    event Created(
        uint128 indexed     _totalSupply,
        /// This is a horrible name I know, up for debate
        address indexed     _creator);

    event Sent(
        address indexed     _operator,
        address indexed     _from,
        address indexed     _to,
        uint128             _amount,
        bytes               _holderData,
        bytes               _operatorData);

    event Thawed(
        address indexed localRecipient,
        uint128 amount,
        bytes32 indexed bridgeId,
        bytes bridgeData,
        bytes32 indexed remoteSender,
        bytes32 remoteBridgeId,
        bytes remoteData);

    event Froze(
        address indexed localSender,
        bytes32 indexed remoteRecipient,
        uint128 amount,
        bytes32 indexed bridgeId,
        bytes localData);

    event Minted(
        address indexed     _operator,
        address indexed     _to,
        uint128             _amount,
        bytes               _operatorData);

    event Burned(
        address indexed     _operator,
        address indexed     _from,
        uint128             _amount,
        bytes               _holderData,
        bytes               _operatorData);

    event AuthorizedOperator(
        address indexed     _Eoperator,
        address indexed     _tokenHolder);


    event RevokedOperator(
        address indexed     _operator,
        address indexed     _tokenHolder);
}

/**
 * @title ERC20 interface
 * @dev see https://github.com/aionnetwork/AIP/issues/4
 *
 * @notice ATS contracts by default are required to implement ERC20 interface
 */
contract ERC20 {
    function totalSupply() public constant returns (uint128);

    function balanceOf(address _who) public constant returns (uint128);

    function allowance(address _owner, address _spender) public constant returns (uint128);

    function transfer(address _to, uint128 _value) public returns (bool);

    function approve(address _spender, uint128 _value) public returns (bool);

    function transferFrom(address _from, address _to, uint128 _value) public returns (bool);

    event Transfer(
        address indexed from,
        address indexed to,
        uint128 value
    );

    event Approval(
        address indexed owner,
        address indexed spender,
        uint128 value
    );
}


contract AionInterfaceRegistry {
    function getManager(address target) public constant returns(address);
    function setManager(address target, address manager) public;
    function getInterfaceDelegate(address target, bytes32 interfaceHash) public constant returns (address);
    function setInterfaceDelegate(address target, bytes32 interfaceHash, address delegate) public;
}

contract AionInterfaceImplementer {
    // TODO: this needs to be deployed, this is just a placeholder address
    AionInterfaceRegistry air = AionInterfaceRegistry(0xa0d270e7759e8fc020df5f1352bf4d329342c1bcdfe9297ef594fa352c7cab26);

    function setInterfaceDelegate(string _interfaceLabel, address impl) internal {
        bytes32 interfaceHash = sha3(_interfaceLabel);
        air.setInterfaceDelegate(this, interfaceHash, impl);
    }

    function getInterfaceDelegate(address addr, string _interfaceLabel) internal constant returns(address) {
        bytes32 interfaceHash = sha3(_interfaceLabel);
        return air.getInterfaceDelegate(addr, interfaceHash);
    }

    function setDelegateManager(address _newManager) internal {
        air.setManager(this, _newManager);
    }
}

interface ATSTokenRecipient {
    function tokensReceived(
        address operator,
        address from,
        address to,
        uint128 amount,
        bytes userData,
        bytes operatorData
    ) public;
}

interface ATSTokenSender {
    function tokensToSend(
        address operator,
        address from,
        address to,
        uint128 amount,
        bytes userData,
        bytes operatorData
    ) public;
}

/**
 * @title ATSImpl
 * @dev see https://github.com/qoire/ATS/blob/master/contracts/ATSImpl.sol
 *
 */

contract ATSImpl is ATS, ERC20, AionInterfaceImplementer {
    using SafeMath for uint128;

    /* -- Constants -- */

    address constant internal addressTypeMask = 0xFF00000000000000000000000000000000000000000000000000000000000000;
    address constant internal zeroAddress = 0x0000000000000000000000000000000000000000000000000000000000000000;

    /* -- ATS Contract State -- */

    string internal mName;
    string internal mSymbol;
    uint128 internal mGranularity;
    uint128 internal mTotalSupply;

    mapping(address => uint128) internal mBalances;
    mapping(address => mapping(address => bool)) internal mAuthorized;

    // for ERC20
    mapping(address => mapping(address => uint128)) internal mAllowed;


    /* -- Constructor -- */
    //
    /// @notice Constructor to create a ReferenceToken
    
    function ATSImpl() {
        
        mName = YOUR_TOKEN_NAME;
        mSymbol = YOUR_TOKEN_SYMBOL;
        mTotalSupply = YOUR_TOKEN_TOTAL_SUPPLY *10**18;
        mGranularity = YOUR_TOKEN_TOTAL_GRANULARITY;
		require(mGranularity >= 1);
        initialize(mTotalSupply);

        // register onto CIR
        setInterfaceDelegate("AIP004Token", this);
    }

    function initialize(uint128 _totalSupply) internal {
        mBalances[msg.sender] = _totalSupply;
        Created(_totalSupply, msg.sender);
    }

    /* -- ERC777 Interface Implementation -- */
    //
    /// @return the name of the token
    function name() public constant returns (string) { return mName; }

    /// @return the symbol of the token
    function symbol() public constant returns (string) { return mSymbol; }

    /// @return the granularity of the token
    function granularity() public constant returns (uint128) { return mGranularity; }

    /// @return the total supply of the token
    function totalSupply() public constant returns (uint128) { return mTotalSupply; }

    /// @notice Return the account balance of some account
    /// @param _tokenHolder Address for which the balance is returned
    /// @return the balance of `_tokenAddress`.
    function balanceOf(address _tokenHolder) public constant returns (uint128) { return mBalances[_tokenHolder]; }

    /// @notice Send `_amount` of tokens to address `_to` passing `_userData` to the recipient
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    function send(address _to, uint128 _amount, bytes _userData) public {
        doSend(msg.sender, msg.sender, _to, _amount, _userData, "", true);

    }

    /// @notice Authorize a third party `_operator` to manage (send) `msg.sender`'s tokens.
    /// @param _operator The operator that wants to be Authorized
    function authorizeOperator(address _operator) public {
        require(_operator != msg.sender);
        mAuthorized[_operator][msg.sender] = true;
        AuthorizedOperator(_operator, msg.sender);
    }

    /// @notice Revoke a third party `_operator`'s rights to manage (send) `msg.sender`'s tokens.
    /// @param _operator The operator that wants to be Revoked
    function revokeOperator(address _operator) public {
        require(_operator != msg.sender);
        mAuthorized[_operator][msg.sender] = false;
        RevokedOperator(_operator, msg.sender);
    }

    /// @notice Check whether the `_operator` address is allowed to manage the tokens held by `_tokenHolder` address.
    /// @param _operator address to check if it has the right to manage the tokens
    /// @param _tokenHolder address which holds the tokens to be managed
    /// @return `true` if `_operator` is authorized for `_tokenHolder`
    function isOperatorFor(address _operator, address _tokenHolder) public constant returns (bool) {
        return (_operator == _tokenHolder || mAuthorized[_operator][_tokenHolder]);
    }

    /// @notice Send `_amount` of tokens on behalf of the address `from` to the address `to`.
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    /// @param _userData Data generated by the user to be sent to the recipient
    /// @param _operatorData Data generated by the operator to be sent to the recipient
    function operatorSend(address _from, address _to, uint128 _amount, bytes _userData, bytes _operatorData) public {
        require(isOperatorFor(msg.sender, _from));
        doSend(msg.sender, _from, _to, _amount, _userData, _operatorData, true);
    }

    function burn(uint128 _amount, bytes _holderData) public {
        doBurn(msg.sender, msg.sender, _amount, _holderData, "");
    }

    function operatorBurn(address _tokenHolder, uint128 _amount, bytes _holderData, bytes _operatorData) public {
        require(isOperatorFor(msg.sender, _tokenHolder));
        doBurn(msg.sender, _tokenHolder, _amount, _holderData, _operatorData);
    }

    /* -- Helper Functions -- */

    /// @notice Internal function that ensures `_amount` is multiple of the granularity
    /// @param _amount The quantity that want's to be checked
    function requireMultiple(uint128 _amount) internal constant {
        require(_amount.div(mGranularity).mul(mGranularity) == _amount);
    }

    /// @notice Check whether an address is a regular address or not.
    /// @param _addr Address of the contract that has to be checked
    /// @return `true` if `_addr` is a regular address (not a contract)
    ///
    /// Ideally, we should propose a better system that extcodesize
    ///
    /// *** TODO: CHANGE ME, going to require a resolution on best approach ***
    ///
    /// Given that we won't be able to detect code size.
    ///
    /// @param _addr The address to be checked
    /// @return `true` if the contract is a regular address, `false` otherwise
    function isRegularAddress(address _addr) internal constant returns (bool) {
        // if (_addr == 0) { return false; }
        // uint size;
        // assembly { size := extcodesize(_addr) }
        // return size == 0;
        return true;
    }

    /// @notice Helper function actually performing the sending of tokens.
    /// @param _operator The address performing the send
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    /// @param _userData Data generated by the user to be passed to the recipient
    /// @param _operatorData Data generated by the operator to be passed to the recipient
    /// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
    ///  implementing `erc777_tokenHolder`.
    ///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
    ///  functions SHOULD set this parameter to `false`.
    function doSend(
        address _operator,
        address _from,
        address _to,
        uint128 _amount,
        bytes _userData,
        bytes _operatorData,
        bool _preventLocking
    )
        internal
    {
        requireMultiple(_amount);

        callSender(_operator, _from, _to, _amount, _userData, _operatorData);

        require(_to != address(0));             // forbid sending to 0x0 (=burning)
        require(_to != address(this));          // forbid sending to the contract itself
        require(mBalances[_from] >= _amount);   // ensure enough funds

        mBalances[_from] = mBalances[_from].sub(_amount);
        mBalances[_to] = mBalances[_to].add(_amount);

        callRecipient(_operator, _from, _to, _amount, _userData, _operatorData, _preventLocking);

        Sent(_operator, _from, _to, _amount, _userData, _operatorData);
    }

    /// @notice Helper function actually performing the burning of tokens.
    /// @param _operator The address performing the burn
    /// @param _tokenHolder The address holding the tokens being burn
    /// @param _amount The number of tokens to be burnt
    /// @param _holderData Data generated by the token holder
    /// @param _operatorData Data generated by the operator
    function doBurn(address _operator, address _tokenHolder, uint128 _amount, bytes _holderData, bytes _operatorData)
        internal
    {
        requireMultiple(_amount);
        require(balanceOf(_tokenHolder) >= _amount);

        mBalances[_tokenHolder] = mBalances[_tokenHolder].sub(_amount);
        mTotalSupply = mTotalSupply.sub(_amount);

        callSender(_operator, _tokenHolder, 0x0, _amount, _holderData, _operatorData);
        Burned(_operator, _tokenHolder, _amount, _holderData, _operatorData);
    }

    /// @notice Helper function that checks for ERC777TokensRecipient on the recipient and calls it.
    ///  May throw according to `_preventLocking`
    /// @param _operator The address performing the send or mint
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be sent
    /// @param _userData Data generated by the user to be passed to the recipient
    /// @param _operatorData Data generated by the operator to be passed to the recipient
    /// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
    ///  implementing `ERC777TokensRecipient`.
    ///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
    ///  functions SHOULD set this parameter to `false`.
    function callRecipient(
        address _operator,
        address _from,
        address _to,
        uint128 _amount,
        bytes _userData,
        bytes _operatorData,
        bool _preventLocking
    )
        internal
    {
        address recipientImplementation = getInterfaceDelegate(_to, "AIP004TokenRecipient");
        if (recipientImplementation != 0) {
            ATSTokenRecipient(recipientImplementation).tokensReceived(
                _operator, _from, _to, _amount, _userData, _operatorData);
        } else if (_preventLocking) {
            require(isRegularAddress(_to));
        }
    }

    /// @notice Helper function that checks for ERC777TokensSender on the sender and calls it.
    ///  May throw according to `_preventLocking`
    /// @param _from The address holding the tokens being sent
    /// @param _to The address of the recipient
    /// @param _amount The amount of tokens to be sent
    /// @param _userData Data generated by the user to be passed to the recipient
    /// @param _operatorData Data generated by the operator to be passed to the recipient
    ///  implementing `ERC777TokensSender`.
    ///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
    ///  functions SHOULD set this parameter to `false`.
    function callSender(
        address _operator,
        address _from,
        address _to,
        uint128 _amount,
        bytes _userData,
        bytes _operatorData
    )
        internal
    {
        address senderImplementation = getInterfaceDelegate(_from, "AIP004TokenSender");
        if (senderImplementation == 0) { return; }
        ATSTokenSender(senderImplementation).tokensToSend(_operator, _from, _to, _amount, _userData, _operatorData);
    }

    function liquidSupply() public constant returns (uint128) {
        return mTotalSupply.sub(balanceOf(this));
    }


    /* -- Cross Chain Functionality -- */

    function thaw(
        address localRecipient,
        uint128 amount,
        bytes32 bridgeId,
        bytes bridgeData,
        bytes32 remoteSender,
        bytes32 remoteBridgeId,
        bytes remoteData)
    public {

    }

    function freeze(
        bytes32 remoteRecipient,
        uint128 amount,
        bytes32 bridgeId,
        bytes localData)
    public {

    }

    function operatorFreeze(address localSender,
        bytes32 remoteRecipient,
        uint128 amount,
        bytes32 bridgeId,
        bytes localData)
    public {

    }

    /* -- ERC20 Functionality -- */
    
    function decimals() public constant returns (uint8) {
        return uint8(18);
    }

    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be transferred
    /// @return `true`, if the transfer can't be done, it should fail.
    function transfer(address _to, uint128 _amount) public returns (bool success) {
        doSend(msg.sender, msg.sender, _to, _amount, "", "", false);
        return true;
    }

    /// @param _from The address holding the tokens being transferred
    /// @param _to The address of the recipient
    /// @param _amount The number of tokens to be transferred
    /// @return `true`, if the transfer can't be done, it should fail.
    function transferFrom(address _from, address _to, uint128 _amount) public returns (bool success) {
        require(_amount <= mAllowed[_from][msg.sender]);

        mAllowed[_from][msg.sender] = mAllowed[_from][msg.sender].sub(_amount);
        doSend(msg.sender, _from, _to, _amount, "", "", false);
        return true;
    }

    ///  `msg.sender` approves `_spender` to spend `_amount` tokens on its behalf.
    /// @param _spender The address of the account able to transfer the tokens
    /// @param _amount The number of tokens to be approved for transfer
    /// @return `true`, if the approve can't be done, it should fail.
    function approve(address _spender, uint128 _amount) public returns (bool success) {
        mAllowed[msg.sender][_spender] = _amount;
        Approval(msg.sender, _spender, _amount);
        return true;
    }

    ///  This function makes it easy to read the `allowed[]` map
    /// @param _owner The address of the account that owns the token
    /// @param _spender The address of the account able to transfer the tokens
    /// @return Amount of remaining tokens of _owner that _spender is allowed
    ///  to spend
    function allowance(address _owner, address _spender) public constant returns (uint128 remaining) {
        return mAllowed[_owner][_spender];
    }
}

2. Let's Get Started

2.1 Setup

Everything you need to succeed

Aion Testnet Node Connection

Titan Suite for Contract Compilation & Deployment

Aion Wallet

AION balance

Guides are available for everything listed above, found under 'Tool Guides'

2.2 Modifying the Template

  1. Open the Titan Suite's IDE, and paste the contract template
  2. In the IDE, go to line 282. In the next steps, you'll be setting the constructor for your token
  3. Define the following variables to your liking. Be mindful of each variable type.
Variable
Type
Description
Example

mName

string

The name of your token.

Bitcoin, Dogecoin, Luunies

mSymbol

string

The symbol of your token.

BTC, DOGE, LUU

mTotalSupply

uint128

The total number of minted tokens. This variable is multipled by 10 to the power of 18. It is important that you leave the *10**18 at the end of the variable.

1000000*10**18, 3141592654*10**18,

100*10**18

mGranularity

uint128

The smallest number of tokens (in the basic unit) which MAY be minted, sent, burned, frozen, or thawed in any transaction.
Leave this as 1. In the future this will be customizable.

1

Important to Remember

ATS Tokens granularity should only be set to 1.

This means that your token, by default, will have 18 decimals. 1 token is represented by 10^18 of its smallest unit of measurement to offer greater precision.

With that in mind, your total supply should be:
mTotalSupply = totalAmountOfTokens * 10^18

3. Deployment

3.1 Compile

Below is an example of the contract being modified for a unique token, then being compiled. You can use your provider address from Nodesmith. If you're unsure on how to compile with Titan Suite - check out this quick guide that will walk you through.

We recommend you run things through the Mastery test network before using the main network, this way you don't loose any real AION coins if something goes wrong.

Compiling the ATS contract

Compiling the ATS contract

3.2 Deploy

Let's deploy our ATS onto the Aion Network. For the purpose of this guide, we'll be deploying to the testnet (so if you mess up, you don't lose any real AION value - but you DO gain valuable knowledge and experience :wink+:)

Steps:

  1. Import or create a new account over in the Accounts tab. Take a look at the Titan Suite - Account Management section if you need a refresher.
  2. In the Run tab double check that the Provide Address, Account, Gas Limit, and Gas Price are all set. These should be filled in automatically by Titan Suite.
  3. Choose 'ATSimpl' as the contract for deployment
  4. Hit that 'Deploy' button! It should take about 30 seconds for your contract to deploy, depending on the speed of the network.
  5. Finally, make a note of your contract address. You can do this by expanding the contract details section, and navigating to the Contract Address field.
Deploying the ATS contract

Deploying the ATS contract

4. Activate your Token

Now that you've deployed your token contract - you'll probably want to see it on the Token Dashboard. But, you'll notice it's not on the dashboard just yet. Don't fear :ghost+:! All you need to do is send one transaction to activate your token. Let's do this using AIWA.

Steps:

  1. Open AIWA and select the account you deployed your contract with. (Do the same for a second account which you'll be transferring to)
  2. Select the dropdown button next to AION and click Add Token.
  3. Paste in the contract address that you just copied in the previous step. All the other field should automatically populate in AIWA.
  4. Click Submit.
Adding your Token to AIWA

Adding your Token to AIWA

Now it's time to send our first transaction!

  1. Switch to your newly added Token on AIWA
  2. Click the dropdown button next to AION and select your Token. You should now see you newly minted coins!
  3. Click then Send button
  4. Enter the receiving address & amount to send
  5. Verify information is correct, then hit the Send button
  6. You should now be able to see your token on the Token Dashboard!
Sending your new Token

Sending your new Token

Share you contract address with your friends! They'll be able to add their token to their AIWA wallets in the same way you did!

Need Help?

If you get stuck, try searching these docs 👆 or head over to our Gitter channels or StackOverflow for answers to some common questions.

Written by Kimcodeashian :fire+:


🎓 Create an ATS Token


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.