Token Standard XSC001

Token Standard

Here is my proposal for a standard token contract:


balances = Hash(default_value=0)
allowances = Hash(default_value=0)

metadata = Hash()

@construct
def seed():
    balances[ctx.caller] = 1_000_000

    metadata['token_name'] = "TEST TOKEN"
    metadata['token_symbol'] = "TST"
    metadata['token_website'] = 'https://some.token.url'
    metadata['operator'] = ctx.caller


@export
def balance_of(address: str):
    return balances[address]


@export
def change_metadata(key: str, value: Any):
    assert ctx.caller == metadata['operator'], 'Only operator can set metadata!'
    metadata[key] = value


@export
def transfer(amount: float, to: str):
    assert amount > 0, 'Cannot send negative balances!'
    assert balances[ctx.caller] >= amount, 'Not enough coins to send!'
    assert not to.startswith('0x'), 'Invalid address!'

    balances[ctx.caller] -= amount
    balances[to] += amount


@export
def approve(amount: float, to: str):
    assert amount > 0, 'Cannot send negative balances!'
    allowances[ctx.caller, to] += amount


@export
def transfer_from(amount: float, to: str, main_account: str):
    approved = allowances[main_account, ctx.caller]

    assert amount > 0, 'Cannot send negative balances!'
    assert approved >= amount, f'You approved {approved} but need {amount}'
    assert balances[main_account] >= amount, 'Not enough tokens to send!'

    allowances[main_account, ctx.caller] -= amount
    balances[main_account] -= amount
    balances[to] += amount
  1. After thinking about it, I removed the text return. I think it could be misleading. People will think it’s what happens. But in reality it’s just a text. Someone can adjust it and imply he is doing something he isn’t. Besides that, if we want to have a clear text for example on the explorer or DEX, we should take the contract and function and interpret it and show the interpretation of what was done but not rely on text someone provided.

  2. I find it weird to save allowances and balances in the same variable. If we split it, it’s cleaner and we can much easier sum balances up etc. We could even do something like balances.all() and get total supply.

  3. Let’s make it easier for everyone to retrieve the balance for a given address with the balance_of function. Other contracts can use it after importing a token contract.

  4. I replaced one of the messages in transfer_from because i don’t like the weird .format texts. It’s complicated, two lines (ugly!) and we should be using modern stuff like f-strings anyway.

  5. I removed token_logo_url from metadata since i think it should be best practice to put token logos into the contract so that we can be sure to have access to them always.

Besides all that, we should have code snippets ready to be included into a standard token contract. One of these things is having a sell() and buy() function in the contract that don’t need to be there but IF they are in there, a DEX should use them to execute buys / sells. That way, the token can easily implement taxes and other functionality that is specifically meant for trading on a DEX.

1 Like

I really like the check you introduced into the transfer method:

assert not to.startswith('0x')

But I think this line should be introduced into approve method as well. The reason is if you’re protected from mistakenly transferring tokens/funds to an ethereum-like address then there is no need to approve tokens/funds to be spent by an ethereum-like address.

1 Like

not a fan of the address check.

    assert not to.startswith('0x'), 'Invalid address!'

We should keep the token standard as lightweight as possible. If this logic can go somewhere else (like a UI, then I think it should go there)

1 Like