# ao Token and Subledger Specification
**Status:** DRAFT-2
**Targeting Network:** ao.TN.1
This specification describes the necessary message handlers and functionality required for a standard ao token process. Implementations of this standard typically offer users the ability control a transferrable asset, whose scarcity is maintained by the process.
Each compliant process will likely implement a ledger of balances in order to encode ownership of the asset that the process represents. Compliant processes have a set of methods that allow for the modification of this ledger, typically with safe-guards to ensure the scarcity of ownership of the token represented by the process.
Additionally, this specification describes a 'subledger' process type which, when implemented, offers the ability to split move a number of the tokens from the parent into a child process that implements the same token interface specification. If the `From-Module` of the subledger process is trusted by the participants, these subledgers can be used to transact in the 'source' token, without directly exchanging messages with it. This allows participants to use the tokens from a process, even if that process is congested. Optionally, if the participants trust the `Module` a subledger process is running, they are able to treat balances across these processes as _fungible_. The result of this is that an arbitrary numbers of parallel processes -- and thus, transactions -- can be processed by a single token at any one time.
# Token Processes
A specification-compliant token process responds to a number of different forms of messages, with each form specified in an `Action` tag. The full set of `Action` messages that the token must support are as follows:
| Name | Description | Read-Only |
|----- | ----------- | --------- |
| Balance | get the balance of an identifer | :heavy_check_mark: |
| Balances | get a list of all ledger/account balances | :heavy_check_mark: |
| Transfer | send 1 or more units from the callers balance to one or move targets with the option to notify targets | :x: |
| Mint | if the ledger process is the root and you would like to increase token supply | :x: |
In the remainder of this section the tags necessary to spawn a compliant token process, along with the form of each of the `Action` messages and their results is described.
## Spawning Parameters
Every compliant token process must carry the following immutable parameters upon its spawning message:
| Tag | Description | Optional? |
| -------- | ----------- | --------- |
| Name | The title of the token, as it should be displayed to users. | :heavy_check_mark: |
| Ticker | A suggested shortened name for the token, such that it can be referenced quickly. | :heavy_check_mark: |
| Logo | An image that applications may deserire to show next to the token, in order to make it quickly visually identifiable. | :heavy_check_mark: |
| Denomination | The number of the token that should be treated as a single unit when quantities and balances are displayed to users. | :x: |
## Messaging Protocol
### Balance(Target? : string)
Returns the balance of a target, if a target is not supplied then the balance of the sender of the message must be returned.
Example `Action` message:
```lua=
Send({
Target = "{TokenProcess Identifier}",
Tags = {
Action = "Balance",
Target = "{IDENTIFIER}"
}
})
```
Example response message:
```
{
Tags = {
Balance = "50",
Target = "LcldyO8wwiGDzC3iXzGofdO8JdR4S1_2A6Qtz-o33-0",
Ticker = "FUN"
}
}
```
### Balances()
Returns the balance of all participants in the token.
```lua
Send({
Target = "[TokenProcess Identifier]",
Tags = {
Action = "Balances",
Limit = 1000, # TODO: Is this necessary if the user is paying for the compute and response?
Cursor? = "BalanceIdentifer"
}
})
```
Example response message:
```lua
{
Data = {
"MV8B3MAKTsUOqyCzQ0Tsa2AR3TiWTBU1Dx0xM4MO-f4": 100,
"LcldyO8wwiGDzC3iXzGofdO8JdR4S1_2A6Qtz-o33-0": 50
}
}
```
### Transfer(Target, Quantity)
If the sender has a sufficient balance, send the `Quantity` to the `Target`, issuing a `Credit-Notice` to the recipient and a `Debit-Notice` to the sender. If the sender has an insufficient balance, fail and notify the sender.
```lua
Send({
Target = "[TokenProcess Identifier]",
Tags = {
{ name = "Action", value = "Transfer" },
{ name = "Recipient", value = "[ADDRESS]" },
{ name = "Quantity", value = "100" }
}
})
```
If a successful transfer occurs a notification message should be sent if `Cast` is not set.
```lua
Send({
Target = "[Recipient Address]",
Tags = {
{ name = "Action", value = "Credit-Notice" },
{ name = "Sender", value = "[ADDRESS]" },
{ name = "Quantity", value = "100"}
}
})
```
Recipients will infer from the `From-Process` tag of the message which tokens they have received.
### Get-Info()
```lua
Send({
Target = "{Token}",
Tags = {
Action = "Info"
}
})
```
### Mint() [optional]
Implementing a `Mint` action gives the process a way of allowing valid participants to create new tokens.
```lua
Send({
Target ="{Token Process}",
Tags = {
Action = "Mint",
Quantity = "1000"
}
})
```
# Subledger Processes
In order to function appropriately, subledgers must implement the full messaging protocol of token contracts (excluding the `Mint` action). Subledgers must also implement additional features and spawn parameters for their processes. These modifications are described in the following section.
### Spawning Parameters
Every compliant subledger process must carry the following immutable parameters upon its spawning message:
| Tag | Description | Optional? |
| -------- | ----------- | --------- |
| Source-Token| The `ID` of the top-most process that this subledger represents. | :x: |
| Parent-Token | The `ID` of the parent process that this subledger is attached to. | :x: |
### `Credit-Notice` Handler
Upon receipt of a `Credit-Notice` message, a compliant subledger process must check if the process in question is the `Parent-Token`. If it is, the subledger must increase the balance of the `Sender` by the specified quantity.
### Transfer(Target, Quantity)
In addition to the normal tags that are passed in the `Credit-Notice` message to the recipient of tokens, a compliant subledger process must also provide both of the `Source-Token` and `Parent-Token` values. This allows the recipient of the `Transfer` message -- if they trust the `Module` of the subledger process -- to credit a receipt that is analogous (fungible with) deposits from the `Source-Token`.
The modified `Credit-Notice` should be structured as follows:
```lua
Send({
Target = "[Recipient Address]",
Tags = {
{ name = "Action", value = "Credit-Notice" },
{ name = "Quantity", value = "100"},
{ name = "Source-Token", value = "[ADDRESS]" },
{ name = "Parent-Token", value = "[ADDRESS]" }
}
})
```
### Withdraw(Target?, Quantity)
All subledgers must allow balance holders to withdraw their tokens to the parent ledger. Upon receipt of an `Action: Withdraw` message, the subledger must send an `Action` message to its `Parent-Ledger`, transferring the requested tokens to the caller's address, while debiting their account locally. This transfer will result in a `Credit-Notice` from the `Parent-Ledger` for the caller.
```lua
Send({
Target = "[TokenProcess Identifier]",
Tags = {
{ name = "Action", value = "Withdraw" },
{ name = "Recipient", value = "[ADDRESS]" },
{ name = "Quantity", value = "100" }
}
})
```
## Mirroring Token Balances
Some Tokens may choose to provide mirror processes that external clients can use to provide balance updates to users.
When you request the `Info` handler you will notice some new Tags that are returned to you as a response.
```lua
{
...
['Balance-Mirror'] = "F-EvpwmZXIlndrEqXOXSSifUeyn-LMBdeJKI6Gflk1g",
['Balances-Mirror'] = "F-EvpwmZXIlndrEqXOXSSifUeyn-LMBdeJKI6Gflk1g"
}
```
These tags provide a value to a Mirror Process that will enable clients to do dry-runs on those actions to retrieve the
same data as on the Token.
### Example
```lua
Send({
Target = "F-EvpwmZXIlndrEqXOXSSifUeyn-LMBdeJKI6Gflk1g",
Action = "Balance",
Recipient = "vh-NTHVvlKZqRxc8LyyTNok65yQ55a_PJ1zWLb9G2JI"
})
```
# Token Example
> NOTE: When implementing a token it is important to remember that all Tags on a message MUST be "string"s. Using the`tostring` function you can convert simple types to strings.
```lua
local bint = require('.bint')(256)
local ao = require('ao')
--[[
This module implements the ao Standard Token Specification.
Terms:
Sender: the wallet or Process that sent the Message
It will first initialize the internal state, and then attach handlers,
according to the ao Standard Token Spec API:
- Info(): return the token parameters, like Name, Ticker, Logo, and Denomination
- Balance(Target?: string): return the token balance of the Target. If Target is not provided, the Sender
is assumed to be the Target
- Balances(): return the token balance of all participants
- Transfer(Target: string, Quantity: number): if the Sender has a sufficient balance, send the specified Quantity
to the Target. It will also issue a Credit-Notice to the Target and a Debit-Notice to the Sender
- Mint(Quantity: number): if the Sender matches the Process Owner, then mint the desired Quantity of tokens, adding
them the Processes' balance
]]
--
local json = require('json')
--[[
utils helper functions to remove the bint complexity.
]]
--
local utils = {
add = function (a,b)
return tostring(bint(a) + bint(b))
end,
subtract = function (a,b)
return tostring(bint(a) - bint(b))
end,
toBalanceValue = function (a)
return tostring(bint(a))
end,
toNumber = function (a)
return tonumber(a)
end
}
--[[
Initialize State
ao.id is equal to the Process.Id
]]
--
Variant = "0.0.2"
if not Balances then Balances = { [ao.id] = utils.toBalanceValue(10000 * 1e12) } end
if Name ~= 'Points Coin' then Name = 'Points Coin' end
if Ticker ~= 'PNTS' then Ticker = 'PNTS' end
if Denomination ~= 12 then Denomination = 12 end
if not Logo then Logo = 'SBCCXwwecBlDqRLUjb8dYABExTJXLieawf7m2aBJ-KY' end
--[[
Add handlers for each incoming Action defined by the ao Standard Token Specification
]]
--
--[[
Info
]]
--
Handlers.add('info', Handlers.utils.hasMatchingTag('Action', 'Info'), function(msg)
Send({
Target = msg.From,
Name = Name,
Ticker = Ticker,
Logo = Logo,
Denomination = tostring(Denomination)
})
end)
--[[
Balance
]]
--
Handlers.add('balance', Handlers.utils.hasMatchingTag('Action', 'Balance'), function(msg)
local bal = '0'
-- If not Recipient is provided, then return the Senders balance
if (msg.Tags.Recipient and Balances[msg.Tags.Recipient]) then
bal = Balances[msg.Tags.Recipient]
elseif Balances[msg.From] then
bal = Balances[msg.From]
end
Send({
Target = msg.From,
Balance = bal,
Ticker = Ticker,
Account = msg.Tags.Recipient or msg.From,
Data = bal
})
end)
--[[
Balances
]]
--
Handlers.add('balances', Handlers.utils.hasMatchingTag('Action', 'Balances'),
function(msg) ao.send({ Target = msg.From, Data = json.encode(Balances) }) end)
--[[
Transfer
]]
--
Handlers.add('transfer', Handlers.utils.hasMatchingTag('Action', 'Transfer'), function(msg)
assert(type(msg.Recipient) == 'string', 'Recipient is required!')
assert(type(msg.Quantity) == 'string', 'Quantity is required!')
assert(bint.__lt(0, bint(msg.Quantity)), 'Quantity must be greater than 0')
if not Balances[msg.From] then Balances[msg.From] = "0" end
if not Balances[msg.Recipient] then Balances[msg.Recipient] = "0" end
if bint(msg.Quantity) <= bint(Balances[msg.From]) then
Balances[msg.From] = utils.subtract(Balances[msg.From], msg.Quantity)
Balances[msg.Recipient] = utils.add(Balances[msg.Recipient], msg.Quantity)
--[[
Only send the notifications to the Sender and Recipient
if the Cast tag is not set on the Transfer message
]]
--
if not msg.Cast then
-- Debit-Notice message template, that is sent to the Sender of the transfer
local debitNotice = {
Target = msg.From,
Action = 'Debit-Notice',
Recipient = msg.Recipient,
Quantity = msg.Quantity,
Data = Colors.gray ..
"You transferred " ..
Colors.blue .. msg.Quantity .. Colors.gray .. " to " .. Colors.green .. msg.Recipient .. Colors.reset
}
-- Credit-Notice message template, that is sent to the Recipient of the transfer
local creditNotice = {
Target = msg.Recipient,
Action = 'Credit-Notice',
Sender = msg.From,
Quantity = msg.Quantity,
Data = Colors.gray ..
"You received " ..
Colors.blue .. msg.Quantity .. Colors.gray .. " from " .. Colors.green .. msg.From .. Colors.reset
}
-- Add forwarded tags to the credit and debit notice messages
for tagName, tagValue in pairs(msg) do
-- Tags beginning with "X-" are forwarded
if string.sub(tagName, 1, 2) == "X-" then
debitNotice[tagName] = tagValue
creditNotice[tagName] = tagValue
end
end
-- Send Debit-Notice and Credit-Notice
Send(debitNotice)
Send(creditNotice)
end
else
Send({
Target = msg.From,
Action = 'Transfer-Error',
['Message-Id'] = msg.Id,
Error = 'Insufficient Balance!'
})
end
end)
--[[
Mint
]]
--
Handlers.add('mint', Handlers.utils.hasMatchingTag('Action', 'Mint'), function(msg)
assert(type(msg.Quantity) == 'string', 'Quantity is required!')
assert(bint(0) < bint(msg.Quantity), 'Quantity must be greater than zero!')
if not Balances[ao.id] then Balances[ao.id] = "0" end
if msg.From == ao.id then
-- Add tokens to the token pool, according to Quantity
Balances[msg.From] = utils.add(Balances[msg.From], msg.Quantity)
Send({
Target = msg.From,
Data = Colors.gray .. "Successfully minted " .. Colors.blue .. msg.Quantity .. Colors.reset
})
else
Send({
Target = msg.From,
Action = 'Mint-Error',
['Message-Id'] = msg.Id,
Error = 'Only the Process Id can mint new ' .. Ticker .. ' tokens!'
})
end
end)
--[[
Total Supply
]]
--
Handlers.add('totalSupply', Handlers.utils.hasMatchingTag('Action', 'Total-Supply'), function(msg)
assert(msg.From ~= ao.id, 'Cannot call Total-Supply from the same process!')
local totalSupply = bint(0)
for _, balance in pairs(Balances) do
totalSupply = utils.add(totalSupply, balance)
end
Send({
Target = msg.From,
Action = 'Total-Supply',
Data = tostring(totalSupply),
Ticker = Ticker
})
end)
```